Compare commits
155 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af6ad2c4ac | ||
|
|
9446d602a4 | ||
|
|
9739430845 | ||
|
|
e105a5ce41 | ||
|
|
65a8d298fc | ||
|
|
1f6750a867 | ||
|
|
3a4bd7a335 | ||
|
|
09488142e3 | ||
|
|
c729573012 | ||
|
|
fa073b82c5 | ||
|
|
1157ae8180 | ||
|
|
39e1ff891d | ||
|
|
2da133cc84 | ||
|
|
121a7cf487 | ||
|
|
e06ed630a2 | ||
|
|
230b1cd3d0 | ||
|
|
3d8acac3cb | ||
|
|
d46015cb8d | ||
|
|
b44f5a29f4 | ||
|
|
a6d96d58d5 | ||
|
|
4eebe5ea25 | ||
|
|
cf1eafb00d | ||
|
|
debf34cf91 | ||
|
|
541dd69625 | ||
|
|
f65d1a34dc | ||
|
|
9d962e5274 | ||
|
|
8b38b47848 | ||
|
|
b7792492cd | ||
|
|
a2600c61b7 | ||
|
|
306ffe21b9 | ||
|
|
326b9a12f4 | ||
|
|
8207a8ca01 | ||
|
|
c6c19e0fb7 | ||
|
|
e09e659817 | ||
|
|
e608d2ba8a | ||
|
|
27965bc3a4 | ||
|
|
a8bddad5e5 | ||
|
|
672ba8a3ac | ||
|
|
3a80d137e1 | ||
|
|
074e8b294d | ||
|
|
f1ca66fc66 | ||
|
|
6360e0db9b | ||
|
|
a380166897 | ||
|
|
b07a08c5eb | ||
|
|
725fb1c850 | ||
|
|
95f4a27227 | ||
|
|
b9877e2b39 | ||
|
|
29ac79522a | ||
|
|
4ae036562a | ||
|
|
268593b9a6 | ||
|
|
1171309110 | ||
|
|
71c69d42b5 | ||
|
|
d51321a7a6 | ||
|
|
5b62ecd0dd | ||
|
|
eff4de28e3 | ||
|
|
a6101c89c6 | ||
|
|
15ae50ae2f | ||
|
|
f77380037b | ||
|
|
6402ff8f9a | ||
|
|
c760995fbc | ||
|
|
ce720b3d0c | ||
|
|
6d144ac474 | ||
|
|
63993128d7 | ||
|
|
3dfda414c6 | ||
|
|
b99f473284 | ||
|
|
acb23a78e0 | ||
|
|
de9f3a655a | ||
|
|
38e68a8ffc | ||
|
|
7012ba221f | ||
|
|
185fd7e675 | ||
|
|
d89080af40 | ||
|
|
6f1b0370bb | ||
|
|
0a593b0c24 | ||
|
|
214f0b0a68 | ||
|
|
812f8bb844 | ||
|
|
de36aa9302 | ||
|
|
b99132354c | ||
|
|
ad4351a3a2 | ||
|
|
146434d459 | ||
|
|
ce17fb5d9d | ||
|
|
2ef8c2618e | ||
|
|
eeef9fe1ab | ||
|
|
82e525eb0f | ||
|
|
0f8b89d701 | ||
|
|
7e91a1e627 | ||
|
|
82a7737224 | ||
|
|
6709541cb1 | ||
|
|
059db2ba93 | ||
|
|
7228fd1adf | ||
|
|
04be163c40 | ||
|
|
26b055458c | ||
|
|
42a45bceaf | ||
|
|
52a34c2089 | ||
|
|
cab0b8c09d | ||
|
|
340ab189bd | ||
|
|
e783798b50 | ||
|
|
ff49162b2d | ||
|
|
fd45b46eda | ||
|
|
efe413bb31 | ||
|
|
82fb91a5d5 | ||
|
|
334892bb20 | ||
|
|
bbe0ccb244 | ||
|
|
ca3b99ea74 | ||
|
|
8a5d2ea4a1 | ||
|
|
112739fb15 | ||
|
|
f95554969e | ||
|
|
9e60aa0a3f | ||
|
|
02c6cc30ae | ||
|
|
18d468666d | ||
|
|
fd73d0264c | ||
|
|
27350b2a98 | ||
|
|
e8987e92e0 | ||
|
|
2d03946378 | ||
|
|
d061fc5469 | ||
|
|
2d2f67ec42 | ||
|
|
9170087739 | ||
|
|
62db90ab13 | ||
|
|
16f2d5c46b | ||
|
|
95443f8d18 | ||
|
|
5022b33bc1 | ||
|
|
c9e14713bc | ||
|
|
39308142df | ||
|
|
48f0edec4d | ||
|
|
36ea1371dc | ||
|
|
de16a30c20 | ||
|
|
48758ceaff | ||
|
|
dd3dff10ba | ||
|
|
4bb01ee5ee | ||
|
|
4b4b2e8afe | ||
|
|
932a853db4 | ||
|
|
e0da58c63f | ||
|
|
af1c1c29a3 | ||
|
|
7688a5060c | ||
|
|
52a32ececf | ||
|
|
83bb5ca3e8 | ||
|
|
4d2337d006 | ||
|
|
7ba7530ad4 | ||
|
|
4d7d83c494 | ||
|
|
a1c1182922 | ||
|
|
dfaa426c29 | ||
|
|
7fa0024f13 | ||
|
|
fc6f9a0800 | ||
|
|
a1d07305b7 | ||
|
|
6e0d8949d8 | ||
|
|
f347eae8eb | ||
|
|
07fe2f30ad | ||
|
|
1843310df4 | ||
|
|
c72cb2ef9c | ||
|
|
a2fd223fcf | ||
|
|
3ba8e1b39d | ||
|
|
52e5222a82 | ||
|
|
a62dfeda7b | ||
|
|
48235f7026 | ||
|
|
9bbcef8fa4 | ||
|
|
411276b07e |
8
.github/workflows/check-dist.yml
vendored
8
.github/workflows/check-dist.yml
vendored
@@ -28,11 +28,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
id: setup-node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: npm
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
- name: Build dist/ Directory
|
||||
id: build
|
||||
run: npm run bundle
|
||||
run: npm run package
|
||||
|
||||
# This will fail the workflow if the `dist/` directory is different than
|
||||
# expected.
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
- if: ${{ failure() && steps.diff.outcome == 'failure' }}
|
||||
name: Upload Artifact
|
||||
id: upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
|
||||
83
.github/workflows/ci.yml
vendored
83
.github/workflows/ci.yml
vendored
@@ -20,11 +20,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
id: setup-node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: npm
|
||||
@@ -54,22 +54,53 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
|
||||
- name: Start Mock Inference Server
|
||||
id: mock-server
|
||||
run: |
|
||||
node script/mock-inference-server.mjs &
|
||||
echo "pid=$!" >> $GITHUB_OUTPUT
|
||||
# Wait for server to be ready
|
||||
for i in {1..10}; do
|
||||
if curl -s http://localhost:3456/health > /dev/null; then
|
||||
echo "Mock server is ready"
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- name: Test Local Action
|
||||
id: test-action
|
||||
continue-on-error: true
|
||||
uses: ./
|
||||
with:
|
||||
prompt: hello
|
||||
endpoint: http://localhost:3456
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: Print Output
|
||||
id: output
|
||||
continue-on-error: true
|
||||
run: echo "${{ steps.test-action.outputs.response }}"
|
||||
|
||||
- name: Verify Output
|
||||
run: |
|
||||
response="${{ steps.test-action.outputs.response }}"
|
||||
if [[ -z "$response" ]]; then
|
||||
echo "Error: No response received"
|
||||
exit 1
|
||||
fi
|
||||
echo "Response received: $response"
|
||||
|
||||
- name: Stop Mock Server
|
||||
if: always()
|
||||
run: kill ${{ steps.mock-server.outputs.pid }} || true
|
||||
|
||||
test-action-prompt-file:
|
||||
name: GitHub Actions Test with Prompt File
|
||||
runs-on: ubuntu-latest
|
||||
@@ -77,7 +108,26 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
|
||||
- name: Start Mock Inference Server
|
||||
id: mock-server
|
||||
run: |
|
||||
node script/mock-inference-server.mjs &
|
||||
echo "pid=$!" >> $GITHUB_OUTPUT
|
||||
# Wait for server to be ready
|
||||
for i in {1..10}; do
|
||||
if curl -s http://localhost:3456/health > /dev/null; then
|
||||
echo "Mock server is ready"
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- name: Create Prompt File
|
||||
run: echo "hello" > prompt.txt
|
||||
@@ -87,16 +137,33 @@ jobs:
|
||||
|
||||
- name: Test Local Action with Prompt File
|
||||
id: test-action-prompt-file
|
||||
continue-on-error: true
|
||||
uses: ./
|
||||
with:
|
||||
prompt-file: prompt.txt
|
||||
system-prompt-file: system-prompt.txt
|
||||
endpoint: http://localhost:3456
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: Print Output
|
||||
continue-on-error: true
|
||||
run: |
|
||||
echo "Response saved to: ${{ steps.test-action-prompt-file.outputs.response-file }}"
|
||||
cat "${{ steps.test-action-prompt-file.outputs.response-file }}"
|
||||
|
||||
- name: Verify Output
|
||||
run: |
|
||||
response_file="${{ steps.test-action-prompt-file.outputs.response-file }}"
|
||||
if [[ ! -f "$response_file" ]]; then
|
||||
echo "Error: Response file not found"
|
||||
exit 1
|
||||
fi
|
||||
content=$(cat "$response_file")
|
||||
if [[ -z "$content" ]]; then
|
||||
echo "Error: Response file is empty"
|
||||
exit 1
|
||||
fi
|
||||
echo "Response file content: $content"
|
||||
|
||||
- name: Stop Mock Server
|
||||
if: always()
|
||||
run: kill ${{ steps.mock-server.outputs.pid }} || true
|
||||
|
||||
10
.github/workflows/codeql-analysis.yml
vendored
10
.github/workflows/codeql-analysis.yml
vendored
@@ -25,24 +25,24 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language:
|
||||
- TypeScript
|
||||
- TypeScript, actions
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Initialize CodeQL
|
||||
id: initialize
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
source-root: src
|
||||
|
||||
- name: Autobuild
|
||||
id: autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
id: analyze
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
|
||||
8
.github/workflows/licensed.yml
vendored
8
.github/workflows/licensed.yml
vendored
@@ -27,11 +27,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
id: setup-node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: npm
|
||||
@@ -42,11 +42,11 @@ jobs:
|
||||
|
||||
- name: Setup Ruby
|
||||
id: setup-ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6
|
||||
with:
|
||||
ruby-version: ruby
|
||||
|
||||
- uses: licensee/setup-licensed@v1.3.2
|
||||
- uses: licensee/setup-licensed@0d52e575b3258417672be0dff2f115d7db8771d8
|
||||
with:
|
||||
version: 4.x
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
4
.github/workflows/linter.yml
vendored
4
.github/workflows/linter.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
- name: Lint Codebase
|
||||
id: super-linter
|
||||
uses: super-linter/super-linter/slim@5119dcd8011e92182ce8219d9e9efc82f16fddb6
|
||||
uses: super-linter/super-linter/slim@61abc07d755095a68f4987d1c2c3d1d64408f1f9
|
||||
env:
|
||||
DEFAULT_BRANCH: main
|
||||
FILTER_REGEX_EXCLUDE: dist/**/*
|
||||
|
||||
@@ -16,6 +16,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Update the ${{ env.TAG_NAME }} tag
|
||||
uses: actions/publish-action@v0.3.0
|
||||
uses: actions/publish-action@v0.4.0
|
||||
with:
|
||||
source-tag: ${{ env.TAG_NAME }}
|
||||
|
||||
11
.licenses/npm/@hono/node-server.dep.yml
Normal file
11
.licenses/npm/@hono/node-server.dep.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: "@hono/node-server"
|
||||
version: 1.19.7
|
||||
type: npm
|
||||
summary: Node.js Adapter for Hono
|
||||
homepage: https://github.com/honojs/node-server
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: README.md
|
||||
text: MIT
|
||||
notices: []
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@modelcontextprotocol/sdk"
|
||||
version: 1.15.1
|
||||
version: 1.25.2
|
||||
type: npm
|
||||
summary: Model Context Protocol implementation for TypeScript
|
||||
homepage: https://modelcontextprotocol.io
|
||||
|
||||
32
.licenses/npm/@types/tmp.dep.yml
Normal file
32
.licenses/npm/@types/tmp.dep.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: "@types/tmp"
|
||||
version: 0.2.6
|
||||
type: npm
|
||||
summary: TypeScript definitions for tmp
|
||||
homepage: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/tmp
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |2
|
||||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
||||
notices: []
|
||||
34
.licenses/npm/ajv-formats.dep.yml
Normal file
34
.licenses/npm/ajv-formats.dep.yml
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: ajv-formats
|
||||
version: 3.0.1
|
||||
type: npm
|
||||
summary: Format validation for Ajv v7+
|
||||
homepage: https://github.com/ajv-validator/ajv-formats#readme
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Evgeny Poberezkin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
- sources: README.md
|
||||
text: "[MIT](https://github.com/ajv-validator/ajv-formats/blob/master/LICENSE)"
|
||||
notices: []
|
||||
@@ -1,16 +1,16 @@
|
||||
---
|
||||
name: ajv
|
||||
version: 6.12.6
|
||||
version: 8.17.1
|
||||
type: npm
|
||||
summary: Another JSON Schema Validator
|
||||
homepage: https://github.com/ajv-validator/ajv
|
||||
homepage: https://ajv.js.org
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |+
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2017 Evgeny Poberezkin
|
||||
Copyright (c) 2015-2021 Evgeny Poberezkin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -31,6 +31,6 @@ licenses:
|
||||
SOFTWARE.
|
||||
|
||||
- sources: README.md
|
||||
text: "[MIT](https://github.com/ajv-validator/ajv/blob/master/LICENSE)"
|
||||
text: "[MIT](./LICENSE)"
|
||||
notices: []
|
||||
...
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: body-parser
|
||||
version: 2.2.0
|
||||
version: 2.2.1
|
||||
type: npm
|
||||
summary: Node.js body parsing middleware
|
||||
homepage:
|
||||
@@ -35,15 +35,13 @@ licenses:
|
||||
text: |-
|
||||
[MIT](LICENSE)
|
||||
|
||||
[ci-image]: https://badgen.net/github/checks/expressjs/body-parser/master?label=ci
|
||||
[ci-image]: https://img.shields.io/github/actions/workflow/status/expressjs/body-parser/ci.yml?branch=master&label=ci
|
||||
[ci-url]: https://github.com/expressjs/body-parser/actions/workflows/ci.yml
|
||||
[coveralls-image]: https://badgen.net/coveralls/c/github/expressjs/body-parser/master
|
||||
[coveralls-image]: https://img.shields.io/coverallsCoverage/github/expressjs/body-parser?branch=master
|
||||
[coveralls-url]: https://coveralls.io/r/expressjs/body-parser?branch=master
|
||||
[node-version-image]: https://badgen.net/npm/node/body-parser
|
||||
[node-version-url]: https://nodejs.org/en/download
|
||||
[npm-downloads-image]: https://badgen.net/npm/dm/body-parser
|
||||
[npm-downloads-image]: https://img.shields.io/npm/dm/body-parser
|
||||
[npm-url]: https://npmjs.org/package/body-parser
|
||||
[npm-version-image]: https://badgen.net/npm/v/body-parser
|
||||
[npm-version-image]: https://img.shields.io/npm/v/body-parser
|
||||
[ossf-scorecard-badge]: https://api.scorecard.dev/projects/github.com/expressjs/body-parser/badge
|
||||
[ossf-scorecard-visualizer]: https://ossf.github.io/scorecard-visualizer/#/projects/github.com/expressjs/body-parser
|
||||
notices: []
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: debug
|
||||
version: 4.4.1
|
||||
version: 4.4.3
|
||||
type: npm
|
||||
summary: Lightweight debugging utility for Node.js and the browser
|
||||
homepage:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: express
|
||||
version: 5.1.0
|
||||
version: 5.2.1
|
||||
type: npm
|
||||
summary: Fast, unopinionated, minimalist web framework
|
||||
homepage: https://expressjs.com/
|
||||
@@ -36,15 +36,16 @@ licenses:
|
||||
text: |-
|
||||
[MIT](LICENSE)
|
||||
|
||||
[coveralls-image]: https://badgen.net/coveralls/c/github/expressjs/express/master
|
||||
[coveralls-image]: https://img.shields.io/coverallsCoverage/github/expressjs/express?branch=master
|
||||
[coveralls-url]: https://coveralls.io/r/expressjs/express?branch=master
|
||||
[github-actions-ci-image]: https://badgen.net/github/checks/expressjs/express/master?label=CI
|
||||
[github-actions-ci-image]: https://img.shields.io/github/actions/workflow/status/expressjs/express/ci.yml?branch=master&label=ci
|
||||
[github-actions-ci-url]: https://github.com/expressjs/express/actions/workflows/ci.yml
|
||||
[npm-downloads-image]: https://badgen.net/npm/dm/express
|
||||
[npm-downloads-image]: https://img.shields.io/npm/dm/express
|
||||
[npm-downloads-url]: https://npmcharts.com/compare/express?minimal=true
|
||||
[npm-url]: https://npmjs.org/package/express
|
||||
[npm-version-image]: https://badgen.net/npm/v/express
|
||||
[npm-version-image]: https://img.shields.io/npm/v/express
|
||||
[ossf-scorecard-badge]: https://api.scorecard.dev/projects/github.com/expressjs/express/badge
|
||||
[ossf-scorecard-visualizer]: https://ossf.github.io/scorecard-visualizer/#/projects/github.com/expressjs/express
|
||||
[Code of Conduct]: https://github.com/expressjs/express/blob/master/Code-Of-Conduct.md
|
||||
[Code of Conduct]: https://github.com/expressjs/.github/blob/HEAD/CODE_OF_CONDUCT.md
|
||||
[Contributing Guide]: https://github.com/expressjs/.github/blob/HEAD/CONTRIBUTING.md
|
||||
notices: []
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
name: fast-json-stable-stringify
|
||||
version: 2.1.0
|
||||
type: npm
|
||||
summary: deterministic `JSON.stringify()` - a faster version of substack's json-stable-strigify
|
||||
without jsonify
|
||||
homepage: https://github.com/epoberezkin/fast-json-stable-stringify
|
||||
license: other
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
This software is released under the MIT license:
|
||||
|
||||
Copyright (c) 2017 Evgeny Poberezkin
|
||||
Copyright (c) 2013 James Halliday
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
- sources: README.md
|
||||
text: "[MIT](https://github.com/epoberezkin/fast-json-stable-stringify/blob/master/LICENSE)"
|
||||
notices: []
|
||||
45
.licenses/npm/fast-uri.dep.yml
Normal file
45
.licenses/npm/fast-uri.dep.yml
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
name: fast-uri
|
||||
version: 3.1.0
|
||||
type: npm
|
||||
summary: Dependency-free RFC 3986 URI toolbox
|
||||
homepage: https://github.com/fastify/fast-uri
|
||||
license: other
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |-
|
||||
Copyright (c) 2011-2021, Gary Court until https://github.com/garycourt/uri-js/commit/a1acf730b4bba3f1097c9f52e7d9d3aba8cdcaae
|
||||
Copyright (c) 2021-present The Fastify team
|
||||
All rights reserved.
|
||||
|
||||
The Fastify team members are listed at https://github.com/fastify/fastify#team.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* The names of any contributors may not be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
* * *
|
||||
|
||||
The complete list of contributors can be found at:
|
||||
- https://github.com/garycourt/uri-js/graphs/contributors
|
||||
- sources: README.md
|
||||
text: Licensed under [BSD-3-Clause](./LICENSE).
|
||||
notices: []
|
||||
34
.licenses/npm/hono.dep.yml
Normal file
34
.licenses/npm/hono.dep.yml
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: hono
|
||||
version: 4.11.3
|
||||
type: npm
|
||||
summary: Web framework built on Web Standards
|
||||
homepage: https://hono.dev
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 - present, Yusuke Wada and Hono contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
- sources: README.md
|
||||
text: Distributed under the MIT License. See [LICENSE](LICENSE) for more information.
|
||||
notices: []
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: http-errors
|
||||
version: 2.0.0
|
||||
version: 2.0.1
|
||||
type: npm
|
||||
summary: Create HTTP error objects
|
||||
homepage:
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
name: iconv-lite
|
||||
version: 0.6.3
|
||||
version: 0.7.0
|
||||
type: npm
|
||||
summary: Convert character encodings in pure javascript.
|
||||
homepage: https://github.com/ashtuchkin/iconv-lite
|
||||
homepage: https://github.com/pillarjs/iconv-lite
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
|
||||
33
.licenses/npm/jose.dep.yml
Normal file
33
.licenses/npm/jose.dep.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: jose
|
||||
version: 6.1.3
|
||||
type: npm
|
||||
summary: JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno,
|
||||
Bun, and other Web-interoperable runtimes
|
||||
homepage: https://github.com/panva/jose
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE.md
|
||||
text: |
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Filip Skokan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
notices: []
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: js-yaml
|
||||
version: 4.1.0
|
||||
version: 4.1.1
|
||||
type: npm
|
||||
summary: YAML 1.2 parser and serializer
|
||||
homepage:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: json-schema-traverse
|
||||
version: 0.4.1
|
||||
version: 1.0.0
|
||||
type: npm
|
||||
summary: Traverse JSON Schema passing each schema object to callback
|
||||
homepage: https://github.com/epoberezkin/json-schema-traverse#readme
|
||||
|
||||
73
.licenses/npm/json-schema-typed.dep.yml
Normal file
73
.licenses/npm/json-schema-typed.dep.yml
Normal file
@@ -0,0 +1,73 @@
|
||||
---
|
||||
name: json-schema-typed
|
||||
version: 8.0.2
|
||||
type: npm
|
||||
summary: JSON Schema TypeScript definitions with complete inline documentation.
|
||||
homepage: https://github.com/RemyRylan/json-schema-typed/tree/main/dist/node
|
||||
license: other
|
||||
licenses:
|
||||
- sources: LICENSE.md
|
||||
text: |
|
||||
BSD 2-Clause License
|
||||
|
||||
Original source code is copyright (c) 2019-2025 Remy Rylan
|
||||
<https://github.com/RemyRylan>
|
||||
|
||||
All JSON Schema documentation and descriptions are copyright (c):
|
||||
|
||||
2009 [draft-0] IETF Trust <https://www.ietf.org/>, Kris Zyp <kris@sitepen.com>,
|
||||
and SitePen (USA) <https://www.sitepen.com/>.
|
||||
|
||||
2009 [draft-1] IETF Trust <https://www.ietf.org/>, Kris Zyp <kris@sitepen.com>,
|
||||
and SitePen (USA) <https://www.sitepen.com/>.
|
||||
|
||||
2010 [draft-2] IETF Trust <https://www.ietf.org/>, Kris Zyp <kris@sitepen.com>,
|
||||
and SitePen (USA) <https://www.sitepen.com/>.
|
||||
|
||||
2010 [draft-3] IETF Trust <https://www.ietf.org/>, Kris Zyp <kris@sitepen.com>,
|
||||
Gary Court <gary.court@gmail.com>, and SitePen (USA) <https://www.sitepen.com/>.
|
||||
|
||||
2013 [draft-4] IETF Trust <https://www.ietf.org/>), Francis Galiegue
|
||||
<fgaliegue@gmail.com>, Kris Zyp <kris@sitepen.com>, Gary Court
|
||||
<gary.court@gmail.com>, and SitePen (USA) <https://www.sitepen.com/>.
|
||||
|
||||
2018 [draft-7] IETF Trust <https://www.ietf.org/>, Austin Wright <aaa@bzfx.net>,
|
||||
Henry Andrews <henry@cloudflare.com>, Geraint Luff <luffgd@gmail.com>, and
|
||||
Cloudflare, Inc. <https://www.cloudflare.com/>.
|
||||
|
||||
2019 [draft-2019-09] IETF Trust <https://www.ietf.org/>, Austin Wright
|
||||
<aaa@bzfx.net>, Henry Andrews <andrews_henry@yahoo.com>, Ben Hutton
|
||||
<bh7@sanger.ac.uk>, and Greg Dennis <gregsdennis@yahoo.com>.
|
||||
|
||||
2020 [draft-2020-12] IETF Trust <https://www.ietf.org/>, Austin Wright
|
||||
<aaa@bzfx.net>, Henry Andrews <andrews_henry@yahoo.com>, Ben Hutton
|
||||
<ben@jsonschema.dev>, and Greg Dennis <gregsdennis@yahoo.com>.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
- sources: README.md
|
||||
text: |-
|
||||
[BSD-2-Clause][license]
|
||||
|
||||
[license]: https://github.com/RemyRylan/json-schema-typed/blob/main/dist/node/LICENSE.md
|
||||
notices: []
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
name: punycode
|
||||
version: 2.3.1
|
||||
type: npm
|
||||
summary: A robust Punycode converter that fully complies to RFC 3492 and RFC 5891,
|
||||
and works on nearly all JavaScript platforms.
|
||||
homepage: https://mths.be/punycode
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE-MIT.txt
|
||||
text: |
|
||||
Copyright Mathias Bynens <https://mathiasbynens.be/>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
- sources: README.md
|
||||
text: Punycode.js is available under the [MIT](https://mths.be/mit) license.
|
||||
notices: []
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: qs
|
||||
version: 6.14.0
|
||||
version: 6.14.1
|
||||
type: npm
|
||||
summary: A querystring parser that supports nesting and arrays, with a depth limit
|
||||
homepage: https://github.com/ljharb/qs
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: raw-body
|
||||
version: 3.0.0
|
||||
version: 3.0.2
|
||||
type: npm
|
||||
summary: Get and validate the raw body of a readable stream.
|
||||
homepage:
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
---
|
||||
name: statuses
|
||||
version: 2.0.1
|
||||
name: require-from-string
|
||||
version: 2.0.2
|
||||
type: npm
|
||||
summary: HTTP status utility
|
||||
summary: Require module from string
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |2
|
||||
|
||||
- sources: license
|
||||
text: |
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
|
||||
Copyright (c) 2016 Douglas Christopher Wilson <doug@somethingdoug.com>
|
||||
Copyright (c) Vsevolod Strukchinsky <floatdrop@gmail.com> (github.com/floatdrop)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -31,17 +29,6 @@ licenses:
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
- sources: README.md
|
||||
text: |-
|
||||
[MIT](LICENSE)
|
||||
|
||||
[ci-image]: https://badgen.net/github/checks/jshttp/statuses/master?label=ci
|
||||
[ci-url]: https://github.com/jshttp/statuses/actions?query=workflow%3Aci
|
||||
[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/statuses/master
|
||||
[coveralls-url]: https://coveralls.io/r/jshttp/statuses?branch=master
|
||||
[node-version-image]: https://badgen.net/npm/node/statuses
|
||||
[node-version-url]: https://nodejs.org/en/download
|
||||
[npm-downloads-image]: https://badgen.net/npm/dm/statuses
|
||||
[npm-url]: https://npmjs.org/package/statuses
|
||||
[npm-version-image]: https://badgen.net/npm/v/statuses
|
||||
- sources: readme.md
|
||||
text: MIT © [Vsevolod Strukchinsky](http://github.com/floatdrop)
|
||||
notices: []
|
||||
32
.licenses/npm/tmp.dep.yml
Normal file
32
.licenses/npm/tmp.dep.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: tmp
|
||||
version: 0.2.5
|
||||
type: npm
|
||||
summary: Temporary file and directory creator
|
||||
homepage: http://github.com/raszi/node-tmp
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 KARASZI István
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
notices: []
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
name: uri-js
|
||||
version: 4.4.1
|
||||
type: npm
|
||||
summary: An RFC 3986/3987 compliant, scheme extendable URI/IRI parsing/validating/resolving
|
||||
library for JavaScript.
|
||||
homepage: https://github.com/garycourt/uri-js
|
||||
license: other
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: "Copyright 2011 Gary Court. All rights reserved.\n\nRedistribution and use
|
||||
in source and binary forms, with or without modification, are permitted provided
|
||||
that the following conditions are met:\n\n1.\tRedistributions of source code must
|
||||
retain the above copyright notice, this list of conditions and the following disclaimer.\n\n2.\tRedistributions
|
||||
in binary form must reproduce the above copyright notice, this list of conditions
|
||||
and the following disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY GARY COURT \"AS IS\" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL GARY COURT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
||||
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
||||
OF SUCH DAMAGE.\n\nThe views and conclusions contained in the software and documentation
|
||||
are those of the authors and should not be interpreted as representing official
|
||||
policies, either expressed or implied, of Gary Court.\n"
|
||||
notices: []
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: zod-to-json-schema
|
||||
version: 3.24.6
|
||||
version: 3.25.0
|
||||
type: npm
|
||||
summary: Converts Zod schemas to Json Schemas
|
||||
homepage:
|
||||
|
||||
@@ -1 +1 @@
|
||||
20.9.0
|
||||
24.4.0
|
||||
143
README.md
143
README.md
@@ -123,6 +123,33 @@ supplied via the `input` parameter in YAML format. Additionally, you can
|
||||
provide file-based variables via `file_input`, where each key maps to a file
|
||||
path.
|
||||
|
||||
### Prompt.yml with model parameters
|
||||
|
||||
You can specify model parameters directly in your `.prompt.yml` files using the
|
||||
`modelParameters` key:
|
||||
|
||||
```yaml
|
||||
messages:
|
||||
- role: system
|
||||
content: Be as concise as possible
|
||||
- role: user
|
||||
content: 'Compare {{a}} and {{b}}, please'
|
||||
model: openai/gpt-4o
|
||||
modelParameters:
|
||||
maxCompletionTokens: 500
|
||||
temperature: 0.7
|
||||
```
|
||||
|
||||
| Key | Type | Description |
|
||||
| --------------------- | ------ | ----------------------------------------------------- |
|
||||
| `maxCompletionTokens` | number | The maximum number of tokens to generate |
|
||||
| `maxTokens` | number | The maximum number of tokens to generate (deprecated) |
|
||||
| `temperature` | number | The sampling temperature to use (0-1) |
|
||||
| `topP` | number | The nucleus sampling parameter to use (0-1) |
|
||||
|
||||
> ![Note]
|
||||
> Parameters set in `modelParameters` take precedence over the corresponding action inputs.
|
||||
|
||||
### Using a system prompt file
|
||||
|
||||
In addition to the regular prompt, you can provide a system prompt file instead
|
||||
@@ -156,12 +183,73 @@ steps:
|
||||
cat "${{ steps.inference.outputs.response-file }}"
|
||||
```
|
||||
|
||||
### Using custom headers
|
||||
|
||||
You can include custom HTTP headers in your API requests, which is useful for integrating with API Management platforms, adding tracking information, or routing requests through custom gateways.
|
||||
|
||||
#### YAML format (recommended for multiple headers)
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: AI Inference with Azure APIM
|
||||
id: inference
|
||||
uses: actions/ai-inference@v1
|
||||
with:
|
||||
prompt: 'Analyze this code for security issues...'
|
||||
endpoint: ${{ secrets.APIM_ENDPOINT }}
|
||||
token: ${{ secrets.APIM_KEY }}
|
||||
custom-headers: |
|
||||
Ocp-Apim-Subscription-Key: ${{ secrets.APIM_SUBSCRIPTION_KEY }}
|
||||
serviceName: code-review-workflow
|
||||
env: production
|
||||
team: security
|
||||
computer: github-actions
|
||||
```
|
||||
|
||||
#### JSON format (alternative for compact syntax)
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: AI Inference with Custom Headers
|
||||
id: inference
|
||||
uses: actions/ai-inference@v1
|
||||
with:
|
||||
prompt: 'Hello!'
|
||||
custom-headers: '{"X-Custom-Header": "value", "X-Team": "engineering", "X-Request-ID": "${{ github.run_id }}"}'
|
||||
```
|
||||
|
||||
#### Use cases for custom headers
|
||||
|
||||
- **API Management**: Integrate with Azure APIM, AWS API Gateway, Kong, or other API management platforms
|
||||
- **Request tracking**: Add correlation IDs, request IDs, or workflow identifiers
|
||||
- **Rate limiting**: Include quota or tier information for custom rate limiting
|
||||
- **Multi-tenancy**: Identify teams, services, or environments
|
||||
- **Observability**: Add metadata for logging, monitoring, and debugging
|
||||
- **Routing**: Control request routing through custom gateways or load balancers
|
||||
|
||||
**Header name requirements**: Header names must follow the HTTP token syntax defined in RFC 7230 (which permits underscores). For maximum compatibility with intermediaries and tooling, we recommend using only alphanumeric characters and hyphens.
|
||||
|
||||
**Security note**: Always use GitHub secrets for sensitive header values like API keys, tokens, or passwords. The action automatically masks common sensitive headers (containing `key`, `token`, `secret`, `password`, or `authorization`) in logs.
|
||||
|
||||
### GitHub MCP Integration (Model Context Protocol)
|
||||
|
||||
This action now supports **read-only** integration with the GitHub-hosted Model
|
||||
Context Protocol (MCP) server, which provides access to GitHub tools like
|
||||
repository management, issue tracking, and pull request operations.
|
||||
|
||||
#### Authentication
|
||||
|
||||
You can authenticate the MCP server with **either**:
|
||||
|
||||
1. **Personal Access Token (PAT)** – user-scoped token
|
||||
2. **GitHub App Installation Token** (`ghs_…`) – short-lived, app-scoped token
|
||||
> The built-in `GITHUB_TOKEN` is **not** accepted by the MCP server.
|
||||
> Using a **GitHub App installation token** is recommended in most CI environments because it is short-lived and least-privilege by design.
|
||||
|
||||
#### Enabling MCP in the action
|
||||
|
||||
Set `enable-github-mcp: true` and provide a token via `github-mcp-token`.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: AI Inference with GitHub Tools
|
||||
@@ -170,7 +258,7 @@ steps:
|
||||
with:
|
||||
prompt: 'List my open pull requests and create a summary'
|
||||
enable-github-mcp: true
|
||||
token: ${{ secrets.USER_PAT }}
|
||||
token: ${{ secrets.USER_PAT }} # or a ghs_ installation token
|
||||
```
|
||||
|
||||
If you want, you can use separate tokens for the AI inference endpoint
|
||||
@@ -185,9 +273,28 @@ steps:
|
||||
prompt: 'List my open pull requests and create a summary'
|
||||
enable-github-mcp: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
github-mcp-token: ${{ secrets.USER_PAT }}
|
||||
github-mcp-token: ${{ secrets.USER_PAT }} # or a ghs_ installation token
|
||||
```
|
||||
|
||||
#### Configuring GitHub MCP Toolsets
|
||||
|
||||
By default, the GitHub MCP server provides a standard set of tools (`context`, `repos`, `issues`, `pull_requests`, `users`). You can customize which toolsets are available by specifying the `github-mcp-toolsets` parameter:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: AI Inference with Custom Toolsets
|
||||
id: inference
|
||||
uses: actions/ai-inference@v2
|
||||
with:
|
||||
prompt: 'Analyze recent workflow runs and check security alerts'
|
||||
enable-github-mcp: true
|
||||
token: ${{ secrets.USER_PAT }}
|
||||
github-mcp-toolsets: 'repos,issues,pull_requests,actions,code_security'
|
||||
```
|
||||
|
||||
**Available toolsets:**
|
||||
See: [Tool configuration](https://github.com/github/github-mcp-server/blob/main/README.md#tool-configuration)
|
||||
|
||||
When MCP is enabled, the AI model will have access to GitHub tools and can
|
||||
perform actions like searching issues and PRs.
|
||||
|
||||
@@ -196,20 +303,24 @@ perform actions like searching issues and PRs.
|
||||
Various inputs are defined in [`action.yml`](action.yml) to let you configure
|
||||
the action:
|
||||
|
||||
| Name | Description | Default |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
|
||||
| `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 (supports .txt and .prompt.yml formats). If both `prompt` and `prompt-file` are provided, `prompt-file` takes precedence | `""` |
|
||||
| `input` | Template variables in YAML format for .prompt.yml files (e.g., `var1: value1` on separate lines) | `""` |
|
||||
| `file_input` | Template variables in YAML where values are file paths. The file contents are read and used for templating | `""` |
|
||||
| `system-prompt` | The system prompt to send to the model | `"You are a helpful assistant"` |
|
||||
| `system-prompt-file` | Path to a file containing the system prompt. If both `system-prompt` and `system-prompt-file` are provided, `system-prompt-file` takes precedence | `""` |
|
||||
| `model` | The model to use for inference. Must be available in the [GitHub Models](https://github.com/marketplace?type=models) catalog | `openai/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` |
|
||||
| `max-tokens` | The max number of tokens to generate | 200 |
|
||||
| `enable-github-mcp` | Enable Model Context Protocol integration with GitHub tools | `false` |
|
||||
| `github-mcp-token` | Token to use for GitHub MCP server (defaults to the main token if not specified). Use a separate PAT for tighter security | `""` |
|
||||
| Name | Description | Default |
|
||||
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------ |
|
||||
| `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 (supports .txt and .prompt.yml formats). If both `prompt` and `prompt-file` are provided, `prompt-file` takes precedence | `""` |
|
||||
| `input` | Template variables in YAML format for .prompt.yml files (e.g., `var1: value1` on separate lines) | `""` |
|
||||
| `file_input` | Template variables in YAML where values are file paths. The file contents are read and used for templating | `""` |
|
||||
| `system-prompt` | The system prompt to send to the model | `"You are a helpful assistant"` |
|
||||
| `system-prompt-file` | Path to a file containing the system prompt. If both `system-prompt` and `system-prompt-file` are provided, `system-prompt-file` takes precedence | `""` |
|
||||
| `model` | The model to use for inference. Must be available in the [GitHub Models](https://github.com/marketplace?type=models) catalog | `openai/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` |
|
||||
| `max-tokens` | The maximum number of tokens to generate (deprecated, use `max-completion-tokens` instead) | 200 |
|
||||
| `max-completion-tokens` | The maximum number of tokens to generate | `""` |
|
||||
| `temperature` | The sampling temperature to use (0-1) | `""` |
|
||||
| `top-p` | The nucleus sampling parameter to use (0-1) | `""` |
|
||||
| `enable-github-mcp` | Enable Model Context Protocol integration with GitHub tools | `false` |
|
||||
| `github-mcp-token` | Token to use for GitHub MCP server (defaults to the main token if not specified). | `""` |
|
||||
| `custom-headers` | Custom HTTP headers to include in API requests. Supports both YAML format (`header1: value1`) and JSON format (`{"header1": "value1"}`). Useful for API Management platforms, rate limiting, and request tracking. | `""` |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
||||
@@ -9,3 +9,4 @@ export const getBooleanInput = vi.fn<typeof core.getBooleanInput>()
|
||||
export const setOutput = vi.fn<typeof core.setOutput>()
|
||||
export const setFailed = vi.fn<typeof core.setFailed>()
|
||||
export const warning = vi.fn<typeof core.warning>()
|
||||
export const setSecret = vi.fn<typeof core.setSecret>()
|
||||
|
||||
@@ -106,7 +106,10 @@ describe('helpers.ts - inference request building', () => {
|
||||
undefined,
|
||||
undefined,
|
||||
'gpt-4',
|
||||
undefined,
|
||||
undefined,
|
||||
100,
|
||||
undefined,
|
||||
'https://api.test.com',
|
||||
'test-token',
|
||||
)
|
||||
@@ -117,7 +120,10 @@ describe('helpers.ts - inference request building', () => {
|
||||
{role: 'user', content: 'User message'},
|
||||
],
|
||||
modelName: 'gpt-4',
|
||||
temperature: undefined,
|
||||
topP: undefined,
|
||||
maxTokens: 100,
|
||||
maxCompletionTokens: undefined,
|
||||
endpoint: 'https://api.test.com',
|
||||
token: 'test-token',
|
||||
responseFormat: {
|
||||
@@ -136,7 +142,10 @@ describe('helpers.ts - inference request building', () => {
|
||||
'System prompt',
|
||||
'User prompt',
|
||||
'gpt-4',
|
||||
undefined,
|
||||
undefined,
|
||||
100,
|
||||
undefined,
|
||||
'https://api.test.com',
|
||||
'test-token',
|
||||
)
|
||||
@@ -147,7 +156,10 @@ describe('helpers.ts - inference request building', () => {
|
||||
{role: 'user', content: 'User prompt'},
|
||||
],
|
||||
modelName: 'gpt-4',
|
||||
temperature: undefined,
|
||||
topP: undefined,
|
||||
maxTokens: 100,
|
||||
maxCompletionTokens: undefined,
|
||||
endpoint: 'https://api.test.com',
|
||||
token: 'test-token',
|
||||
responseFormat: undefined,
|
||||
|
||||
@@ -11,7 +11,7 @@ vi.mock('fs', () => ({
|
||||
|
||||
vi.mock('@actions/core', () => core)
|
||||
|
||||
const {loadContentFromFileOrInput} = await import('../src/helpers.js')
|
||||
const {loadContentFromFileOrInput, parseCustomHeaders} = await import('../src/helpers.js')
|
||||
|
||||
describe('helpers.ts', () => {
|
||||
beforeEach(() => {
|
||||
@@ -132,4 +132,241 @@ describe('helpers.ts', () => {
|
||||
expect(result).toBe(defaultValue)
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseCustomHeaders', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('parses YAML format headers correctly', () => {
|
||||
const yamlInput = `header1: value1
|
||||
header2: value2
|
||||
X-Custom-Header: custom-value`
|
||||
|
||||
const result = parseCustomHeaders(yamlInput)
|
||||
|
||||
expect(result).toEqual({
|
||||
header1: 'value1',
|
||||
header2: 'value2',
|
||||
'X-Custom-Header': 'custom-value',
|
||||
})
|
||||
expect(core.debug).toHaveBeenCalledWith('Custom header added: header1: value1')
|
||||
expect(core.debug).toHaveBeenCalledWith('Custom header added: header2: value2')
|
||||
expect(core.debug).toHaveBeenCalledWith('Custom header added: X-Custom-Header: custom-value')
|
||||
})
|
||||
|
||||
it('parses JSON format headers correctly', () => {
|
||||
const jsonInput = '{"header1": "value1", "header2": "value2", "X-Team": "engineering"}'
|
||||
|
||||
const result = parseCustomHeaders(jsonInput)
|
||||
|
||||
expect(result).toEqual({
|
||||
header1: 'value1',
|
||||
header2: 'value2',
|
||||
'X-Team': 'engineering',
|
||||
})
|
||||
expect(core.debug).toHaveBeenCalledWith('Custom header added: header1: value1')
|
||||
expect(core.debug).toHaveBeenCalledWith('Custom header added: header2: value2')
|
||||
expect(core.debug).toHaveBeenCalledWith('Custom header added: X-Team: engineering')
|
||||
})
|
||||
|
||||
it('returns empty object for empty input', () => {
|
||||
expect(parseCustomHeaders('')).toEqual({})
|
||||
expect(parseCustomHeaders(' ')).toEqual({})
|
||||
expect(core.warning).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('masks sensitive header values in logs', () => {
|
||||
const yamlInput = `Ocp-Apim-Subscription-Key: secret123
|
||||
X-Api-Token: token456
|
||||
Authorization: Bearer abc123
|
||||
serviceName: my-service
|
||||
password: pass123`
|
||||
|
||||
const result = parseCustomHeaders(yamlInput)
|
||||
|
||||
expect(result).toEqual({
|
||||
'Ocp-Apim-Subscription-Key': 'secret123',
|
||||
'X-Api-Token': 'token456',
|
||||
Authorization: 'Bearer abc123',
|
||||
serviceName: 'my-service',
|
||||
password: 'pass123',
|
||||
})
|
||||
|
||||
// Sensitive headers should be masked
|
||||
expect(core.debug).toHaveBeenCalledWith('Custom header added: Ocp-Apim-Subscription-Key: ***MASKED***')
|
||||
expect(core.debug).toHaveBeenCalledWith('Custom header added: X-Api-Token: ***MASKED***')
|
||||
expect(core.debug).toHaveBeenCalledWith('Custom header added: Authorization: ***MASKED***')
|
||||
expect(core.debug).toHaveBeenCalledWith('Custom header added: password: ***MASKED***')
|
||||
|
||||
// Non-sensitive headers should not be masked
|
||||
expect(core.debug).toHaveBeenCalledWith('Custom header added: serviceName: my-service')
|
||||
})
|
||||
|
||||
it('validates header names and skips invalid ones', () => {
|
||||
const yamlInput = `valid-header: value1
|
||||
invalid header: value2
|
||||
invalid_underscore: value3
|
||||
invalid@header: value4
|
||||
valid123: value5`
|
||||
|
||||
const result = parseCustomHeaders(yamlInput)
|
||||
|
||||
expect(result).toEqual({
|
||||
'valid-header': 'value1',
|
||||
invalid_underscore: 'value3',
|
||||
valid123: 'value5',
|
||||
})
|
||||
|
||||
expect(core.warning).toHaveBeenCalledWith(expect.stringContaining('Skipping invalid header name: invalid header'))
|
||||
expect(core.warning).toHaveBeenCalledWith(expect.stringContaining('Skipping invalid header name: invalid@header'))
|
||||
})
|
||||
|
||||
it('warns and returns empty object for invalid JSON', () => {
|
||||
const invalidJson = '{invalid json}'
|
||||
|
||||
const result = parseCustomHeaders(invalidJson)
|
||||
|
||||
expect(result).toEqual({})
|
||||
expect(core.warning).toHaveBeenCalledWith(expect.stringContaining('Failed to parse custom headers'))
|
||||
})
|
||||
|
||||
it('warns and returns empty object for invalid YAML', () => {
|
||||
const invalidYaml = 'invalid: yaml: structure: bad'
|
||||
|
||||
const result = parseCustomHeaders(invalidYaml)
|
||||
|
||||
expect(result).toEqual({})
|
||||
expect(core.warning).toHaveBeenCalledWith(expect.stringContaining('Failed to parse custom headers'))
|
||||
})
|
||||
|
||||
it('warns and returns empty object for JSON array', () => {
|
||||
const jsonArray = '["header1", "header2"]'
|
||||
|
||||
const result = parseCustomHeaders(jsonArray)
|
||||
|
||||
expect(result).toEqual({})
|
||||
expect(core.warning).toHaveBeenCalledWith('Custom headers JSON must be an object, not null or an array')
|
||||
})
|
||||
|
||||
it('warns and returns empty object for null value', () => {
|
||||
// The string 'null' is valid YAML and gets parsed as null
|
||||
const nullValue = 'null'
|
||||
|
||||
const result = parseCustomHeaders(nullValue)
|
||||
|
||||
expect(result).toEqual({})
|
||||
expect(core.warning).toHaveBeenCalledWith('Custom headers YAML must be an object')
|
||||
})
|
||||
|
||||
it('warns and returns empty object for YAML array', () => {
|
||||
const yamlArray = `- header1
|
||||
- header2`
|
||||
|
||||
const result = parseCustomHeaders(yamlArray)
|
||||
|
||||
expect(result).toEqual({})
|
||||
expect(core.warning).toHaveBeenCalledWith('Custom headers YAML must be an object')
|
||||
})
|
||||
|
||||
it('converts non-string values to strings', () => {
|
||||
const jsonInput = '{"numericHeader": 123, "boolHeader": true, "nullHeader": null}'
|
||||
|
||||
const result = parseCustomHeaders(jsonInput)
|
||||
|
||||
expect(result).toEqual({
|
||||
numericHeader: '123',
|
||||
boolHeader: 'true',
|
||||
nullHeader: 'null',
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects header values with newline characters (LF)', () => {
|
||||
const jsonInput = '{"X-Custom-Header": "value\\nwith\\nnewline", "header1": "safe-value"}'
|
||||
|
||||
const result = parseCustomHeaders(jsonInput)
|
||||
|
||||
// Only the safe header should be accepted
|
||||
expect(result).toEqual({
|
||||
header1: 'safe-value',
|
||||
})
|
||||
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
'Skipping header "X-Custom-Header" because its value contains newline characters, which are not allowed in HTTP header values.',
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects header values with carriage return characters (CR)', () => {
|
||||
const jsonInput = '{"X-Injected": "value\\rwith\\rcarriage", "X-Safe": "safe-value"}'
|
||||
|
||||
const result = parseCustomHeaders(jsonInput)
|
||||
|
||||
// Only the safe header should be accepted
|
||||
expect(result).toEqual({
|
||||
'X-Safe': 'safe-value',
|
||||
})
|
||||
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
'Skipping header "X-Injected" because its value contains newline characters, which are not allowed in HTTP header values.',
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects header values with CRLF sequences', () => {
|
||||
const jsonInput = '{"X-Attack": "value\\r\\nInjected-Header: malicious", "X-Valid": "normal"}'
|
||||
|
||||
const result = parseCustomHeaders(jsonInput)
|
||||
|
||||
// Only the valid header should be accepted
|
||||
expect(result).toEqual({
|
||||
'X-Valid': 'normal',
|
||||
})
|
||||
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
'Skipping header "X-Attack" because its value contains newline characters, which are not allowed in HTTP header values.',
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects multiline YAML values for security', () => {
|
||||
const yamlInput = `header1: value1
|
||||
header2: |
|
||||
multiline
|
||||
value
|
||||
here`
|
||||
|
||||
const result = parseCustomHeaders(yamlInput)
|
||||
|
||||
// header2 should be rejected because it contains newlines
|
||||
expect(result).toEqual({
|
||||
header1: 'value1',
|
||||
})
|
||||
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
'Skipping header "header2" because its value contains newline characters, which are not allowed in HTTP header values.',
|
||||
)
|
||||
})
|
||||
|
||||
it('handles complex real-world Azure APIM example', () => {
|
||||
const apimHeaders = `Ocp-Apim-Subscription-Key: my-subscription-key-123
|
||||
serviceName: terraform-plan-workflow
|
||||
env: prod
|
||||
team: infrastructure
|
||||
computer: github-actions
|
||||
systemID: terraform-ci`
|
||||
|
||||
const result = parseCustomHeaders(apimHeaders)
|
||||
|
||||
expect(result).toEqual({
|
||||
'Ocp-Apim-Subscription-Key': 'my-subscription-key-123',
|
||||
serviceName: 'terraform-plan-workflow',
|
||||
env: 'prod',
|
||||
team: 'infrastructure',
|
||||
computer: 'github-actions',
|
||||
systemID: 'terraform-ci',
|
||||
})
|
||||
|
||||
// Only the subscription key should be masked
|
||||
expect(core.debug).toHaveBeenCalledWith('Custom header added: Ocp-Apim-Subscription-Key: ***MASKED***')
|
||||
expect(core.debug).toHaveBeenCalledWith('Custom header added: serviceName: terraform-plan-workflow')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -31,7 +31,7 @@ describe('inference.ts', () => {
|
||||
{role: 'user' as const, content: 'Hello, AI!'},
|
||||
],
|
||||
modelName: 'gpt-4',
|
||||
maxTokens: 100,
|
||||
maxCompletionTokens: 100,
|
||||
endpoint: 'https://api.test.com',
|
||||
token: 'test-token',
|
||||
}
|
||||
@@ -58,7 +58,7 @@ describe('inference.ts', () => {
|
||||
|
||||
expect(result).toBe('Hello, user!')
|
||||
expect(core.info).toHaveBeenCalledWith('Running simple inference without tools')
|
||||
expect(core.info).toHaveBeenCalledWith('Model response: Hello, user!')
|
||||
expect(core.debug).toHaveBeenCalledWith('Model response: Hello, user!')
|
||||
|
||||
// Verify the request structure
|
||||
expect(mockCreate).toHaveBeenCalledWith({
|
||||
@@ -72,9 +72,52 @@ describe('inference.ts', () => {
|
||||
content: 'Hello, AI!',
|
||||
},
|
||||
],
|
||||
max_tokens: 100,
|
||||
max_completion_tokens: 100,
|
||||
model: 'gpt-4',
|
||||
})
|
||||
|
||||
// Verify OpenAI client was initialized with empty custom headers
|
||||
expect(mockOpenAIClient).toHaveBeenCalledWith({
|
||||
apiKey: 'test-token',
|
||||
baseURL: 'https://api.test.com',
|
||||
defaultHeaders: {},
|
||||
})
|
||||
})
|
||||
|
||||
it('includes custom headers in OpenAI client', async () => {
|
||||
const requestWithHeaders = {
|
||||
...mockRequest,
|
||||
customHeaders: {
|
||||
'X-Custom-Header': 'custom-value',
|
||||
'Ocp-Apim-Subscription-Key': 'secret123',
|
||||
},
|
||||
}
|
||||
|
||||
const mockResponse = {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'Response with headers',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockCreate.mockResolvedValue(mockResponse)
|
||||
|
||||
const result = await simpleInference(requestWithHeaders)
|
||||
|
||||
expect(result).toBe('Response with headers')
|
||||
|
||||
// Verify OpenAI client was initialized with custom headers
|
||||
expect(mockOpenAIClient).toHaveBeenCalledWith({
|
||||
apiKey: 'test-token',
|
||||
baseURL: 'https://api.test.com',
|
||||
defaultHeaders: {
|
||||
'X-Custom-Header': 'custom-value',
|
||||
'Ocp-Apim-Subscription-Key': 'secret123',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('handles null response content', async () => {
|
||||
@@ -93,7 +136,7 @@ describe('inference.ts', () => {
|
||||
const result = await simpleInference(mockRequest)
|
||||
|
||||
expect(result).toBeNull()
|
||||
expect(core.info).toHaveBeenCalledWith('Model response: No response content')
|
||||
expect(core.debug).toHaveBeenCalledWith('Model response: No response content')
|
||||
})
|
||||
|
||||
it('includes response format when specified', async () => {
|
||||
@@ -133,7 +176,7 @@ describe('inference.ts', () => {
|
||||
content: 'Hello, AI!',
|
||||
},
|
||||
],
|
||||
max_tokens: 100,
|
||||
max_completion_tokens: 100,
|
||||
model: 'gpt-4',
|
||||
response_format: requestWithResponseFormat.responseFormat,
|
||||
})
|
||||
@@ -185,7 +228,51 @@ describe('inference.ts', () => {
|
||||
expect(callArgs.tools).toEqual(mockMcpClient.tools)
|
||||
expect(callArgs.response_format).toBeUndefined()
|
||||
expect(callArgs.model).toBe('gpt-4')
|
||||
expect(callArgs.max_tokens).toBe(100)
|
||||
expect(callArgs.max_completion_tokens).toBe(100)
|
||||
|
||||
// Verify OpenAI client was initialized with empty custom headers
|
||||
expect(mockOpenAIClient).toHaveBeenCalledWith({
|
||||
apiKey: 'test-token',
|
||||
baseURL: 'https://api.test.com',
|
||||
defaultHeaders: {},
|
||||
})
|
||||
})
|
||||
|
||||
it('includes custom headers in MCP inference', async () => {
|
||||
const requestWithHeaders = {
|
||||
...mockRequest,
|
||||
customHeaders: {
|
||||
serviceName: 'test-service',
|
||||
'X-Team': 'engineering',
|
||||
},
|
||||
}
|
||||
|
||||
const mockResponse = {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'MCP response with headers',
|
||||
tool_calls: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockCreate.mockResolvedValue(mockResponse)
|
||||
|
||||
const result = await mcpInference(requestWithHeaders, mockMcpClient)
|
||||
|
||||
expect(result).toBe('MCP response with headers')
|
||||
|
||||
// Verify OpenAI client was initialized with custom headers
|
||||
expect(mockOpenAIClient).toHaveBeenCalledWith({
|
||||
apiKey: 'test-token',
|
||||
baseURL: 'https://api.test.com',
|
||||
defaultHeaders: {
|
||||
serviceName: 'test-service',
|
||||
'X-Team': 'engineering',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('executes tool calls and continues conversation', async () => {
|
||||
@@ -546,4 +633,64 @@ describe('inference.ts', () => {
|
||||
expect(result).toBe('{"immediate": "result"}')
|
||||
})
|
||||
})
|
||||
|
||||
describe('token param routing', () => {
|
||||
it('sends max_tokens when only maxTokens is set', async () => {
|
||||
const requestWithMaxTokens = {
|
||||
...mockRequest,
|
||||
maxCompletionTokens: undefined,
|
||||
maxTokens: 100,
|
||||
}
|
||||
|
||||
const mockResponse = {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'Direct max_tokens response',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockCreate.mockResolvedValueOnce(mockResponse)
|
||||
|
||||
const result = await simpleInference(requestWithMaxTokens)
|
||||
|
||||
expect(result).toBe('Direct max_tokens response')
|
||||
expect(mockCreate).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Should have sent max_tokens directly
|
||||
expect(mockCreate.mock.calls[0][0]).toHaveProperty('max_tokens', 100)
|
||||
expect(mockCreate.mock.calls[0][0]).not.toHaveProperty('max_completion_tokens')
|
||||
})
|
||||
|
||||
it('sends neither token param when both are undefined', async () => {
|
||||
const requestWithNoTokens = {
|
||||
...mockRequest,
|
||||
maxCompletionTokens: undefined,
|
||||
maxTokens: undefined,
|
||||
}
|
||||
|
||||
const mockResponse = {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'No token limit response',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockCreate.mockResolvedValueOnce(mockResponse)
|
||||
|
||||
const result = await simpleInference(requestWithNoTokens)
|
||||
|
||||
expect(result).toBe('No token limit response')
|
||||
expect(mockCreate).toHaveBeenCalledTimes(1)
|
||||
|
||||
const params = mockCreate.mock.calls[0][0]
|
||||
expect(params).not.toHaveProperty('max_tokens')
|
||||
expect(params).not.toHaveProperty('max_completion_tokens')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -66,7 +66,7 @@ function mockInputs(inputs: Record<string, string> = {}): void {
|
||||
*/
|
||||
function verifyStandardResponse(): void {
|
||||
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'response', 'Hello, user!')
|
||||
expect(core.setOutput).toHaveBeenNthCalledWith(2, 'response-file', expect.stringContaining('modelResponse.txt'))
|
||||
expect(core.setOutput).toHaveBeenNthCalledWith(2, 'response-file', expect.stringContaining('modelResponse-'))
|
||||
}
|
||||
|
||||
vi.mock('fs', () => ({
|
||||
@@ -75,6 +75,15 @@ vi.mock('fs', () => ({
|
||||
writeFileSync: mockWriteFileSync,
|
||||
}))
|
||||
|
||||
// Mocks for tmp module to control temporary file creation
|
||||
const mockFileSync = vi.fn().mockReturnValue({
|
||||
name: '/secure/temp/dir/modelResponse-abc123.txt',
|
||||
})
|
||||
|
||||
vi.mock('tmp', () => ({
|
||||
fileSync: mockFileSync,
|
||||
}))
|
||||
|
||||
// Mock MCP and inference modules
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const mockConnectToGitHubMCP = vi.fn() as MockedFunction<any>
|
||||
@@ -127,6 +136,7 @@ describe('main.ts', () => {
|
||||
await run()
|
||||
|
||||
expect(core.setOutput).toHaveBeenCalled()
|
||||
expect(core.setSecret).toHaveBeenCalledWith('fake-token')
|
||||
verifyStandardResponse()
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(0)
|
||||
})
|
||||
@@ -159,9 +169,13 @@ describe('main.ts', () => {
|
||||
],
|
||||
modelName: 'gpt-4',
|
||||
maxTokens: 100,
|
||||
maxCompletionTokens: undefined,
|
||||
endpoint: 'https://api.test.com',
|
||||
token: 'fake-token',
|
||||
responseFormat: undefined,
|
||||
temperature: undefined,
|
||||
topP: undefined,
|
||||
customHeaders: {},
|
||||
})
|
||||
expect(mockConnectToGitHubMCP).not.toHaveBeenCalled()
|
||||
expect(mockMcpInference).not.toHaveBeenCalled()
|
||||
@@ -186,7 +200,8 @@ describe('main.ts', () => {
|
||||
|
||||
await run()
|
||||
|
||||
expect(mockConnectToGitHubMCP).toHaveBeenCalledWith('fake-token')
|
||||
expect(core.setSecret).toHaveBeenCalledWith('fake-token')
|
||||
expect(mockConnectToGitHubMCP).toHaveBeenCalledWith('fake-token', '')
|
||||
expect(mockMcpInference).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
messages: [
|
||||
@@ -213,7 +228,7 @@ describe('main.ts', () => {
|
||||
|
||||
await run()
|
||||
|
||||
expect(mockConnectToGitHubMCP).toHaveBeenCalledWith('fake-token')
|
||||
expect(mockConnectToGitHubMCP).toHaveBeenCalledWith('fake-token', '')
|
||||
expect(mockSimpleInference).toHaveBeenCalled()
|
||||
expect(mockMcpInference).not.toHaveBeenCalled()
|
||||
expect(core.warning).toHaveBeenCalledWith('MCP connection failed, falling back to simple inference')
|
||||
@@ -247,9 +262,13 @@ describe('main.ts', () => {
|
||||
],
|
||||
modelName: 'gpt-4',
|
||||
maxTokens: 100,
|
||||
maxCompletionTokens: undefined,
|
||||
endpoint: 'https://api.test.com',
|
||||
token: 'fake-token',
|
||||
responseFormat: undefined,
|
||||
temperature: undefined,
|
||||
topP: undefined,
|
||||
customHeaders: {},
|
||||
})
|
||||
verifyStandardResponse()
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(0)
|
||||
@@ -269,4 +288,25 @@ describe('main.ts', () => {
|
||||
expect(core.setFailed).toHaveBeenCalledWith(`File for prompt-file was not found: ${promptFile}`)
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(1)
|
||||
})
|
||||
|
||||
it('creates temporary files that persist for downstream steps', async () => {
|
||||
mockInputs({
|
||||
prompt: 'Test prompt',
|
||||
'system-prompt': 'You are a test assistant.',
|
||||
})
|
||||
|
||||
await run()
|
||||
|
||||
// Verify temp file is created with keep: true so it persists
|
||||
expect(mockFileSync).toHaveBeenCalledWith({
|
||||
prefix: 'modelResponse-',
|
||||
postfix: '.txt',
|
||||
keep: true,
|
||||
})
|
||||
|
||||
expect(core.setOutput).toHaveBeenNthCalledWith(2, 'response-file', '/secure/temp/dir/modelResponse-abc123.txt')
|
||||
expect(mockWriteFileSync).toHaveBeenCalledWith('/secure/temp/dir/modelResponse-abc123.txt', 'Hello, user!', 'utf-8')
|
||||
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -113,6 +113,40 @@ describe('mcp.ts', () => {
|
||||
expect(result?.tools).toHaveLength(0)
|
||||
expect(core.info).toHaveBeenCalledWith('Retrieved 0 tools from GitHub MCP server')
|
||||
})
|
||||
|
||||
it('uses default toolsets when toolsets parameter is not provided', async () => {
|
||||
const token = 'test-token'
|
||||
|
||||
mockConnect.mockResolvedValue(undefined)
|
||||
mockListTools.mockResolvedValue({tools: []})
|
||||
|
||||
await connectToGitHubMCP(token)
|
||||
|
||||
expect(core.info).toHaveBeenCalledWith('Using default GitHub MCP toolsets')
|
||||
})
|
||||
|
||||
it('uses custom toolsets when toolsets parameter is provided', async () => {
|
||||
const token = 'test-token'
|
||||
const toolsets = 'repos,issues,pull_requests,actions'
|
||||
|
||||
mockConnect.mockResolvedValue(undefined)
|
||||
mockListTools.mockResolvedValue({tools: []})
|
||||
|
||||
await connectToGitHubMCP(token, toolsets)
|
||||
|
||||
expect(core.info).toHaveBeenCalledWith('Using GitHub MCP toolsets: repos,issues,pull_requests,actions')
|
||||
})
|
||||
|
||||
it('ignores empty toolsets parameter', async () => {
|
||||
const token = 'test-token'
|
||||
|
||||
mockConnect.mockResolvedValue(undefined)
|
||||
mockListTools.mockResolvedValue({tools: []})
|
||||
|
||||
await connectToGitHubMCP(token, ' ')
|
||||
|
||||
expect(core.info).toHaveBeenCalledWith('Using default GitHub MCP toolsets')
|
||||
})
|
||||
})
|
||||
|
||||
describe('executeToolCall', () => {
|
||||
@@ -143,8 +177,8 @@ describe('mcp.ts', () => {
|
||||
name: 'test-tool',
|
||||
content: JSON.stringify(toolResult.content),
|
||||
})
|
||||
expect(core.info).toHaveBeenCalledWith('Executing GitHub MCP tool: test-tool with args: {"param": "value"}')
|
||||
expect(core.info).toHaveBeenCalledWith('GitHub MCP tool test-tool executed successfully')
|
||||
expect(core.debug).toHaveBeenCalledWith('Executing GitHub MCP tool: test-tool with args: {"param": "value"}')
|
||||
expect(core.debug).toHaveBeenCalledWith('GitHub MCP tool test-tool executed successfully')
|
||||
})
|
||||
|
||||
it('handles tool execution errors gracefully', async () => {
|
||||
|
||||
@@ -135,5 +135,17 @@ describe('prompt.ts', () => {
|
||||
it('errors on missing files', () => {
|
||||
expect(() => parseFileTemplateVariables('x: ./does-not-exist.txt')).toThrow('was not found')
|
||||
})
|
||||
|
||||
it('errors on non-string file paths', () => {
|
||||
expect(() => parseFileTemplateVariables('x: 123')).toThrow(
|
||||
"File template variable 'x' must be a string file path",
|
||||
)
|
||||
expect(() => parseFileTemplateVariables('x: true')).toThrow(
|
||||
"File template variable 'x' must be a string file path",
|
||||
)
|
||||
expect(() => parseFileTemplateVariables('x: { nested: "object" }')).toThrow(
|
||||
"File template variable 'x' must be a string file path",
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
26
action.yml
26
action.yml
@@ -43,9 +43,21 @@ inputs:
|
||||
required: false
|
||||
default: ''
|
||||
max-tokens:
|
||||
description: The maximum number of tokens to generate
|
||||
description: The maximum number of tokens to generate (deprecated)
|
||||
required: false
|
||||
default: '200'
|
||||
max-completion-tokens:
|
||||
description: The maximum number of tokens to generate
|
||||
required: false
|
||||
default: ''
|
||||
temperature:
|
||||
description: The sampling temperature to use (0-1)
|
||||
required: false
|
||||
default: ''
|
||||
top-p:
|
||||
description: The nucleus sampling parameter to use (0-1)
|
||||
required: false
|
||||
default: ''
|
||||
token:
|
||||
description: The token to use
|
||||
required: false
|
||||
@@ -55,7 +67,15 @@ inputs:
|
||||
required: false
|
||||
default: 'false'
|
||||
github-mcp-token:
|
||||
description: The token to use for GitHub MCP server (defaults to GITHUB_TOKEN if not specified)
|
||||
description: The token to use for GitHub MCP server (defaults to the main token if not specified). This must be a PAT for MCP to work.
|
||||
required: false
|
||||
default: ''
|
||||
github-mcp-toolsets:
|
||||
description: 'Comma-separated list of toolsets to enable for GitHub MCP (e.g., "repos,issues,pull_requests,actions"). Use "all" for all toolsets, "default" for default set. If not specified, uses default toolsets (context,repos,issues,pull_requests,users).'
|
||||
required: false
|
||||
default: ''
|
||||
custom-headers:
|
||||
description: 'Custom HTTP headers to include in API requests. Supports both YAML format (header1: value1) and JSON format ({"header1": "value1"}). Useful for API Management platforms, rate limiting, and request tracking.'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
@@ -67,5 +87,5 @@ outputs:
|
||||
description: The file path where the response is saved
|
||||
|
||||
runs:
|
||||
using: node20
|
||||
using: node24
|
||||
main: dist/index.js
|
||||
|
||||
34187
dist/index.js
generated
vendored
34187
dist/index.js
generated
vendored
File diff suppressed because it is too large
Load Diff
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
@@ -19,7 +19,7 @@ const compat = new FlatCompat({
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/coverage', '**/dist', '**/linter', '**/node_modules'],
|
||||
ignores: ['**/coverage', '**/dist', '**/linter', '**/node_modules', 'script/**'],
|
||||
},
|
||||
...compat.extends(
|
||||
'eslint:recommended',
|
||||
@@ -46,7 +46,7 @@ export default [
|
||||
|
||||
parserOptions: {
|
||||
project: ['tsconfig.eslint.json'],
|
||||
tsconfigRootDir: '.',
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
1251
package-lock.json
generated
1251
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
@@ -6,7 +6,7 @@
|
||||
".": "./dist/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": ">=24"
|
||||
},
|
||||
"scripts": {
|
||||
"bundle": "npm run format:write && npm run package",
|
||||
@@ -23,11 +23,13 @@
|
||||
"license": "MIT",
|
||||
"prettier": "@github/prettier-config",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.11.1",
|
||||
"@modelcontextprotocol/sdk": "^1.15.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"openai": "^5.11.0",
|
||||
"pkce-challenge": "^5.0.0"
|
||||
"@actions/core": "^3.0.0",
|
||||
"@modelcontextprotocol/sdk": "^1.26.0",
|
||||
"@types/tmp": "^0.2.6",
|
||||
"js-yaml": "^4.1.1",
|
||||
"openai": "^6.28.0",
|
||||
"pkce-challenge": "^5.0.0",
|
||||
"tmp": "^0.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.3.0",
|
||||
@@ -35,21 +37,21 @@
|
||||
"@github/prettier-config": "^0.0.6",
|
||||
"@rollup/plugin-commonjs": "^28.0.5",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||
"@rollup/plugin-typescript": "^12.3.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "^22.15.31",
|
||||
"@typescript-eslint/eslint-plugin": "^8.34.0",
|
||||
"@types/node": "^24.12.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.57.0",
|
||||
"@typescript-eslint/parser": "^8.32.1",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-import-resolver-typescript": "^4.4.3",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-prettier": "^5.4.1",
|
||||
"prettier": "^3.5.3",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-eslint": "^16.4.2",
|
||||
"rollup": "^4.43.0",
|
||||
"typescript": "^5.8.3",
|
||||
"rollup": "^4.59.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
71
script/mock-inference-server.mjs
Normal file
71
script/mock-inference-server.mjs
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* A simple mock OpenAI-compatible inference server for CI testing.
|
||||
* This returns predictable responses without needing real API credentials.
|
||||
*/
|
||||
|
||||
import http from 'http'
|
||||
|
||||
const PORT = process.env.MOCK_SERVER_PORT || 3456
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
let body = ''
|
||||
|
||||
req.on('data', chunk => {
|
||||
body += chunk.toString()
|
||||
})
|
||||
|
||||
req.on('end', () => {
|
||||
console.log(`[Mock Server] ${req.method} ${req.url}`)
|
||||
|
||||
// Handle chat completions endpoint
|
||||
if (req.url === '/chat/completions' && req.method === 'POST') {
|
||||
const request = JSON.parse(body)
|
||||
const userMessage = request.messages?.find(m => m.role === 'user')?.content || 'No prompt'
|
||||
|
||||
const response = {
|
||||
id: 'mock-completion-id',
|
||||
object: 'chat.completion',
|
||||
created: Date.now(),
|
||||
model: request.model || 'mock-model',
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: `Mock response to: "${userMessage.slice(0, 50)}..."`,
|
||||
},
|
||||
finish_reason: 'stop',
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
prompt_tokens: 10,
|
||||
completion_tokens: 20,
|
||||
total_tokens: 30,
|
||||
},
|
||||
}
|
||||
|
||||
res.writeHead(200, {'Content-Type': 'application/json'})
|
||||
res.end(JSON.stringify(response))
|
||||
return
|
||||
}
|
||||
|
||||
// Health check endpoint
|
||||
if (req.url === '/health' || req.url === '/') {
|
||||
res.writeHead(200, {'Content-Type': 'application/json'})
|
||||
res.end(JSON.stringify({status: 'ok'}))
|
||||
return
|
||||
}
|
||||
|
||||
// 404 for unknown routes
|
||||
res.writeHead(404, {'Content-Type': 'application/json'})
|
||||
res.end(JSON.stringify({error: 'Not found'}))
|
||||
})
|
||||
})
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(`[Mock Server] Listening on http://localhost:${PORT}`)
|
||||
console.log('[Mock Server] Endpoints:')
|
||||
console.log(' POST /chat/completions - Mock chat completion')
|
||||
console.log(' GET /health - Health check')
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as fs from 'fs'
|
||||
import * as yaml from 'js-yaml'
|
||||
import {PromptConfig} from './prompt.js'
|
||||
import {InferenceRequest} from './inference.js'
|
||||
|
||||
@@ -74,6 +75,84 @@ export function buildResponseFormat(
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse custom headers from YAML or JSON format
|
||||
* @param input - String in YAML or JSON format containing headers
|
||||
* @returns Record of header names to values, or empty object if invalid
|
||||
*/
|
||||
export function parseCustomHeaders(input: string): Record<string, string> {
|
||||
if (!input || input.trim() === '') {
|
||||
return {}
|
||||
}
|
||||
|
||||
const trimmedInput = input.trim()
|
||||
|
||||
try {
|
||||
// Try JSON first (check if it starts with { or [)
|
||||
if (trimmedInput.startsWith('{') || trimmedInput.startsWith('[')) {
|
||||
const parsed = JSON.parse(trimmedInput)
|
||||
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
||||
core.warning('Custom headers JSON must be an object, not null or an array')
|
||||
return {}
|
||||
}
|
||||
return validateAndMaskHeaders(parsed as Record<string, unknown>)
|
||||
}
|
||||
|
||||
// Try YAML
|
||||
const parsed = yaml.load(trimmedInput)
|
||||
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
||||
core.warning('Custom headers YAML must be an object')
|
||||
return {}
|
||||
}
|
||||
return validateAndMaskHeaders(parsed as Record<string, unknown>)
|
||||
} catch (error) {
|
||||
core.warning(`Failed to parse custom headers: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate header names and mask sensitive values in logs
|
||||
* @param headers - Raw headers object
|
||||
* @returns Validated headers with string values
|
||||
*/
|
||||
function validateAndMaskHeaders(headers: Record<string, unknown>): Record<string, string> {
|
||||
const validHeaders: Record<string, string> = {}
|
||||
const sensitivePatterns = ['key', 'token', 'secret', 'password', 'authorization']
|
||||
|
||||
for (const [name, value] of Object.entries(headers)) {
|
||||
// Validate header name (RFC 7230: token = 1*tchar)
|
||||
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
|
||||
if (!/^[A-Za-z0-9!#$%&'*+\-.^_`|~]+$/.test(name)) {
|
||||
core.warning(`Skipping invalid header name: ${name} (contains invalid characters)`)
|
||||
continue
|
||||
}
|
||||
|
||||
// Convert value to string
|
||||
const stringValue = String(value)
|
||||
|
||||
// Validate header value to prevent CRLF/header injection
|
||||
if (stringValue.includes('\r') || stringValue.includes('\n')) {
|
||||
core.warning(
|
||||
`Skipping header "${name}" because its value contains newline characters, which are not allowed in HTTP header values.`,
|
||||
)
|
||||
continue
|
||||
}
|
||||
validHeaders[name] = stringValue
|
||||
|
||||
// Mask sensitive headers in logs
|
||||
const lowerName = name.toLowerCase()
|
||||
const isSensitive = sensitivePatterns.some(pattern => lowerName.includes(pattern))
|
||||
if (isSensitive) {
|
||||
core.debug(`Custom header added: ${name}: ***MASKED***`)
|
||||
} else {
|
||||
core.debug(`Custom header added: ${name}: ${stringValue}`)
|
||||
}
|
||||
}
|
||||
|
||||
return validHeaders
|
||||
}
|
||||
|
||||
/**
|
||||
* Build complete InferenceRequest from prompt config and inputs
|
||||
*/
|
||||
@@ -82,9 +161,13 @@ export function buildInferenceRequest(
|
||||
systemPrompt: string | undefined,
|
||||
prompt: string | undefined,
|
||||
modelName: string,
|
||||
maxTokens: number,
|
||||
temperature: number | undefined,
|
||||
topP: number | undefined,
|
||||
maxTokens: number | undefined, // Deprecated
|
||||
maxCompletionTokens: number | undefined,
|
||||
endpoint: string,
|
||||
token: string,
|
||||
customHeaders?: Record<string, string>,
|
||||
): InferenceRequest {
|
||||
const messages = buildMessages(promptConfig, systemPrompt, prompt)
|
||||
const responseFormat = buildResponseFormat(promptConfig)
|
||||
@@ -92,9 +175,13 @@ export function buildInferenceRequest(
|
||||
return {
|
||||
messages,
|
||||
modelName,
|
||||
maxTokens,
|
||||
temperature,
|
||||
topP,
|
||||
maxTokens, // Deprecated
|
||||
maxCompletionTokens,
|
||||
endpoint,
|
||||
token,
|
||||
responseFormat,
|
||||
customHeaders,
|
||||
}
|
||||
}
|
||||
|
||||
107
src/inference.ts
107
src/inference.ts
@@ -12,10 +12,14 @@ interface ChatMessage {
|
||||
export interface InferenceRequest {
|
||||
messages: Array<{role: 'system' | 'user' | 'assistant' | 'tool'; content: string}>
|
||||
modelName: string
|
||||
maxTokens: number
|
||||
maxTokens?: number // Deprecated
|
||||
maxCompletionTokens?: number
|
||||
endpoint: string
|
||||
token: string
|
||||
temperature?: number
|
||||
topP?: number
|
||||
responseFormat?: {type: 'json_schema'; json_schema: unknown} // Processed response format for the API
|
||||
customHeaders?: Record<string, string> // Custom HTTP headers to include in API requests
|
||||
}
|
||||
|
||||
export interface InferenceResponse {
|
||||
@@ -30,6 +34,20 @@ export interface InferenceResponse {
|
||||
}>
|
||||
}
|
||||
|
||||
/**
|
||||
* Build according to what input was passed, default to max_tokens.
|
||||
* Only one of max_tokens or max_completion_tokens will be set.
|
||||
*/
|
||||
function buildMaxTokensParam(request: InferenceRequest): {max_tokens?: number; max_completion_tokens?: number} {
|
||||
if (request.maxCompletionTokens != null) {
|
||||
return {max_completion_tokens: request.maxCompletionTokens}
|
||||
}
|
||||
if (request.maxTokens != null) {
|
||||
return {max_tokens: request.maxTokens}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple one-shot inference without tools
|
||||
*/
|
||||
@@ -39,12 +57,15 @@ export async function simpleInference(request: InferenceRequest): Promise<string
|
||||
const client = new OpenAI({
|
||||
apiKey: request.token,
|
||||
baseURL: request.endpoint,
|
||||
defaultHeaders: request.customHeaders || {},
|
||||
})
|
||||
|
||||
const chatCompletionRequest: OpenAI.Chat.Completions.ChatCompletionCreateParams = {
|
||||
messages: request.messages as OpenAI.Chat.Completions.ChatCompletionMessageParam[],
|
||||
max_tokens: request.maxTokens,
|
||||
model: request.modelName,
|
||||
temperature: request.temperature,
|
||||
top_p: request.topP,
|
||||
...buildMaxTokensParam(request), // Note: solution around models using different underlying max tokens properties
|
||||
}
|
||||
|
||||
// Add response format if specified
|
||||
@@ -53,21 +74,10 @@ export async function simpleInference(request: InferenceRequest): Promise<string
|
||||
chatCompletionRequest.response_format = request.responseFormat as any
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await client.chat.completions.create(chatCompletionRequest)
|
||||
|
||||
if ('choices' in response) {
|
||||
const modelResponse = response.choices[0]?.message?.content
|
||||
core.info(`Model response: ${modelResponse || 'No response content'}`)
|
||||
return modelResponse || null
|
||||
} else {
|
||||
core.error(`Unexpected response format from API: ${JSON.stringify(response)}`)
|
||||
return null
|
||||
}
|
||||
} catch (error) {
|
||||
core.error(`API error: ${error}`)
|
||||
throw error
|
||||
}
|
||||
const response = await chatCompletion(client, chatCompletionRequest, 'simpleInference')
|
||||
const modelResponse = response.choices[0]?.message?.content
|
||||
core.debug(`Model response: ${modelResponse || 'No response content'}`)
|
||||
return modelResponse || null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,6 +92,7 @@ export async function mcpInference(
|
||||
const client = new OpenAI({
|
||||
apiKey: request.token,
|
||||
baseURL: request.endpoint,
|
||||
defaultHeaders: request.customHeaders || {},
|
||||
})
|
||||
|
||||
// Start with the pre-processed messages
|
||||
@@ -99,8 +110,10 @@ export async function mcpInference(
|
||||
|
||||
const chatCompletionRequest: OpenAI.Chat.Completions.ChatCompletionCreateParams = {
|
||||
messages: messages as OpenAI.Chat.Completions.ChatCompletionMessageParam[],
|
||||
max_tokens: request.maxTokens,
|
||||
model: request.modelName,
|
||||
temperature: request.temperature,
|
||||
top_p: request.topP,
|
||||
...buildMaxTokensParam(request), // Note: solution around models using different underlying max tokens properties
|
||||
}
|
||||
|
||||
// Add response format if specified (only on final iteration to avoid conflicts with tool calls)
|
||||
@@ -112,17 +125,13 @@ export async function mcpInference(
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await client.chat.completions.create(chatCompletionRequest)
|
||||
|
||||
if (!('choices' in response)) {
|
||||
throw new Error(`Unexpected response format from API: ${JSON.stringify(response)}`)
|
||||
}
|
||||
const response = await chatCompletion(client, chatCompletionRequest, `mcpInference iteration ${iterationCount}`)
|
||||
|
||||
const assistantMessage = response.choices[0]?.message
|
||||
const modelResponse = assistantMessage?.content
|
||||
const toolCalls = assistantMessage?.tool_calls
|
||||
|
||||
core.info(`Model response: ${modelResponse || 'No response content'}`)
|
||||
core.debug(`Model response: ${modelResponse || 'No response content'}`)
|
||||
|
||||
messages.push({
|
||||
role: 'assistant',
|
||||
@@ -133,20 +142,13 @@ export async function mcpInference(
|
||||
if (!toolCalls || toolCalls.length === 0) {
|
||||
core.info('No tool calls requested, ending GitHub MCP inference loop')
|
||||
|
||||
// If we have a response format set and we haven't explicitly run one final message iteration,
|
||||
// do another loop with the response format set
|
||||
if (request.responseFormat && !finalMessage) {
|
||||
core.info('Making one more MCP loop with the requested response format...')
|
||||
|
||||
// Add a user message requesting JSON format and try again
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content: `Please provide your response in the exact ${request.responseFormat.type} format specified.`,
|
||||
})
|
||||
|
||||
finalMessage = true
|
||||
|
||||
// Continue the loop to get a properly formatted response
|
||||
continue
|
||||
} else {
|
||||
return modelResponse || null
|
||||
@@ -154,13 +156,8 @@ export async function mcpInference(
|
||||
}
|
||||
|
||||
core.info(`Model requested ${toolCalls.length} tool calls`)
|
||||
|
||||
// Execute all tool calls via GitHub MCP
|
||||
const toolResults = await executeToolCalls(githubMcpClient.client, toolCalls as ToolCall[])
|
||||
|
||||
// Add tool results to the conversation
|
||||
messages.push(...toolResults)
|
||||
|
||||
core.info('Tool results added, continuing conversation...')
|
||||
} catch (error) {
|
||||
core.error(`OpenAI API error: ${error}`)
|
||||
@@ -178,3 +175,41 @@ export async function mcpInference(
|
||||
|
||||
return lastAssistantMessage?.content || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around OpenAI chat.completions.create with defensive handling for cases where
|
||||
* the SDK returns a raw string (e.g., unexpected content-type or streaming body) instead of
|
||||
* a parsed object. Ensures an object with a 'choices' array is returned or throws a descriptive error.
|
||||
*/
|
||||
async function chatCompletion(
|
||||
client: OpenAI,
|
||||
params: OpenAI.Chat.Completions.ChatCompletionCreateParams,
|
||||
context: string,
|
||||
): Promise<OpenAI.Chat.Completions.ChatCompletion> {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let response: any = await client.chat.completions.create(params)
|
||||
core.debug(`${context}: raw response typeof=${typeof response}`)
|
||||
|
||||
if (typeof response === 'string') {
|
||||
// Attempt to parse if we unexpectedly received a string
|
||||
try {
|
||||
response = JSON.parse(response)
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`${context}: Chat completion response was a string and not valid JSON (${(e as Error).message})`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!response || typeof response !== 'object' || !('choices' in response)) {
|
||||
throw new Error(`${context}: Unexpected response shape (no choices)`)
|
||||
}
|
||||
|
||||
return response as OpenAI.Chat.Completions.ChatCompletion
|
||||
} catch (err) {
|
||||
// Re-throw after logging for upstream handling
|
||||
core.error(`${context}: chatCompletion failed: ${err}`)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
56
src/main.ts
56
src/main.ts
@@ -1,10 +1,9 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as fs from 'fs'
|
||||
import * as os from 'os'
|
||||
import * as path from 'path'
|
||||
import * as tmp from 'tmp'
|
||||
import {connectToGitHubMCP} from './mcp.js'
|
||||
import {simpleInference, mcpInference} from './inference.js'
|
||||
import {loadContentFromFileOrInput, buildInferenceRequest} from './helpers.js'
|
||||
import {loadContentFromFileOrInput, buildInferenceRequest, parseCustomHeaders} from './helpers.js'
|
||||
import {
|
||||
loadPromptFile,
|
||||
parseTemplateVariables,
|
||||
@@ -13,8 +12,6 @@ import {
|
||||
parseFileTemplateVariables,
|
||||
} from './prompt.js'
|
||||
|
||||
const RESPONSE_FILE = 'modelResponse.txt'
|
||||
|
||||
/**
|
||||
* The main function for the action.
|
||||
*
|
||||
@@ -51,27 +48,53 @@ export async function run(): Promise<void> {
|
||||
|
||||
// Get common parameters
|
||||
const modelName = promptConfig?.model || core.getInput('model')
|
||||
const maxTokens = parseInt(core.getInput('max-tokens'), 10)
|
||||
|
||||
// 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
|
||||
|
||||
const token = process.env['GITHUB_TOKEN'] || core.getInput('token')
|
||||
if (token === undefined) {
|
||||
throw new Error('GITHUB_TOKEN is not set')
|
||||
}
|
||||
core.setSecret(token)
|
||||
|
||||
// Get GitHub MCP token (use dedicated token if provided, otherwise fall back to main token)
|
||||
const githubMcpToken = core.getInput('github-mcp-token') || token
|
||||
core.setSecret(githubMcpToken)
|
||||
|
||||
const githubMcpToolsets = core.getInput('github-mcp-toolsets')
|
||||
|
||||
const endpoint = core.getInput('endpoint')
|
||||
|
||||
// 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)
|
||||
|
||||
// Build the inference request with pre-processed messages and response format
|
||||
const inferenceRequest = buildInferenceRequest(
|
||||
promptConfig,
|
||||
systemPrompt,
|
||||
prompt,
|
||||
modelName,
|
||||
temperature,
|
||||
topP,
|
||||
maxTokens,
|
||||
maxCompletionTokens,
|
||||
endpoint,
|
||||
token,
|
||||
customHeaders,
|
||||
)
|
||||
|
||||
const enableMcp = core.getBooleanInput('enable-github-mcp') || false
|
||||
@@ -79,7 +102,7 @@ export async function run(): Promise<void> {
|
||||
let modelResponse: string | null = null
|
||||
|
||||
if (enableMcp) {
|
||||
const mcpClient = await connectToGitHubMCP(githubMcpToken)
|
||||
const mcpClient = await connectToGitHubMCP(githubMcpToken, githubMcpToolsets)
|
||||
|
||||
if (mcpClient) {
|
||||
modelResponse = await mcpInference(inferenceRequest, mcpClient)
|
||||
@@ -93,11 +116,19 @@ export async function run(): Promise<void> {
|
||||
|
||||
core.setOutput('response', modelResponse || '')
|
||||
|
||||
const responseFilePath = path.join(tempDir(), RESPONSE_FILE)
|
||||
core.setOutput('response-file', responseFilePath)
|
||||
// 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(responseFilePath, modelResponse, 'utf-8')
|
||||
fs.writeFileSync(responseFile.name, modelResponse, 'utf-8')
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
@@ -112,8 +143,3 @@ export async function run(): Promise<void> {
|
||||
// Force exit to prevent hanging on open connections
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
function tempDir(): string {
|
||||
const tempDirectory = process.env['RUNNER_TEMP'] || os.tmpdir()
|
||||
return tempDirectory
|
||||
}
|
||||
|
||||
24
src/mcp.ts
24
src/mcp.ts
@@ -35,17 +35,27 @@ export interface GitHubMCPClient {
|
||||
/**
|
||||
* Connect to the GitHub MCP server and retrieve available tools
|
||||
*/
|
||||
export async function connectToGitHubMCP(token: string): Promise<GitHubMCPClient | null> {
|
||||
export async function connectToGitHubMCP(token: string, toolsets?: string): Promise<GitHubMCPClient | null> {
|
||||
const githubMcpUrl = 'https://api.githubcopilot.com/mcp/'
|
||||
|
||||
core.info('Connecting to GitHub MCP server...')
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'X-MCP-Readonly': 'true',
|
||||
}
|
||||
|
||||
// Add toolsets header if specified
|
||||
if (toolsets && toolsets.trim() !== '') {
|
||||
headers['X-MCP-Toolsets'] = toolsets
|
||||
core.info(`Using GitHub MCP toolsets: ${toolsets}`)
|
||||
} else {
|
||||
core.info('Using default GitHub MCP toolsets')
|
||||
}
|
||||
|
||||
const transport = new StreamableHTTPClientTransport(new URL(githubMcpUrl), {
|
||||
requestInit: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'X-MCP-Readonly': 'true',
|
||||
},
|
||||
headers,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -86,7 +96,7 @@ export async function connectToGitHubMCP(token: string): Promise<GitHubMCPClient
|
||||
* Execute a single tool call via GitHub MCP
|
||||
*/
|
||||
export async function executeToolCall(githubMcpClient: Client, toolCall: ToolCall): Promise<ToolResult> {
|
||||
core.info(`Executing GitHub MCP tool: ${toolCall.function.name} with args: ${toolCall.function.arguments}`)
|
||||
core.debug(`Executing GitHub MCP tool: ${toolCall.function.name} with args: ${toolCall.function.arguments}`)
|
||||
|
||||
try {
|
||||
const args = JSON.parse(toolCall.function.arguments)
|
||||
@@ -96,7 +106,7 @@ export async function executeToolCall(githubMcpClient: Client, toolCall: ToolCal
|
||||
arguments: args,
|
||||
})
|
||||
|
||||
core.info(`GitHub MCP tool ${toolCall.function.name} executed successfully`)
|
||||
core.debug(`GitHub MCP tool ${toolCall.function.name} executed successfully`)
|
||||
|
||||
return {
|
||||
tool_call_id: toolCall.id,
|
||||
|
||||
@@ -7,9 +7,17 @@ export interface PromptMessage {
|
||||
content: string
|
||||
}
|
||||
|
||||
export interface ModelParameters {
|
||||
maxTokens?: number // Deprecated
|
||||
maxCompletionTokens?: number
|
||||
temperature?: number
|
||||
topP?: number
|
||||
}
|
||||
|
||||
export interface PromptConfig {
|
||||
messages: PromptMessage[]
|
||||
model?: string
|
||||
modelParameters?: ModelParameters
|
||||
responseFormat?: 'text' | 'json_schema'
|
||||
jsonSchema?: string
|
||||
}
|
||||
@@ -101,11 +109,8 @@ export function loadPromptFile(filePath: string, templateVariables: TemplateVari
|
||||
|
||||
const fileContent = fs.readFileSync(filePath, 'utf-8')
|
||||
|
||||
// Apply template variable substitution
|
||||
const processedContent = replaceTemplateVariables(fileContent, templateVariables)
|
||||
|
||||
try {
|
||||
const config = yaml.load(processedContent) as PromptConfig
|
||||
const config = yaml.load(fileContent) as PromptConfig
|
||||
|
||||
if (!config.messages || !Array.isArray(config.messages)) {
|
||||
throw new Error('Prompt file must contain a "messages" array')
|
||||
@@ -121,6 +126,14 @@ export function loadPromptFile(filePath: string, templateVariables: TemplateVari
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare messages by replacing template variables with actual content
|
||||
config.messages = config.messages.map(msg => {
|
||||
return {
|
||||
...msg,
|
||||
content: replaceTemplateVariables(msg.content, templateVariables),
|
||||
}
|
||||
})
|
||||
|
||||
return config
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse prompt file: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
||||
|
||||
Reference in New Issue
Block a user