read prompt from file and print output to file

This commit is contained in:
Aiqiao Yan
2025-04-17 17:41:35 +00:00
parent c7105a4c1e
commit 43f6a3831f
8 changed files with 123 additions and 34 deletions

View File

@@ -46,7 +46,7 @@ avoid having to include the `node_modules/` directory in the repository.
1. Make your change, add tests, and make sure the tests still pass: 1. Make your change, add tests, and make sure the tests still pass:
`npm run test` `npm run test`
1. Make sure your code is correctly formatted: `npm run format` 1. Make sure your code is correctly formatted: `npm run format`
1. Update `dist/index.js` using `npm run build`. This creates a single 1. Update `dist/index.js` using `npm run bundle`. This creates a single
JavaScript file that is used as an entrypoint for the action JavaScript file that is used as an entrypoint for the action
1. Push to your fork and [submit a pull request][pr] 1. Push to your fork and [submit a pull request][pr]
1. Pat yourself on the back and wait for your pull request to be reviewed and 1. Pat yourself on the back and wait for your pull request to be reviewed and

View File

@@ -34,6 +34,30 @@ jobs:
run: echo "${{ steps.inference.outputs.response }}" run: echo "${{ steps.inference.outputs.response }}"
``` ```
### Using a Prompt File
You can also provide a prompt file instead of an inline prompt:
```yaml
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Run AI Inference with Prompt File
id: inference
uses: actions/ai-inference@v1
with:
prompt-file: './path/to/prompt.txt'
- name: Use Response File
run: |
echo "Response saved to: ${{ steps.inference.outputs.response-path }}"
cat "${{ steps.inference.outputs.response-path }}"
```
This is particularly useful for longer prompts or when you need to use the file
path where the response is stored.
## Inputs ## Inputs
Various inputs are defined in [`action.yml`](action.yml) to let you configure Various inputs are defined in [`action.yml`](action.yml) to let you configure
@@ -43,6 +67,7 @@ the action:
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| `token` | Token to use for inference. Typically the GITHUB_TOKEN secret | `github.token` | | `token` | Token to use for inference. Typically the GITHUB_TOKEN secret | `github.token` |
| `prompt` | The prompt to send to the model | N/A | | `prompt` | The prompt to send to the model | N/A |
| `prompt-file` | Path to a file containing the prompt. If both `prompt` and `prompt-file` are provided, `prompt-file` takes precedence | `""` |
| `system-prompt` | The system prompt to send to the model | `""` | | `system-prompt` | The system prompt to send to the model | `""` |
| `model` | The model to use for inference. Must be available in the [GitHub Models](https://github.com/marketplace?type=models) catalog | `gpt-4o` | | `model` | The model to use for inference. Must be available in the [GitHub Models](https://github.com/marketplace?type=models) catalog | `gpt-4o` |
| `endpoint` | The endpoint to use for inference. If you're running this as part of an org, you should probably use the org-specific Models endpoint | `https://models.github.ai/inference` | | `endpoint` | The endpoint to use for inference. If you're running this as part of an org, you should probably use the org-specific Models endpoint | `https://models.github.ai/inference` |
@@ -52,9 +77,10 @@ the action:
The AI inference action provides the following outputs: The AI inference action provides the following outputs:
| Name | Description | | Name | Description |
| ---------- | --------------------------- | | --------------- | ----------------------------------------------------------------------- |
| `response` | The response from the model | | `response` | The response from the model |
| `response-path` | The file path where the response is saved (useful for larger responses) |
## Required Permissions ## Required Permissions

View File

@@ -7,7 +7,6 @@
*/ */
import { jest } from '@jest/globals' import { jest } from '@jest/globals'
import * as core from '../__fixtures__/core.js' import * as core from '../__fixtures__/core.js'
const mockPost = jest.fn().mockImplementation(() => ({ const mockPost = jest.fn().mockImplementation(() => ({
body: { body: {
choices: [ choices: [
@@ -58,11 +57,21 @@ describe('main.ts', () => {
'response', 'response',
'Hello, user!' 'Hello, user!'
) )
expect(core.setOutput).toHaveBeenNthCalledWith(
2,
'response-path',
expect.stringContaining('modelResponse.txt')
)
}) })
it('Sets a failed status', async () => { it('Sets a failed status', async () => {
// Clear the getInput mock and return an empty prompt // Clear the getInput mock and simulate no prompt or prompt-file input
core.getInput.mockClear().mockReturnValueOnce('') core.getInput.mockImplementation((name) => {
if (name === 'prompt') return ''
if (name === 'prompt_file') return ''
return ''
})
await run() await run()

View File

@@ -11,7 +11,11 @@ branding:
inputs: inputs:
prompt: prompt:
description: The prompt for the model description: The prompt for the model
required: true required: false
default: ''
prompt-file:
description: Path to a file containing the prompt
required: false
default: '' default: ''
model: model:
description: The model to use description: The model to use
@@ -38,6 +42,8 @@ inputs:
outputs: outputs:
response: response:
description: The response from the model description: The response from the model
response-path:
description: The file path where the response is saved
runs: runs:
using: node20 using: node20

65
dist/index.js generated vendored
View File

@@ -1,7 +1,10 @@
import require$$0 from 'os'; import * as require$$0 from 'os';
import require$$0__default from 'os';
import require$$0$1, { randomUUID as randomUUID$1 } from 'crypto'; import require$$0$1, { randomUUID as randomUUID$1 } from 'crypto';
import require$$1 from 'fs'; import * as fs from 'fs';
import require$$1$5 from 'path'; import fs__default from 'fs';
import * as require$$1 from 'path';
import require$$1__default from 'path';
import require$$2 from 'http'; import require$$2 from 'http';
import require$$1$1 from 'https'; import require$$1$1 from 'https';
import require$$0$4 from 'net'; import require$$0$4 from 'net';
@@ -30,7 +33,7 @@ import require$$6$1 from 'timers';
import * as os from 'node:os'; import * as os from 'node:os';
import { EOL } from 'node:os'; import { EOL } from 'node:os';
import * as process$1 from 'node:process'; import * as process$1 from 'node:process';
import require$$1$6 from 'tty'; import require$$1$5 from 'tty';
import * as http from 'node:http'; import * as http from 'node:http';
import * as https from 'node:https'; import * as https from 'node:https';
import * as zlib from 'node:zlib'; import * as zlib from 'node:zlib';
@@ -120,7 +123,7 @@ function requireCommand () {
}; };
Object.defineProperty(command, "__esModule", { value: true }); Object.defineProperty(command, "__esModule", { value: true });
command.issue = command.issueCommand = void 0; command.issue = command.issueCommand = void 0;
const os = __importStar(require$$0); const os = __importStar(require$$0__default);
const utils_1 = requireUtils$1(); const utils_1 = requireUtils$1();
/** /**
* Commands * Commands
@@ -229,8 +232,8 @@ function requireFileCommand () {
// We use any as a valid input type // We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
const crypto = __importStar(require$$0$1); const crypto = __importStar(require$$0$1);
const fs = __importStar(require$$1); const fs = __importStar(fs__default);
const os = __importStar(require$$0); const os = __importStar(require$$0__default);
const utils_1 = requireUtils$1(); const utils_1 = requireUtils$1();
function issueFileCommand(command, message) { function issueFileCommand(command, message) {
const filePath = process.env[`GITHUB_${command}`]; const filePath = process.env[`GITHUB_${command}`];
@@ -25206,8 +25209,8 @@ function requireSummary () {
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.summary = exports.markdownSummary = exports.SUMMARY_DOCS_URL = exports.SUMMARY_ENV_VAR = void 0; exports.summary = exports.markdownSummary = exports.SUMMARY_DOCS_URL = exports.SUMMARY_ENV_VAR = void 0;
const os_1 = require$$0; const os_1 = require$$0__default;
const fs_1 = require$$1; const fs_1 = fs__default;
const { access, appendFile, writeFile } = fs_1.promises; const { access, appendFile, writeFile } = fs_1.promises;
exports.SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY'; exports.SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY';
exports.SUMMARY_DOCS_URL = 'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary'; exports.SUMMARY_DOCS_URL = 'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary';
@@ -25513,7 +25516,7 @@ function requirePathUtils () {
}; };
Object.defineProperty(pathUtils, "__esModule", { value: true }); Object.defineProperty(pathUtils, "__esModule", { value: true });
pathUtils.toPlatformPath = pathUtils.toWin32Path = pathUtils.toPosixPath = void 0; pathUtils.toPlatformPath = pathUtils.toWin32Path = pathUtils.toPosixPath = void 0;
const path = __importStar(require$$1$5); const path = __importStar(require$$1__default);
/** /**
* toPosixPath converts the given path to the posix form. On Windows, \\ will be * toPosixPath converts the given path to the posix form. On Windows, \\ will be
* replaced with /. * replaced with /.
@@ -25599,8 +25602,8 @@ function requireIoUtil () {
var _a; var _a;
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.getCmdPath = exports.tryGetExecutablePath = exports.isRooted = exports.isDirectory = exports.exists = exports.READONLY = exports.UV_FS_O_EXLOCK = exports.IS_WINDOWS = exports.unlink = exports.symlink = exports.stat = exports.rmdir = exports.rm = exports.rename = exports.readlink = exports.readdir = exports.open = exports.mkdir = exports.lstat = exports.copyFile = exports.chmod = void 0; exports.getCmdPath = exports.tryGetExecutablePath = exports.isRooted = exports.isDirectory = exports.exists = exports.READONLY = exports.UV_FS_O_EXLOCK = exports.IS_WINDOWS = exports.unlink = exports.symlink = exports.stat = exports.rmdir = exports.rm = exports.rename = exports.readlink = exports.readdir = exports.open = exports.mkdir = exports.lstat = exports.copyFile = exports.chmod = void 0;
const fs = __importStar(require$$1); const fs = __importStar(fs__default);
const path = __importStar(require$$1$5); const path = __importStar(require$$1__default);
_a = fs.promises _a = fs.promises
// export const {open} = 'fs' // export const {open} = 'fs'
, exports.chmod = _a.chmod, exports.copyFile = _a.copyFile, exports.lstat = _a.lstat, exports.mkdir = _a.mkdir, exports.open = _a.open, exports.readdir = _a.readdir, exports.readlink = _a.readlink, exports.rename = _a.rename, exports.rm = _a.rm, exports.rmdir = _a.rmdir, exports.stat = _a.stat, exports.symlink = _a.symlink, exports.unlink = _a.unlink; , exports.chmod = _a.chmod, exports.copyFile = _a.copyFile, exports.lstat = _a.lstat, exports.mkdir = _a.mkdir, exports.open = _a.open, exports.readdir = _a.readdir, exports.readlink = _a.readlink, exports.rename = _a.rename, exports.rm = _a.rm, exports.rmdir = _a.rmdir, exports.stat = _a.stat, exports.symlink = _a.symlink, exports.unlink = _a.unlink;
@@ -25790,7 +25793,7 @@ function requireIo () {
Object.defineProperty(io, "__esModule", { value: true }); Object.defineProperty(io, "__esModule", { value: true });
io.findInPath = io.which = io.mkdirP = io.rmRF = io.mv = io.cp = void 0; io.findInPath = io.which = io.mkdirP = io.rmRF = io.mv = io.cp = void 0;
const assert_1 = require$$0$3; const assert_1 = require$$0$3;
const path = __importStar(require$$1$5); const path = __importStar(require$$1__default);
const ioUtil = __importStar(requireIoUtil()); const ioUtil = __importStar(requireIoUtil());
/** /**
* Copies a file or folder. * Copies a file or folder.
@@ -26095,10 +26098,10 @@ function requireToolrunner () {
}; };
Object.defineProperty(toolrunner, "__esModule", { value: true }); Object.defineProperty(toolrunner, "__esModule", { value: true });
toolrunner.argStringToArray = toolrunner.ToolRunner = void 0; toolrunner.argStringToArray = toolrunner.ToolRunner = void 0;
const os = __importStar(require$$0); const os = __importStar(require$$0__default);
const events = __importStar(require$$4); const events = __importStar(require$$4);
const child = __importStar(require$$2$2); const child = __importStar(require$$2$2);
const path = __importStar(require$$1$5); const path = __importStar(require$$1__default);
const io = __importStar(requireIo()); const io = __importStar(requireIo());
const ioUtil = __importStar(requireIoUtil()); const ioUtil = __importStar(requireIoUtil());
const timers_1 = require$$6$1; const timers_1 = require$$6$1;
@@ -26838,7 +26841,7 @@ function requirePlatform () {
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.getDetails = exports.isLinux = exports.isMacOS = exports.isWindows = exports.arch = exports.platform = void 0; exports.getDetails = exports.isLinux = exports.isMacOS = exports.isWindows = exports.arch = exports.platform = void 0;
const os_1 = __importDefault(require$$0); const os_1 = __importDefault(require$$0__default);
const exec = __importStar(requireExec()); const exec = __importStar(requireExec());
const getWindowsInfo = () => __awaiter(void 0, void 0, void 0, function* () { const getWindowsInfo = () => __awaiter(void 0, void 0, void 0, function* () {
const { stdout: version } = yield exec.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Version"', undefined, { const { stdout: version } = yield exec.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Version"', undefined, {
@@ -26941,8 +26944,8 @@ function requireCore () {
const command_1 = requireCommand(); const command_1 = requireCommand();
const file_command_1 = requireFileCommand(); const file_command_1 = requireFileCommand();
const utils_1 = requireUtils$1(); const utils_1 = requireUtils$1();
const os = __importStar(require$$0); const os = __importStar(require$$0__default);
const path = __importStar(require$$1$5); const path = __importStar(require$$1__default);
const oidc_utils_1 = requireOidcUtils(); const oidc_utils_1 = requireOidcUtils();
/** /**
* The code to exit an action * The code to exit an action
@@ -29841,8 +29844,8 @@ var hasRequiredSupportsColor;
function requireSupportsColor () { function requireSupportsColor () {
if (hasRequiredSupportsColor) return supportsColor_1; if (hasRequiredSupportsColor) return supportsColor_1;
hasRequiredSupportsColor = 1; hasRequiredSupportsColor = 1;
const os = require$$0; const os = require$$0__default;
const tty = require$$1$6; const tty = require$$1$5;
const hasFlag = requireHasFlag(); const hasFlag = requireHasFlag();
const {env} = process; const {env} = process;
@@ -29988,7 +29991,7 @@ function requireNode () {
if (hasRequiredNode) return node.exports; if (hasRequiredNode) return node.exports;
hasRequiredNode = 1; hasRequiredNode = 1;
(function (module, exports) { (function (module, exports) {
const tty = require$$1$6; const tty = require$$1$5;
const util = require$$0$2; const util = require$$0$2;
/** /**
@@ -33548,6 +33551,7 @@ function getPathFromMapKey(mapKey) {
return mapKey.slice(pathStart); return mapKey.slice(pathStart);
} }
const RESPONSE_FILE = 'modelResponse.txt';
/** /**
* The main function for the action. * The main function for the action.
* *
@@ -33555,7 +33559,14 @@ function getPathFromMapKey(mapKey) {
*/ */
async function run() { async function run() {
try { try {
const prompt = coreExports.getInput('prompt'); const promptFile = coreExports.getInput('prompt-file');
let prompt = coreExports.getInput('prompt');
if (promptFile !== undefined && promptFile !== '') {
if (!fs.existsSync(promptFile)) {
throw new Error(`Prompt file not found: ${promptFile}`);
}
prompt = fs.readFileSync(promptFile, 'utf-8');
}
if (prompt === undefined || prompt === '') { if (prompt === undefined || prompt === '') {
throw new Error('prompt is not set'); throw new Error('prompt is not set');
} }
@@ -33595,6 +33606,12 @@ async function run() {
const modelResponse = response.body.choices[0].message.content; const modelResponse = response.body.choices[0].message.content;
// Set outputs for other workflow steps to use // Set outputs for other workflow steps to use
coreExports.setOutput('response', modelResponse || ''); coreExports.setOutput('response', modelResponse || '');
// Save the response to a file in case the response overflow the output limit
const responseFilePath = require$$1.join(tempDir(), RESPONSE_FILE);
coreExports.setOutput('response-path', responseFilePath);
if (modelResponse && modelResponse !== '') {
fs.writeFileSync(responseFilePath, modelResponse, 'utf-8');
}
} }
catch (error) { catch (error) {
// Fail the workflow run if an error occurs // Fail the workflow run if an error occurs
@@ -33606,6 +33623,10 @@ async function run() {
} }
} }
} }
function tempDir() {
const tempDirectory = process.env['RUNNER_TEMP'] || require$$0.tmpdir();
return tempDirectory;
}
/** /**
* The entrypoint for the action. This file simply imports and runs the action's * The entrypoint for the action. This file simply imports and runs the action's

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "typescript-action", "name": "typescript-action",
"version": "0.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "typescript-action", "name": "typescript-action",
"version": "0.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.11.1" "@actions/core": "^1.11.1"

View File

@@ -1,6 +1,11 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import ModelClient, { isUnexpected } from '@azure-rest/ai-inference' import ModelClient, { isUnexpected } from '@azure-rest/ai-inference'
import { AzureKeyCredential } from '@azure/core-auth' import { AzureKeyCredential } from '@azure/core-auth'
import * as fs from 'fs'
import * as os from 'os'
import * as path from 'path'
const RESPONSE_FILE = 'modelResponse.txt'
/** /**
* The main function for the action. * The main function for the action.
@@ -9,7 +14,16 @@ import { AzureKeyCredential } from '@azure/core-auth'
*/ */
export async function run(): Promise<void> { export async function run(): Promise<void> {
try { try {
const prompt: string = core.getInput('prompt') const promptFile: string = core.getInput('prompt-file')
let prompt: string = core.getInput('prompt')
if (promptFile !== undefined && promptFile !== '') {
if (!fs.existsSync(promptFile)) {
throw new Error(`Prompt file not found: ${promptFile}`)
}
prompt = fs.readFileSync(promptFile, 'utf-8')
}
if (prompt === undefined || prompt === '') { if (prompt === undefined || prompt === '') {
throw new Error('prompt is not set') throw new Error('prompt is not set')
} }
@@ -60,6 +74,14 @@ export async function run(): Promise<void> {
// Set outputs for other workflow steps to use // Set outputs for other workflow steps to use
core.setOutput('response', modelResponse || '') core.setOutput('response', modelResponse || '')
// Save the response to a file in case the response overflow the output limit
const responseFilePath = path.join(tempDir(), RESPONSE_FILE)
core.setOutput('response-path', responseFilePath)
if (modelResponse && modelResponse !== '') {
fs.writeFileSync(responseFilePath, modelResponse, 'utf-8')
}
} catch (error) { } catch (error) {
// Fail the workflow run if an error occurs // Fail the workflow run if an error occurs
if (error instanceof Error) { if (error instanceof Error) {
@@ -69,3 +91,8 @@ export async function run(): Promise<void> {
} }
} }
} }
function tempDir(): string {
const tempDirectory = process.env['RUNNER_TEMP'] || os.tmpdir()
return tempDirectory
}