Compare commits
231 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 | ||
|
|
b81b2afb83 | ||
|
|
9133f81330 | ||
|
|
7923b92ef8 | ||
|
|
e44da102bf | ||
|
|
866ae2b5d7 | ||
|
|
4685e0dcd4 | ||
|
|
0cbed4a106 | ||
|
|
009d5e6e28 | ||
|
|
18367df745 | ||
|
|
3c6ec33d64 | ||
|
|
0347935cb1 | ||
|
|
8c9e538880 | ||
|
|
de436346ec | ||
|
|
4b5bb5c538 | ||
|
|
ea4e7d8bb9 | ||
|
|
aaf9c5af33 | ||
|
|
15868b88f4 | ||
|
|
c37f296c98 | ||
|
|
e7ddc840ba | ||
|
|
fa321d4c78 | ||
|
|
3b5da63917 | ||
|
|
a620b9fa98 | ||
|
|
a6d2a86ab3 | ||
|
|
4b591cc529 | ||
|
|
ea24ec2ed4 | ||
|
|
b9f9444fb7 | ||
|
|
419f171f16 | ||
|
|
9bbcef8fa4 | ||
|
|
fc8527d1d9 | ||
|
|
719349dfcc | ||
|
|
2762750922 | ||
|
|
9386906af5 | ||
|
|
ca9eff7051 | ||
|
|
6bef1d0031 | ||
|
|
a5af2ca963 | ||
|
|
7e2aa19f3b | ||
|
|
a2235c5511 | ||
|
|
b1fc21bd19 | ||
|
|
305e9d3933 | ||
|
|
b1c0a96f18 | ||
|
|
ea289a3b79 | ||
|
|
77f8afc857 | ||
|
|
4ba8e6bc1e | ||
|
|
64cbe74d35 | ||
|
|
d045ae4018 | ||
|
|
0b15edbb56 | ||
|
|
8726487e22 | ||
|
|
79c7fc388f | ||
|
|
e43f4c40d0 | ||
|
|
7396fddf1d | ||
|
|
afe6f4df95 | ||
|
|
a915345307 | ||
|
|
9693b137b6 | ||
|
|
d0b2f23c43 | ||
|
|
0df96479bc | ||
|
|
446f075e3b | ||
|
|
ce58b26ac7 | ||
|
|
1cf96b0212 | ||
|
|
f79e4e11cb | ||
|
|
72102e50bf | ||
|
|
2bc30a525a | ||
|
|
8f64ac1284 | ||
|
|
1f89e942aa | ||
|
|
77a7cbe11b | ||
|
|
fcc8550115 | ||
|
|
29b5f08d0f | ||
|
|
6f7bd88d1d | ||
|
|
8ae5306787 | ||
|
|
9445295106 | ||
|
|
e385879671 | ||
|
|
1780121e3b | ||
|
|
b002da2928 | ||
|
|
ba509f9275 | ||
|
|
c05344404e | ||
|
|
aff9eb000b | ||
|
|
0479ac822e | ||
|
|
5a874b9aa1 | ||
|
|
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/
|
||||
|
||||
88
.github/workflows/ci.yml
vendored
88
.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
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
- name: Test
|
||||
id: npm-ci-test
|
||||
run: npm run ci-test
|
||||
run: npm run test
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
@@ -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,27 +108,62 @@ 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
|
||||
|
||||
- name: Create System Prompt File
|
||||
run:
|
||||
echo "You are a helpful AI assistant for testing." > system-prompt.txt
|
||||
run: echo "You are a helpful AI assistant for testing." > system-prompt.txt
|
||||
|
||||
- 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@12150456a73e248bdc94d0794898f94e23127c88
|
||||
uses: super-linter/super-linter/slim@61abc07d755095a68f4987d1c2c3d1d64408f1f9
|
||||
env:
|
||||
DEFAULT_BRANCH: main
|
||||
FILTER_REGEX_EXCLUDE: dist/**/*
|
||||
|
||||
21
.github/workflows/release-new-action-version.yml
vendored
Normal file
21
.github/workflows/release-new-action-version.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Release new action version
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
env:
|
||||
TAG_NAME: ${{ github.event.release.tag_name }}
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update_tag:
|
||||
name: Update the major tag to include the ${{ github.event.release.tag_name }}
|
||||
changes
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Update the ${{ env.TAG_NAME }} tag
|
||||
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
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
name: "@types/estree"
|
||||
version: 1.0.7
|
||||
name: "@types/tmp"
|
||||
version: 0.2.6
|
||||
type: npm
|
||||
summary: TypeScript definitions for estree
|
||||
homepage: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/estree
|
||||
summary: TypeScript definitions for tmp
|
||||
homepage: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/tmp
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
@@ -1,18 +1,16 @@
|
||||
---
|
||||
name: picomatch
|
||||
version: 4.0.2
|
||||
name: ajv-formats
|
||||
version: 3.0.1
|
||||
type: npm
|
||||
summary: Blazing fast and accurate glob matcher written in JavaScript, with no dependencies
|
||||
and full support for standard and extended Bash glob features, including braces,
|
||||
extglobs, POSIX brackets, and regular expressions.
|
||||
homepage: https://github.com/micromatch/picomatch
|
||||
summary: Format validation for Ajv v7+
|
||||
homepage: https://github.com/ajv-validator/ajv-formats#readme
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The MIT License (MIT)
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-present, Jon Schlinkert.
|
||||
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
|
||||
@@ -21,18 +19,16 @@ licenses:
|
||||
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 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.
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
- sources: README.md
|
||||
text: |-
|
||||
Copyright © 2017-present, [Jon Schlinkert](https://github.com/jonschlinkert).
|
||||
Released under the [MIT License](LICENSE).
|
||||
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: []
|
||||
...
|
||||
|
||||
265
.licenses/npm/argparse.dep.yml
Normal file
265
.licenses/npm/argparse.dep.yml
Normal file
@@ -0,0 +1,265 @@
|
||||
---
|
||||
name: argparse
|
||||
version: 2.0.1
|
||||
type: npm
|
||||
summary: CLI arguments parser. Native port of python's argparse.
|
||||
homepage:
|
||||
license: other
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
A. HISTORY OF THE SOFTWARE
|
||||
==========================
|
||||
|
||||
Python was created in the early 1990s by Guido van Rossum at Stichting
|
||||
Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
|
||||
as a successor of a language called ABC. Guido remains Python's
|
||||
principal author, although it includes many contributions from others.
|
||||
|
||||
In 1995, Guido continued his work on Python at the Corporation for
|
||||
National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
|
||||
in Reston, Virginia where he released several versions of the
|
||||
software.
|
||||
|
||||
In May 2000, Guido and the Python core development team moved to
|
||||
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
|
||||
year, the PythonLabs team moved to Digital Creations, which became
|
||||
Zope Corporation. In 2001, the Python Software Foundation (PSF, see
|
||||
https://www.python.org/psf/) was formed, a non-profit organization
|
||||
created specifically to own Python-related Intellectual Property.
|
||||
Zope Corporation was a sponsoring member of the PSF.
|
||||
|
||||
All Python releases are Open Source (see http://www.opensource.org for
|
||||
the Open Source Definition). Historically, most, but not all, Python
|
||||
releases have also been GPL-compatible; the table below summarizes
|
||||
the various releases.
|
||||
|
||||
Release Derived Year Owner GPL-
|
||||
from compatible? (1)
|
||||
|
||||
0.9.0 thru 1.2 1991-1995 CWI yes
|
||||
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
|
||||
1.6 1.5.2 2000 CNRI no
|
||||
2.0 1.6 2000 BeOpen.com no
|
||||
1.6.1 1.6 2001 CNRI yes (2)
|
||||
2.1 2.0+1.6.1 2001 PSF no
|
||||
2.0.1 2.0+1.6.1 2001 PSF yes
|
||||
2.1.1 2.1+2.0.1 2001 PSF yes
|
||||
2.1.2 2.1.1 2002 PSF yes
|
||||
2.1.3 2.1.2 2002 PSF yes
|
||||
2.2 and above 2.1.1 2001-now PSF yes
|
||||
|
||||
Footnotes:
|
||||
|
||||
(1) GPL-compatible doesn't mean that we're distributing Python under
|
||||
the GPL. All Python licenses, unlike the GPL, let you distribute
|
||||
a modified version without making your changes open source. The
|
||||
GPL-compatible licenses make it possible to combine Python with
|
||||
other software that is released under the GPL; the others don't.
|
||||
|
||||
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
|
||||
because its license has a choice of law clause. According to
|
||||
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
|
||||
is "not incompatible" with the GPL.
|
||||
|
||||
Thanks to the many outside volunteers who have worked under Guido's
|
||||
direction to make these releases possible.
|
||||
|
||||
|
||||
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
|
||||
===============================================================
|
||||
|
||||
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
||||
--------------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
||||
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
||||
otherwise using this software ("Python") in source or binary form and
|
||||
its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
||||
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
||||
analyze, test, perform and/or display publicly, prepare derivative works,
|
||||
distribute, and otherwise use Python alone or in any derivative version,
|
||||
provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
||||
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
|
||||
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation;
|
||||
All Rights Reserved" are retained in Python alone or in any derivative version
|
||||
prepared by Licensee.
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on
|
||||
or incorporates Python or any part thereof, and wants to make
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python.
|
||||
|
||||
4. PSF is making Python available to Licensee on an "AS IS"
|
||||
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. Nothing in this License Agreement shall be deemed to create any
|
||||
relationship of agency, partnership, or joint venture between PSF and
|
||||
Licensee. This License Agreement does not grant permission to use PSF
|
||||
trademarks or trade name in a trademark sense to endorse or promote
|
||||
products or services of Licensee, or any third party.
|
||||
|
||||
8. By copying, installing or otherwise using Python, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
||||
|
||||
|
||||
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
|
||||
-------------------------------------------
|
||||
|
||||
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
|
||||
|
||||
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
|
||||
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
|
||||
Individual or Organization ("Licensee") accessing and otherwise using
|
||||
this software in source or binary form and its associated
|
||||
documentation ("the Software").
|
||||
|
||||
2. Subject to the terms and conditions of this BeOpen Python License
|
||||
Agreement, BeOpen hereby grants Licensee a non-exclusive,
|
||||
royalty-free, world-wide license to reproduce, analyze, test, perform
|
||||
and/or display publicly, prepare derivative works, distribute, and
|
||||
otherwise use the Software alone or in any derivative version,
|
||||
provided, however, that the BeOpen Python License is retained in the
|
||||
Software, alone or in any derivative version prepared by Licensee.
|
||||
|
||||
3. BeOpen is making the Software available to Licensee on an "AS IS"
|
||||
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
|
||||
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
|
||||
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
|
||||
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
5. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
6. This License Agreement shall be governed by and interpreted in all
|
||||
respects by the law of the State of California, excluding conflict of
|
||||
law provisions. Nothing in this License Agreement shall be deemed to
|
||||
create any relationship of agency, partnership, or joint venture
|
||||
between BeOpen and Licensee. This License Agreement does not grant
|
||||
permission to use BeOpen trademarks or trade names in a trademark
|
||||
sense to endorse or promote products or services of Licensee, or any
|
||||
third party. As an exception, the "BeOpen Python" logos available at
|
||||
http://www.pythonlabs.com/logos.html may be used according to the
|
||||
permissions granted on that web page.
|
||||
|
||||
7. By copying, installing or otherwise using the software, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
||||
|
||||
|
||||
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
|
||||
---------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Corporation for National
|
||||
Research Initiatives, having an office at 1895 Preston White Drive,
|
||||
Reston, VA 20191 ("CNRI"), and the Individual or Organization
|
||||
("Licensee") accessing and otherwise using Python 1.6.1 software in
|
||||
source or binary form and its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, CNRI
|
||||
hereby grants Licensee a nonexclusive, royalty-free, world-wide
|
||||
license to reproduce, analyze, test, perform and/or display publicly,
|
||||
prepare derivative works, distribute, and otherwise use Python 1.6.1
|
||||
alone or in any derivative version, provided, however, that CNRI's
|
||||
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
|
||||
1995-2001 Corporation for National Research Initiatives; All Rights
|
||||
Reserved" are retained in Python 1.6.1 alone or in any derivative
|
||||
version prepared by Licensee. Alternately, in lieu of CNRI's License
|
||||
Agreement, Licensee may substitute the following text (omitting the
|
||||
quotes): "Python 1.6.1 is made available subject to the terms and
|
||||
conditions in CNRI's License Agreement. This Agreement together with
|
||||
Python 1.6.1 may be located on the Internet using the following
|
||||
unique, persistent identifier (known as a handle): 1895.22/1013. This
|
||||
Agreement may also be obtained from a proxy server on the Internet
|
||||
using the following URL: http://hdl.handle.net/1895.22/1013".
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on
|
||||
or incorporates Python 1.6.1 or any part thereof, and wants to make
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python 1.6.1.
|
||||
|
||||
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
|
||||
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. This License Agreement shall be governed by the federal
|
||||
intellectual property law of the United States, including without
|
||||
limitation the federal copyright law, and, to the extent such
|
||||
U.S. federal law does not apply, by the law of the Commonwealth of
|
||||
Virginia, excluding Virginia's conflict of law provisions.
|
||||
Notwithstanding the foregoing, with regard to derivative works based
|
||||
on Python 1.6.1 that incorporate non-separable material that was
|
||||
previously distributed under the GNU General Public License (GPL), the
|
||||
law of the Commonwealth of Virginia shall govern this License
|
||||
Agreement only as to issues arising under or with respect to
|
||||
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
|
||||
License Agreement shall be deemed to create any relationship of
|
||||
agency, partnership, or joint venture between CNRI and Licensee. This
|
||||
License Agreement does not grant permission to use CNRI trademarks or
|
||||
trade name in a trademark sense to endorse or promote products or
|
||||
services of Licensee, or any third party.
|
||||
|
||||
8. By clicking on the "ACCEPT" button where indicated, or by copying,
|
||||
installing or otherwise using Python 1.6.1, Licensee agrees to be
|
||||
bound by the terms and conditions of this License Agreement.
|
||||
|
||||
ACCEPT
|
||||
|
||||
|
||||
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
|
||||
--------------------------------------------------
|
||||
|
||||
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
|
||||
The Netherlands. All rights reserved.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software and its
|
||||
documentation for any purpose and without fee is hereby granted,
|
||||
provided that the above copyright notice appear in all copies and that
|
||||
both that copyright notice and this permission notice appear in
|
||||
supporting documentation, and that the name of Stichting Mathematisch
|
||||
Centrum or CWI not be used in advertising or publicity pertaining to
|
||||
distribution of the software without specific, written prior
|
||||
permission.
|
||||
|
||||
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
|
||||
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
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,20 +0,0 @@
|
||||
---
|
||||
name: estree-walker
|
||||
version: 2.0.2
|
||||
type: npm
|
||||
summary: Traverse an ESTree-compliant AST
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |-
|
||||
Copyright (c) 2015-20 [these people](https://github.com/Rich-Harris/estree-walker/graphs/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: MIT
|
||||
notices: []
|
||||
@@ -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,16 +1,16 @@
|
||||
---
|
||||
name: "@rollup/pluginutils"
|
||||
version: 5.1.4
|
||||
name: js-yaml
|
||||
version: 4.1.1
|
||||
type: npm
|
||||
summary: A set of utility functions commonly used by Rollup plugins
|
||||
homepage: https://github.com/rollup/plugins/tree/master/packages/pluginutils#readme
|
||||
summary: YAML 1.2 parser and serializer
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The MIT License (MIT)
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2019 RollupJS Plugin Contributors (https://github.com/rollup/plugins/graphs/contributors)
|
||||
Copyright (C) 2011-2015 by Vitaly Puzrin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -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: []
|
||||
212
.licenses/npm/openai.dep.yml
Normal file
212
.licenses/npm/openai.dep.yml
Normal file
@@ -0,0 +1,212 @@
|
||||
---
|
||||
name: openai
|
||||
version: 5.11.0
|
||||
type: npm
|
||||
summary: The official TypeScript library for the OpenAI API
|
||||
homepage:
|
||||
license: apache-2.0
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |2
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2025 OpenAI
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
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,16 +1,16 @@
|
||||
---
|
||||
name: "@rollup/plugin-json"
|
||||
version: 6.1.0
|
||||
name: require-from-string
|
||||
version: 2.0.2
|
||||
type: npm
|
||||
summary: Convert .json files to ES6 modules
|
||||
homepage: https://github.com/rollup/plugins/tree/master/packages/json#readme
|
||||
summary: Require module from string
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
- sources: license
|
||||
text: |
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 RollupJS Plugin Contributors (https://github.com/rollup/plugins/graphs/contributors)
|
||||
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
|
||||
@@ -29,4 +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 © [Vsevolod Strukchinsky](http://github.com/floatdrop)
|
||||
notices: []
|
||||
@@ -1,438 +0,0 @@
|
||||
---
|
||||
name: rollup
|
||||
version: 4.43.0
|
||||
type: npm
|
||||
summary: Next-generation ES module bundler
|
||||
homepage: https://rollupjs.org/
|
||||
license: other
|
||||
licenses:
|
||||
- sources: LICENSE.md
|
||||
text: "# Rollup core license\nRollup is released under the MIT license:\n\nThe MIT
|
||||
License (MIT)\n\nCopyright (c) 2017 [these people](https://github.com/rollup/rollup/graphs/contributors)\n\nPermission
|
||||
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:\n\nThe above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.\n\nTHE 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.\n\n# Licenses of bundled dependencies\nThe
|
||||
published Rollup artifact additionally contains code with the following licenses:\nMIT,
|
||||
ISC, 0BSD\n\n# Bundled dependencies:\n## @jridgewell/sourcemap-codec\nLicense:
|
||||
MIT\nBy: Rich Harris\nRepository: git+https://github.com/jridgewell/sourcemap-codec.git\n\n>
|
||||
The MIT License\n> \n> Copyright (c) 2015 Rich Harris\n> \n> Permission is hereby
|
||||
granted, free of charge, to any person obtaining a copy\n> of this software and
|
||||
associated documentation files (the \"Software\"), to deal\n> in the Software
|
||||
without restriction, including without limitation the rights\n> to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell\n> copies of the Software,
|
||||
and to permit persons to whom the Software is\n> furnished to do so, subject to
|
||||
the following conditions:\n> \n> The above copyright notice and this permission
|
||||
notice shall be included in\n> all copies or substantial portions of the Software.\n>
|
||||
\n> THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR\n> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n>
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n>
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n> LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n> OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n> THE SOFTWARE.\n\n---------------------------------------\n\n##
|
||||
@rollup/pluginutils\nLicense: MIT\nBy: Rich Harris\nRepository: rollup/plugins\n\n>
|
||||
The MIT License (MIT)\n> \n> Copyright (c) 2019 RollupJS Plugin Contributors (https://github.com/rollup/plugins/graphs/contributors)\n>
|
||||
\n> Permission is hereby granted, free of charge, to any person obtaining a copy\n>
|
||||
of this software and associated documentation files (the \"Software\"), to deal\n>
|
||||
in the Software without restriction, including without limitation the rights\n>
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n> copies
|
||||
of the Software, and to permit persons to whom the Software is\n> furnished to
|
||||
do so, subject to the following conditions:\n> \n> The above copyright notice
|
||||
and this permission notice shall be included in\n> all copies or substantial portions
|
||||
of the Software.\n> \n> THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR\n> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY,\n> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
||||
EVENT SHALL THE\n> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER\n> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM,\n> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN\n> THE SOFTWARE.\n\n---------------------------------------\n\n## anymatch\nLicense:
|
||||
ISC\nBy: Elan Shanker\nRepository: https://github.com/micromatch/anymatch\n\n>
|
||||
The ISC License\n> \n> Copyright (c) 2019 Elan Shanker, Paul Miller (https://paulmillr.com)\n>
|
||||
\n> Permission to use, copy, modify, and/or distribute this software for any\n>
|
||||
purpose with or without fee is hereby granted, provided that the above\n> copyright
|
||||
notice and this permission notice appear in all copies.\n> \n> THE SOFTWARE IS
|
||||
PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n> WITH REGARD TO THIS
|
||||
SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n> MERCHANTABILITY AND FITNESS. IN
|
||||
NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
DAMAGES OR ANY DAMAGES\n> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||||
WHETHER IN AN\n> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
OUT OF OR\n> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n---------------------------------------\n\n##
|
||||
binary-extensions\nLicense: MIT\nBy: Sindre Sorhus\nRepository: sindresorhus/binary-extensions\n\n>
|
||||
MIT License\n> \n> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)\n>
|
||||
Copyright (c) Paul Miller (https://paulmillr.com)\n> \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:\n>
|
||||
\n> The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.\n> \n> 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.\n\n---------------------------------------\n\n##
|
||||
braces\nLicense: MIT\nBy: Jon Schlinkert, Brian Woodward, Elan Shanker, Eugene
|
||||
Sharygin, hemanth.hm\nRepository: micromatch/braces\n\n> The MIT License (MIT)\n>
|
||||
\n> Copyright (c) 2014-present, Jon Schlinkert.\n> \n> Permission is hereby granted,
|
||||
free of charge, to any person obtaining a copy\n> of this software and associated
|
||||
documentation files (the \"Software\"), to deal\n> in the Software without restriction,
|
||||
including without limitation the rights\n> to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell\n> copies of the Software, and to permit persons
|
||||
to whom the Software is\n> furnished to do so, subject to the following conditions:\n>
|
||||
\n> The above copyright notice and this permission notice shall be included in\n>
|
||||
all copies or substantial portions of the Software.\n> \n> THE SOFTWARE IS PROVIDED
|
||||
\"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n> IMPLIED, INCLUDING BUT
|
||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n> FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n> AUTHORS OR COPYRIGHT HOLDERS
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n> LIABILITY, WHETHER IN AN ACTION OF
|
||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n> OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN\n> THE SOFTWARE.\n\n---------------------------------------\n\n##
|
||||
builtin-modules\nLicense: MIT\nBy: Sindre Sorhus\nRepository: sindresorhus/builtin-modules\n\n>
|
||||
MIT License\n> \n> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)\n>
|
||||
\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:\n> \n> The above copyright notice and
|
||||
this permission notice shall be included in all copies or substantial portions
|
||||
of the Software.\n> \n> 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.\n\n---------------------------------------\n\n##
|
||||
chokidar\nLicense: MIT\nBy: Paul Miller, Elan Shanker\nRepository: git+https://github.com/paulmillr/chokidar.git\n\n>
|
||||
The MIT License (MIT)\n> \n> Copyright (c) 2012-2019 Paul Miller (https://paulmillr.com),
|
||||
Elan Shanker\n> \n> Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy\n> of this software and associated documentation files (the “Software”),
|
||||
to deal\n> in the Software without restriction, including without limitation the
|
||||
rights\n> to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell\n> copies of the Software, and to permit persons to whom the Software is\n>
|
||||
furnished to do so, subject to the following conditions:\n> \n> The above copyright
|
||||
notice and this permission notice shall be included in\n> all copies or substantial
|
||||
portions of the Software.\n> \n> THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY
|
||||
OF ANY KIND, EXPRESS OR\n> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY,\n> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE\n> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER\n> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM,\n> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN\n> THE SOFTWARE.\n\n---------------------------------------\n\n## date-time\nLicense:
|
||||
MIT\nBy: Sindre Sorhus\nRepository: sindresorhus/date-time\n\n> MIT License\n>
|
||||
\n> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)\n>
|
||||
\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:\n> \n> The above copyright notice and
|
||||
this permission notice shall be included in all copies or substantial portions
|
||||
of the Software.\n> \n> 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.\n\n---------------------------------------\n\n##
|
||||
fill-range\nLicense: MIT\nBy: Jon Schlinkert, Edo Rivai, Paul Miller, Rouven Weßling\nRepository:
|
||||
jonschlinkert/fill-range\n\n> The MIT License (MIT)\n> \n> Copyright (c) 2014-present,
|
||||
Jon Schlinkert.\n> \n> Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy\n> of this software and associated documentation files (the \"Software\"),
|
||||
to deal\n> in the Software without restriction, including without limitation the
|
||||
rights\n> to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell\n> copies of the Software, and to permit persons to whom the Software is\n>
|
||||
furnished to do so, subject to the following conditions:\n> \n> The above copyright
|
||||
notice and this permission notice shall be included in\n> all copies or substantial
|
||||
portions of the Software.\n> \n> THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY
|
||||
OF ANY KIND, EXPRESS OR\n> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY,\n> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE\n> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER\n> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM,\n> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN\n> THE SOFTWARE.\n\n---------------------------------------\n\n## flru\nLicense:
|
||||
MIT\nBy: Luke Edwards\nRepository: lukeed/flru\n\n> MIT License\n> \n> Copyright
|
||||
(c) Luke Edwards <luke.edwards05@gmail.com> (lukeed.com)\n> \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:\n> \n> The above copyright notice and this permission notice shall
|
||||
be included in all copies or substantial portions of the Software.\n> \n> 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.\n\n---------------------------------------\n\n##
|
||||
glob-parent\nLicense: ISC\nBy: Gulp Team, Elan Shanker, Blaine Bublitz\nRepository:
|
||||
gulpjs/glob-parent\n\n> The ISC License\n> \n> Copyright (c) 2015, 2019 Elan Shanker\n>
|
||||
\n> Permission to use, copy, modify, and/or distribute this software for any\n>
|
||||
purpose with or without fee is hereby granted, provided that the above\n> copyright
|
||||
notice and this permission notice appear in all copies.\n> \n> THE SOFTWARE IS
|
||||
PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n> WITH REGARD TO THIS
|
||||
SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n> MERCHANTABILITY AND FITNESS. IN
|
||||
NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
DAMAGES OR ANY DAMAGES\n> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||||
WHETHER IN AN\n> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
OUT OF OR\n> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n---------------------------------------\n\n##
|
||||
is-binary-path\nLicense: MIT\nBy: Sindre Sorhus\nRepository: sindresorhus/is-binary-path\n\n>
|
||||
MIT License\n> \n> Copyright (c) 2019 Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com),
|
||||
Paul Miller (https://paulmillr.com)\n> \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:\n> \n>
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.\n> \n> 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.\n\n---------------------------------------\n\n##
|
||||
is-extglob\nLicense: MIT\nBy: Jon Schlinkert\nRepository: jonschlinkert/is-extglob\n\n>
|
||||
The MIT License (MIT)\n> \n> Copyright (c) 2014-2016, Jon Schlinkert\n> \n> Permission
|
||||
is hereby granted, free of charge, to any person obtaining a copy\n> of this software
|
||||
and associated documentation files (the \"Software\"), to deal\n> in the Software
|
||||
without restriction, including without limitation the rights\n> to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell\n> copies of the Software,
|
||||
and to permit persons to whom the Software is\n> furnished to do so, subject to
|
||||
the following conditions:\n> \n> The above copyright notice and this permission
|
||||
notice shall be included in\n> all copies or substantial portions of the Software.\n>
|
||||
\n> THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR\n> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n>
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n>
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n> LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n> OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n> THE SOFTWARE.\n\n---------------------------------------\n\n##
|
||||
is-glob\nLicense: MIT\nBy: Jon Schlinkert, Brian Woodward, Daniel Perez\nRepository:
|
||||
micromatch/is-glob\n\n> The MIT License (MIT)\n> \n> Copyright (c) 2014-2017,
|
||||
Jon Schlinkert.\n> \n> Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy\n> of this software and associated documentation files (the \"Software\"),
|
||||
to deal\n> in the Software without restriction, including without limitation the
|
||||
rights\n> to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell\n> copies of the Software, and to permit persons to whom the Software is\n>
|
||||
furnished to do so, subject to the following conditions:\n> \n> The above copyright
|
||||
notice and this permission notice shall be included in\n> all copies or substantial
|
||||
portions of the Software.\n> \n> THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY
|
||||
OF ANY KIND, EXPRESS OR\n> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY,\n> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE\n> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER\n> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM,\n> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN\n> THE SOFTWARE.\n\n---------------------------------------\n\n## is-number\nLicense:
|
||||
MIT\nBy: Jon Schlinkert, Olsten Larck, Rouven Weßling\nRepository: jonschlinkert/is-number\n\n>
|
||||
The MIT License (MIT)\n> \n> Copyright (c) 2014-present, Jon Schlinkert.\n> \n>
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy\n>
|
||||
of this software and associated documentation files (the \"Software\"), to deal\n>
|
||||
in the Software without restriction, including without limitation the rights\n>
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n> copies
|
||||
of the Software, and to permit persons to whom the Software is\n> furnished to
|
||||
do so, subject to the following conditions:\n> \n> The above copyright notice
|
||||
and this permission notice shall be included in\n> all copies or substantial portions
|
||||
of the Software.\n> \n> THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR\n> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY,\n> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
||||
EVENT SHALL THE\n> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER\n> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM,\n> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN\n> THE SOFTWARE.\n\n---------------------------------------\n\n## is-reference\nLicense:
|
||||
MIT\nBy: Rich Harris\nRepository: git+https://github.com/Rich-Harris/is-reference.git\n\n---------------------------------------\n\n##
|
||||
locate-character\nLicense: MIT\nBy: Rich Harris\nRepository: git+https://gitlab.com/Rich-Harris/locate-character.git\n\n---------------------------------------\n\n##
|
||||
magic-string\nLicense: MIT\nBy: Rich Harris\nRepository: https://github.com/rich-harris/magic-string\n\n>
|
||||
Copyright 2018 Rich Harris\n> \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:\n> \n> The above copyright
|
||||
notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.\n> \n> 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.\n\n---------------------------------------\n\n## normalize-path\nLicense:
|
||||
MIT\nBy: Jon Schlinkert, Blaine Bublitz\nRepository: jonschlinkert/normalize-path\n\n>
|
||||
The MIT License (MIT)\n> \n> Copyright (c) 2014-2018, Jon Schlinkert.\n> \n> Permission
|
||||
is hereby granted, free of charge, to any person obtaining a copy\n> of this software
|
||||
and associated documentation files (the \"Software\"), to deal\n> in the Software
|
||||
without restriction, including without limitation the rights\n> to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell\n> copies of the Software,
|
||||
and to permit persons to whom the Software is\n> furnished to do so, subject to
|
||||
the following conditions:\n> \n> The above copyright notice and this permission
|
||||
notice shall be included in\n> all copies or substantial portions of the Software.\n>
|
||||
\n> THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR\n> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n>
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n>
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n> LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n> OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n> THE SOFTWARE.\n\n---------------------------------------\n\n##
|
||||
parse-ms\nLicense: MIT\nBy: Sindre Sorhus\nRepository: sindresorhus/parse-ms\n\n>
|
||||
MIT License\n> \n> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)\n>
|
||||
\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:\n> \n> The above copyright notice and
|
||||
this permission notice shall be included in all copies or substantial portions
|
||||
of the Software.\n> \n> 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.\n\n---------------------------------------\n\n##
|
||||
picocolors\nLicense: ISC\nBy: Alexey Raspopov\nRepository: alexeyraspopov/picocolors\n\n>
|
||||
ISC License\n> \n> Copyright (c) 2021-2024 Oleksii Raspopov, Kostiantyn Denysov,
|
||||
Anton Verinov\n> \n> Permission to use, copy, modify, and/or distribute this software
|
||||
for any\n> purpose with or without fee is hereby granted, provided that the above\n>
|
||||
copyright notice and this permission notice appear in all copies.\n> \n> THE SOFTWARE
|
||||
IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n> WITH REGARD TO
|
||||
THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n> MERCHANTABILITY AND FITNESS.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n> ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n> WHATSOEVER RESULTING FROM LOSS OF USE,
|
||||
DATA OR PROFITS, WHETHER IN AN\n> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
|
||||
ACTION, ARISING OUT OF\n> OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
||||
SOFTWARE.\n\n---------------------------------------\n\n## picomatch\nLicense:
|
||||
MIT\nBy: Jon Schlinkert\nRepository: micromatch/picomatch\n\n> The MIT License
|
||||
(MIT)\n> \n> Copyright (c) 2017-present, Jon Schlinkert.\n> \n> Permission is
|
||||
hereby granted, free of charge, to any person obtaining a copy\n> of this software
|
||||
and associated documentation files (the \"Software\"), to deal\n> in the Software
|
||||
without restriction, including without limitation the rights\n> to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell\n> copies of the Software,
|
||||
and to permit persons to whom the Software is\n> furnished to do so, subject to
|
||||
the following conditions:\n> \n> The above copyright notice and this permission
|
||||
notice shall be included in\n> all copies or substantial portions of the Software.\n>
|
||||
\n> THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR\n> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n>
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n>
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n> LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n> OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n> THE SOFTWARE.\n\n---------------------------------------\n\n##
|
||||
pretty-bytes\nLicense: MIT\nBy: Sindre Sorhus\nRepository: sindresorhus/pretty-bytes\n\n>
|
||||
MIT License\n> \n> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)\n>
|
||||
\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:\n> \n> The above copyright notice and
|
||||
this permission notice shall be included in all copies or substantial portions
|
||||
of the Software.\n> \n> 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.\n\n---------------------------------------\n\n##
|
||||
pretty-ms\nLicense: MIT\nBy: Sindre Sorhus\nRepository: sindresorhus/pretty-ms\n\n>
|
||||
MIT License\n> \n> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)\n>
|
||||
\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:\n> \n> The above copyright notice and
|
||||
this permission notice shall be included in all copies or substantial portions
|
||||
of the Software.\n> \n> 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.\n\n---------------------------------------\n\n##
|
||||
readdirp\nLicense: MIT\nBy: Thorsten Lorenz, Paul Miller\nRepository: git://github.com/paulmillr/readdirp.git\n\n>
|
||||
MIT License\n> \n> Copyright (c) 2012-2019 Thorsten Lorenz, Paul Miller (https://paulmillr.com)\n>
|
||||
\n> Permission is hereby granted, free of charge, to any person obtaining a copy\n>
|
||||
of this software and associated documentation files (the \"Software\"), to deal\n>
|
||||
in the Software without restriction, including without limitation the rights\n>
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n> copies
|
||||
of the Software, and to permit persons to whom the Software is\n> furnished to
|
||||
do so, subject to the following conditions:\n> \n> The above copyright notice
|
||||
and this permission notice shall be included in all\n> copies or substantial portions
|
||||
of the Software.\n> \n> THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR\n> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY,\n> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
||||
EVENT SHALL THE\n> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER\n> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM,\n> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE\n> SOFTWARE.\n\n---------------------------------------\n\n## signal-exit\nLicense:
|
||||
ISC\nBy: Ben Coe\nRepository: https://github.com/tapjs/signal-exit.git\n\n> The
|
||||
ISC License\n> \n> Copyright (c) 2015-2023 Benjamin Coe, Isaac Z. Schlueter, and
|
||||
Contributors\n> \n> Permission to use, copy, modify, and/or distribute this software\n>
|
||||
for any purpose with or without fee is hereby granted, provided\n> that the above
|
||||
copyright notice and this permission notice\n> appear in all copies.\n> \n> THE
|
||||
SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n> WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES\n> OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE\n> LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES\n> OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS,\n> WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
OR OTHER TORTIOUS ACTION,\n> ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
||||
OF THIS SOFTWARE.\n\n---------------------------------------\n\n## time-zone\nLicense:
|
||||
MIT\nBy: Sindre Sorhus\nRepository: sindresorhus/time-zone\n\n> MIT License\n>
|
||||
\n> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)\n>
|
||||
\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:\n> \n> The above copyright notice and
|
||||
this permission notice shall be included in all copies or substantial portions
|
||||
of the Software.\n> \n> 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.\n\n---------------------------------------\n\n##
|
||||
to-regex-range\nLicense: MIT\nBy: Jon Schlinkert, Rouven Weßling\nRepository:
|
||||
micromatch/to-regex-range\n\n> The MIT License (MIT)\n> \n> Copyright (c) 2015-present,
|
||||
Jon Schlinkert.\n> \n> Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy\n> of this software and associated documentation files (the \"Software\"),
|
||||
to deal\n> in the Software without restriction, including without limitation the
|
||||
rights\n> to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell\n> copies of the Software, and to permit persons to whom the Software is\n>
|
||||
furnished to do so, subject to the following conditions:\n> \n> The above copyright
|
||||
notice and this permission notice shall be included in\n> all copies or substantial
|
||||
portions of the Software.\n> \n> THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY
|
||||
OF ANY KIND, EXPRESS OR\n> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY,\n> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE\n> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER\n> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM,\n> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN\n> THE SOFTWARE.\n\n---------------------------------------\n\n## tslib\nLicense:
|
||||
0BSD\nBy: Microsoft Corp.\nRepository: https://github.com/Microsoft/tslib.git\n\n>
|
||||
Copyright (c) Microsoft Corporation.\n> \n> Permission to use, copy, modify, and/or
|
||||
distribute this software for any\n> purpose with or without fee is hereby granted.\n>
|
||||
\n> THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH\n> REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n>
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n>
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n>
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n>
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n> PERFORMANCE
|
||||
OF THIS SOFTWARE.\n\n---------------------------------------\n\n## yargs-parser\nLicense:
|
||||
ISC\nBy: Ben Coe\nRepository: https://github.com/yargs/yargs-parser.git\n\n> Copyright
|
||||
(c) 2016, Contributors\n> \n> Permission to use, copy, modify, and/or distribute
|
||||
this software\n> for any purpose with or without fee is hereby granted, provided\n>
|
||||
that the above copyright notice and this permission notice\n> appear in all copies.\n>
|
||||
\n> THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n>
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES\n> OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE\n> LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES\n> OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS,\n> WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
OR OTHER TORTIOUS ACTION,\n> ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
||||
OF THIS SOFTWARE.\n"
|
||||
- sources: README.md
|
||||
text: "[MIT](https://github.com/rollup/rollup/blob/master/LICENSE.md)"
|
||||
notices: []
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
name: statuses
|
||||
version: 2.0.1
|
||||
type: npm
|
||||
summary: HTTP status utility
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |2
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
|
||||
Copyright (c) 2016 Douglas Christopher Wilson <doug@somethingdoug.com>
|
||||
|
||||
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](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
|
||||
notices: []
|
||||
@@ -1,14 +1,16 @@
|
||||
---
|
||||
name: "@rollup/rollup-linux-x64-musl"
|
||||
version: 4.43.0
|
||||
name: tmp
|
||||
version: 0.2.5
|
||||
type: npm
|
||||
summary: Native bindings for Rollup
|
||||
homepage: https://rollupjs.org/
|
||||
summary: Temporary file and directory creator
|
||||
homepage: http://github.com/raszi/node-tmp
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: Auto-generated MIT license text
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
MIT License
|
||||
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
|
||||
@@ -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
|
||||
@@ -1,16 +0,0 @@
|
||||
# See: https://prettier.io/docs/en/configuration
|
||||
|
||||
printWidth: 80
|
||||
tabWidth: 2
|
||||
useTabs: false
|
||||
semi: false
|
||||
singleQuote: true
|
||||
quoteProps: as-needed
|
||||
jsxSingleQuote: false
|
||||
trailingComma: none
|
||||
bracketSpacing: true
|
||||
bracketSameLine: true
|
||||
arrowParens: always
|
||||
proseWrap: always
|
||||
htmlWhitespaceSensitivity: css
|
||||
endOfLine: lf
|
||||
239
README.md
239
README.md
@@ -4,7 +4,6 @@
|
||||

|
||||
[](https://github.com/actions/typescript-action/actions/workflows/check-dist.yml)
|
||||
[](https://github.com/actions/typescript-action/actions/workflows/codeql-analysis.yml)
|
||||
[](./badges/coverage.svg)
|
||||
|
||||
Use AI models from [GitHub Models](https://github.com/marketplace/models) in
|
||||
your workflows.
|
||||
@@ -36,17 +35,121 @@ jobs:
|
||||
|
||||
### Using a prompt file
|
||||
|
||||
You can also provide a prompt file instead of an inline prompt:
|
||||
You can also provide a prompt file instead of an inline prompt. The action
|
||||
supports both plain text files and structured `.prompt.yml` files:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: Run AI Inference with Prompt File
|
||||
- name: Run AI Inference with Text File
|
||||
id: inference
|
||||
uses: actions/ai-inference@v1
|
||||
with:
|
||||
prompt-file: './path/to/prompt.txt'
|
||||
```
|
||||
|
||||
### Using GitHub prompt.yml files
|
||||
|
||||
For more advanced use cases, you can use structured `.prompt.yml` files that
|
||||
support templating, custom models, and JSON schema responses:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: Run AI Inference with Prompt YAML
|
||||
id: inference
|
||||
uses: actions/ai-inference@v1
|
||||
with:
|
||||
prompt-file: './.github/prompts/sample.prompt.yml'
|
||||
input: |
|
||||
var1: hello
|
||||
var2: ${{ steps.some-step.outputs.output }}
|
||||
var3: |
|
||||
Lorem Ipsum
|
||||
Hello World
|
||||
file_input: |
|
||||
var4: ./path/to/long-text.txt
|
||||
var5: ./path/to/config.json
|
||||
```
|
||||
|
||||
#### Simple prompt.yml example
|
||||
|
||||
```yaml
|
||||
messages:
|
||||
- role: system
|
||||
content: Be as concise as possible
|
||||
- role: user
|
||||
content: 'Compare {{a}} and {{b}}, please'
|
||||
model: openai/gpt-4o
|
||||
```
|
||||
|
||||
#### Prompt.yml with JSON schema support
|
||||
|
||||
```yaml
|
||||
messages:
|
||||
- role: system
|
||||
content: You are a helpful assistant that describes animals using JSON format
|
||||
- role: user
|
||||
content: |-
|
||||
Describe a {{animal}}
|
||||
Use JSON format as specified in the response schema
|
||||
model: openai/gpt-4o
|
||||
responseFormat: json_schema
|
||||
jsonSchema: |-
|
||||
{
|
||||
"name": "describe_animal",
|
||||
"strict": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the animal"
|
||||
},
|
||||
"habitat": {
|
||||
"type": "string",
|
||||
"description": "The habitat the animal lives in"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"habitat"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Variables in prompt.yml files are templated using `{{variable}}` format and are
|
||||
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
|
||||
@@ -80,44 +183,144 @@ 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
|
||||
id: inference
|
||||
uses: actions/ai-inference@v1
|
||||
uses: actions/ai-inference@v1.2
|
||||
with:
|
||||
prompt: 'List my open pull requests and create a summary'
|
||||
enable-github-mcp: true
|
||||
token: ${{ secrets.USER_PAT }} # or a ghs_ installation token
|
||||
```
|
||||
|
||||
If you want, you can use separate tokens for the AI inference endpoint
|
||||
and the GitHub MCP server:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: AI Inference with Separate MCP Token
|
||||
id: inference
|
||||
uses: actions/ai-inference@v1.2
|
||||
with:
|
||||
prompt: 'List my open pull requests and create a summary'
|
||||
enable-github-mcp: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
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.
|
||||
|
||||
**Note:** MCP integration requires your workflow token to have appropriate
|
||||
GitHub permissions for the operations the AI will perform.
|
||||
|
||||
## Inputs
|
||||
|
||||
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. If both `prompt` and `prompt-file` are provided, `prompt-file` takes precedence | `""` |
|
||||
| `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-4.1` |
|
||||
| `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` |
|
||||
| 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
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import type * as core from '@actions/core'
|
||||
import { jest } from '@jest/globals'
|
||||
import {vi} from 'vitest'
|
||||
|
||||
export const debug = jest.fn<typeof core.debug>()
|
||||
export const error = jest.fn<typeof core.error>()
|
||||
export const info = jest.fn<typeof core.info>()
|
||||
export const getInput = jest.fn<typeof core.getInput>()
|
||||
export const getBooleanInput = jest.fn<typeof core.getBooleanInput>()
|
||||
export const setOutput = jest.fn<typeof core.setOutput>()
|
||||
export const setFailed = jest.fn<typeof core.setFailed>()
|
||||
export const warning = jest.fn<typeof core.warning>()
|
||||
export const debug = vi.fn<typeof core.debug>()
|
||||
export const error = vi.fn<typeof core.error>()
|
||||
export const info = vi.fn<typeof core.info>()
|
||||
export const getInput = vi.fn<typeof core.getInput>()
|
||||
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>()
|
||||
|
||||
40
__fixtures__/prompts/json-schema.prompt.yml
Normal file
40
__fixtures__/prompts/json-schema.prompt.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
messages:
|
||||
- role: system
|
||||
content: You are a helpful assistant that describes animals using JSON format
|
||||
- role: user
|
||||
content: |-
|
||||
Describe a {{animal}}
|
||||
Use JSON format as specified in the response schema
|
||||
model: openai/gpt-4o
|
||||
responseFormat: json_schema
|
||||
jsonSchema: |-
|
||||
{
|
||||
"name": "describe_animal",
|
||||
"strict": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the animal"
|
||||
},
|
||||
"habitat": {
|
||||
"type": "string",
|
||||
"description": "The habitat the animal lives in"
|
||||
},
|
||||
"characteristics": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Key characteristics of the animal"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"habitat",
|
||||
"characteristics"
|
||||
]
|
||||
}
|
||||
}
|
||||
6
__fixtures__/prompts/simple.prompt.yml
Normal file
6
__fixtures__/prompts/simple.prompt.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
messages:
|
||||
- role: system
|
||||
content: Be as concise as possible
|
||||
- role: user
|
||||
content: 'Compare {{a}} and {{b}}, please'
|
||||
model: openai/gpt-4o
|
||||
@@ -1,3 +1,3 @@
|
||||
import { jest } from '@jest/globals'
|
||||
import {vi} from 'vitest'
|
||||
|
||||
export const wait = jest.fn<typeof import('../src/wait.js').wait>()
|
||||
export const wait = vi.fn<typeof import('../src/wait.js').wait>()
|
||||
|
||||
169
__tests__/helpers-inference.test.ts
Normal file
169
__tests__/helpers-inference.test.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import {describe, it, expect} from 'vitest'
|
||||
import {buildMessages, buildResponseFormat, buildInferenceRequest} from '../src/helpers'
|
||||
import {PromptConfig} from '../src/prompt'
|
||||
|
||||
describe('helpers.ts - inference request building', () => {
|
||||
describe('buildMessages', () => {
|
||||
it('should build messages from prompt config', () => {
|
||||
const promptConfig: PromptConfig = {
|
||||
messages: [
|
||||
{role: 'system', content: 'System message'},
|
||||
{role: 'user', content: 'User message'},
|
||||
],
|
||||
}
|
||||
|
||||
const result = buildMessages(promptConfig)
|
||||
expect(result).toEqual([
|
||||
{role: 'system', content: 'System message'},
|
||||
{role: 'user', content: 'User message'},
|
||||
])
|
||||
})
|
||||
|
||||
it('should build messages from legacy format', () => {
|
||||
const result = buildMessages(undefined, 'System prompt', 'User prompt')
|
||||
expect(result).toEqual([
|
||||
{role: 'system', content: 'System prompt'},
|
||||
{role: 'user', content: 'User prompt'},
|
||||
])
|
||||
})
|
||||
|
||||
it('should use default system prompt when none provided', () => {
|
||||
const result = buildMessages(undefined, undefined, 'User prompt')
|
||||
expect(result).toEqual([
|
||||
{role: 'system', content: 'You are a helpful assistant'},
|
||||
{role: 'user', content: 'User prompt'},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildResponseFormat', () => {
|
||||
it('should build JSON schema response format', () => {
|
||||
const promptConfig: PromptConfig = {
|
||||
messages: [],
|
||||
responseFormat: 'json_schema',
|
||||
jsonSchema: JSON.stringify({
|
||||
name: 'test_schema',
|
||||
schema: {type: 'object'},
|
||||
}),
|
||||
}
|
||||
|
||||
const result = buildResponseFormat(promptConfig)
|
||||
expect(result).toEqual({
|
||||
type: 'json_schema',
|
||||
json_schema: {
|
||||
name: 'test_schema',
|
||||
schema: {type: 'object'},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should return undefined for text format', () => {
|
||||
const promptConfig: PromptConfig = {
|
||||
messages: [],
|
||||
responseFormat: 'text',
|
||||
}
|
||||
|
||||
const result = buildResponseFormat(promptConfig)
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined when no response format specified', () => {
|
||||
const promptConfig: PromptConfig = {
|
||||
messages: [],
|
||||
}
|
||||
|
||||
const result = buildResponseFormat(promptConfig)
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should throw error for invalid JSON schema', () => {
|
||||
const promptConfig: PromptConfig = {
|
||||
messages: [],
|
||||
responseFormat: 'json_schema',
|
||||
jsonSchema: 'invalid json',
|
||||
}
|
||||
|
||||
expect(() => buildResponseFormat(promptConfig)).toThrow('Invalid JSON schema')
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildInferenceRequest', () => {
|
||||
it('should build complete inference request from prompt config', () => {
|
||||
const promptConfig: PromptConfig = {
|
||||
messages: [
|
||||
{role: 'system', content: 'System message'},
|
||||
{role: 'user', content: 'User message'},
|
||||
],
|
||||
responseFormat: 'json_schema',
|
||||
jsonSchema: JSON.stringify({
|
||||
name: 'test_schema',
|
||||
schema: {type: 'object'},
|
||||
}),
|
||||
}
|
||||
|
||||
const result = buildInferenceRequest(
|
||||
promptConfig,
|
||||
undefined,
|
||||
undefined,
|
||||
'gpt-4',
|
||||
undefined,
|
||||
undefined,
|
||||
100,
|
||||
undefined,
|
||||
'https://api.test.com',
|
||||
'test-token',
|
||||
)
|
||||
|
||||
expect(result).toEqual({
|
||||
messages: [
|
||||
{role: 'system', content: 'System message'},
|
||||
{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: {
|
||||
type: 'json_schema',
|
||||
json_schema: {
|
||||
name: 'test_schema',
|
||||
schema: {type: 'object'},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should build inference request from legacy format', () => {
|
||||
const result = buildInferenceRequest(
|
||||
undefined,
|
||||
'System prompt',
|
||||
'User prompt',
|
||||
'gpt-4',
|
||||
undefined,
|
||||
undefined,
|
||||
100,
|
||||
undefined,
|
||||
'https://api.test.com',
|
||||
'test-token',
|
||||
)
|
||||
|
||||
expect(result).toEqual({
|
||||
messages: [
|
||||
{role: 'system', content: 'System prompt'},
|
||||
{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,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,26 +1,21 @@
|
||||
/**
|
||||
* Unit tests for the helpers module, src/helpers.ts
|
||||
*/
|
||||
import { jest } from '@jest/globals'
|
||||
import {vi, it, expect, beforeEach, describe} from 'vitest'
|
||||
import * as core from '../__fixtures__/core.js'
|
||||
|
||||
// Mock fs module
|
||||
const mockExistsSync = jest.fn()
|
||||
const mockReadFileSync = jest.fn()
|
||||
const mockExistsSync = vi.fn()
|
||||
const mockReadFileSync = vi.fn()
|
||||
|
||||
jest.unstable_mockModule('fs', () => ({
|
||||
vi.mock('fs', () => ({
|
||||
existsSync: mockExistsSync,
|
||||
readFileSync: mockReadFileSync
|
||||
readFileSync: mockReadFileSync,
|
||||
}))
|
||||
|
||||
jest.unstable_mockModule('@actions/core', () => core)
|
||||
vi.mock('@actions/core', () => core)
|
||||
|
||||
// Import the module being tested
|
||||
const { loadContentFromFileOrInput } = await import('../src/helpers.js')
|
||||
const {loadContentFromFileOrInput, parseCustomHeaders} = await import('../src/helpers.js')
|
||||
|
||||
describe('helpers.ts', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('loadContentFromFileOrInput', () => {
|
||||
@@ -108,11 +103,7 @@ describe('helpers.ts', () => {
|
||||
|
||||
core.getInput.mockImplementation(() => '')
|
||||
|
||||
const result = loadContentFromFileOrInput(
|
||||
'file-input',
|
||||
'content-input',
|
||||
defaultValue
|
||||
)
|
||||
const result = loadContentFromFileOrInput('file-input', 'content-input', defaultValue)
|
||||
|
||||
expect(result).toBe(defaultValue)
|
||||
expect(mockExistsSync).not.toHaveBeenCalled()
|
||||
@@ -136,13 +127,246 @@ describe('helpers.ts', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
core.getInput.mockImplementation(() => undefined as any)
|
||||
|
||||
const result = loadContentFromFileOrInput(
|
||||
'file-input',
|
||||
'content-input',
|
||||
defaultValue
|
||||
)
|
||||
const result = loadContentFromFileOrInput('file-input', 'content-input', defaultValue)
|
||||
|
||||
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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,114 +1,185 @@
|
||||
/**
|
||||
* Unit tests for the inference module, src/inference.ts
|
||||
*/
|
||||
import { jest } from '@jest/globals'
|
||||
import {vi, type MockedFunction, beforeEach, expect, describe, it} from 'vitest'
|
||||
import * as core from '../__fixtures__/core.js'
|
||||
|
||||
// Mock Azure AI Inference
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const mockPost = jest.fn() as jest.MockedFunction<any>
|
||||
const mockPath = jest.fn(() => ({ post: mockPost }))
|
||||
const mockClient = jest.fn(() => ({ path: mockPath }))
|
||||
|
||||
jest.unstable_mockModule('@azure-rest/ai-inference', () => ({
|
||||
default: mockClient,
|
||||
isUnexpected: jest.fn(() => false)
|
||||
const mockCreate = vi.fn() as MockedFunction<any>
|
||||
const mockCompletions = {create: mockCreate}
|
||||
const mockChat = {completions: mockCompletions}
|
||||
const mockOpenAIClient = vi.fn(() => ({
|
||||
chat: mockChat,
|
||||
}))
|
||||
|
||||
jest.unstable_mockModule('@azure/core-auth', () => ({
|
||||
AzureKeyCredential: jest.fn()
|
||||
vi.mock('openai', () => ({
|
||||
default: mockOpenAIClient,
|
||||
}))
|
||||
|
||||
// Mock MCP functions
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const mockExecuteToolCalls = jest.fn() as jest.MockedFunction<any>
|
||||
jest.unstable_mockModule('../src/mcp.js', () => ({
|
||||
executeToolCalls: mockExecuteToolCalls
|
||||
const mockExecuteToolCalls = vi.fn() as MockedFunction<any>
|
||||
vi.mock('../src/mcp.js', () => ({
|
||||
executeToolCalls: mockExecuteToolCalls,
|
||||
}))
|
||||
|
||||
jest.unstable_mockModule('@actions/core', () => core)
|
||||
vi.mock('@actions/core', () => core)
|
||||
|
||||
// Import the module being tested
|
||||
const { simpleInference, mcpInference } = await import('../src/inference.js')
|
||||
const {simpleInference, mcpInference} = await import('../src/inference.js')
|
||||
|
||||
describe('inference.ts', () => {
|
||||
const mockRequest = {
|
||||
systemPrompt: 'You are a test assistant',
|
||||
prompt: 'Hello, AI!',
|
||||
messages: [
|
||||
{role: 'system' as const, content: 'You are a test assistant'},
|
||||
{role: 'user' as const, content: 'Hello, AI!'},
|
||||
],
|
||||
modelName: 'gpt-4',
|
||||
maxTokens: 100,
|
||||
maxCompletionTokens: 100,
|
||||
endpoint: 'https://api.test.com',
|
||||
token: 'test-token'
|
||||
token: 'test-token',
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('simpleInference', () => {
|
||||
it('performs simple inference without tools', async () => {
|
||||
const mockResponse = {
|
||||
body: {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'Hello, user!'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'Hello, user!',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockPost.mockResolvedValue(mockResponse)
|
||||
mockCreate.mockResolvedValue(mockResponse)
|
||||
|
||||
const result = await simpleInference(mockRequest)
|
||||
|
||||
expect(result).toBe('Hello, user!')
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
'Running simple inference without tools'
|
||||
)
|
||||
expect(core.info).toHaveBeenCalledWith('Model response: Hello, user!')
|
||||
expect(core.info).toHaveBeenCalledWith('Running simple inference without tools')
|
||||
expect(core.debug).toHaveBeenCalledWith('Model response: Hello, user!')
|
||||
|
||||
// Verify the request structure
|
||||
expect(mockPost).toHaveBeenCalledWith({
|
||||
body: {
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: 'You are a test assistant'
|
||||
expect(mockCreate).toHaveBeenCalledWith({
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: 'You are a test assistant',
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Hello, AI!',
|
||||
},
|
||||
],
|
||||
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',
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Hello, AI!'
|
||||
}
|
||||
],
|
||||
max_tokens: 100,
|
||||
model: 'gpt-4'
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
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 () => {
|
||||
const mockResponse = {
|
||||
body: {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockPost.mockResolvedValue(mockResponse)
|
||||
mockCreate.mockResolvedValue(mockResponse)
|
||||
|
||||
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 () => {
|
||||
const requestWithResponseFormat = {
|
||||
...mockRequest,
|
||||
responseFormat: {
|
||||
type: 'json_schema' as const,
|
||||
json_schema: {type: 'object'},
|
||||
},
|
||||
}
|
||||
|
||||
const mockResponse = {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: '{"result": "success"}',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockCreate.mockResolvedValue(mockResponse)
|
||||
|
||||
const result = await simpleInference(requestWithResponseFormat)
|
||||
|
||||
expect(result).toBe('{"result": "success"}')
|
||||
|
||||
// Verify response format was included in the request
|
||||
expect(mockCreate).toHaveBeenCalledWith({
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: 'You are a test assistant',
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Hello, AI!',
|
||||
},
|
||||
],
|
||||
max_completion_tokens: 100,
|
||||
model: 'gpt-4',
|
||||
response_format: requestWithResponseFormat.responseFormat,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -122,47 +193,86 @@ describe('inference.ts', () => {
|
||||
function: {
|
||||
name: 'test-tool',
|
||||
description: 'A test tool',
|
||||
parameters: { type: 'object' }
|
||||
}
|
||||
}
|
||||
]
|
||||
parameters: {type: 'object'},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
it('performs inference without tool calls', async () => {
|
||||
const mockResponse = {
|
||||
body: {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'Hello, user!',
|
||||
tool_calls: null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'Hello, user!',
|
||||
tool_calls: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockPost.mockResolvedValue(mockResponse)
|
||||
mockCreate.mockResolvedValue(mockResponse)
|
||||
|
||||
const result = await mcpInference(mockRequest, mockMcpClient)
|
||||
|
||||
expect(result).toBe('Hello, user!')
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
'Running GitHub MCP inference with tools'
|
||||
)
|
||||
expect(core.info).toHaveBeenCalledWith('Running GitHub MCP inference with tools')
|
||||
expect(core.info).toHaveBeenCalledWith('MCP inference iteration 1')
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
'No tool calls requested, ending GitHub MCP inference loop'
|
||||
)
|
||||
expect(core.info).toHaveBeenCalledWith('No tool calls requested, ending GitHub MCP inference loop')
|
||||
|
||||
// The MCP inference loop will always add the assistant message, even when there are no tool calls
|
||||
// So we don't check the exact messages, just that tools were included
|
||||
expect(mockPost).toHaveBeenCalledTimes(1)
|
||||
expect(mockCreate).toHaveBeenCalledTimes(1)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callArgs = mockPost.mock.calls[0][0] as any
|
||||
expect(callArgs.body.tools).toEqual(mockMcpClient.tools)
|
||||
expect(callArgs.body.model).toBe('gpt-4')
|
||||
expect(callArgs.body.max_tokens).toBe(100)
|
||||
const callArgs = mockCreate.mock.calls[0][0] as any
|
||||
expect(callArgs.tools).toEqual(mockMcpClient.tools)
|
||||
expect(callArgs.response_format).toBeUndefined()
|
||||
expect(callArgs.model).toBe('gpt-4')
|
||||
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 () => {
|
||||
@@ -171,9 +281,9 @@ describe('inference.ts', () => {
|
||||
id: 'call-123',
|
||||
function: {
|
||||
name: 'test-tool',
|
||||
arguments: '{"param": "value"}'
|
||||
}
|
||||
}
|
||||
arguments: '{"param": "value"}',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const toolResults = [
|
||||
@@ -181,60 +291,51 @@ describe('inference.ts', () => {
|
||||
tool_call_id: 'call-123',
|
||||
role: 'tool',
|
||||
name: 'test-tool',
|
||||
content: 'Tool result'
|
||||
}
|
||||
content: 'Tool result',
|
||||
},
|
||||
]
|
||||
|
||||
// First response with tool calls
|
||||
const firstResponse = {
|
||||
body: {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'I need to use a tool.',
|
||||
tool_calls: toolCalls
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'I need to use a tool.',
|
||||
tool_calls: toolCalls,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// Second response after tool execution
|
||||
const secondResponse = {
|
||||
body: {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'Here is the final answer.',
|
||||
tool_calls: null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'Here is the final answer.',
|
||||
tool_calls: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockPost
|
||||
.mockResolvedValueOnce(firstResponse)
|
||||
.mockResolvedValueOnce(secondResponse)
|
||||
mockCreate.mockResolvedValueOnce(firstResponse).mockResolvedValueOnce(secondResponse)
|
||||
|
||||
mockExecuteToolCalls.mockResolvedValue(toolResults)
|
||||
|
||||
const result = await mcpInference(mockRequest, mockMcpClient)
|
||||
|
||||
expect(result).toBe('Here is the final answer.')
|
||||
expect(mockExecuteToolCalls).toHaveBeenCalledWith(
|
||||
mockMcpClient.client,
|
||||
toolCalls
|
||||
)
|
||||
expect(mockPost).toHaveBeenCalledTimes(2)
|
||||
expect(mockExecuteToolCalls).toHaveBeenCalledWith(mockMcpClient.client, toolCalls)
|
||||
expect(mockCreate).toHaveBeenCalledTimes(2)
|
||||
|
||||
// Verify the second call includes the conversation history
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const secondCall = mockPost.mock.calls[1][0] as any
|
||||
expect(secondCall.body.messages).toHaveLength(5) // system, user, assistant, tool, assistant
|
||||
expect(secondCall.body.messages[2].role).toBe('assistant')
|
||||
expect(secondCall.body.messages[2].tool_calls).toEqual(toolCalls)
|
||||
expect(secondCall.body.messages[3]).toEqual(toolResults[0])
|
||||
const secondCall = mockCreate.mock.calls[1][0] as any
|
||||
expect(secondCall.messages).toHaveLength(5) // system, user, assistant, tool, assistant
|
||||
expect(secondCall.messages[2].role).toBe('assistant')
|
||||
expect(secondCall.messages[2].tool_calls).toEqual(toolCalls)
|
||||
expect(secondCall.messages[3]).toEqual(toolResults[0])
|
||||
})
|
||||
|
||||
it('handles maximum iteration limit', async () => {
|
||||
@@ -243,9 +344,9 @@ describe('inference.ts', () => {
|
||||
id: 'call-123',
|
||||
function: {
|
||||
name: 'test-tool',
|
||||
arguments: '{}'
|
||||
}
|
||||
}
|
||||
arguments: '{}',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const toolResults = [
|
||||
@@ -253,58 +354,50 @@ describe('inference.ts', () => {
|
||||
tool_call_id: 'call-123',
|
||||
role: 'tool',
|
||||
name: 'test-tool',
|
||||
content: 'Tool result'
|
||||
}
|
||||
content: 'Tool result',
|
||||
},
|
||||
]
|
||||
|
||||
// Always respond with tool calls to trigger infinite loop
|
||||
const responseWithToolCalls = {
|
||||
body: {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'Using tool again.',
|
||||
tool_calls: toolCalls
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'Using tool again.',
|
||||
tool_calls: toolCalls,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockPost.mockResolvedValue(responseWithToolCalls)
|
||||
mockCreate.mockResolvedValue(responseWithToolCalls)
|
||||
mockExecuteToolCalls.mockResolvedValue(toolResults)
|
||||
|
||||
const result = await mcpInference(mockRequest, mockMcpClient)
|
||||
|
||||
expect(mockPost).toHaveBeenCalledTimes(5) // Max iterations reached
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
'GitHub MCP inference loop exceeded maximum iterations (5)'
|
||||
)
|
||||
expect(mockCreate).toHaveBeenCalledTimes(5) // Max iterations reached
|
||||
expect(core.warning).toHaveBeenCalledWith('GitHub MCP inference loop exceeded maximum iterations (5)')
|
||||
expect(result).toBe('Using tool again.') // Last assistant message
|
||||
})
|
||||
|
||||
it('handles empty tool calls array', async () => {
|
||||
const mockResponse = {
|
||||
body: {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'Hello, user!',
|
||||
tool_calls: []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'Hello, user!',
|
||||
tool_calls: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockPost.mockResolvedValue(mockResponse)
|
||||
mockCreate.mockResolvedValue(mockResponse)
|
||||
|
||||
const result = await mcpInference(mockRequest, mockMcpClient)
|
||||
|
||||
expect(result).toBe('Hello, user!')
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
'No tool calls requested, ending GitHub MCP inference loop'
|
||||
)
|
||||
expect(core.info).toHaveBeenCalledWith('No tool calls requested, ending GitHub MCP inference loop')
|
||||
expect(mockExecuteToolCalls).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -312,52 +405,292 @@ describe('inference.ts', () => {
|
||||
const toolCalls = [
|
||||
{
|
||||
id: 'call-123',
|
||||
function: { name: 'test-tool', arguments: '{}' }
|
||||
}
|
||||
function: {name: 'test-tool', arguments: '{}'},
|
||||
},
|
||||
]
|
||||
|
||||
const firstResponse = {
|
||||
body: {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'First message',
|
||||
tool_calls: toolCalls
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'First message',
|
||||
tool_calls: toolCalls,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const secondResponse = {
|
||||
body: {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'Second message',
|
||||
tool_calls: toolCalls
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'Second message',
|
||||
tool_calls: toolCalls,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockPost
|
||||
.mockResolvedValueOnce(firstResponse)
|
||||
.mockResolvedValue(secondResponse)
|
||||
mockCreate.mockResolvedValueOnce(firstResponse).mockResolvedValue(secondResponse)
|
||||
|
||||
mockExecuteToolCalls.mockResolvedValue([
|
||||
{
|
||||
tool_call_id: 'call-123',
|
||||
role: 'tool',
|
||||
name: 'test-tool',
|
||||
content: 'result'
|
||||
}
|
||||
content: 'result',
|
||||
},
|
||||
])
|
||||
|
||||
const result = await mcpInference(mockRequest, mockMcpClient)
|
||||
|
||||
expect(result).toBe('Second message')
|
||||
})
|
||||
|
||||
it('makes additional loop with response format when no tool calls are made', async () => {
|
||||
const requestWithResponseFormat = {
|
||||
...mockRequest,
|
||||
responseFormat: {
|
||||
type: 'json_schema' as const,
|
||||
json_schema: {type: 'object'},
|
||||
},
|
||||
}
|
||||
|
||||
// First response without tool calls
|
||||
const firstResponse = {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'First response',
|
||||
tool_calls: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// Second response with response format applied
|
||||
const secondResponse = {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: '{"result": "formatted response"}',
|
||||
tool_calls: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockCreate.mockResolvedValueOnce(firstResponse).mockResolvedValueOnce(secondResponse)
|
||||
|
||||
const result = await mcpInference(requestWithResponseFormat, mockMcpClient)
|
||||
|
||||
expect(result).toBe('{"result": "formatted response"}')
|
||||
expect(mockCreate).toHaveBeenCalledTimes(2)
|
||||
expect(core.info).toHaveBeenCalledWith('Making one more MCP loop with the requested response format...')
|
||||
|
||||
// First call should have tools but no response format
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const firstCall = mockCreate.mock.calls[0][0] as any
|
||||
expect(firstCall.tools).toEqual(mockMcpClient.tools)
|
||||
expect(firstCall.response_format).toBeUndefined()
|
||||
|
||||
// Second call should have response format but no tools
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const secondCall = mockCreate.mock.calls[1][0] as any
|
||||
expect(secondCall.tools).toBeUndefined()
|
||||
expect(secondCall.response_format).toEqual(requestWithResponseFormat.responseFormat)
|
||||
|
||||
// Second call should include the user message requesting JSON format
|
||||
expect(secondCall.messages).toHaveLength(5) // system, user, assistant, user, assistant
|
||||
expect(secondCall.messages[3].role).toBe('user')
|
||||
expect(secondCall.messages[3].content).toContain('Please provide your response in the exact')
|
||||
})
|
||||
|
||||
it('uses response format only on final iteration after tool calls', async () => {
|
||||
const requestWithResponseFormat = {
|
||||
...mockRequest,
|
||||
responseFormat: {
|
||||
type: 'json_schema' as const,
|
||||
json_schema: {type: 'object'},
|
||||
},
|
||||
}
|
||||
|
||||
const toolCalls = [
|
||||
{
|
||||
id: 'call-123',
|
||||
function: {
|
||||
name: 'test-tool',
|
||||
arguments: '{"param": "value"}',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const toolResults = [
|
||||
{
|
||||
tool_call_id: 'call-123',
|
||||
role: 'tool',
|
||||
name: 'test-tool',
|
||||
content: 'Tool result',
|
||||
},
|
||||
]
|
||||
|
||||
// First response with tool calls
|
||||
const firstResponse = {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'Using tool',
|
||||
tool_calls: toolCalls,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// Second response without tool calls, but should trigger final message loop
|
||||
const secondResponse = {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'Intermediate result',
|
||||
tool_calls: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// Third response with response format
|
||||
const thirdResponse = {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: '{"final": "result"}',
|
||||
tool_calls: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockCreate
|
||||
.mockResolvedValueOnce(firstResponse)
|
||||
.mockResolvedValueOnce(secondResponse)
|
||||
.mockResolvedValueOnce(thirdResponse)
|
||||
|
||||
mockExecuteToolCalls.mockResolvedValue(toolResults)
|
||||
|
||||
const result = await mcpInference(requestWithResponseFormat, mockMcpClient)
|
||||
|
||||
expect(result).toBe('{"final": "result"}')
|
||||
expect(mockCreate).toHaveBeenCalledTimes(3)
|
||||
|
||||
// First call: tools but no response format
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const firstCall = mockCreate.mock.calls[0][0] as any
|
||||
expect(firstCall.tools).toEqual(mockMcpClient.tools)
|
||||
expect(firstCall.response_format).toBeUndefined()
|
||||
|
||||
// Second call: tools but no response format (after tool execution)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const secondCall = mockCreate.mock.calls[1][0] as any
|
||||
expect(secondCall.tools).toEqual(mockMcpClient.tools)
|
||||
expect(secondCall.response_format).toBeUndefined()
|
||||
|
||||
// Third call: response format but no tools (final message)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const thirdCall = mockCreate.mock.calls[2][0] as any
|
||||
expect(thirdCall.tools).toBeUndefined()
|
||||
expect(thirdCall.response_format).toEqual(requestWithResponseFormat.responseFormat)
|
||||
})
|
||||
|
||||
it('returns immediately when response format is set and finalMessage is already true', async () => {
|
||||
const requestWithResponseFormat = {
|
||||
...mockRequest,
|
||||
responseFormat: {
|
||||
type: 'json_schema' as const,
|
||||
json_schema: {type: 'object'},
|
||||
},
|
||||
}
|
||||
|
||||
// Response without tool calls on what would be the final message iteration
|
||||
const mockResponse = {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: '{"immediate": "result"}',
|
||||
tool_calls: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockCreate.mockResolvedValue(mockResponse)
|
||||
|
||||
// We need to test a scenario where finalMessage would already be true
|
||||
// This happens when we're already in the final iteration
|
||||
const result = await mcpInference(requestWithResponseFormat, mockMcpClient)
|
||||
|
||||
// The function should make two calls: one normal, then one with response format
|
||||
expect(mockCreate).toHaveBeenCalledTimes(2)
|
||||
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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
236
__tests__/main-prompt-integration.test.ts
Normal file
236
__tests__/main-prompt-integration.test.ts
Normal file
@@ -0,0 +1,236 @@
|
||||
import {describe, it, expect, beforeEach, vi, type MockedFunction, type Mock} from 'vitest'
|
||||
import * as core from '../__fixtures__/core.js'
|
||||
|
||||
// Create fs mocks
|
||||
const mockExistsSync = vi.fn()
|
||||
const mockReadFileSync = vi.fn()
|
||||
const mockWriteFileSync = vi.fn()
|
||||
|
||||
// Create inference mocks
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const mockSimpleInference = vi.fn() as MockedFunction<any>
|
||||
const mockMcpInference = vi.fn()
|
||||
|
||||
// Create MCP mocks
|
||||
const mockConnectToGitHubMCP = vi.fn()
|
||||
|
||||
// Mock fs module
|
||||
vi.mock('fs', () => ({
|
||||
existsSync: mockExistsSync,
|
||||
readFileSync: mockReadFileSync,
|
||||
writeFileSync: mockWriteFileSync,
|
||||
}))
|
||||
|
||||
// Mock the inference functions
|
||||
vi.mock('../src/inference.js', () => ({
|
||||
simpleInference: mockSimpleInference,
|
||||
mcpInference: mockMcpInference,
|
||||
}))
|
||||
|
||||
// Mock the MCP connection
|
||||
vi.mock('../src/mcp.js', () => ({
|
||||
connectToGitHubMCP: mockConnectToGitHubMCP,
|
||||
}))
|
||||
|
||||
vi.mock('@actions/core', () => core)
|
||||
|
||||
// Mock process.exit to prevent it from actually exiting during tests
|
||||
const mockProcessExit = vi.spyOn(process, 'exit').mockImplementation(() => {
|
||||
// Prevent actual exit, but don't throw - just return
|
||||
return undefined as never
|
||||
})
|
||||
|
||||
// The module being tested should be imported dynamically. This ensures that the
|
||||
// mocks are used in place of any actual dependencies.
|
||||
const {run} = await import('../src/main.js')
|
||||
|
||||
describe('main.ts - prompt.yml integration', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockProcessExit.mockClear()
|
||||
|
||||
// Mock environment variables
|
||||
process.env['GITHUB_TOKEN'] = 'test-token'
|
||||
|
||||
// Mock core.getInput to return appropriate values
|
||||
core.getInput.mockImplementation((name: string) => {
|
||||
switch (name) {
|
||||
case 'model':
|
||||
return 'openai/gpt-4o'
|
||||
case 'max-tokens':
|
||||
return '200'
|
||||
case 'endpoint':
|
||||
return 'https://models.github.ai/inference'
|
||||
case 'enable-github-mcp':
|
||||
return 'false'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
// Mock core.getBooleanInput
|
||||
const mockGetBooleanInput = core.getBooleanInput as Mock
|
||||
mockGetBooleanInput.mockReturnValue(false)
|
||||
|
||||
// Mock fs.readFileSync for prompt file
|
||||
mockReadFileSync.mockReturnValue(`
|
||||
messages:
|
||||
- role: system
|
||||
content: Be as concise as possible
|
||||
- role: user
|
||||
content: 'Compare {{a}} and {{b}}, please'
|
||||
model: openai/gpt-4o
|
||||
`)
|
||||
|
||||
// Mock fs.writeFileSync
|
||||
mockWriteFileSync.mockImplementation(() => {})
|
||||
|
||||
// Mock simpleInference
|
||||
mockSimpleInference.mockResolvedValue('Mocked AI response')
|
||||
})
|
||||
|
||||
it('should handle prompt YAML files with template variables', async () => {
|
||||
mockExistsSync.mockReturnValue(true)
|
||||
core.getInput.mockImplementation((name: string) => {
|
||||
switch (name) {
|
||||
case 'prompt-file':
|
||||
return 'test.prompt.yml'
|
||||
case 'input':
|
||||
return 'a: cats\nb: dogs'
|
||||
case 'model':
|
||||
return 'openai/gpt-4o'
|
||||
case 'max-tokens':
|
||||
return '200'
|
||||
case 'endpoint':
|
||||
return 'https://models.github.ai/inference'
|
||||
case 'enable-github-mcp':
|
||||
return 'false'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
// Expect the run function to complete successfully
|
||||
await run()
|
||||
|
||||
// Verify process.exit was called with code 0 (success)
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(0)
|
||||
|
||||
// Verify simpleInference was called with the correct message structure
|
||||
expect(mockSimpleInference).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: 'Be as concise as possible',
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Compare cats and dogs, please',
|
||||
},
|
||||
],
|
||||
modelName: 'openai/gpt-4o',
|
||||
maxTokens: 200,
|
||||
endpoint: 'https://models.github.ai/inference',
|
||||
token: 'test-token',
|
||||
}),
|
||||
)
|
||||
|
||||
// Verify outputs were set
|
||||
expect(core.setOutput).toHaveBeenCalledWith('response', 'Mocked AI response')
|
||||
expect(core.setOutput).toHaveBeenCalledWith('response-file', expect.any(String))
|
||||
})
|
||||
|
||||
it('supports file_input variables to load file contents', async () => {
|
||||
mockExistsSync.mockReturnValue(true)
|
||||
|
||||
// First call: reading the prompt file. Second call: reading file_input referenced file contents.
|
||||
const externalFilePath = 'vars.txt'
|
||||
mockReadFileSync.mockImplementation((path: string) => {
|
||||
if (path === 'test.prompt.yml') {
|
||||
return `messages:\n - role: user\n content: 'Here is the data: {{blob}}'\nmodel: openai/gpt-4o\n`
|
||||
}
|
||||
if (path === externalFilePath) {
|
||||
return 'FILE_CONTENTS'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
core.getInput.mockImplementation((name: string) => {
|
||||
switch (name) {
|
||||
case 'prompt-file':
|
||||
return 'test.prompt.yml'
|
||||
case 'file_input':
|
||||
return `blob: ${externalFilePath}`
|
||||
case 'model':
|
||||
return 'openai/gpt-4o'
|
||||
case 'max-tokens':
|
||||
return '200'
|
||||
case 'endpoint':
|
||||
return 'https://models.github.ai/inference'
|
||||
case 'enable-github-mcp':
|
||||
return 'false'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
await run()
|
||||
|
||||
expect(mockSimpleInference).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
messages: [{role: 'user', content: 'Here is the data: FILE_CONTENTS'}],
|
||||
}),
|
||||
)
|
||||
|
||||
// Verify process.exit was called with code 0 (success)
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(0)
|
||||
})
|
||||
|
||||
it('should fall back to legacy format when not using prompt YAML', async () => {
|
||||
mockExistsSync.mockReturnValue(false)
|
||||
core.getInput.mockImplementation((name: string) => {
|
||||
switch (name) {
|
||||
case 'prompt':
|
||||
return 'Hello, world!'
|
||||
case 'system-prompt':
|
||||
return 'You are helpful'
|
||||
case 'model':
|
||||
return 'openai/gpt-4o'
|
||||
case 'max-tokens':
|
||||
return '200'
|
||||
case 'endpoint':
|
||||
return 'https://models.github.ai/inference'
|
||||
case 'enable-github-mcp':
|
||||
return 'false'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
await run()
|
||||
|
||||
// Verify simpleInference was called with converted message format
|
||||
expect(mockSimpleInference).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: 'You are helpful',
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Hello, world!',
|
||||
},
|
||||
],
|
||||
modelName: 'openai/gpt-4o',
|
||||
maxTokens: 200,
|
||||
endpoint: 'https://models.github.ai/inference',
|
||||
token: 'test-token',
|
||||
}),
|
||||
)
|
||||
|
||||
// Verify process.exit was called with code 0 (success)
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(0)
|
||||
})
|
||||
})
|
||||
@@ -1,31 +1,21 @@
|
||||
/**
|
||||
* Unit tests for the action's main functionality, src/main.ts
|
||||
*/
|
||||
import { jest } from '@jest/globals'
|
||||
import {vi, describe, expect, it, beforeEach, type MockedFunction} from 'vitest'
|
||||
import * as core from '../__fixtures__/core.js'
|
||||
|
||||
// Default to throwing errors to catch unexpected calls
|
||||
const mockExistsSync = jest.fn().mockImplementation(() => {
|
||||
throw new Error(
|
||||
'Unexpected call to existsSync - test should override this implementation'
|
||||
)
|
||||
const mockExistsSync = vi.fn().mockImplementation(() => {
|
||||
throw new Error('Unexpected call to existsSync - test should override this implementation')
|
||||
})
|
||||
const mockReadFileSync = jest.fn().mockImplementation(() => {
|
||||
throw new Error(
|
||||
'Unexpected call to readFileSync - test should override this implementation'
|
||||
)
|
||||
const mockReadFileSync = vi.fn().mockImplementation(() => {
|
||||
throw new Error('Unexpected call to readFileSync - test should override this implementation')
|
||||
})
|
||||
const mockWriteFileSync = jest.fn()
|
||||
const mockWriteFileSync = vi.fn()
|
||||
|
||||
/**
|
||||
* Helper function to mock file system operations for one or more files
|
||||
* @param fileContents - Object mapping file paths to their contents
|
||||
* @param nonExistentFiles - Array of file paths that should be treated as non-existent
|
||||
*/
|
||||
function mockFileContent(
|
||||
fileContents: Record<string, string> = {},
|
||||
nonExistentFiles: string[] = []
|
||||
): void {
|
||||
function mockFileContent(fileContents: Record<string, string> = {}, nonExistentFiles: string[] = []): void {
|
||||
// Mock existsSync to return true for files that exist, false for those that don't
|
||||
mockExistsSync.mockImplementation((...args: unknown[]): boolean => {
|
||||
const [path] = args as [string]
|
||||
@@ -55,11 +45,11 @@ function mockInputs(inputs: Record<string, string> = {}): void {
|
||||
token: 'fake-token',
|
||||
model: 'gpt-4',
|
||||
'max-tokens': '100',
|
||||
endpoint: 'https://api.test.com'
|
||||
endpoint: 'https://api.test.com',
|
||||
}
|
||||
|
||||
// Combine defaults with user-provided inputs
|
||||
const allInputs: Record<string, string> = { ...defaultInputs, ...inputs }
|
||||
const allInputs: Record<string, string> = {...defaultInputs, ...inputs}
|
||||
|
||||
core.getInput.mockImplementation((name: string) => {
|
||||
return allInputs[name] || ''
|
||||
@@ -76,46 +66,58 @@ 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-'))
|
||||
}
|
||||
|
||||
jest.unstable_mockModule('fs', () => ({
|
||||
vi.mock('fs', () => ({
|
||||
existsSync: mockExistsSync,
|
||||
readFileSync: mockReadFileSync,
|
||||
writeFileSync: mockWriteFileSync
|
||||
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 = jest.fn() as jest.MockedFunction<any>
|
||||
const mockConnectToGitHubMCP = vi.fn() as MockedFunction<any>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const mockSimpleInference = jest.fn() as jest.MockedFunction<any>
|
||||
const mockSimpleInference = vi.fn() as MockedFunction<any>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const mockMcpInference = jest.fn() as jest.MockedFunction<any>
|
||||
const mockMcpInference = vi.fn() as MockedFunction<any>
|
||||
|
||||
jest.unstable_mockModule('../src/mcp.js', () => ({
|
||||
connectToGitHubMCP: mockConnectToGitHubMCP
|
||||
vi.mock('../src/mcp.js', () => ({
|
||||
connectToGitHubMCP: mockConnectToGitHubMCP,
|
||||
}))
|
||||
|
||||
jest.unstable_mockModule('../src/inference.js', () => ({
|
||||
vi.mock('../src/inference.js', () => ({
|
||||
simpleInference: mockSimpleInference,
|
||||
mcpInference: mockMcpInference
|
||||
mcpInference: mockMcpInference,
|
||||
}))
|
||||
|
||||
jest.unstable_mockModule('@actions/core', () => core)
|
||||
vi.mock('@actions/core', () => core)
|
||||
|
||||
// Mock process.exit to prevent it from actually exiting during tests
|
||||
const mockProcessExit = vi.spyOn(process, 'exit').mockImplementation(() => {
|
||||
// Prevent actual exit, but don't throw - just return
|
||||
return undefined as never
|
||||
})
|
||||
|
||||
// The module being tested should be imported dynamically. This ensures that the
|
||||
// mocks are used in place of any actual dependencies.
|
||||
const { run } = await import('../src/main.js')
|
||||
const {run} = await import('../src/main.js')
|
||||
|
||||
describe('main.ts', () => {
|
||||
// Reset all mocks before each test
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
mockProcessExit.mockClear()
|
||||
|
||||
// Remove any existing GITHUB_TOKEN
|
||||
delete process.env.GITHUB_TOKEN
|
||||
@@ -128,99 +130,110 @@ describe('main.ts', () => {
|
||||
it('Sets the response output', async () => {
|
||||
mockInputs({
|
||||
prompt: 'Hello, AI!',
|
||||
'system-prompt': 'You are a test assistant.'
|
||||
'system-prompt': 'You are a test assistant.',
|
||||
})
|
||||
|
||||
await run()
|
||||
|
||||
expect(core.setOutput).toHaveBeenCalled()
|
||||
expect(core.setSecret).toHaveBeenCalledWith('fake-token')
|
||||
verifyStandardResponse()
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(0)
|
||||
})
|
||||
|
||||
it('Sets a failed status when no prompt is set', async () => {
|
||||
mockInputs({
|
||||
prompt: '',
|
||||
'prompt-file': ''
|
||||
'prompt-file': '',
|
||||
})
|
||||
|
||||
await run()
|
||||
|
||||
expect(core.setFailed).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'Neither prompt-file nor prompt was set'
|
||||
)
|
||||
expect(core.setFailed).toHaveBeenCalledWith('Neither prompt-file nor prompt was set')
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(1)
|
||||
})
|
||||
|
||||
it('uses simple inference when MCP is disabled', async () => {
|
||||
mockInputs({
|
||||
prompt: 'Hello, AI!',
|
||||
'system-prompt': 'You are a test assistant.',
|
||||
'enable-github-mcp': 'false'
|
||||
'enable-github-mcp': 'false',
|
||||
})
|
||||
|
||||
await run()
|
||||
|
||||
expect(mockSimpleInference).toHaveBeenCalledWith({
|
||||
systemPrompt: 'You are a test assistant.',
|
||||
prompt: 'Hello, AI!',
|
||||
messages: [
|
||||
{role: 'system', content: 'You are a test assistant.'},
|
||||
{role: 'user', content: 'Hello, AI!'},
|
||||
],
|
||||
modelName: 'gpt-4',
|
||||
maxTokens: 100,
|
||||
maxCompletionTokens: undefined,
|
||||
endpoint: 'https://api.test.com',
|
||||
token: 'fake-token'
|
||||
token: 'fake-token',
|
||||
responseFormat: undefined,
|
||||
temperature: undefined,
|
||||
topP: undefined,
|
||||
customHeaders: {},
|
||||
})
|
||||
expect(mockConnectToGitHubMCP).not.toHaveBeenCalled()
|
||||
expect(mockMcpInference).not.toHaveBeenCalled()
|
||||
verifyStandardResponse()
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(0)
|
||||
})
|
||||
|
||||
it('uses MCP inference when enabled and connection succeeds', async () => {
|
||||
const mockMcpClient = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
client: {} as any,
|
||||
tools: [{ type: 'function', function: { name: 'test-tool' } }]
|
||||
tools: [{type: 'function', function: {name: 'test-tool'}}],
|
||||
}
|
||||
|
||||
mockInputs({
|
||||
prompt: 'Hello, AI!',
|
||||
'system-prompt': 'You are a test assistant.',
|
||||
'enable-github-mcp': 'true'
|
||||
'enable-github-mcp': 'true',
|
||||
})
|
||||
|
||||
mockConnectToGitHubMCP.mockResolvedValue(mockMcpClient)
|
||||
|
||||
await run()
|
||||
|
||||
expect(mockConnectToGitHubMCP).toHaveBeenCalledWith('fake-token')
|
||||
expect(core.setSecret).toHaveBeenCalledWith('fake-token')
|
||||
expect(mockConnectToGitHubMCP).toHaveBeenCalledWith('fake-token', '')
|
||||
expect(mockMcpInference).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
systemPrompt: 'You are a test assistant.',
|
||||
prompt: 'Hello, AI!',
|
||||
token: 'fake-token'
|
||||
messages: [
|
||||
{role: 'system', content: 'You are a test assistant.'},
|
||||
{role: 'user', content: 'Hello, AI!'},
|
||||
],
|
||||
token: 'fake-token',
|
||||
}),
|
||||
mockMcpClient
|
||||
mockMcpClient,
|
||||
)
|
||||
expect(mockSimpleInference).not.toHaveBeenCalled()
|
||||
verifyStandardResponse()
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(0)
|
||||
})
|
||||
|
||||
it('falls back to simple inference when MCP connection fails', async () => {
|
||||
mockInputs({
|
||||
prompt: 'Hello, AI!',
|
||||
'system-prompt': 'You are a test assistant.',
|
||||
'enable-github-mcp': 'true'
|
||||
'enable-github-mcp': 'true',
|
||||
})
|
||||
|
||||
mockConnectToGitHubMCP.mockResolvedValue(null)
|
||||
|
||||
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'
|
||||
)
|
||||
expect(core.warning).toHaveBeenCalledWith('MCP connection failed, falling back to simple inference')
|
||||
verifyStandardResponse()
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(0)
|
||||
})
|
||||
|
||||
it('properly integrates with loadContentFromFileOrInput', async () => {
|
||||
@@ -231,26 +244,34 @@ describe('main.ts', () => {
|
||||
|
||||
mockFileContent({
|
||||
[promptFile]: promptContent,
|
||||
[systemPromptFile]: systemPromptContent
|
||||
[systemPromptFile]: systemPromptContent,
|
||||
})
|
||||
|
||||
mockInputs({
|
||||
'prompt-file': promptFile,
|
||||
'system-prompt-file': systemPromptFile,
|
||||
'enable-github-mcp': 'false'
|
||||
'enable-github-mcp': 'false',
|
||||
})
|
||||
|
||||
await run()
|
||||
|
||||
expect(mockSimpleInference).toHaveBeenCalledWith({
|
||||
systemPrompt: systemPromptContent,
|
||||
prompt: promptContent,
|
||||
messages: [
|
||||
{role: 'system', content: systemPromptContent},
|
||||
{role: 'user', content: promptContent},
|
||||
],
|
||||
modelName: 'gpt-4',
|
||||
maxTokens: 100,
|
||||
maxCompletionTokens: undefined,
|
||||
endpoint: 'https://api.test.com',
|
||||
token: 'fake-token'
|
||||
token: 'fake-token',
|
||||
responseFormat: undefined,
|
||||
temperature: undefined,
|
||||
topP: undefined,
|
||||
customHeaders: {},
|
||||
})
|
||||
verifyStandardResponse()
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(0)
|
||||
})
|
||||
|
||||
it('handles non-existent prompt-file with an error', async () => {
|
||||
@@ -259,13 +280,33 @@ describe('main.ts', () => {
|
||||
mockFileContent({}, [promptFile])
|
||||
|
||||
mockInputs({
|
||||
'prompt-file': promptFile
|
||||
'prompt-file': promptFile,
|
||||
})
|
||||
|
||||
await run()
|
||||
|
||||
expect(core.setFailed).toHaveBeenCalledWith(
|
||||
`File for prompt-file was not found: ${promptFile}`
|
||||
)
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,45 +1,37 @@
|
||||
/**
|
||||
* Unit tests for the MCP module, src/mcp.ts
|
||||
*/
|
||||
import { jest } from '@jest/globals'
|
||||
import {vi, type MockedFunction, describe, it, expect, beforeEach} from 'vitest'
|
||||
import * as core from '../__fixtures__/core.js'
|
||||
|
||||
// Mock MCP SDK
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const mockConnect = jest.fn() as jest.MockedFunction<any>
|
||||
const mockConnect = vi.fn() as MockedFunction<any>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const mockListTools = jest.fn() as jest.MockedFunction<any>
|
||||
const mockListTools = vi.fn() as MockedFunction<any>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const mockCallTool = jest.fn() as jest.MockedFunction<any>
|
||||
const mockCallTool = vi.fn() as MockedFunction<any>
|
||||
|
||||
const mockClient = {
|
||||
connect: mockConnect,
|
||||
listTools: mockListTools,
|
||||
callTool: mockCallTool
|
||||
callTool: mockCallTool,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any
|
||||
|
||||
jest.unstable_mockModule('@modelcontextprotocol/sdk/client/index.js', () => ({
|
||||
Client: jest.fn(() => mockClient)
|
||||
vi.mock('@modelcontextprotocol/sdk/client/index.js', () => ({
|
||||
Client: vi.fn(() => mockClient),
|
||||
}))
|
||||
|
||||
jest.unstable_mockModule(
|
||||
'@modelcontextprotocol/sdk/client/streamableHttp.js',
|
||||
() => ({
|
||||
StreamableHTTPClientTransport: jest.fn()
|
||||
})
|
||||
)
|
||||
vi.mock('@modelcontextprotocol/sdk/client/streamableHttp.js', () => ({
|
||||
StreamableHTTPClientTransport: vi.fn(),
|
||||
}))
|
||||
|
||||
jest.unstable_mockModule('@actions/core', () => core)
|
||||
vi.mock('@actions/core', () => core)
|
||||
|
||||
// Import the module being tested
|
||||
const { connectToGitHubMCP, executeToolCall, executeToolCalls } = await import(
|
||||
'../src/mcp.js'
|
||||
)
|
||||
const {connectToGitHubMCP, executeToolCall, executeToolCalls} = await import('../src/mcp.js')
|
||||
|
||||
describe('mcp.ts', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('connectToGitHubMCP', () => {
|
||||
@@ -49,20 +41,20 @@ describe('mcp.ts', () => {
|
||||
{
|
||||
name: 'test-tool-1',
|
||||
description: 'Test tool 1',
|
||||
inputSchema: { type: 'object', properties: {} }
|
||||
inputSchema: {type: 'object', properties: {}},
|
||||
},
|
||||
{
|
||||
name: 'test-tool-2',
|
||||
description: 'Test tool 2',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: { param: { type: 'string' } }
|
||||
}
|
||||
}
|
||||
properties: {param: {type: 'string'}},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
mockConnect.mockResolvedValue(undefined)
|
||||
mockListTools.mockResolvedValue({ tools: mockTools })
|
||||
mockListTools.mockResolvedValue({tools: mockTools})
|
||||
|
||||
const result = await connectToGitHubMCP(token)
|
||||
|
||||
@@ -74,21 +66,13 @@ describe('mcp.ts', () => {
|
||||
function: {
|
||||
name: 'test-tool-1',
|
||||
description: 'Test tool 1',
|
||||
parameters: { type: 'object', properties: {} }
|
||||
}
|
||||
parameters: {type: 'object', properties: {}},
|
||||
},
|
||||
})
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
'Connecting to GitHub MCP server...'
|
||||
)
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
'Successfully connected to GitHub MCP server'
|
||||
)
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
'Retrieved 2 tools from GitHub MCP server'
|
||||
)
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
'Mapped 2 GitHub MCP tools for Azure AI Inference'
|
||||
)
|
||||
expect(core.info).toHaveBeenCalledWith('Connecting to GitHub MCP server...')
|
||||
expect(core.info).toHaveBeenCalledWith('Successfully connected to GitHub MCP server')
|
||||
expect(core.info).toHaveBeenCalledWith('Retrieved 2 tools from GitHub MCP server')
|
||||
expect(core.info).toHaveBeenCalledWith('Mapped 2 GitHub MCP tools for Azure AI Inference')
|
||||
})
|
||||
|
||||
it('returns null when connection fails', async () => {
|
||||
@@ -100,27 +84,21 @@ describe('mcp.ts', () => {
|
||||
const result = await connectToGitHubMCP(token)
|
||||
|
||||
expect(result).toBeNull()
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
'Failed to connect to GitHub MCP server: Error: Connection failed'
|
||||
)
|
||||
expect(core.warning).toHaveBeenCalledWith('Failed to connect to GitHub MCP server: Error: Connection failed')
|
||||
})
|
||||
|
||||
it('handles empty tools list', async () => {
|
||||
const token = 'test-token'
|
||||
|
||||
mockConnect.mockResolvedValue(undefined)
|
||||
mockListTools.mockResolvedValue({ tools: [] })
|
||||
mockListTools.mockResolvedValue({tools: []})
|
||||
|
||||
const result = await connectToGitHubMCP(token)
|
||||
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.tools).toHaveLength(0)
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
'Retrieved 0 tools from GitHub MCP server'
|
||||
)
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
'Mapped 0 GitHub MCP tools for Azure AI Inference'
|
||||
)
|
||||
expect(core.info).toHaveBeenCalledWith('Retrieved 0 tools from GitHub MCP server')
|
||||
expect(core.info).toHaveBeenCalledWith('Mapped 0 GitHub MCP tools for Azure AI Inference')
|
||||
})
|
||||
|
||||
it('handles undefined tools list', async () => {
|
||||
@@ -133,9 +111,41 @@ describe('mcp.ts', () => {
|
||||
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.tools).toHaveLength(0)
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
'Retrieved 0 tools from GitHub MCP server'
|
||||
)
|
||||
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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -146,11 +156,11 @@ describe('mcp.ts', () => {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'test-tool',
|
||||
arguments: '{"param": "value"}'
|
||||
}
|
||||
arguments: '{"param": "value"}',
|
||||
},
|
||||
}
|
||||
const toolResult = {
|
||||
content: [{ type: 'text', text: 'Tool execution result' }]
|
||||
content: [{type: 'text', text: 'Tool execution result'}],
|
||||
}
|
||||
|
||||
mockCallTool.mockResolvedValue(toolResult)
|
||||
@@ -159,20 +169,16 @@ describe('mcp.ts', () => {
|
||||
|
||||
expect(mockCallTool).toHaveBeenCalledWith({
|
||||
name: 'test-tool',
|
||||
arguments: { param: 'value' }
|
||||
arguments: {param: 'value'},
|
||||
})
|
||||
expect(result).toEqual({
|
||||
tool_call_id: 'call-123',
|
||||
role: 'tool',
|
||||
name: 'test-tool',
|
||||
content: JSON.stringify(toolResult.content)
|
||||
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 () => {
|
||||
@@ -181,8 +187,8 @@ describe('mcp.ts', () => {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'failing-tool',
|
||||
arguments: '{"param": "value"}'
|
||||
}
|
||||
arguments: '{"param": "value"}',
|
||||
},
|
||||
}
|
||||
const toolError = new Error('Tool execution failed')
|
||||
|
||||
@@ -194,10 +200,10 @@ describe('mcp.ts', () => {
|
||||
tool_call_id: 'call-456',
|
||||
role: 'tool',
|
||||
name: 'failing-tool',
|
||||
content: 'Error: Error: Tool execution failed'
|
||||
content: 'Error: Error: Tool execution failed',
|
||||
})
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
'Failed to execute GitHub MCP tool failing-tool: Error: Tool execution failed'
|
||||
'Failed to execute GitHub MCP tool failing-tool: Error: Tool execution failed',
|
||||
)
|
||||
})
|
||||
|
||||
@@ -207,8 +213,8 @@ describe('mcp.ts', () => {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'test-tool',
|
||||
arguments: 'invalid-json'
|
||||
}
|
||||
arguments: 'invalid-json',
|
||||
},
|
||||
}
|
||||
|
||||
const result = await executeToolCall(mockClient, toolCall)
|
||||
@@ -217,9 +223,7 @@ describe('mcp.ts', () => {
|
||||
expect(result.role).toBe('tool')
|
||||
expect(result.name).toBe('test-tool')
|
||||
expect(result.content).toContain('Error:')
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Failed to execute GitHub MCP tool test-tool:')
|
||||
)
|
||||
expect(core.warning).toHaveBeenCalledWith(expect.stringContaining('Failed to execute GitHub MCP tool test-tool:'))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -229,21 +233,21 @@ describe('mcp.ts', () => {
|
||||
{
|
||||
id: 'call-1',
|
||||
type: 'function',
|
||||
function: { name: 'tool-1', arguments: '{}' }
|
||||
function: {name: 'tool-1', arguments: '{}'},
|
||||
},
|
||||
{
|
||||
id: 'call-2',
|
||||
type: 'function',
|
||||
function: { name: 'tool-2', arguments: '{"param": "value"}' }
|
||||
}
|
||||
function: {name: 'tool-2', arguments: '{"param": "value"}'},
|
||||
},
|
||||
]
|
||||
|
||||
mockCallTool
|
||||
.mockResolvedValueOnce({
|
||||
content: [{ type: 'text', text: 'Result 1' }]
|
||||
content: [{type: 'text', text: 'Result 1'}],
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
content: [{ type: 'text', text: 'Result 2' }]
|
||||
content: [{type: 'text', text: 'Result 2'}],
|
||||
})
|
||||
|
||||
const results = await executeToolCalls(mockClient, toolCalls)
|
||||
@@ -266,18 +270,18 @@ describe('mcp.ts', () => {
|
||||
{
|
||||
id: 'call-1',
|
||||
type: 'function',
|
||||
function: { name: 'tool-1', arguments: '{}' }
|
||||
function: {name: 'tool-1', arguments: '{}'},
|
||||
},
|
||||
{
|
||||
id: 'call-2',
|
||||
type: 'function',
|
||||
function: { name: 'tool-2', arguments: '{}' }
|
||||
}
|
||||
function: {name: 'tool-2', arguments: '{}'},
|
||||
},
|
||||
]
|
||||
|
||||
mockCallTool
|
||||
.mockResolvedValueOnce({
|
||||
content: [{ type: 'text', text: 'Result 1' }]
|
||||
content: [{type: 'text', text: 'Result 1'}],
|
||||
})
|
||||
.mockRejectedValueOnce(new Error('Tool 2 failed'))
|
||||
|
||||
|
||||
151
__tests__/prompt.test.ts
Normal file
151
__tests__/prompt.test.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import {describe, it, expect} from 'vitest'
|
||||
import * as path from 'path'
|
||||
import {fileURLToPath} from 'url'
|
||||
import {
|
||||
parseTemplateVariables,
|
||||
replaceTemplateVariables,
|
||||
loadPromptFile,
|
||||
isPromptYamlFile,
|
||||
parseFileTemplateVariables,
|
||||
} from '../src/prompt'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
describe('prompt.ts', () => {
|
||||
describe('parseTemplateVariables', () => {
|
||||
it('should parse simple YAML variables', () => {
|
||||
const input = `
|
||||
a: hello
|
||||
b: world
|
||||
`
|
||||
const result = parseTemplateVariables(input)
|
||||
expect(result).toEqual({a: 'hello', b: 'world'})
|
||||
})
|
||||
|
||||
it('should parse multiline variables', () => {
|
||||
const input = `
|
||||
var1: hello
|
||||
var2: |
|
||||
This is a
|
||||
multiline string
|
||||
`
|
||||
const result = parseTemplateVariables(input)
|
||||
expect(result.var1).toBe('hello')
|
||||
expect(result.var2).toContain('This is a')
|
||||
expect(result.var2).toContain('multiline string')
|
||||
})
|
||||
|
||||
it('should return empty object for empty input', () => {
|
||||
const result = parseTemplateVariables('')
|
||||
expect(result).toEqual({})
|
||||
})
|
||||
|
||||
it('should throw error for invalid YAML', () => {
|
||||
const input = 'invalid: yaml: content:'
|
||||
expect(() => parseTemplateVariables(input)).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe('replaceTemplateVariables', () => {
|
||||
it('should replace simple variables', () => {
|
||||
const text = 'Hello {{name}}, welcome to {{place}}!'
|
||||
const variables = {name: 'John', place: 'GitHub'}
|
||||
const result = replaceTemplateVariables(text, variables)
|
||||
expect(result).toBe('Hello John, welcome to GitHub!')
|
||||
})
|
||||
|
||||
it('should leave unreplaced variables as is', () => {
|
||||
const text = 'Hello {{name}}, welcome to {{unknown}}!'
|
||||
const variables = {name: 'John'}
|
||||
const result = replaceTemplateVariables(text, variables)
|
||||
expect(result).toBe('Hello John, welcome to {{unknown}}!')
|
||||
})
|
||||
|
||||
it('should handle no variables', () => {
|
||||
const text = 'No variables here'
|
||||
const variables = {}
|
||||
const result = replaceTemplateVariables(text, variables)
|
||||
expect(result).toBe('No variables here')
|
||||
})
|
||||
})
|
||||
|
||||
describe('isPromptYamlFile', () => {
|
||||
it('should detect .prompt.yml files', () => {
|
||||
expect(isPromptYamlFile('test.prompt.yml')).toBe(true)
|
||||
expect(isPromptYamlFile('path/to/test.prompt.yml')).toBe(true)
|
||||
})
|
||||
|
||||
it('should detect .prompt.yaml files', () => {
|
||||
expect(isPromptYamlFile('test.prompt.yaml')).toBe(true)
|
||||
expect(isPromptYamlFile('path/to/test.prompt.yaml')).toBe(true)
|
||||
})
|
||||
|
||||
it('should reject other file types', () => {
|
||||
expect(isPromptYamlFile('test.txt')).toBe(false)
|
||||
expect(isPromptYamlFile('test.yml')).toBe(false)
|
||||
expect(isPromptYamlFile('test.yaml')).toBe(false)
|
||||
expect(isPromptYamlFile('test.prompt')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('loadPromptFile', () => {
|
||||
it('should load simple prompt file', () => {
|
||||
const filePath = path.join(__dirname, '../__fixtures__/prompts/simple.prompt.yml')
|
||||
const variables = {a: 'cats', b: 'dogs'}
|
||||
const result = loadPromptFile(filePath, variables)
|
||||
|
||||
expect(result.messages).toHaveLength(2)
|
||||
expect(result.messages[0]).toEqual({
|
||||
role: 'system',
|
||||
content: 'Be as concise as possible',
|
||||
})
|
||||
expect(result.messages[1]).toEqual({
|
||||
role: 'user',
|
||||
content: 'Compare cats and dogs, please',
|
||||
})
|
||||
expect(result.model).toBe('openai/gpt-4o')
|
||||
})
|
||||
|
||||
it('should load JSON schema prompt file', () => {
|
||||
const filePath = path.join(__dirname, '../__fixtures__/prompts/json-schema.prompt.yml')
|
||||
const variables = {animal: 'dog'}
|
||||
const result = loadPromptFile(filePath, variables)
|
||||
|
||||
expect(result.messages).toHaveLength(2)
|
||||
expect(result.messages[1].content).toContain('Describe a dog')
|
||||
expect(result.responseFormat).toBe('json_schema')
|
||||
expect(result.jsonSchema).toBeDefined()
|
||||
expect(result.jsonSchema).toContain('describe_animal')
|
||||
})
|
||||
|
||||
it('should throw error for non-existent file', () => {
|
||||
expect(() => loadPromptFile('non-existent.prompt.yml')).toThrow('Prompt file not found')
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseFileTemplateVariables', () => {
|
||||
it('reads file contents for variables', () => {
|
||||
const configPath = path.join(__dirname, '../__fixtures__/prompts/json-schema.prompt.yml')
|
||||
const data = parseFileTemplateVariables(`sample: ${configPath}`)
|
||||
expect(data.sample).toContain('messages:')
|
||||
expect(data.sample).toContain('responseFormat:')
|
||||
})
|
||||
|
||||
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",
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
39
action.yml
39
action.yml
@@ -14,7 +14,16 @@ inputs:
|
||||
required: false
|
||||
default: ''
|
||||
prompt-file:
|
||||
description: Path to a file containing the prompt
|
||||
description: Path to a file containing the prompt (supports .txt and .prompt.yml
|
||||
formats)
|
||||
required: false
|
||||
default: ''
|
||||
input:
|
||||
description: Template variables in YAML format for .prompt.yml files
|
||||
required: false
|
||||
default: ''
|
||||
file_input:
|
||||
description: Template variables in YAML format mapping variable names to file paths. The file contents will be used for templating.
|
||||
required: false
|
||||
default: ''
|
||||
model:
|
||||
@@ -34,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
|
||||
@@ -45,6 +66,18 @@ inputs:
|
||||
description: Enable Model Context Protocol integration with GitHub tools
|
||||
required: false
|
||||
default: 'false'
|
||||
github-mcp-token:
|
||||
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: ''
|
||||
|
||||
# Define your outputs here.
|
||||
outputs:
|
||||
@@ -54,5 +87,5 @@ outputs:
|
||||
description: The file path where the response is saved
|
||||
|
||||
runs:
|
||||
using: node20
|
||||
using: node24
|
||||
main: dist/index.js
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="116" height="20" role="img" aria-label="Coverage: 84.21%"><title>Coverage: 84.21%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="116" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="63" height="20" fill="#555"/><rect x="63" width="53" height="20" fill="#dfb317"/><rect width="116" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="325" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="530">Coverage</text><text x="325" y="140" transform="scale(.1)" fill="#fff" textLength="530">Coverage</text><text aria-hidden="true" x="885" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">84.21%</text><text x="885" y="140" transform="scale(.1)" fill="#fff" textLength="430">84.21%</text></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
50705
dist/index.js
generated
vendored
50705
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
@@ -1,50 +1,43 @@
|
||||
// See: https://eslint.org/docs/latest/use/configure/configuration-files
|
||||
|
||||
import { fixupPluginRules } from '@eslint/compat'
|
||||
import { FlatCompat } from '@eslint/eslintrc'
|
||||
import {FlatCompat} from '@eslint/eslintrc'
|
||||
import js from '@eslint/js'
|
||||
import typescriptEslint from '@typescript-eslint/eslint-plugin'
|
||||
import tsParser from '@typescript-eslint/parser'
|
||||
import _import from 'eslint-plugin-import'
|
||||
import jest from 'eslint-plugin-jest'
|
||||
import prettier from 'eslint-plugin-prettier'
|
||||
import globals from 'globals'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import {fileURLToPath} from 'node:url'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all
|
||||
allConfig: js.configs.all,
|
||||
})
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/coverage', '**/dist', '**/linter', '**/node_modules']
|
||||
ignores: ['**/coverage', '**/dist', '**/linter', '**/node_modules', 'script/**'],
|
||||
},
|
||||
...compat.extends(
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:jest/recommended',
|
||||
'plugin:prettier/recommended'
|
||||
'plugin:prettier/recommended',
|
||||
),
|
||||
{
|
||||
plugins: {
|
||||
import: fixupPluginRules(_import),
|
||||
jest,
|
||||
prettier,
|
||||
'@typescript-eslint': typescriptEslint
|
||||
'@typescript-eslint': typescriptEslint,
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
Atomics: 'readonly',
|
||||
SharedArrayBuffer: 'readonly'
|
||||
SharedArrayBuffer: 'readonly',
|
||||
},
|
||||
|
||||
parser: tsParser,
|
||||
@@ -53,17 +46,17 @@ export default [
|
||||
|
||||
parserOptions: {
|
||||
project: ['tsconfig.eslint.json'],
|
||||
tsconfigRootDir: '.'
|
||||
}
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
},
|
||||
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
alwaysTryTypes: true,
|
||||
project: 'tsconfig.eslint.json'
|
||||
}
|
||||
}
|
||||
project: 'tsconfig.eslint.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
rules: {
|
||||
@@ -75,7 +68,7 @@ export default [
|
||||
'no-console': 'off',
|
||||
'no-shadow': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'prettier/prettier': 'error'
|
||||
}
|
||||
}
|
||||
'prettier/prettier': 'error',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
// See: https://jestjs.io/docs/configuration
|
||||
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} **/
|
||||
export default {
|
||||
clearMocks: true,
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: ['./src/**'],
|
||||
coverageDirectory: './coverage',
|
||||
coveragePathIgnorePatterns: ['/node_modules/', '/dist/'],
|
||||
coverageReporters: ['json-summary', 'text', 'lcov'],
|
||||
// Uncomment the below lines if you would like to enforce a coverage threshold
|
||||
// for your action. This will fail the build if the coverage is below the
|
||||
// specified thresholds.
|
||||
// coverageThreshold: {
|
||||
// global: {
|
||||
// branches: 100,
|
||||
// functions: 100,
|
||||
// lines: 100,
|
||||
// statements: 100
|
||||
// }
|
||||
// },
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
moduleFileExtensions: ['ts', 'js'],
|
||||
preset: 'ts-jest',
|
||||
reporters: ['default'],
|
||||
resolver: 'ts-jest-resolver',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/*.test.ts'],
|
||||
testPathIgnorePatterns: ['/dist/', '/node_modules/'],
|
||||
transform: {
|
||||
'^.+\\.ts$': [
|
||||
'ts-jest',
|
||||
{
|
||||
tsconfig: 'tsconfig.eslint.json',
|
||||
useESM: true
|
||||
}
|
||||
]
|
||||
},
|
||||
verbose: true
|
||||
}
|
||||
6713
package-lock.json
generated
6713
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
75
package.json
75
package.json
@@ -1,75 +1,58 @@
|
||||
{
|
||||
"name": "typescript-action",
|
||||
"description": "GitHub Actions TypeScript template",
|
||||
"name": "ai-inference",
|
||||
"version": "1.0.0",
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/actions/typescript-action",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/actions/typescript-action.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/actions/typescript-action/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"actions"
|
||||
],
|
||||
"exports": {
|
||||
".": "./dist/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": ">=24"
|
||||
},
|
||||
"scripts": {
|
||||
"bundle": "npm run format:write && npm run package",
|
||||
"ci-test": "NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 npx jest",
|
||||
"coverage": "npx make-coverage-badge --output-path ./badges/coverage.svg",
|
||||
"format:write": "npx prettier --write .",
|
||||
"format:check": "npx prettier --check .",
|
||||
"lint": "npx eslint .",
|
||||
"local-action": "npx @github/local-action . src/main.ts .env",
|
||||
"package": "npx rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript",
|
||||
"package:watch": "npm run package -- --watch",
|
||||
"test": "npx cross-env NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 npx jest",
|
||||
"all": "npm run format:write && npm run lint && npm run test && npm run coverage && npm run package"
|
||||
"test": "vitest --run",
|
||||
"test:watch": "vitest --watch",
|
||||
"all": "npm run format:write && npm run lint && npm run test && npm run package"
|
||||
},
|
||||
"license": "MIT",
|
||||
"prettier": "@github/prettier-config",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.11.1",
|
||||
"@modelcontextprotocol/sdk": "^1.15.1",
|
||||
"@rollup/plugin-json": "^6.1.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": {
|
||||
"@azure-rest/ai-inference": "latest",
|
||||
"@azure/core-auth": "latest",
|
||||
"@azure/core-sse": "latest",
|
||||
"@eslint/compat": "^1.3.0",
|
||||
"@github/local-action": "^3.2.1",
|
||||
"@jest/globals": "^30.0.2",
|
||||
"@github/local-action": "^5.1.0",
|
||||
"@github/prettier-config": "^0.0.6",
|
||||
"@rollup/plugin-commonjs": "^28.0.5",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.15.31",
|
||||
"@typescript-eslint/eslint-plugin": "^8.34.0",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||
"@rollup/plugin-typescript": "^12.3.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@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-jest": "^28.14.0",
|
||||
"eslint-plugin-prettier": "^5.4.1",
|
||||
"jest": "^29.7.0",
|
||||
"make-coverage-badge": "^1.2.0",
|
||||
"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",
|
||||
"ts-jest": "^29.4.0",
|
||||
"ts-jest-resolver": "^2.0.1",
|
||||
"typescript": "^5.8.3"
|
||||
"rollup": "^4.59.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-linux-x64-gnu": "*"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// See: https://rollupjs.org/introduction/
|
||||
|
||||
import {builtinModules} from 'node:module'
|
||||
import commonjs from '@rollup/plugin-commonjs'
|
||||
import nodeResolve from '@rollup/plugin-node-resolve'
|
||||
import typescript from '@rollup/plugin-typescript'
|
||||
@@ -11,14 +11,21 @@ const config = {
|
||||
esModule: true,
|
||||
file: 'dist/index.js',
|
||||
format: 'es',
|
||||
sourcemap: true
|
||||
sourcemap: true,
|
||||
},
|
||||
external: [...builtinModules, /^node:/],
|
||||
plugins: [
|
||||
typescript(),
|
||||
nodeResolve({ preferBuiltins: true }),
|
||||
commonjs(),
|
||||
json()
|
||||
]
|
||||
nodeResolve({
|
||||
preferBuiltins: true,
|
||||
browser: false,
|
||||
exportConditions: ['node'],
|
||||
}),
|
||||
commonjs({
|
||||
include: /node_modules/,
|
||||
}),
|
||||
json(),
|
||||
],
|
||||
}
|
||||
|
||||
export default config
|
||||
|
||||
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')
|
||||
})
|
||||
191
src/helpers.ts
191
src/helpers.ts
@@ -1,6 +1,8 @@
|
||||
import * as core from '@actions/core'
|
||||
import { GetChatCompletionsDefaultResponse } from '@azure-rest/ai-inference'
|
||||
import * as fs from 'fs'
|
||||
import * as yaml from 'js-yaml'
|
||||
import {PromptConfig} from './prompt.js'
|
||||
import {InferenceRequest} from './inference.js'
|
||||
|
||||
/**
|
||||
* Helper function to load content from a file or use fallback input
|
||||
@@ -9,11 +11,7 @@ import * as fs from 'fs'
|
||||
* @param defaultValue - Default value to use if neither file nor content is provided
|
||||
* @returns The loaded content
|
||||
*/
|
||||
export function loadContentFromFileOrInput(
|
||||
filePathInput: string,
|
||||
contentInput: string,
|
||||
defaultValue?: string
|
||||
): string {
|
||||
export function loadContentFromFileOrInput(filePathInput: string, contentInput: string, defaultValue?: string): string {
|
||||
const filePath = core.getInput(filePathInput)
|
||||
const contentString = core.getInput(contentInput)
|
||||
|
||||
@@ -32,35 +30,158 @@ export function loadContentFromFileOrInput(
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to handle unexpected responses from AI service
|
||||
* @param response - The response object from the AI service
|
||||
* @throws Error with appropriate error message based on response content
|
||||
* Build messages array from either prompt config or legacy format
|
||||
*/
|
||||
export function handleUnexpectedResponse(
|
||||
response: GetChatCompletionsDefaultResponse
|
||||
): never {
|
||||
// Extract x-ms-error-code from headers if available
|
||||
const errorCode = response.headers['x-ms-error-code']
|
||||
const errorCodeMsg = errorCode ? ` (error code: ${errorCode})` : ''
|
||||
|
||||
// Check if response body exists and contains error details
|
||||
if (response.body && response.body.error) {
|
||||
throw response.body.error
|
||||
export function buildMessages(
|
||||
promptConfig?: PromptConfig,
|
||||
systemPrompt?: string,
|
||||
prompt?: string,
|
||||
): Array<{role: 'system' | 'user' | 'assistant' | 'tool'; content: string}> {
|
||||
if (promptConfig?.messages && promptConfig.messages.length > 0) {
|
||||
// Use new message format
|
||||
return promptConfig.messages.map(msg => ({
|
||||
role: msg.role as 'system' | 'user' | 'assistant' | 'tool',
|
||||
content: msg.content,
|
||||
}))
|
||||
} else {
|
||||
// Use legacy format
|
||||
return [
|
||||
{
|
||||
role: 'system',
|
||||
content: systemPrompt || 'You are a helpful assistant',
|
||||
},
|
||||
{role: 'user', content: prompt || ''},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build response format object for API from prompt config
|
||||
*/
|
||||
export function buildResponseFormat(
|
||||
promptConfig?: PromptConfig,
|
||||
): {type: 'json_schema'; json_schema: unknown} | undefined {
|
||||
if (promptConfig?.responseFormat === 'json_schema' && promptConfig.jsonSchema) {
|
||||
try {
|
||||
const schema = JSON.parse(promptConfig.jsonSchema)
|
||||
return {
|
||||
type: 'json_schema',
|
||||
json_schema: schema,
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`Invalid JSON schema: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
||||
}
|
||||
}
|
||||
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
|
||||
*/
|
||||
export function buildInferenceRequest(
|
||||
promptConfig: PromptConfig | undefined,
|
||||
systemPrompt: string | undefined,
|
||||
prompt: string | undefined,
|
||||
modelName: string,
|
||||
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)
|
||||
|
||||
return {
|
||||
messages,
|
||||
modelName,
|
||||
temperature,
|
||||
topP,
|
||||
maxTokens, // Deprecated
|
||||
maxCompletionTokens,
|
||||
endpoint,
|
||||
token,
|
||||
responseFormat,
|
||||
customHeaders,
|
||||
}
|
||||
|
||||
// Handle case where response body is missing
|
||||
if (!response.body) {
|
||||
throw new Error(
|
||||
`Failed to get response from AI service (status: ${response.status})${errorCodeMsg}. ` +
|
||||
'Please check network connection and endpoint configuration.'
|
||||
)
|
||||
}
|
||||
|
||||
// Handle other error cases
|
||||
throw new Error(
|
||||
`AI service returned error response (status: ${response.status})${errorCodeMsg}: ` +
|
||||
(typeof response.body === 'string'
|
||||
? response.body
|
||||
: JSON.stringify(response.body))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* The entrypoint for the action. This file simply imports and runs the action's
|
||||
* main logic.
|
||||
*/
|
||||
import { run } from './main.js'
|
||||
import {run} from './main.js'
|
||||
|
||||
/* istanbul ignore next */
|
||||
run()
|
||||
|
||||
239
src/inference.ts
239
src/inference.ts
@@ -1,16 +1,25 @@
|
||||
import * as core from '@actions/core'
|
||||
import ModelClient, { isUnexpected } from '@azure-rest/ai-inference'
|
||||
import { AzureKeyCredential } from '@azure/core-auth'
|
||||
import { GitHubMCPClient, executeToolCalls } from './mcp.js'
|
||||
import { handleUnexpectedResponse } from './helpers.js'
|
||||
import OpenAI from 'openai'
|
||||
import {GitHubMCPClient, executeToolCalls, ToolCall} from './mcp.js'
|
||||
|
||||
interface ChatMessage {
|
||||
role: 'system' | 'user' | 'assistant' | 'tool'
|
||||
content: string | null
|
||||
tool_calls?: ToolCall[]
|
||||
tool_call_id?: string
|
||||
}
|
||||
|
||||
export interface InferenceRequest {
|
||||
systemPrompt: string
|
||||
prompt: string
|
||||
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 {
|
||||
@@ -25,46 +34,50 @@ 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
|
||||
*/
|
||||
export async function simpleInference(
|
||||
request: InferenceRequest
|
||||
): Promise<string | null> {
|
||||
export async function simpleInference(request: InferenceRequest): Promise<string | null> {
|
||||
core.info('Running simple inference without tools')
|
||||
|
||||
const client = ModelClient(
|
||||
request.endpoint,
|
||||
new AzureKeyCredential(request.token),
|
||||
{
|
||||
userAgentOptions: { userAgentPrefix: 'github-actions-ai-inference' }
|
||||
}
|
||||
)
|
||||
|
||||
const requestBody = {
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: request.systemPrompt
|
||||
},
|
||||
{ role: 'user', content: request.prompt }
|
||||
],
|
||||
max_tokens: request.maxTokens,
|
||||
model: request.modelName
|
||||
}
|
||||
|
||||
const response = await client.path('/chat/completions').post({
|
||||
body: requestBody
|
||||
const client = new OpenAI({
|
||||
apiKey: request.token,
|
||||
baseURL: request.endpoint,
|
||||
defaultHeaders: request.customHeaders || {},
|
||||
})
|
||||
|
||||
if (isUnexpected(response)) {
|
||||
handleUnexpectedResponse(response)
|
||||
const chatCompletionRequest: OpenAI.Chat.Completions.ChatCompletionCreateParams = {
|
||||
messages: request.messages as OpenAI.Chat.Completions.ChatCompletionMessageParam[],
|
||||
model: request.modelName,
|
||||
temperature: request.temperature,
|
||||
top_p: request.topP,
|
||||
...buildMaxTokensParam(request), // Note: solution around models using different underlying max tokens properties
|
||||
}
|
||||
|
||||
const modelResponse = response.body.choices[0].message.content
|
||||
core.info(`Model response: ${modelResponse || 'No response content'}`)
|
||||
// Add response format if specified
|
||||
if (request.responseFormat) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
chatCompletionRequest.response_format = request.responseFormat as any
|
||||
}
|
||||
|
||||
return modelResponse
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,89 +85,131 @@ export async function simpleInference(
|
||||
*/
|
||||
export async function mcpInference(
|
||||
request: InferenceRequest,
|
||||
githubMcpClient: GitHubMCPClient
|
||||
githubMcpClient: GitHubMCPClient,
|
||||
): Promise<string | null> {
|
||||
core.info('Running GitHub MCP inference with tools')
|
||||
|
||||
const client = ModelClient(
|
||||
request.endpoint,
|
||||
new AzureKeyCredential(request.token),
|
||||
{
|
||||
userAgentOptions: { userAgentPrefix: 'github-actions-ai-inference' }
|
||||
}
|
||||
)
|
||||
const client = new OpenAI({
|
||||
apiKey: request.token,
|
||||
baseURL: request.endpoint,
|
||||
defaultHeaders: request.customHeaders || {},
|
||||
})
|
||||
|
||||
// Start with the initial conversation
|
||||
const messages = [
|
||||
{
|
||||
role: 'system',
|
||||
content: request.systemPrompt
|
||||
},
|
||||
{ role: 'user', content: request.prompt }
|
||||
]
|
||||
// Start with the pre-processed messages
|
||||
const messages: ChatMessage[] = [...request.messages]
|
||||
|
||||
let iterationCount = 0
|
||||
const maxIterations = 5 // Prevent infinite loops
|
||||
// We want to use response_format (e.g. JSON) on the last iteration only, so the model can output
|
||||
// the final result in the expected format without interfering with tool calls
|
||||
let finalMessage = false
|
||||
|
||||
while (iterationCount < maxIterations) {
|
||||
iterationCount++
|
||||
core.info(`MCP inference iteration ${iterationCount}`)
|
||||
|
||||
const requestBody = {
|
||||
messages: messages,
|
||||
max_tokens: request.maxTokens,
|
||||
const chatCompletionRequest: OpenAI.Chat.Completions.ChatCompletionCreateParams = {
|
||||
messages: messages as OpenAI.Chat.Completions.ChatCompletionMessageParam[],
|
||||
model: request.modelName,
|
||||
tools: githubMcpClient.tools
|
||||
temperature: request.temperature,
|
||||
top_p: request.topP,
|
||||
...buildMaxTokensParam(request), // Note: solution around models using different underlying max tokens properties
|
||||
}
|
||||
|
||||
const response = await client.path('/chat/completions').post({
|
||||
body: requestBody
|
||||
})
|
||||
|
||||
if (isUnexpected(response)) {
|
||||
handleUnexpectedResponse(response)
|
||||
// Add response format if specified (only on final iteration to avoid conflicts with tool calls)
|
||||
if (finalMessage && request.responseFormat) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
chatCompletionRequest.response_format = request.responseFormat as any
|
||||
} else {
|
||||
chatCompletionRequest.tools = githubMcpClient.tools as OpenAI.Chat.Completions.ChatCompletionTool[]
|
||||
}
|
||||
|
||||
const assistantMessage = response.body.choices[0].message
|
||||
const modelResponse = assistantMessage.content
|
||||
const toolCalls = assistantMessage.tool_calls
|
||||
try {
|
||||
const response = await chatCompletion(client, chatCompletionRequest, `mcpInference iteration ${iterationCount}`)
|
||||
|
||||
core.info(`Model response: ${modelResponse || 'No response content'}`)
|
||||
const assistantMessage = response.choices[0]?.message
|
||||
const modelResponse = assistantMessage?.content
|
||||
const toolCalls = assistantMessage?.tool_calls
|
||||
|
||||
messages.push({
|
||||
role: 'assistant',
|
||||
content: modelResponse || '',
|
||||
...(toolCalls && { tool_calls: toolCalls })
|
||||
})
|
||||
core.debug(`Model response: ${modelResponse || 'No response content'}`)
|
||||
|
||||
if (!toolCalls || toolCalls.length === 0) {
|
||||
core.info('No tool calls requested, ending GitHub MCP inference loop')
|
||||
return modelResponse
|
||||
messages.push({
|
||||
role: 'assistant',
|
||||
content: modelResponse || '',
|
||||
...(toolCalls && {tool_calls: toolCalls as ToolCall[]}),
|
||||
})
|
||||
|
||||
if (!toolCalls || toolCalls.length === 0) {
|
||||
core.info('No tool calls requested, ending GitHub MCP inference loop')
|
||||
|
||||
if (request.responseFormat && !finalMessage) {
|
||||
core.info('Making one more MCP loop with the requested response format...')
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content: `Please provide your response in the exact ${request.responseFormat.type} format specified.`,
|
||||
})
|
||||
finalMessage = true
|
||||
continue
|
||||
} else {
|
||||
return modelResponse || null
|
||||
}
|
||||
}
|
||||
|
||||
core.info(`Model requested ${toolCalls.length} tool calls`)
|
||||
const toolResults = await executeToolCalls(githubMcpClient.client, toolCalls as ToolCall[])
|
||||
messages.push(...toolResults)
|
||||
core.info('Tool results added, continuing conversation...')
|
||||
} catch (error) {
|
||||
core.error(`OpenAI API error: ${error}`)
|
||||
throw error
|
||||
}
|
||||
|
||||
core.info(`Model requested ${toolCalls.length} tool calls`)
|
||||
|
||||
// Execute all tool calls via GitHub MCP
|
||||
const toolResults = await executeToolCalls(
|
||||
githubMcpClient.client,
|
||||
toolCalls
|
||||
)
|
||||
|
||||
// Add tool results to the conversation
|
||||
messages.push(...toolResults)
|
||||
|
||||
core.info('Tool results added, continuing conversation...')
|
||||
}
|
||||
|
||||
core.warning(
|
||||
`GitHub MCP inference loop exceeded maximum iterations (${maxIterations})`
|
||||
)
|
||||
core.warning(`GitHub MCP inference loop exceeded maximum iterations (${maxIterations})`)
|
||||
|
||||
// Return the last assistant message content
|
||||
const lastAssistantMessage = messages
|
||||
.slice()
|
||||
.reverse()
|
||||
.find((msg) => msg.role === 'assistant')
|
||||
.find(msg => msg.role === 'assistant')
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
120
src/main.ts
120
src/main.ts
@@ -1,12 +1,16 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as fs from 'fs'
|
||||
import * as os from 'os'
|
||||
import * as path from 'path'
|
||||
import { connectToGitHubMCP } from './mcp.js'
|
||||
import { simpleInference, mcpInference, InferenceRequest } from './inference.js'
|
||||
import { loadContentFromFileOrInput } from './helpers.js'
|
||||
|
||||
const RESPONSE_FILE = 'modelResponse.txt'
|
||||
import * as tmp from 'tmp'
|
||||
import {connectToGitHubMCP} from './mcp.js'
|
||||
import {simpleInference, mcpInference} from './inference.js'
|
||||
import {loadContentFromFileOrInput, buildInferenceRequest, parseCustomHeaders} from './helpers.js'
|
||||
import {
|
||||
loadPromptFile,
|
||||
parseTemplateVariables,
|
||||
isPromptYamlFile,
|
||||
PromptConfig,
|
||||
parseFileTemplateVariables,
|
||||
} from './prompt.js'
|
||||
|
||||
/**
|
||||
* The main function for the action.
|
||||
@@ -15,38 +19,90 @@ const RESPONSE_FILE = 'modelResponse.txt'
|
||||
*/
|
||||
export async function run(): Promise<void> {
|
||||
try {
|
||||
const prompt = loadContentFromFileOrInput('prompt-file', 'prompt')
|
||||
const promptFilePath = core.getInput('prompt-file')
|
||||
const inputVariables = core.getInput('input')
|
||||
const fileInputVariables = core.getInput('file_input')
|
||||
|
||||
const systemPrompt = loadContentFromFileOrInput(
|
||||
'system-prompt-file',
|
||||
'system-prompt',
|
||||
'You are a helpful assistant'
|
||||
)
|
||||
let promptConfig: PromptConfig | undefined = undefined
|
||||
let systemPrompt: string | undefined = undefined
|
||||
let prompt: string | undefined = undefined
|
||||
|
||||
const modelName: string = core.getInput('model')
|
||||
const maxTokens: number = parseInt(core.getInput('max-tokens'), 10)
|
||||
// Check if we're using a prompt YAML file
|
||||
if (promptFilePath && isPromptYamlFile(promptFilePath)) {
|
||||
core.info('Using prompt YAML file format')
|
||||
|
||||
// Parse template variables from both string inputs and file-based inputs
|
||||
const stringVars = parseTemplateVariables(inputVariables)
|
||||
const fileVars = parseFileTemplateVariables(fileInputVariables)
|
||||
const templateVariables = {...stringVars, ...fileVars}
|
||||
|
||||
// Load and process prompt file
|
||||
promptConfig = loadPromptFile(promptFilePath, templateVariables)
|
||||
} else {
|
||||
// Use legacy format
|
||||
core.info('Using legacy prompt format')
|
||||
|
||||
prompt = loadContentFromFileOrInput('prompt-file', 'prompt')
|
||||
systemPrompt = loadContentFromFileOrInput('system-prompt-file', 'system-prompt', 'You are a helpful assistant')
|
||||
}
|
||||
|
||||
// Get common parameters
|
||||
const modelName = promptConfig?.model || core.getInput('model')
|
||||
|
||||
// Parse token limit inputs
|
||||
const maxCompletionTokensInput =
|
||||
promptConfig?.modelParameters?.maxCompletionTokens ?? core.getInput('max-completion-tokens')
|
||||
const maxCompletionTokens = maxCompletionTokensInput ? Number(maxCompletionTokensInput) : undefined
|
||||
|
||||
const maxTokensInput = promptConfig?.modelParameters?.maxTokens ?? core.getInput('max-tokens')
|
||||
const maxTokens = maxCompletionTokens != null ? undefined : maxTokensInput ? Number(maxTokensInput) : undefined
|
||||
|
||||
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')
|
||||
const enableMcp = core.getBooleanInput('enable-github-mcp') || false
|
||||
|
||||
const inferenceRequest: InferenceRequest = {
|
||||
// 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
|
||||
}
|
||||
token,
|
||||
customHeaders,
|
||||
)
|
||||
|
||||
const enableMcp = core.getBooleanInput('enable-github-mcp') || false
|
||||
|
||||
let modelResponse: string | null = null
|
||||
|
||||
if (enableMcp) {
|
||||
const mcpClient = await connectToGitHubMCP(token)
|
||||
const mcpClient = await connectToGitHubMCP(githubMcpToken, githubMcpToolsets)
|
||||
|
||||
if (mcpClient) {
|
||||
modelResponse = await mcpInference(inferenceRequest, mcpClient)
|
||||
@@ -60,22 +116,30 @@ 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) {
|
||||
core.setFailed(error.message)
|
||||
} else {
|
||||
core.setFailed('An unexpected error occurred')
|
||||
core.setFailed(`An unexpected error occurred: ${JSON.stringify(error, null, 2)}`)
|
||||
}
|
||||
// Force exit to prevent hanging on open connections
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
function tempDir(): string {
|
||||
const tempDirectory = process.env['RUNNER_TEMP'] || os.tmpdir()
|
||||
return tempDirectory
|
||||
// Force exit to prevent hanging on open connections
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
68
src/mcp.ts
68
src/mcp.ts
@@ -1,6 +1,6 @@
|
||||
import * as core from '@actions/core'
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
||||
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
||||
import {Client} from '@modelcontextprotocol/sdk/client/index.js'
|
||||
import {StreamableHTTPClientTransport} from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
||||
|
||||
export interface ToolResult {
|
||||
tool_call_id: string
|
||||
@@ -35,26 +35,34 @@ 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,
|
||||
},
|
||||
})
|
||||
|
||||
const client = new Client({
|
||||
name: 'ai-inference-action',
|
||||
version: '1.0.0',
|
||||
transport
|
||||
transport,
|
||||
})
|
||||
|
||||
try {
|
||||
@@ -67,62 +75,53 @@ export async function connectToGitHubMCP(
|
||||
core.info('Successfully connected to GitHub MCP server')
|
||||
|
||||
const toolsResponse = await client.listTools()
|
||||
core.info(
|
||||
`Retrieved ${toolsResponse.tools?.length || 0} tools from GitHub MCP server`
|
||||
)
|
||||
core.info(`Retrieved ${toolsResponse.tools?.length || 0} tools from GitHub MCP server`)
|
||||
|
||||
// Map GitHub MCP tools → Azure AI Inference tool definitions
|
||||
const tools = (toolsResponse.tools || []).map((t) => ({
|
||||
const tools = (toolsResponse.tools || []).map(t => ({
|
||||
type: 'function' as const,
|
||||
function: {
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
parameters: t.inputSchema
|
||||
}
|
||||
parameters: t.inputSchema,
|
||||
},
|
||||
}))
|
||||
|
||||
core.info(`Mapped ${tools.length} GitHub MCP tools for Azure AI Inference`)
|
||||
|
||||
return { client, tools }
|
||||
return {client, tools}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}`
|
||||
)
|
||||
export async function executeToolCall(githubMcpClient: Client, toolCall: ToolCall): Promise<ToolResult> {
|
||||
core.debug(`Executing GitHub MCP tool: ${toolCall.function.name} with args: ${toolCall.function.arguments}`)
|
||||
|
||||
try {
|
||||
const args = JSON.parse(toolCall.function.arguments)
|
||||
|
||||
const result = await githubMcpClient.callTool({
|
||||
name: toolCall.function.name,
|
||||
arguments: args
|
||||
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,
|
||||
role: 'tool',
|
||||
name: toolCall.function.name,
|
||||
content: JSON.stringify(result.content)
|
||||
content: JSON.stringify(result.content),
|
||||
}
|
||||
} catch (toolError) {
|
||||
core.warning(
|
||||
`Failed to execute GitHub MCP tool ${toolCall.function.name}: ${toolError}`
|
||||
)
|
||||
core.warning(`Failed to execute GitHub MCP tool ${toolCall.function.name}: ${toolError}`)
|
||||
|
||||
return {
|
||||
tool_call_id: toolCall.id,
|
||||
role: 'tool',
|
||||
name: toolCall.function.name,
|
||||
content: `Error: ${toolError}`
|
||||
content: `Error: ${toolError}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,10 +129,7 @@ export async function executeToolCall(
|
||||
/**
|
||||
* Execute all tool calls from a response via GitHub MCP
|
||||
*/
|
||||
export async function executeToolCalls(
|
||||
githubMcpClient: Client,
|
||||
toolCalls: ToolCall[]
|
||||
): Promise<ToolResult[]> {
|
||||
export async function executeToolCalls(githubMcpClient: Client, toolCalls: ToolCall[]): Promise<ToolResult[]> {
|
||||
const toolResults: ToolResult[] = []
|
||||
|
||||
for (const toolCall of toolCalls) {
|
||||
|
||||
148
src/prompt.ts
Normal file
148
src/prompt.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as fs from 'fs'
|
||||
import * as yaml from 'js-yaml'
|
||||
|
||||
export interface PromptMessage {
|
||||
role: 'system' | 'user' | 'assistant'
|
||||
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
|
||||
}
|
||||
|
||||
export interface TemplateVariables {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse template variables from YAML input string
|
||||
*/
|
||||
export function parseTemplateVariables(input: string): TemplateVariables {
|
||||
if (!input.trim()) {
|
||||
return {}
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = yaml.load(input) as TemplateVariables
|
||||
if (typeof parsed !== 'object' || parsed === null) {
|
||||
throw new Error('Template variables must be a YAML object')
|
||||
}
|
||||
return parsed
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse template variables: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse file-based template variables from YAML input string. The YAML should map
|
||||
* variable names to file paths. File contents are read and returned as variables.
|
||||
*/
|
||||
export function parseFileTemplateVariables(fileInput: string): TemplateVariables {
|
||||
if (!fileInput.trim()) {
|
||||
return {}
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = yaml.load(fileInput) as Record<string, unknown>
|
||||
if (typeof parsed !== 'object' || parsed === null) {
|
||||
throw new Error('File template variables must be a YAML object')
|
||||
}
|
||||
|
||||
const result: TemplateVariables = {}
|
||||
for (const [key, value] of Object.entries(parsed)) {
|
||||
if (typeof value !== 'string') {
|
||||
throw new Error(`File template variable '${key}' must be a string file path`)
|
||||
}
|
||||
const filePath = value
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw new Error(`File for template variable '${key}' was not found: ${filePath}`)
|
||||
}
|
||||
try {
|
||||
result[key] = fs.readFileSync(filePath, 'utf-8')
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Failed to read file for template variable '${key}' at path '${filePath}': ${err instanceof Error ? err.message : 'Unknown error'}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to parse file template variables: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace template variables in text using {{variable}} syntax
|
||||
*/
|
||||
export function replaceTemplateVariables(text: string, variables: TemplateVariables): string {
|
||||
return text.replace(/\{\{([\w.-]+)\}\}/g, (match, variableName) => {
|
||||
if (variableName in variables) {
|
||||
return variables[variableName]
|
||||
}
|
||||
core.warning(`Template variable '${variableName}' not found in input variables`)
|
||||
return match // Return the original placeholder if variable not found
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and parse a prompt YAML file with template variable substitution
|
||||
*/
|
||||
export function loadPromptFile(filePath: string, templateVariables: TemplateVariables = {}): PromptConfig {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw new Error(`Prompt file not found: ${filePath}`)
|
||||
}
|
||||
|
||||
const fileContent = fs.readFileSync(filePath, 'utf-8')
|
||||
|
||||
try {
|
||||
const config = yaml.load(fileContent) as PromptConfig
|
||||
|
||||
if (!config.messages || !Array.isArray(config.messages)) {
|
||||
throw new Error('Prompt file must contain a "messages" array')
|
||||
}
|
||||
|
||||
// Validate messages
|
||||
for (const message of config.messages) {
|
||||
if (!message.role || !message.content) {
|
||||
throw new Error('Each message must have "role" and "content" properties')
|
||||
}
|
||||
if (!['system', 'user', 'assistant'].includes(message.role)) {
|
||||
throw new Error(`Invalid message role: ${message.role}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 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'}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file is a prompt YAML file based on extension
|
||||
*/
|
||||
export function isPromptYamlFile(filePath: string): boolean {
|
||||
return filePath.endsWith('.prompt.yml') || filePath.endsWith('.prompt.yaml')
|
||||
}
|
||||
@@ -6,12 +6,5 @@
|
||||
"noEmit": true
|
||||
},
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"include": [
|
||||
"__fixtures__",
|
||||
"__tests__",
|
||||
"src",
|
||||
"eslint.config.mjs",
|
||||
"jest.config.js",
|
||||
"rollup.config.ts"
|
||||
]
|
||||
"include": ["__fixtures__", "__tests__", "src", "eslint.config.mjs", "rollup.config.ts"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user