read prompt from file and print output to file
This commit is contained in:
@@ -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:
|
||||
`npm run test`
|
||||
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
|
||||
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
|
||||
|
||||
32
README.md
32
README.md
@@ -34,6 +34,30 @@ jobs:
|
||||
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
|
||||
|
||||
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` |
|
||||
| `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 | `""` |
|
||||
| `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` |
|
||||
@@ -52,9 +77,10 @@ the action:
|
||||
|
||||
The AI inference action provides the following outputs:
|
||||
|
||||
| Name | Description |
|
||||
| ---------- | --------------------------- |
|
||||
| `response` | The response from the model |
|
||||
| Name | Description |
|
||||
| --------------- | ----------------------------------------------------------------------- |
|
||||
| `response` | The response from the model |
|
||||
| `response-path` | The file path where the response is saved (useful for larger responses) |
|
||||
|
||||
## Required Permissions
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
*/
|
||||
import { jest } from '@jest/globals'
|
||||
import * as core from '../__fixtures__/core.js'
|
||||
|
||||
const mockPost = jest.fn().mockImplementation(() => ({
|
||||
body: {
|
||||
choices: [
|
||||
@@ -58,11 +57,21 @@ describe('main.ts', () => {
|
||||
'response',
|
||||
'Hello, user!'
|
||||
)
|
||||
|
||||
expect(core.setOutput).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'response-path',
|
||||
expect.stringContaining('modelResponse.txt')
|
||||
)
|
||||
})
|
||||
|
||||
it('Sets a failed status', async () => {
|
||||
// Clear the getInput mock and return an empty prompt
|
||||
core.getInput.mockClear().mockReturnValueOnce('')
|
||||
// Clear the getInput mock and simulate no prompt or prompt-file input
|
||||
core.getInput.mockImplementation((name) => {
|
||||
if (name === 'prompt') return ''
|
||||
if (name === 'prompt_file') return ''
|
||||
return ''
|
||||
})
|
||||
|
||||
await run()
|
||||
|
||||
|
||||
@@ -11,7 +11,11 @@ branding:
|
||||
inputs:
|
||||
prompt:
|
||||
description: The prompt for the model
|
||||
required: true
|
||||
required: false
|
||||
default: ''
|
||||
prompt-file:
|
||||
description: Path to a file containing the prompt
|
||||
required: false
|
||||
default: ''
|
||||
model:
|
||||
description: The model to use
|
||||
@@ -38,6 +42,8 @@ inputs:
|
||||
outputs:
|
||||
response:
|
||||
description: The response from the model
|
||||
response-path:
|
||||
description: The file path where the response is saved
|
||||
|
||||
runs:
|
||||
using: node20
|
||||
|
||||
65
dist/index.js
generated
vendored
65
dist/index.js
generated
vendored
@@ -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$$1 from 'fs';
|
||||
import require$$1$5 from 'path';
|
||||
import * as fs from 'fs';
|
||||
import fs__default from 'fs';
|
||||
import * as require$$1 from 'path';
|
||||
import require$$1__default from 'path';
|
||||
import require$$2 from 'http';
|
||||
import require$$1$1 from 'https';
|
||||
import require$$0$4 from 'net';
|
||||
@@ -30,7 +33,7 @@ import require$$6$1 from 'timers';
|
||||
import * as os from 'node:os';
|
||||
import { EOL } from 'node:os';
|
||||
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 https from 'node:https';
|
||||
import * as zlib from 'node:zlib';
|
||||
@@ -120,7 +123,7 @@ function requireCommand () {
|
||||
};
|
||||
Object.defineProperty(command, "__esModule", { value: true });
|
||||
command.issue = command.issueCommand = void 0;
|
||||
const os = __importStar(require$$0);
|
||||
const os = __importStar(require$$0__default);
|
||||
const utils_1 = requireUtils$1();
|
||||
/**
|
||||
* Commands
|
||||
@@ -229,8 +232,8 @@ function requireFileCommand () {
|
||||
// We use any as a valid input type
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const crypto = __importStar(require$$0$1);
|
||||
const fs = __importStar(require$$1);
|
||||
const os = __importStar(require$$0);
|
||||
const fs = __importStar(fs__default);
|
||||
const os = __importStar(require$$0__default);
|
||||
const utils_1 = requireUtils$1();
|
||||
function issueFileCommand(command, message) {
|
||||
const filePath = process.env[`GITHUB_${command}`];
|
||||
@@ -25206,8 +25209,8 @@ function requireSummary () {
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.summary = exports.markdownSummary = exports.SUMMARY_DOCS_URL = exports.SUMMARY_ENV_VAR = void 0;
|
||||
const os_1 = require$$0;
|
||||
const fs_1 = require$$1;
|
||||
const os_1 = require$$0__default;
|
||||
const fs_1 = fs__default;
|
||||
const { access, appendFile, writeFile } = fs_1.promises;
|
||||
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';
|
||||
@@ -25513,7 +25516,7 @@ function requirePathUtils () {
|
||||
};
|
||||
Object.defineProperty(pathUtils, "__esModule", { value: true });
|
||||
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
|
||||
* replaced with /.
|
||||
@@ -25599,8 +25602,8 @@ function requireIoUtil () {
|
||||
var _a;
|
||||
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;
|
||||
const fs = __importStar(require$$1);
|
||||
const path = __importStar(require$$1$5);
|
||||
const fs = __importStar(fs__default);
|
||||
const path = __importStar(require$$1__default);
|
||||
_a = fs.promises
|
||||
// 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;
|
||||
@@ -25790,7 +25793,7 @@ function requireIo () {
|
||||
Object.defineProperty(io, "__esModule", { value: true });
|
||||
io.findInPath = io.which = io.mkdirP = io.rmRF = io.mv = io.cp = void 0;
|
||||
const assert_1 = require$$0$3;
|
||||
const path = __importStar(require$$1$5);
|
||||
const path = __importStar(require$$1__default);
|
||||
const ioUtil = __importStar(requireIoUtil());
|
||||
/**
|
||||
* Copies a file or folder.
|
||||
@@ -26095,10 +26098,10 @@ function requireToolrunner () {
|
||||
};
|
||||
Object.defineProperty(toolrunner, "__esModule", { value: true });
|
||||
toolrunner.argStringToArray = toolrunner.ToolRunner = void 0;
|
||||
const os = __importStar(require$$0);
|
||||
const os = __importStar(require$$0__default);
|
||||
const events = __importStar(require$$4);
|
||||
const child = __importStar(require$$2$2);
|
||||
const path = __importStar(require$$1$5);
|
||||
const path = __importStar(require$$1__default);
|
||||
const io = __importStar(requireIo());
|
||||
const ioUtil = __importStar(requireIoUtil());
|
||||
const timers_1 = require$$6$1;
|
||||
@@ -26838,7 +26841,7 @@ function requirePlatform () {
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
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 getWindowsInfo = () => __awaiter(void 0, void 0, void 0, function* () {
|
||||
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 file_command_1 = requireFileCommand();
|
||||
const utils_1 = requireUtils$1();
|
||||
const os = __importStar(require$$0);
|
||||
const path = __importStar(require$$1$5);
|
||||
const os = __importStar(require$$0__default);
|
||||
const path = __importStar(require$$1__default);
|
||||
const oidc_utils_1 = requireOidcUtils();
|
||||
/**
|
||||
* The code to exit an action
|
||||
@@ -29841,8 +29844,8 @@ var hasRequiredSupportsColor;
|
||||
function requireSupportsColor () {
|
||||
if (hasRequiredSupportsColor) return supportsColor_1;
|
||||
hasRequiredSupportsColor = 1;
|
||||
const os = require$$0;
|
||||
const tty = require$$1$6;
|
||||
const os = require$$0__default;
|
||||
const tty = require$$1$5;
|
||||
const hasFlag = requireHasFlag();
|
||||
|
||||
const {env} = process;
|
||||
@@ -29988,7 +29991,7 @@ function requireNode () {
|
||||
if (hasRequiredNode) return node.exports;
|
||||
hasRequiredNode = 1;
|
||||
(function (module, exports) {
|
||||
const tty = require$$1$6;
|
||||
const tty = require$$1$5;
|
||||
const util = require$$0$2;
|
||||
|
||||
/**
|
||||
@@ -33548,6 +33551,7 @@ function getPathFromMapKey(mapKey) {
|
||||
return mapKey.slice(pathStart);
|
||||
}
|
||||
|
||||
const RESPONSE_FILE = 'modelResponse.txt';
|
||||
/**
|
||||
* The main function for the action.
|
||||
*
|
||||
@@ -33555,7 +33559,14 @@ function getPathFromMapKey(mapKey) {
|
||||
*/
|
||||
async function run() {
|
||||
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 === '') {
|
||||
throw new Error('prompt is not set');
|
||||
}
|
||||
@@ -33595,6 +33606,12 @@ async function run() {
|
||||
const modelResponse = response.body.choices[0].message.content;
|
||||
// Set outputs for other workflow steps to use
|
||||
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) {
|
||||
// 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
|
||||
|
||||
2
dist/index.js.map
generated
vendored
2
dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "typescript-action",
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "typescript-action",
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.11.1"
|
||||
|
||||
29
src/main.ts
29
src/main.ts
@@ -1,6 +1,11 @@
|
||||
import * as core from '@actions/core'
|
||||
import ModelClient, { isUnexpected } from '@azure-rest/ai-inference'
|
||||
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.
|
||||
@@ -9,7 +14,16 @@ import { AzureKeyCredential } from '@azure/core-auth'
|
||||
*/
|
||||
export async function run(): Promise<void> {
|
||||
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 === '') {
|
||||
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
|
||||
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) {
|
||||
// Fail the workflow run if an error occurs
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user