2025-07-21 00:11:26 +00:00
|
|
|
import * as core from '@actions/core'
|
|
|
|
|
import * as fs from 'fs'
|
|
|
|
|
import * as yaml from 'js-yaml'
|
|
|
|
|
|
|
|
|
|
export interface PromptMessage {
|
|
|
|
|
role: 'system' | 'user' | 'assistant'
|
|
|
|
|
content: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface PromptConfig {
|
|
|
|
|
messages: PromptMessage[]
|
|
|
|
|
model?: string
|
|
|
|
|
responseFormat?: 'text' | 'json_schema'
|
|
|
|
|
jsonSchema?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface TemplateVariables {
|
|
|
|
|
[key: string]: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Parse template variables from YAML input string
|
|
|
|
|
*/
|
|
|
|
|
export function parseTemplateVariables(input: string): TemplateVariables {
|
|
|
|
|
if (!input.trim()) {
|
|
|
|
|
return {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const parsed = yaml.load(input) as TemplateVariables
|
|
|
|
|
if (typeof parsed !== 'object' || parsed === null) {
|
|
|
|
|
throw new Error('Template variables must be a YAML object')
|
|
|
|
|
}
|
|
|
|
|
return parsed
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Failed to parse template variables: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Replace template variables in text using {{variable}} syntax
|
|
|
|
|
*/
|
|
|
|
|
export function replaceTemplateVariables(
|
|
|
|
|
text: string,
|
|
|
|
|
variables: TemplateVariables
|
|
|
|
|
): string {
|
2025-07-21 14:55:49 +10:00
|
|
|
return text.replace(/\{\{([\w.-]+)\}\}/g, (match, variableName) => {
|
2025-07-21 00:11:26 +00:00
|
|
|
if (variableName in variables) {
|
|
|
|
|
return variables[variableName]
|
|
|
|
|
}
|
|
|
|
|
core.warning(
|
|
|
|
|
`Template variable '${variableName}' not found in input variables`
|
|
|
|
|
)
|
|
|
|
|
return match // Return the original placeholder if variable not found
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Load and parse a prompt YAML file with template variable substitution
|
|
|
|
|
*/
|
|
|
|
|
export function loadPromptFile(
|
|
|
|
|
filePath: string,
|
|
|
|
|
templateVariables: TemplateVariables = {}
|
|
|
|
|
): PromptConfig {
|
|
|
|
|
if (!fs.existsSync(filePath)) {
|
|
|
|
|
throw new Error(`Prompt file not found: ${filePath}`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fileContent = fs.readFileSync(filePath, 'utf-8')
|
|
|
|
|
|
|
|
|
|
// Apply template variable substitution
|
|
|
|
|
const processedContent = replaceTemplateVariables(
|
|
|
|
|
fileContent,
|
|
|
|
|
templateVariables
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const config = yaml.load(processedContent) as PromptConfig
|
|
|
|
|
|
|
|
|
|
if (!config.messages || !Array.isArray(config.messages)) {
|
|
|
|
|
throw new Error('Prompt file must contain a "messages" array')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate messages
|
|
|
|
|
for (const message of config.messages) {
|
|
|
|
|
if (!message.role || !message.content) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
'Each message must have "role" and "content" properties'
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
if (!['system', 'user', 'assistant'].includes(message.role)) {
|
|
|
|
|
throw new Error(`Invalid message role: ${message.role}`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return config
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Failed to parse prompt file: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if a file is a prompt YAML file based on extension
|
|
|
|
|
*/
|
|
|
|
|
export function isPromptYamlFile(filePath: string): boolean {
|
|
|
|
|
return filePath.endsWith('.prompt.yml') || filePath.endsWith('.prompt.yaml')
|
|
|
|
|
}
|