Files
ai-inference/src/main.ts

143 lines
5.1 KiB
TypeScript
Raw Normal View History

2025-04-04 07:27:58 +11:00
import * as core from '@actions/core'
import * as fs from 'fs'
import * as tmp from 'tmp'
import {connectToGitHubMCP} from './mcp.js'
import {simpleInference, mcpInference} from './inference.js'
import {loadContentFromFileOrInput, buildInferenceRequest, parseCustomHeaders} from './helpers.js'
2025-08-05 01:32:32 +00:00
import {
loadPromptFile,
parseTemplateVariables,
isPromptYamlFile,
PromptConfig,
parseFileTemplateVariables,
} from './prompt.js'
2025-04-04 07:27:58 +11:00
/**
* The main function for the action.
*
* @returns Resolves when the action is complete.
*/
export async function run(): Promise<void> {
try {
2025-07-21 00:11:26 +00:00
const promptFilePath = core.getInput('prompt-file')
const inputVariables = core.getInput('input')
2025-08-05 01:32:32 +00:00
const fileInputVariables = core.getInput('file_input')
2025-07-21 04:31:06 +00:00
let promptConfig: PromptConfig | undefined = undefined
2025-07-21 00:11:26 +00:00
let systemPrompt: string | undefined = undefined
let prompt: string | undefined = undefined
// Check if we're using a prompt YAML file
if (promptFilePath && isPromptYamlFile(promptFilePath)) {
core.info('Using prompt YAML file format')
2025-08-05 01:32:32 +00:00
// Parse template variables from both string inputs and file-based inputs
const stringVars = parseTemplateVariables(inputVariables)
const fileVars = parseFileTemplateVariables(fileInputVariables)
const templateVariables = {...stringVars, ...fileVars}
2025-07-21 00:11:26 +00:00
// Load and process prompt file
promptConfig = loadPromptFile(promptFilePath, templateVariables)
} else {
// Use legacy format
core.info('Using legacy prompt format')
prompt = loadContentFromFileOrInput('prompt-file', 'prompt')
systemPrompt = loadContentFromFileOrInput('system-prompt-file', 'system-prompt', 'You are a helpful assistant')
2025-07-21 00:11:26 +00:00
}
feat: Add system-prompt-file input for file-based system prompts This enhancement adds the ability to load a system prompt from a file, similar to the existing prompt-file functionality, providing more flexibility when working with complex system prompts. Key changes: - Added new `system-prompt-file` input to action.yml with proper description - Updated main.ts implementation to handle file-based system prompts with: - File existence checking and appropriate error handling - Proper precedence (system-prompt-file takes priority over system-prompt) - Consistent error messages with existing prompt-file implementation Test coverage added: - Basic usage: Test verifies system-prompt-file loads content correctly - Error handling: Test ensures proper errors when system-prompt-file doesn't exist - Precedence: Test confirms system-prompt-file overrides system-prompt when both exist - Integration: Test validates both prompt-file and system-prompt-file work together - Max tokens: Test verifies custom token limits are properly passed to the model - Testing infrastructure: Improved mock implementations that detect unexpected calls Documentation: - Updated README.md with system-prompt-file in inputs table - Added dedicated usage example for system-prompt-file - Fixed formatting in inputs table CI/CD: - Updated workflow to test system-prompt-file functionality with actual file content This feature maintains backward compatibility while adding a useful option for workflows that need to use more complex system prompts stored in files.
2025-05-24 03:50:15 +02:00
2025-07-21 00:11:26 +00:00
// Get common parameters
const modelName = promptConfig?.model || core.getInput('model')
// Parse token limit inputs
const maxCompletionTokensInput =
promptConfig?.modelParameters?.maxCompletionTokens ?? core.getInput('max-completion-tokens')
const maxCompletionTokens = maxCompletionTokensInput ? Number(maxCompletionTokensInput) : undefined
const maxTokensInput = promptConfig?.modelParameters?.maxTokens ?? core.getInput('max-tokens')
const maxTokens = maxCompletionTokens != null ? undefined : maxTokensInput ? Number(maxTokensInput) : undefined
2025-04-04 07:27:58 +11:00
2025-07-15 23:23:39 +00:00
const token = process.env['GITHUB_TOKEN'] || core.getInput('token')
if (token === undefined) {
throw new Error('GITHUB_TOKEN is not set')
}
2025-04-08 04:55:27 +00:00
2025-08-04 03:06:53 +00:00
// Get GitHub MCP token (use dedicated token if provided, otherwise fall back to main token)
const githubMcpToken = core.getInput('github-mcp-token') || token
const githubMcpToolsets = core.getInput('github-mcp-toolsets')
2025-08-04 03:06:53 +00:00
2025-04-08 20:22:47 +00:00
const endpoint = core.getInput('endpoint')
2025-07-15 23:23:39 +00:00
// Get temperature and topP (prompt YAML modelParameters takes precedence over action inputs)
const temperatureInput = core.getInput('temperature')
const topPInput = core.getInput('top-p')
const temperature =
promptConfig?.modelParameters?.temperature ?? (temperatureInput ? parseFloat(temperatureInput) : undefined)
const topP = promptConfig?.modelParameters?.topP ?? (topPInput ? parseFloat(topPInput) : undefined)
// Parse custom headers
const customHeadersInput = core.getInput('custom-headers')
const customHeaders = parseCustomHeaders(customHeadersInput)
2025-07-21 00:11:26 +00:00
// Build the inference request with pre-processed messages and response format
const inferenceRequest = buildInferenceRequest(
promptConfig,
2025-07-16 00:12:41 +00:00
systemPrompt,
prompt,
modelName,
temperature,
topP,
2025-07-16 00:12:41 +00:00
maxTokens,
maxCompletionTokens,
2025-07-16 00:12:41 +00:00
endpoint,
token,
customHeaders,
2025-07-21 00:11:26 +00:00
)
const enableMcp = core.getBooleanInput('enable-github-mcp') || false
2025-07-15 23:23:39 +00:00
2025-07-16 00:12:41 +00:00
let modelResponse: string | null = null
2025-07-15 23:23:39 +00:00
2025-07-16 00:12:41 +00:00
if (enableMcp) {
const mcpClient = await connectToGitHubMCP(githubMcpToken, githubMcpToolsets)
2025-07-16 00:12:41 +00:00
if (mcpClient) {
modelResponse = await mcpInference(inferenceRequest, mcpClient)
} else {
core.warning('MCP connection failed, falling back to simple inference')
modelResponse = await simpleInference(inferenceRequest)
2025-07-15 23:23:39 +00:00
}
2025-07-16 00:12:41 +00:00
} else {
modelResponse = await simpleInference(inferenceRequest)
2025-07-15 23:23:39 +00:00
}
core.setOutput('response', modelResponse || '')
// Create a temporary file for the response that persists for downstream steps.
// We use keep: true to prevent automatic cleanup - the file will be cleaned up
// by the runner when the job completes.
const responseFile = tmp.fileSync({
prefix: 'modelResponse-',
postfix: '.txt',
keep: true,
})
core.setOutput('response-file', responseFile.name)
if (modelResponse && modelResponse !== '') {
fs.writeFileSync(responseFile.name, modelResponse, 'utf-8')
}
2025-04-04 07:27:58 +11:00
} catch (error) {
2025-04-07 23:52:33 +00:00
if (error instanceof Error) {
core.setFailed(error.message)
} else {
core.setFailed(`An unexpected error occurred: ${JSON.stringify(error, null, 2)}`)
2025-04-07 23:52:33 +00:00
}
2025-08-04 22:40:30 +00:00
// Force exit to prevent hanging on open connections
process.exit(1)
2025-04-04 07:27:58 +11:00
}
// Force exit to prevent hanging on open connections
process.exit(0)
2025-04-04 07:27:58 +11:00
}