2025-04-04 07:27:58 +11:00
|
|
|
import * as core from '@actions/core'
|
2025-04-03 21:55:32 +00:00
|
|
|
import ModelClient, { isUnexpected } from '@azure-rest/ai-inference'
|
|
|
|
|
import { AzureKeyCredential } from '@azure/core-auth'
|
2025-04-17 17:41:35 +00:00
|
|
|
import * as fs from 'fs'
|
|
|
|
|
import * as os from 'os'
|
|
|
|
|
import * as path from 'path'
|
|
|
|
|
|
|
|
|
|
const RESPONSE_FILE = 'modelResponse.txt'
|
2025-04-04 07:27:58 +11:00
|
|
|
|
2025-05-25 19:08:36 +02:00
|
|
|
/**
|
|
|
|
|
* Helper function to load content from a file or use fallback input
|
|
|
|
|
* @param filePathInput - Input name for the file path
|
|
|
|
|
* @param contentInput - Input name for the direct content
|
|
|
|
|
* @param defaultValue - Default value to use if neither file nor content is provided
|
|
|
|
|
* @returns The loaded content
|
|
|
|
|
*/
|
|
|
|
|
function loadContentFromFileOrInput(
|
|
|
|
|
filePathInput: string,
|
|
|
|
|
contentInput: string,
|
|
|
|
|
defaultValue?: string
|
|
|
|
|
): string {
|
|
|
|
|
const filePath = core.getInput(filePathInput)
|
|
|
|
|
const contentString = core.getInput(contentInput)
|
|
|
|
|
|
|
|
|
|
if (filePath !== undefined && filePath !== '') {
|
|
|
|
|
if (!fs.existsSync(filePath)) {
|
|
|
|
|
throw new Error(`File for ${filePathInput} was not found: ${filePath}`)
|
|
|
|
|
}
|
|
|
|
|
return fs.readFileSync(filePath, 'utf-8')
|
|
|
|
|
} else if (contentString !== undefined && contentString !== '') {
|
|
|
|
|
return contentString
|
|
|
|
|
} else if (defaultValue !== undefined) {
|
|
|
|
|
return defaultValue
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error(`Neither ${filePathInput} nor ${contentInput} was set`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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-05-25 19:08:36 +02:00
|
|
|
// Load prompt content - required
|
|
|
|
|
const prompt = loadContentFromFileOrInput(
|
|
|
|
|
'prompt-file',
|
|
|
|
|
'prompt'
|
|
|
|
|
)
|
2025-04-17 17:41:35 +00:00
|
|
|
|
2025-05-25 19:08:36 +02:00
|
|
|
// Load system prompt with default value
|
|
|
|
|
const systemPrompt = loadContentFromFileOrInput(
|
|
|
|
|
'system-prompt-file',
|
|
|
|
|
'system-prompt',
|
|
|
|
|
'You are a helpful assistant'
|
|
|
|
|
)
|
2025-05-24 03:50:15 +02:00
|
|
|
|
2025-04-07 00:09:42 +00:00
|
|
|
const modelName: string = core.getInput('model')
|
2025-04-07 04:53:19 +00:00
|
|
|
const maxTokens: number = parseInt(core.getInput('max-tokens'), 10)
|
2025-04-04 07:27:58 +11:00
|
|
|
|
2025-04-07 04:53:19 +00:00
|
|
|
const token = core.getInput('token') || process.env['GITHUB_TOKEN']
|
2025-04-03 21:55:32 +00:00
|
|
|
if (token === undefined) {
|
|
|
|
|
throw new Error('GITHUB_TOKEN is not set')
|
|
|
|
|
}
|
2025-04-08 04:55:27 +00:00
|
|
|
|
2025-04-08 20:22:47 +00:00
|
|
|
const endpoint = core.getInput('endpoint')
|
2025-04-04 07:27:58 +11:00
|
|
|
|
2025-04-10 02:53:54 +00:00
|
|
|
const client = ModelClient(endpoint, new AzureKeyCredential(token), {
|
|
|
|
|
userAgentOptions: { userAgentPrefix: 'github-actions-ai-inference' }
|
|
|
|
|
})
|
2025-04-03 21:55:32 +00:00
|
|
|
|
|
|
|
|
const response = await client.path('/chat/completions').post({
|
|
|
|
|
body: {
|
|
|
|
|
messages: [
|
|
|
|
|
{
|
|
|
|
|
role: 'system',
|
2025-04-07 04:53:19 +00:00
|
|
|
content: systemPrompt
|
2025-04-03 21:55:32 +00:00
|
|
|
},
|
|
|
|
|
{ role: 'user', content: prompt }
|
|
|
|
|
],
|
2025-04-07 00:09:42 +00:00
|
|
|
max_tokens: maxTokens,
|
2025-04-03 21:55:32 +00:00
|
|
|
model: modelName
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (isUnexpected(response)) {
|
2025-04-07 23:52:33 +00:00
|
|
|
if (response.body.error) {
|
|
|
|
|
throw response.body.error
|
|
|
|
|
}
|
|
|
|
|
throw new Error(
|
|
|
|
|
'An error occurred while fetching the response (' +
|
|
|
|
|
response.status +
|
|
|
|
|
'): ' +
|
|
|
|
|
response.body
|
|
|
|
|
)
|
2025-04-03 21:55:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const modelResponse: string | null =
|
|
|
|
|
response.body.choices[0].message.content
|
2025-04-04 07:27:58 +11:00
|
|
|
|
|
|
|
|
// Set outputs for other workflow steps to use
|
2025-04-03 21:55:32 +00:00
|
|
|
core.setOutput('response', modelResponse || '')
|
2025-04-17 17:41:35 +00:00
|
|
|
|
|
|
|
|
// Save the response to a file in case the response overflow the output limit
|
|
|
|
|
const responseFilePath = path.join(tempDir(), RESPONSE_FILE)
|
2025-04-17 20:23:07 +00:00
|
|
|
core.setOutput('response-file', responseFilePath)
|
2025-04-17 17:41:35 +00:00
|
|
|
|
|
|
|
|
if (modelResponse && modelResponse !== '') {
|
|
|
|
|
fs.writeFileSync(responseFilePath, modelResponse, 'utf-8')
|
|
|
|
|
}
|
2025-04-04 07:27:58 +11:00
|
|
|
} catch (error) {
|
|
|
|
|
// Fail the workflow run if an error occurs
|
2025-04-07 23:52:33 +00:00
|
|
|
if (error instanceof Error) {
|
|
|
|
|
core.setFailed(error.message)
|
|
|
|
|
} else {
|
|
|
|
|
core.setFailed('An unexpected error occurred')
|
|
|
|
|
}
|
2025-04-04 07:27:58 +11:00
|
|
|
}
|
|
|
|
|
}
|
2025-04-17 17:41:35 +00:00
|
|
|
|
|
|
|
|
function tempDir(): string {
|
|
|
|
|
const tempDirectory = process.env['RUNNER_TEMP'] || os.tmpdir()
|
|
|
|
|
return tempDirectory
|
|
|
|
|
}
|