From 8f6a656f1add0a8fc9faaec643144c8a8272f4e4 Mon Sep 17 00:00:00 2001 From: n-devs Date: Fri, 26 Jun 2026 15:52:56 +0700 Subject: [PATCH 01/13] feat: add configuration and testing for API types and providers - Introduced `config.ts` to manage API type paths, provider bases, and default types. - Implemented functions to resolve API types and URLs based on provider and environment variables. - Added unit tests for API type paths, provider bases, and utility functions using Vitest. - Updated `index.ts` to utilize new configuration and resolution functions, improving maintainability. - Enhanced package.json with Vitest as a testing dependency and added test scripts. --- package-lock.json | 1252 +++++++++++++++++++++++++++++++++- package.json | 7 +- src/__tests__/config.test.ts | 178 +++++ src/config.ts | 70 ++ src/index.ts | 51 +- 5 files changed, 1521 insertions(+), 37 deletions(-) create mode 100644 src/__tests__/config.test.ts create mode 100644 src/config.ts diff --git a/package-lock.json b/package-lock.json index 39b6ece..33fa0bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,387 @@ "devDependencies": { "@types/node": "^22.0.0", "@vercel/ncc": "^0.44.0", - "typescript": "^5.0.0" + "typescript": "^5.0.0", + "vitest": "^4.1.9" } }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", + "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", + "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", + "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", + "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", + "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", + "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", + "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", + "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", + "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", + "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", + "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", + "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", + "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", + "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", + "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", + "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.20.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.0.tgz", @@ -33,6 +411,693 @@ "ncc": "dist/ncc/cli.js" } }, + "node_modules/@vitest/expect": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", + "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", + "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", + "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz", + "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.9", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz", + "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.9", + "@vitest/utils": "4.1.9", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz", + "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", + "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.9", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rolldown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", + "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.137.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.3", + "@rolldown/binding-darwin-arm64": "1.1.3", + "@rolldown/binding-darwin-x64": "1.1.3", + "@rolldown/binding-freebsd-x64": "1.1.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", + "@rolldown/binding-linux-arm64-gnu": "1.1.3", + "@rolldown/binding-linux-arm64-musl": "1.1.3", + "@rolldown/binding-linux-ppc64-gnu": "1.1.3", + "@rolldown/binding-linux-s390x-gnu": "1.1.3", + "@rolldown/binding-linux-x64-gnu": "1.1.3", + "@rolldown/binding-linux-x64-musl": "1.1.3", + "@rolldown/binding-openharmony-arm64": "1.1.3", + "@rolldown/binding-wasm32-wasi": "1.1.3", + "@rolldown/binding-win32-arm64-msvc": "1.1.3", + "@rolldown/binding-win32-x64-msvc": "1.1.3" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -53,6 +1118,191 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" + }, + "node_modules/vite": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz", + "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "~1.1.2", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz", + "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.9", + "@vitest/mocker": "4.1.9", + "@vitest/pretty-format": "4.1.9", + "@vitest/runner": "4.1.9", + "@vitest/snapshot": "4.1.9", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.9", + "@vitest/browser-preview": "4.1.9", + "@vitest/browser-webdriverio": "4.1.9", + "@vitest/coverage-istanbul": "4.1.9", + "@vitest/coverage-v8": "4.1.9", + "@vitest/ui": "4.1.9", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } } } } diff --git a/package.json b/package.json index ebe11e2..d58cd91 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,14 @@ "main": "dist/index.js", "scripts": { "build": "ncc build src/index.ts -o dist --license licenses.txt", - "start": "node dist/index.js" + "start": "node dist/index.js", + "test": "vitest run", + "test:watch": "vitest" }, "devDependencies": { "@types/node": "^22.0.0", "@vercel/ncc": "^0.44.0", - "typescript": "^5.0.0" + "typescript": "^5.0.0", + "vitest": "^4.1.9" } } diff --git a/src/__tests__/config.test.ts b/src/__tests__/config.test.ts new file mode 100644 index 0000000..e220bd7 --- /dev/null +++ b/src/__tests__/config.test.ts @@ -0,0 +1,178 @@ +import { describe, it, expect } from "vitest"; +import { + API_TYPE_PATHS, + PROVIDER_BASES, + PROVIDER_DEFAULT_TYPE, + resolveApiType, + resolveApiUrl, + isCopilotUrl, + isAzureProvider, + requiresApiUrl, +} from "../config"; + +// --------------------------------------------------------------------------- +// API_TYPE_PATHS +// --------------------------------------------------------------------------- +describe("API_TYPE_PATHS", () => { + it("chat → /chat/completions", () => { + expect(API_TYPE_PATHS.chat).toBe("/chat/completions"); + }); + it("responses → /responses", () => { + expect(API_TYPE_PATHS.responses).toBe("/responses"); + }); + it("messages → /messages", () => { + expect(API_TYPE_PATHS.messages).toBe("/messages"); + }); + it("text → /completions", () => { + expect(API_TYPE_PATHS.text).toBe("/completions"); + }); +}); + +// --------------------------------------------------------------------------- +// PROVIDER_BASES +// --------------------------------------------------------------------------- +describe("PROVIDER_BASES", () => { + it("copilot base URL", () => { + expect(PROVIDER_BASES.copilot).toBe("https://api.githubcopilot.com"); + }); + it("openai base URL", () => { + expect(PROVIDER_BASES.openai).toBe("https://api.openai.com/v1"); + }); + it("anthropic base URL", () => { + expect(PROVIDER_BASES.anthropic).toBe("https://api.anthropic.com/v1"); + }); + it("openrouter base URL", () => { + expect(PROVIDER_BASES.openrouter).toBe("https://openrouter.ai/api/v1"); + }); + it("ollama base URL", () => { + expect(PROVIDER_BASES.ollama).toBe("http://localhost:11434/v1"); + }); + it("xai base URL", () => { + expect(PROVIDER_BASES.xai).toBe("https://api.x.ai/v1"); + }); + it("google base URL", () => { + expect(PROVIDER_BASES.google).toBe( + "https://generativelanguage.googleapis.com/v1beta/openai" + ); + }); + it("azure/aws/custom have no base URL", () => { + expect(PROVIDER_BASES.azure).toBeUndefined(); + expect(PROVIDER_BASES.aws).toBeUndefined(); + expect(PROVIDER_BASES.custom).toBeUndefined(); + }); +}); + +// --------------------------------------------------------------------------- +// PROVIDER_DEFAULT_TYPE +// --------------------------------------------------------------------------- +describe("PROVIDER_DEFAULT_TYPE", () => { + it("anthropic defaults to messages", () => { + expect(PROVIDER_DEFAULT_TYPE.anthropic).toBe("messages"); + }); + it("other providers have no default type override", () => { + expect(PROVIDER_DEFAULT_TYPE.openai).toBeUndefined(); + expect(PROVIDER_DEFAULT_TYPE.copilot).toBeUndefined(); + }); +}); + +// --------------------------------------------------------------------------- +// resolveApiType +// --------------------------------------------------------------------------- +describe("resolveApiType", () => { + it("returns explicit env value when provided", () => { + expect(resolveApiType("openai", "responses")).toBe("responses"); + }); + it("returns provider default when no env value", () => { + expect(resolveApiType("anthropic", undefined)).toBe("messages"); + }); + it("falls back to chat when no env value and no provider default", () => { + expect(resolveApiType("openai", undefined)).toBe("chat"); + expect(resolveApiType(undefined, undefined)).toBe("chat"); + }); +}); + +// --------------------------------------------------------------------------- +// resolveApiUrl +// --------------------------------------------------------------------------- +describe("resolveApiUrl", () => { + it("constructs URL from provider + api_type", () => { + expect(resolveApiUrl("openai", "chat")).toBe( + "https://api.openai.com/v1/chat/completions" + ); + }); + it("constructs Anthropic messages URL", () => { + expect(resolveApiUrl("anthropic", "messages")).toBe( + "https://api.anthropic.com/v1/messages" + ); + }); + it("constructs Copilot chat URL", () => { + expect(resolveApiUrl("copilot", "chat")).toBe( + "https://api.githubcopilot.com/chat/completions" + ); + }); + it("constructs Ollama chat URL", () => { + expect(resolveApiUrl("ollama", "chat")).toBe( + "http://localhost:11434/v1/chat/completions" + ); + }); + it("constructs OpenRouter responses URL", () => { + expect(resolveApiUrl("openrouter", "responses")).toBe( + "https://openrouter.ai/api/v1/responses" + ); + }); + it("uses override URL when provided, ignoring provider+type", () => { + expect( + resolveApiUrl("custom", "chat", "https://my-proxy.internal/v1/chat/completions") + ).toBe("https://my-proxy.internal/v1/chat/completions"); + }); + it("falls back to copilot URL when provider is undefined", () => { + expect(resolveApiUrl(undefined, "chat")).toBe( + "https://api.githubcopilot.com/chat/completions" + ); + }); + it("strips trailing slash from base URL", () => { + expect(resolveApiUrl("openai", "chat")).not.toContain("//chat"); + }); +}); + +// --------------------------------------------------------------------------- +// isCopilotUrl +// --------------------------------------------------------------------------- +describe("isCopilotUrl", () => { + it("returns true for githubcopilot.com URL", () => { + expect(isCopilotUrl("https://api.githubcopilot.com/chat/completions")).toBe(true); + }); + it("returns false for non-Copilot URL", () => { + expect(isCopilotUrl("https://api.openai.com/v1/chat/completions")).toBe(false); + }); +}); + +// --------------------------------------------------------------------------- +// isAzureProvider +// --------------------------------------------------------------------------- +describe("isAzureProvider", () => { + it("returns true when provider is azure", () => { + expect(isAzureProvider("azure", "https://my-resource.openai.azure.com/...")).toBe(true); + }); + it("returns true when URL contains .openai.azure.com", () => { + expect(isAzureProvider("custom", "https://my-resource.openai.azure.com/...")).toBe(true); + }); + it("returns false for non-Azure provider and URL", () => { + expect(isAzureProvider("openai", "https://api.openai.com/v1/chat/completions")).toBe(false); + }); + it("returns false when provider is undefined and URL is not Azure", () => { + expect(isAzureProvider(undefined, "https://api.openai.com/v1/chat/completions")).toBe(false); + }); +}); + +// --------------------------------------------------------------------------- +// requiresApiUrl +// --------------------------------------------------------------------------- +describe("requiresApiUrl", () => { + it("returns true for custom", () => expect(requiresApiUrl("custom")).toBe(true)); + it("returns true for azure", () => expect(requiresApiUrl("azure")).toBe(true)); + it("returns true for aws", () => expect(requiresApiUrl("aws")).toBe(true)); + it("returns false for copilot", () => expect(requiresApiUrl("copilot")).toBe(false)); + it("returns false for openai", () => expect(requiresApiUrl("openai")).toBe(false)); + it("returns false for undefined", () => expect(requiresApiUrl(undefined)).toBe(false)); +}); diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..93a0884 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,70 @@ +export type ApiType = "chat" | "responses" | "messages" | "text"; +export type Provider = + | "copilot" + | "openai" + | "anthropic" + | "openrouter" + | "ollama" + | "xai" + | "zai" + | "google" + | "azure" + | "aws" + | "custom"; + +export const API_TYPE_PATHS: Record = { + chat: "/chat/completions", + responses: "/responses", + messages: "/messages", + text: "/completions", +}; + +export const PROVIDER_BASES: Partial> = { + copilot: "https://api.githubcopilot.com", + openai: "https://api.openai.com/v1", + anthropic: "https://api.anthropic.com/v1", + openrouter: "https://openrouter.ai/api/v1", + ollama: "http://localhost:11434/v1", + xai: "https://api.x.ai/v1", + zai: "https://api.z.ai/api/coding/paas/v4", + google: "https://generativelanguage.googleapis.com/v1beta/openai", + // azure/aws/custom: must provide api_url +}; + +export const PROVIDER_DEFAULT_TYPE: Partial> = { + anthropic: "messages", +}; + +export const REQUIRES_API_URL: Provider[] = ["custom", "azure", "aws"]; + +export function resolveApiType( + provider: Provider | undefined, + envApiType?: string +): ApiType { + return (envApiType ?? (provider && PROVIDER_DEFAULT_TYPE[provider]) ?? "chat") as ApiType; +} + +export function resolveApiUrl( + provider: Provider | undefined, + apiType: ApiType, + override?: string +): string { + if (override) return override; + const base = ((provider && PROVIDER_BASES[provider]) ?? "https://api.githubcopilot.com").replace( + /\/$/, + "" + ); + return base + API_TYPE_PATHS[apiType]; +} + +export function isCopilotUrl(url: string): boolean { + return url.includes("githubcopilot.com"); +} + +export function isAzureProvider(provider: Provider | undefined, url: string): boolean { + return provider === "azure" || url.includes(".openai.azure.com"); +} + +export function requiresApiUrl(provider: Provider | undefined): boolean { + return REQUIRES_API_URL.includes(provider as Provider); +} diff --git a/src/index.ts b/src/index.ts index 9e965d1..3d568e0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,13 @@ import * as https from "https"; +import { + type ApiType, + type Provider, + resolveApiType, + resolveApiUrl, + isCopilotUrl, + isAzureProvider, + requiresApiUrl, +} from "./config"; // ดึงค่าจาก Environment Variables (รองรับทั้ง GitHub Actions inputs และ env vars ตรง) const GH_PAT = process.env.INPUT_GH_PAT ?? process.env.GH_PAT; @@ -9,40 +18,14 @@ const MAX_DIFF_CHARS = parseInt(process.env.INPUT_MAX_DIFF_CHARS ?? process.env. const MAX_TOKENS = parseInt(process.env.INPUT_MAX_TOKENS ?? process.env.MAX_TOKENS ?? "4096", 10); const LANGUAGE = process.env.INPUT_LANGUAGE ?? process.env.LANGUAGE ?? "English"; -type ApiType = "chat" | "responses" | "messages" | "text"; -type Provider = "copilot" | "openai" | "anthropic" | "openrouter" | "ollama" | "xai" | "zai" | "google" | "azure" | "aws" | "custom"; - -const API_TYPE_PATHS: Record = { - chat: "/chat/completions", - responses: "/responses", - messages: "/messages", - text: "/completions", -}; - -const PROVIDER_BASES: Partial> = { - copilot: "https://api.githubcopilot.com", - openai: "https://api.openai.com/v1", - anthropic: "https://api.anthropic.com/v1", - openrouter: "https://openrouter.ai/api/v1", - ollama: "http://localhost:11434/v1", - xai: "https://api.x.ai/v1", - zai: "https://api.z.ai/api/coding/paas/v4", - google: "https://generativelanguage.googleapis.com/v1beta/openai", - // azure: URL is resource-specific — use api_url - // aws: URL is region-specific — use api_url - // custom: must provide api_url -}; - -const PROVIDER_DEFAULT_TYPE: Partial> = { - anthropic: "messages", -}; - const PROVIDER = (process.env.INPUT_PROVIDER ?? process.env.PROVIDER) as Provider | undefined; -const API_TYPE = (process.env.INPUT_API_TYPE ?? process.env.INPUT_COMPLETION_TYPE ?? process.env.API_TYPE ?? process.env.COMPLETION_TYPE ?? (PROVIDER && PROVIDER_DEFAULT_TYPE[PROVIDER]) ?? "chat") as ApiType; -const API_BASE = ((PROVIDER && PROVIDER_BASES[PROVIDER]) ?? "https://api.githubcopilot.com").replace(/\/$/, ""); -const API_URL = process.env.INPUT_API_URL ?? process.env.API_URL ?? (API_BASE + API_TYPE_PATHS[API_TYPE]); +const API_TYPE: ApiType = resolveApiType( + PROVIDER, + process.env.INPUT_API_TYPE ?? process.env.INPUT_COMPLETION_TYPE ?? process.env.API_TYPE ?? process.env.COMPLETION_TYPE +); +const API_URL = resolveApiUrl(PROVIDER, API_TYPE, process.env.INPUT_API_URL ?? process.env.API_URL); -if (["custom", "azure", "aws"].includes(PROVIDER ?? "") && !process.env.INPUT_API_URL && !process.env.API_URL) { +if (requiresApiUrl(PROVIDER) && !process.env.INPUT_API_URL && !process.env.API_URL) { console.error(`api_url is required when provider is '${PROVIDER}'.`); process.exit(1); } @@ -179,12 +162,12 @@ async function askOpenAICompat(systemPrompt: string, userPrompt: string): Promis }; // Azure uses api-key header instead of Bearer - if (PROVIDER === "azure" || API_URL.includes(".openai.azure.com")) { + if (isAzureProvider(PROVIDER, API_URL)) { delete (headers as Record)["Authorization"]; headers["api-key"] = API_KEY; } - if (API_URL.includes("githubcopilot.com")) { + if (isCopilotUrl(API_URL)) { headers["editor-version"] = "vscode/1.95.0"; headers["editor-plugin-version"] = "copilot-chat/0.22.0"; headers["openai-organization"] = "github-copilot"; From d57b3c604f31d8c2bdfee79dbfaffb32611f5bf5 Mon Sep 17 00:00:00 2001 From: n-devs Date: Fri, 26 Jun 2026 16:01:44 +0700 Subject: [PATCH 02/13] feat: enhance action inputs and implement API type resolution for improved flexibility --- action.yml | 4 ++ dist/index.js | 116 +++++++++++++++++++++++++++++++++++++------------- src/index.ts | 31 +++++++++++++- 3 files changed, 119 insertions(+), 32 deletions(-) diff --git a/action.yml b/action.yml index b0a19a4..c167851 100644 --- a/action.yml +++ b/action.yml @@ -12,6 +12,10 @@ inputs: pr_number: description: "Pull request number to review" required: true + title: + description: "Title for the code review comment" + required: false + default: "AI Code Review" model: description: "AI model to use (e.g. gpt-4o, claude-3-5-sonnet-20241022, llama3)" required: false diff --git a/dist/index.js b/dist/index.js index 7ece52f..131545b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2,6 +2,60 @@ /******/ "use strict"; /******/ var __webpack_modules__ = ({ +/***/ 973: +/***/ ((__unused_webpack_module, exports) => { + + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.REQUIRES_API_URL = exports.PROVIDER_DEFAULT_TYPE = exports.PROVIDER_BASES = exports.API_TYPE_PATHS = void 0; +exports.resolveApiType = resolveApiType; +exports.resolveApiUrl = resolveApiUrl; +exports.isCopilotUrl = isCopilotUrl; +exports.isAzureProvider = isAzureProvider; +exports.requiresApiUrl = requiresApiUrl; +exports.API_TYPE_PATHS = { + chat: "/chat/completions", + responses: "/responses", + messages: "/messages", + text: "/completions", +}; +exports.PROVIDER_BASES = { + copilot: "https://api.githubcopilot.com", + openai: "https://api.openai.com/v1", + anthropic: "https://api.anthropic.com/v1", + openrouter: "https://openrouter.ai/api/v1", + ollama: "http://localhost:11434/v1", + xai: "https://api.x.ai/v1", + zai: "https://api.z.ai/api/coding/paas/v4", + google: "https://generativelanguage.googleapis.com/v1beta/openai", + // azure/aws/custom: must provide api_url +}; +exports.PROVIDER_DEFAULT_TYPE = { + anthropic: "messages", +}; +exports.REQUIRES_API_URL = ["custom", "azure", "aws"]; +function resolveApiType(provider, envApiType) { + return (envApiType ?? (provider && exports.PROVIDER_DEFAULT_TYPE[provider]) ?? "chat"); +} +function resolveApiUrl(provider, apiType, override) { + if (override) + return override; + const base = ((provider && exports.PROVIDER_BASES[provider]) ?? "https://api.githubcopilot.com").replace(/\/$/, ""); + return base + exports.API_TYPE_PATHS[apiType]; +} +function isCopilotUrl(url) { + return url.includes("githubcopilot.com"); +} +function isAzureProvider(provider, url) { + return provider === "azure" || url.includes(".openai.azure.com"); +} +function requiresApiUrl(provider) { + return exports.REQUIRES_API_URL.includes(provider); +} + + +/***/ }), + /***/ 407: /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { @@ -41,6 +95,7 @@ var __importStar = (this && this.__importStar) || (function () { })(); Object.defineProperty(exports, "__esModule", ({ value: true })); const https = __importStar(__nccwpck_require__(692)); +const config_1 = __nccwpck_require__(973); // ดึงค่าจาก Environment Variables (รองรับทั้ง GitHub Actions inputs และ env vars ตรง) const GH_PAT = process.env.INPUT_GH_PAT ?? process.env.GH_PAT; const PR_NUMBER = process.env.INPUT_PR_NUMBER ?? process.env.PR_NUMBER; @@ -49,37 +104,15 @@ const MODEL = process.env.INPUT_MODEL ?? process.env.INPUT_COPILOT_MODEL ?? proc const MAX_DIFF_CHARS = parseInt(process.env.INPUT_MAX_DIFF_CHARS ?? process.env.MAX_DIFF_CHARS ?? "30000", 10); const MAX_TOKENS = parseInt(process.env.INPUT_MAX_TOKENS ?? process.env.MAX_TOKENS ?? "4096", 10); const LANGUAGE = process.env.INPUT_LANGUAGE ?? process.env.LANGUAGE ?? "English"; -const API_TYPE_PATHS = { - chat: "/chat/completions", - responses: "/responses", - messages: "/messages", - text: "/completions", -}; -const PROVIDER_BASES = { - copilot: "https://api.githubcopilot.com", - openai: "https://api.openai.com/v1", - anthropic: "https://api.anthropic.com/v1", - openrouter: "https://openrouter.ai/api/v1", - ollama: "http://localhost:11434/v1", - xai: "https://api.x.ai/v1", - zai: "https://api.z.ai/api/coding/paas/v4", - google: "https://generativelanguage.googleapis.com/v1beta/openai", - // azure: URL is resource-specific — use api_url - // aws: URL is region-specific — use api_url - // custom: must provide api_url -}; -const PROVIDER_DEFAULT_TYPE = { - anthropic: "messages", -}; +const TITLE = process.env.INPUT_TITLE ?? process.env.TITLE ?? "AI Code Review"; const PROVIDER = (process.env.INPUT_PROVIDER ?? process.env.PROVIDER); -const API_TYPE = (process.env.INPUT_API_TYPE ?? process.env.INPUT_COMPLETION_TYPE ?? process.env.API_TYPE ?? process.env.COMPLETION_TYPE ?? (PROVIDER && PROVIDER_DEFAULT_TYPE[PROVIDER]) ?? "chat"); -const API_BASE = ((PROVIDER && PROVIDER_BASES[PROVIDER]) ?? "https://api.githubcopilot.com").replace(/\/$/, ""); -const API_URL = process.env.INPUT_API_URL ?? process.env.API_URL ?? (API_BASE + API_TYPE_PATHS[API_TYPE]); -if (["custom", "azure", "aws"].includes(PROVIDER ?? "") && !process.env.INPUT_API_URL && !process.env.API_URL) { +const API_TYPE = (0, config_1.resolveApiType)(PROVIDER, process.env.INPUT_API_TYPE ?? process.env.INPUT_COMPLETION_TYPE ?? process.env.API_TYPE ?? process.env.COMPLETION_TYPE); +const API_URL = (0, config_1.resolveApiUrl)(PROVIDER, API_TYPE, process.env.INPUT_API_URL ?? process.env.API_URL); +if ((0, config_1.requiresApiUrl)(PROVIDER) && !process.env.INPUT_API_URL && !process.env.API_URL) { console.error(`api_url is required when provider is '${PROVIDER}'.`); process.exit(1); } -const API_KEY = process.env.INPUT_API_KEY ?? process.env.API_KEY ?? GH_PAT ?? ""; +let API_KEY = process.env.INPUT_API_KEY ?? process.env.API_KEY ?? GH_PAT ?? ""; function fetchJson(url, options, body) { return new Promise((resolve, reject) => { const req = https.request(url, options, (res) => { @@ -171,11 +204,11 @@ async function askOpenAICompat(systemPrompt, userPrompt) { "User-Agent": "ai-code-review", }; // Azure uses api-key header instead of Bearer - if (PROVIDER === "azure" || API_URL.includes(".openai.azure.com")) { + if ((0, config_1.isAzureProvider)(PROVIDER, API_URL)) { delete headers["Authorization"]; headers["api-key"] = API_KEY; } - if (API_URL.includes("githubcopilot.com")) { + if ((0, config_1.isCopilotUrl)(API_URL)) { headers["editor-version"] = "vscode/1.95.0"; headers["editor-plugin-version"] = "copilot-chat/0.22.0"; headers["openai-organization"] = "github-copilot"; @@ -258,8 +291,9 @@ async function askResponses(systemPrompt, userPrompt) { // 3. โพสต์คำตอบกลับลงใน GitHub PR async function postCommentToPr(message) { const url = `https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/comments`; + const provider = PROVIDER ?? "copilot"; const payload = JSON.stringify({ - body: `## 🤖 GitHub Copilot Code Review Bot\n\n${message}`, + body: `## ${TITLE} — \`${MODEL}\` via \`${provider}\`\n\n${message}`, }); const options = { method: "POST", @@ -280,12 +314,34 @@ async function postCommentToPr(message) { } } // --- Main Execution --- +async function getCopilotToken(ghPat) { + const url = "https://api.github.com/copilot_internal/v2/token"; + const options = { + method: "GET", + headers: { + Authorization: `token ${ghPat}`, + Accept: "application/json", + "User-Agent": "ai-code-review", + }, + }; + const { status, data, text } = await fetchJson(url, options); + if (status !== 200 || !data.token) { + throw new Error(`Failed to get Copilot token: ${status} ${text.slice(0, 200)}`); + } + console.log("Copilot token obtained."); + return data.token; +} async function main() { if (!GH_PAT || !PR_NUMBER || !REPO) { console.error("Missing required environment variables. Set GH_PAT, PR_NUMBER, REPO."); process.exit(1); } console.log("Fetching PR diff..."); + // Exchange PAT for Copilot session token when using Copilot API + if ((0, config_1.isCopilotUrl)(API_URL) && GH_PAT && !process.env.INPUT_API_KEY && !process.env.API_KEY) { + console.log("Exchanging GitHub PAT for Copilot session token..."); + API_KEY = await getCopilotToken(GH_PAT); + } const diff = await getPrDiff(); if (!diff) { console.log("No diff found or diff is too large."); diff --git a/src/index.ts b/src/index.ts index 3d568e0..a659be4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,7 @@ const MODEL = process.env.INPUT_MODEL ?? process.env.INPUT_COPILOT_MODEL ?? proc const MAX_DIFF_CHARS = parseInt(process.env.INPUT_MAX_DIFF_CHARS ?? process.env.MAX_DIFF_CHARS ?? "30000", 10); const MAX_TOKENS = parseInt(process.env.INPUT_MAX_TOKENS ?? process.env.MAX_TOKENS ?? "4096", 10); const LANGUAGE = process.env.INPUT_LANGUAGE ?? process.env.LANGUAGE ?? "English"; +const TITLE = process.env.INPUT_TITLE ?? process.env.TITLE ?? "AI Code Review"; const PROVIDER = (process.env.INPUT_PROVIDER ?? process.env.PROVIDER) as Provider | undefined; const API_TYPE: ApiType = resolveApiType( @@ -29,7 +30,7 @@ if (requiresApiUrl(PROVIDER) && !process.env.INPUT_API_URL && !process.env.API_U console.error(`api_url is required when provider is '${PROVIDER}'.`); process.exit(1); } -const API_KEY = process.env.INPUT_API_KEY ?? process.env.API_KEY ?? GH_PAT ?? ""; +let API_KEY = process.env.INPUT_API_KEY ?? process.env.API_KEY ?? GH_PAT ?? ""; interface GitHubFile { filename: string; @@ -267,8 +268,9 @@ async function askResponses(systemPrompt: string, userPrompt: string): Promise { const url = `https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/comments`; + const provider = PROVIDER ?? "copilot"; const payload = JSON.stringify({ - body: `## 🤖 GitHub Copilot Code Review Bot\n\n${message}`, + body: `## ${TITLE} — \`${MODEL}\` via \`${provider}\`\n\n${message}`, }); const options: https.RequestOptions = { @@ -292,6 +294,24 @@ async function postCommentToPr(message: string): Promise { } // --- Main Execution --- +async function getCopilotToken(ghPat: string): Promise { + const url = "https://api.github.com/copilot_internal/v2/token"; + const options: https.RequestOptions = { + method: "GET", + headers: { + Authorization: `token ${ghPat}`, + Accept: "application/json", + "User-Agent": "ai-code-review", + }, + }; + const { status, data, text } = await fetchJson<{ token?: string }>(url, options); + if (status !== 200 || !data.token) { + throw new Error(`Failed to get Copilot token: ${status} ${text.slice(0, 200)}`); + } + console.log("Copilot token obtained."); + return data.token; +} + async function main(): Promise { if (!GH_PAT || !PR_NUMBER || !REPO) { console.error("Missing required environment variables. Set GH_PAT, PR_NUMBER, REPO."); @@ -299,6 +319,13 @@ async function main(): Promise { } console.log("Fetching PR diff..."); + + // Exchange PAT for Copilot session token when using Copilot API + if (isCopilotUrl(API_URL) && GH_PAT && !process.env.INPUT_API_KEY && !process.env.API_KEY) { + console.log("Exchanging GitHub PAT for Copilot session token..."); + API_KEY = await getCopilotToken(GH_PAT); + } + const diff = await getPrDiff(); if (!diff) { From 8ff3423927cdbc6189406b02394cbc75ba0ce2d2 Mon Sep 17 00:00:00 2001 From: n-devs Date: Fri, 26 Jun 2026 16:06:54 +0700 Subject: [PATCH 03/13] feat: update API key handling and improve Copilot integration documentation --- action.yml | 4 ++-- dist/index.js | 18 ++++++++++++++---- src/index.ts | 18 ++++++++++++++---- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/action.yml b/action.yml index c167851..86c8354 100644 --- a/action.yml +++ b/action.yml @@ -25,7 +25,7 @@ inputs: required: false default: "copilot" api_key: - description: "API key for the selected provider (not needed for copilot — uses gh_pat)" + description: "API key for the selected provider. For copilot: provide a personal GitHub PAT with Copilot subscription (GITHUB_TOKEN will not work — it lacks Copilot access)" required: false api_url: description: "Full API endpoint URL (e.g. https://api.anthropic.com/v1/messages). Overrides provider + api_type defaults when set." @@ -48,5 +48,5 @@ inputs: default: "English" runs: - using: "node20" + using: "node24" main: "dist/index.js" diff --git a/dist/index.js b/dist/index.js index 131545b..c85dc74 100644 --- a/dist/index.js +++ b/dist/index.js @@ -112,7 +112,10 @@ if ((0, config_1.requiresApiUrl)(PROVIDER) && !process.env.INPUT_API_URL && !pro console.error(`api_url is required when provider is '${PROVIDER}'.`); process.exit(1); } -let API_KEY = process.env.INPUT_API_KEY ?? process.env.API_KEY ?? GH_PAT ?? ""; +// EXPLICIT_API_KEY is the raw api_key input (personal PAT or provider key) +// For Copilot, api_key should be a personal GitHub PAT with Copilot subscription +const EXPLICIT_API_KEY = process.env.INPUT_API_KEY ?? process.env.API_KEY; +let API_KEY = EXPLICIT_API_KEY ?? GH_PAT ?? ""; function fetchJson(url, options, body) { return new Promise((resolve, reject) => { const req = https.request(url, options, (res) => { @@ -337,10 +340,17 @@ async function main() { process.exit(1); } console.log("Fetching PR diff..."); - // Exchange PAT for Copilot session token when using Copilot API - if ((0, config_1.isCopilotUrl)(API_URL) && GH_PAT && !process.env.INPUT_API_KEY && !process.env.API_KEY) { + // Exchange PAT for Copilot session token when using Copilot API. + // Uses api_key (personal PAT with Copilot subscription) if provided, + // otherwise falls back to gh_pat. Note: GITHUB_TOKEN will fail here — use a personal PAT. + if ((0, config_1.isCopilotUrl)(API_URL)) { + const patForExchange = EXPLICIT_API_KEY ?? GH_PAT ?? ""; + if (!patForExchange) { + console.error("Copilot requires a GitHub personal PAT with Copilot subscription. Set api_key (preferred) or gh_pat."); + process.exit(1); + } console.log("Exchanging GitHub PAT for Copilot session token..."); - API_KEY = await getCopilotToken(GH_PAT); + API_KEY = await getCopilotToken(patForExchange); } const diff = await getPrDiff(); if (!diff) { diff --git a/src/index.ts b/src/index.ts index a659be4..71e5967 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,7 +30,10 @@ if (requiresApiUrl(PROVIDER) && !process.env.INPUT_API_URL && !process.env.API_U console.error(`api_url is required when provider is '${PROVIDER}'.`); process.exit(1); } -let API_KEY = process.env.INPUT_API_KEY ?? process.env.API_KEY ?? GH_PAT ?? ""; +// EXPLICIT_API_KEY is the raw api_key input (personal PAT or provider key) +// For Copilot, api_key should be a personal GitHub PAT with Copilot subscription +const EXPLICIT_API_KEY = process.env.INPUT_API_KEY ?? process.env.API_KEY; +let API_KEY = EXPLICIT_API_KEY ?? GH_PAT ?? ""; interface GitHubFile { filename: string; @@ -320,10 +323,17 @@ async function main(): Promise { console.log("Fetching PR diff..."); - // Exchange PAT for Copilot session token when using Copilot API - if (isCopilotUrl(API_URL) && GH_PAT && !process.env.INPUT_API_KEY && !process.env.API_KEY) { + // Exchange PAT for Copilot session token when using Copilot API. + // Uses api_key (personal PAT with Copilot subscription) if provided, + // otherwise falls back to gh_pat. Note: GITHUB_TOKEN will fail here — use a personal PAT. + if (isCopilotUrl(API_URL)) { + const patForExchange = EXPLICIT_API_KEY ?? GH_PAT ?? ""; + if (!patForExchange) { + console.error("Copilot requires a GitHub personal PAT with Copilot subscription. Set api_key (preferred) or gh_pat."); + process.exit(1); + } console.log("Exchanging GitHub PAT for Copilot session token..."); - API_KEY = await getCopilotToken(GH_PAT); + API_KEY = await getCopilotToken(patForExchange); } const diff = await getPrDiff(); From b51c58ef976e7842ac737fbadb15431525e17dff Mon Sep 17 00:00:00 2001 From: n-devs Date: Fri, 26 Jun 2026 16:17:52 +0700 Subject: [PATCH 04/13] feat: add API key to AI Code Review action configuration --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d89896a..b8e9cca 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,4 +24,5 @@ jobs: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} + api_key: ${{ secrets.COPILOG_API_KEY }} language: Thai From 0c4804aed5eda775dd629b698eb07ea68b7ab9ce Mon Sep 17 00:00:00 2001 From: n-devs Date: Fri, 26 Jun 2026 16:22:43 +0700 Subject: [PATCH 05/13] fix: update API key handling to allow empty strings and improve GitHub Actions permissions --- .github/workflows/test.yml | 3 ++- dist/index.js | 3 ++- src/index.ts | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b8e9cca..f7acf6a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,6 +7,7 @@ on: permissions: pull-requests: write contents: read + manage_billing:copilot: write jobs: ai-code-review: @@ -24,5 +25,5 @@ jobs: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} - api_key: ${{ secrets.COPILOG_API_KEY }} + api_key: ${{ secrets.COPILOT_API_KEY }} language: Thai diff --git a/dist/index.js b/dist/index.js index c85dc74..5d413be 100644 --- a/dist/index.js +++ b/dist/index.js @@ -114,7 +114,8 @@ if ((0, config_1.requiresApiUrl)(PROVIDER) && !process.env.INPUT_API_URL && !pro } // EXPLICIT_API_KEY is the raw api_key input (personal PAT or provider key) // For Copilot, api_key should be a personal GitHub PAT with Copilot subscription -const EXPLICIT_API_KEY = process.env.INPUT_API_KEY ?? process.env.API_KEY; +// Use || (not ??) so empty strings (from unset GitHub Actions secrets) fall through +const EXPLICIT_API_KEY = process.env.INPUT_API_KEY || process.env.API_KEY || undefined; let API_KEY = EXPLICIT_API_KEY ?? GH_PAT ?? ""; function fetchJson(url, options, body) { return new Promise((resolve, reject) => { diff --git a/src/index.ts b/src/index.ts index 71e5967..8cced0a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,7 +32,8 @@ if (requiresApiUrl(PROVIDER) && !process.env.INPUT_API_URL && !process.env.API_U } // EXPLICIT_API_KEY is the raw api_key input (personal PAT or provider key) // For Copilot, api_key should be a personal GitHub PAT with Copilot subscription -const EXPLICIT_API_KEY = process.env.INPUT_API_KEY ?? process.env.API_KEY; +// Use || (not ??) so empty strings (from unset GitHub Actions secrets) fall through +const EXPLICIT_API_KEY = process.env.INPUT_API_KEY || process.env.API_KEY || undefined; let API_KEY = EXPLICIT_API_KEY ?? GH_PAT ?? ""; interface GitHubFile { From 262190daab4df6231778f135c5aa6886cf0b9924 Mon Sep 17 00:00:00 2001 From: n-devs Date: Fri, 26 Jun 2026 16:23:16 +0700 Subject: [PATCH 06/13] fix: remove unnecessary manage_billing permission from workflow --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f7acf6a..bc2a64b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,6 @@ on: permissions: pull-requests: write contents: read - manage_billing:copilot: write jobs: ai-code-review: From 43e65684115a3c78e078f62c4b1be21b4948e394 Mon Sep 17 00:00:00 2001 From: n-devs Date: Fri, 26 Jun 2026 16:41:17 +0700 Subject: [PATCH 07/13] feat: add support for GitHub Models provider and update related configurations --- .github/workflows/test.yml | 5 ++++- action.yml | 2 +- dist/index.js | 1 + src/__tests__/config.test.ts | 11 +++++++++++ src/config.ts | 2 ++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bc2a64b..629c033 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,6 +7,7 @@ on: permissions: pull-requests: write contents: read + models: read jobs: ai-code-review: @@ -24,5 +25,7 @@ jobs: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} - api_key: ${{ secrets.COPILOT_API_KEY }} + provider: github-models + model: gpt-4o-mini + api_key: ${{ secrets.GITHUB_TOKEN }} language: Thai diff --git a/action.yml b/action.yml index 86c8354..096effd 100644 --- a/action.yml +++ b/action.yml @@ -21,7 +21,7 @@ inputs: required: false default: "gpt-5-mini" provider: - description: "AI provider shorthand: copilot, openai, anthropic, openrouter, ollama, xai, zai, google, azure, aws, custom — sets api_url and api_type defaults automatically (azure/aws require api_url)" + description: "AI provider shorthand: copilot, github-models, openai, anthropic, openrouter, ollama, xai, zai, google, azure, aws, custom — sets api_url and api_type defaults automatically (azure/aws require api_url). Use github-models to authenticate with GITHUB_TOKEN without needing a personal PAT." required: false default: "copilot" api_key: diff --git a/dist/index.js b/dist/index.js index 5d413be..f958ca6 100644 --- a/dist/index.js +++ b/dist/index.js @@ -21,6 +21,7 @@ exports.API_TYPE_PATHS = { }; exports.PROVIDER_BASES = { copilot: "https://api.githubcopilot.com", + "github-models": "https://models.inference.ai.azure.com", openai: "https://api.openai.com/v1", anthropic: "https://api.anthropic.com/v1", openrouter: "https://openrouter.ai/api/v1", diff --git a/src/__tests__/config.test.ts b/src/__tests__/config.test.ts index e220bd7..68e8787 100644 --- a/src/__tests__/config.test.ts +++ b/src/__tests__/config.test.ts @@ -55,6 +55,9 @@ describe("PROVIDER_BASES", () => { "https://generativelanguage.googleapis.com/v1beta/openai" ); }); + it("github-models base URL", () => { + expect(PROVIDER_BASES["github-models"]).toBe("https://models.inference.ai.azure.com"); + }); it("azure/aws/custom have no base URL", () => { expect(PROVIDER_BASES.azure).toBeUndefined(); expect(PROVIDER_BASES.aws).toBeUndefined(); @@ -120,6 +123,11 @@ describe("resolveApiUrl", () => { "https://openrouter.ai/api/v1/responses" ); }); + it("constructs GitHub Models chat URL", () => { + expect(resolveApiUrl("github-models", "chat")).toBe( + "https://models.inference.ai.azure.com/chat/completions" + ); + }); it("uses override URL when provided, ignoring provider+type", () => { expect( resolveApiUrl("custom", "chat", "https://my-proxy.internal/v1/chat/completions") @@ -145,6 +153,9 @@ describe("isCopilotUrl", () => { it("returns false for non-Copilot URL", () => { expect(isCopilotUrl("https://api.openai.com/v1/chat/completions")).toBe(false); }); + it("returns false for GitHub Models URL", () => { + expect(isCopilotUrl("https://models.inference.ai.azure.com/chat/completions")).toBe(false); + }); }); // --------------------------------------------------------------------------- diff --git a/src/config.ts b/src/config.ts index 93a0884..f159484 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,6 +1,7 @@ export type ApiType = "chat" | "responses" | "messages" | "text"; export type Provider = | "copilot" + | "github-models" | "openai" | "anthropic" | "openrouter" @@ -21,6 +22,7 @@ export const API_TYPE_PATHS: Record = { export const PROVIDER_BASES: Partial> = { copilot: "https://api.githubcopilot.com", + "github-models": "https://models.inference.ai.azure.com", openai: "https://api.openai.com/v1", anthropic: "https://api.anthropic.com/v1", openrouter: "https://openrouter.ai/api/v1", From 412e69683d5cff60a1a45b039ff712107ff9e2db Mon Sep 17 00:00:00 2001 From: n-devs Date: Fri, 26 Jun 2026 16:42:43 +0700 Subject: [PATCH 08/13] fix: update AI model from gpt-4o-mini to gpt-4 in workflow configuration --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 629c033..8cb3dad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,6 +26,6 @@ jobs: repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} provider: github-models - model: gpt-4o-mini + model: gpt-4o api_key: ${{ secrets.GITHUB_TOKEN }} language: Thai From 868423e7205f2b3b49104e3bfbb08c7f73a11cea Mon Sep 17 00:00:00 2001 From: n-devs Date: Fri, 26 Jun 2026 16:44:19 +0700 Subject: [PATCH 09/13] fix: add max_tokens and max_diff_chars parameters to AI Code Review action --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8cb3dad..bbb93d7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,3 +29,5 @@ jobs: model: gpt-4o api_key: ${{ secrets.GITHUB_TOKEN }} language: Thai + max_tokens: 2048 + max_diff_chars: 10000 From af8b2f74bcec24b400cc302faf9ca4bf536affc3 Mon Sep 17 00:00:00 2001 From: n-devs Date: Fri, 26 Jun 2026 17:39:34 +0700 Subject: [PATCH 10/13] fix: update action and workflow configurations for GitHub Models support --- .github/workflows/test.yml | 2 -- README.md | 73 ++++++++++++++++++++++++++------------ action.yml | 12 +++---- src/config.ts | 2 +- 4 files changed, 57 insertions(+), 32 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bbb93d7..6a36364 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,8 +25,6 @@ jobs: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} - provider: github-models - model: gpt-4o api_key: ${{ secrets.GITHUB_TOKEN }} language: Thai max_tokens: 2048 diff --git a/README.md b/README.md index e0ef9b8..77a7f43 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ -# AI Code Review Action +# AI Code Review Action -Automated PR code review powered by AI — supports GitHub Copilot, OpenAI, Anthropic, Google Gemini, xAI, OpenRouter, Ollama, Azure, AWS, and more. +Automated PR code review powered by AI — supports GitHub Models, GitHub Copilot, OpenAI, Anthropic, Google Gemini, xAI, OpenRouter, Ollama, Azure, AWS, and more. ## Quick Start +Zero-config setup using **GitHub Models** — works with `GITHUB_TOKEN`, no extra secrets needed: + ```yaml name: AI Code Review on: @@ -13,6 +15,7 @@ on: permissions: pull-requests: write contents: read + models: read jobs: review: @@ -24,52 +27,70 @@ jobs: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} + api_key: ${{ secrets.GITHUB_TOKEN }} ``` ## Inputs | Input | Required | Default | Description | |---|---|---|---| -| `gh_pat` | ✅ | — | GitHub PAT (for Copilot provider, also used as API key) | +| `gh_pat` | ✅ | — | GitHub PAT for GitHub API (fetching diff, posting comments) | | `repo` | ✅ | — | Repository in `owner/repo` format | | `pr_number` | ✅ | — | Pull request number to review | -| `provider` | | `copilot` | AI provider shorthand (see [Providers](#providers)) | -| `model` | | `gpt-5-mini` | Model name | -| `api_key` | | — | API key for the provider (not needed when using `copilot`) | +| `api_key` | ✅ | — | API key for the provider. For `github-models`: use `GITHUB_TOKEN`. For `copilot`: use a personal PAT with Copilot subscription | +| `provider` | | `github-models` | AI provider shorthand (see [Providers](#providers)) | +| `model` | | `gpt-4o` | Model name | +| `title` | | `AI Code Review` | Title shown in the PR comment header | | `api_url` | | — | Full endpoint URL — overrides provider + api_type defaults | | `api_type` | | `chat` | API format: `chat`, `responses`, or `messages` | -| `max_tokens` | | `4096` | Maximum tokens in AI response | -| `max_diff_chars` | | `30000` | Maximum characters of diff sent to AI | +| `max_tokens` | | `2048` | Maximum tokens in AI response | +| `max_diff_chars` | | `10000` | Maximum characters of diff sent to AI | | `language` | | `English` | Language for the review response | ## Providers -| `provider` | `api_type` default | Base URL | -|---|---|---| -| `copilot` | `chat` | `https://api.githubcopilot.com` | -| `openai` | `chat` | `https://api.openai.com/v1` | -| `anthropic` | `messages` | `https://api.anthropic.com/v1` | -| `openrouter` | `chat` | `https://openrouter.ai/api/v1` | -| `ollama` | `chat` | `http://localhost:11434/v1` | -| `xai` | `chat` | `https://api.x.ai/v1` (Grok) | -| `zai` | `chat` | `https://api.z.ai/api/coding/paas/v4` (GLM) | -| `google` | `chat` | `https://generativelanguage.googleapis.com/v1beta/openai` | -| `azure` | `chat` | ⚠️ requires `api_url` | -| `aws` | `chat` | ⚠️ requires `api_url` | -| `custom` | `chat` | ⚠️ requires `api_url` | +| `provider` | `api_type` default | Base URL | Auth | +|---|---|---|---| +| `github-models` | `chat` | `https://models.inference.ai.azure.com` | `GITHUB_TOKEN` ✅ | +| `copilot` | `chat` | `https://api.githubcopilot.com` | Personal PAT with Copilot subscription | +| `openai` | `chat` | `https://api.openai.com/v1` | API key | +| `anthropic` | `messages` | `https://api.anthropic.com/v1` | API key | +| `openrouter` | `chat` | `https://openrouter.ai/api/v1` | API key | +| `ollama` | `chat` | `http://localhost:11434/v1` | — | +| `xai` | `chat` | `https://api.x.ai/v1` | API key (Grok) | +| `zai` | `chat` | `https://api.z.ai/api/coding/paas/v4` | API key (GLM) | +| `google` | `chat` | `https://generativelanguage.googleapis.com/v1beta/openai` | API key | +| `azure` | `chat` | ⚠️ requires `api_url` | `api-key` header | +| `aws` | `chat` | ⚠️ requires `api_url` | API key | +| `custom` | `chat` | ⚠️ requires `api_url` | API key | + +> **Note:** `copilot` provider exchanges a personal PAT for a short-lived session token automatically. `GITHUB_TOKEN` from GitHub Actions will not work for Copilot — use `github-models` instead for zero-config setups. ## API Types | `api_type` | Path appended | Compatible with | |---|---|---| -| `chat` | `/chat/completions` | Copilot, OpenAI, OpenRouter, Ollama, xAI, Google, Azure | +| `chat` | `/chat/completions` | GitHub Models, Copilot, OpenAI, OpenRouter, Ollama, xAI, Google, Azure | | `responses` | `/responses` | OpenAI Responses API | | `messages` | `/messages` | Anthropic Claude | | `text` | `/completions` | Legacy completions endpoint | ## Examples -### GitHub Copilot (default) +### GitHub Models (default — zero config) + +```yaml +- uses: n-devs/ai-code-review@v1 + with: + gh_pat: ${{ secrets.GITHUB_TOKEN }} + repo: ${{ github.repository }} + pr_number: ${{ github.event.pull_request.number }} + api_key: ${{ secrets.GITHUB_TOKEN }} +``` + +### GitHub Copilot + +> Requires a personal PAT with Copilot subscription stored as `COPILOT_PAT` secret. ```yaml - uses: n-devs/ai-code-review@v1 @@ -77,6 +98,9 @@ jobs: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} + provider: copilot + api_key: ${{ secrets.COPILOT_PAT }} + model: gpt-4o ``` ### OpenAI GPT-4o @@ -154,6 +178,7 @@ jobs: repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} provider: ollama + api_key: "" api_url: http://my-server:11434/v1/chat/completions model: llama3 ``` @@ -208,6 +233,7 @@ jobs: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} + api_key: ${{ secrets.GITHUB_TOKEN }} language: Thai ``` @@ -216,4 +242,5 @@ jobs: ```bash npm install npm run build # outputs dist/index.js via @vercel/ncc +npm test # runs unit tests via vitest ``` diff --git a/action.yml b/action.yml index 096effd..8a73f08 100644 --- a/action.yml +++ b/action.yml @@ -12,6 +12,9 @@ inputs: pr_number: description: "Pull request number to review" required: true + api_key: + description: "API key for the selected provider. For copilot: provide a personal GitHub PAT with Copilot subscription (GITHUB_TOKEN will not work — it lacks Copilot access)" + required: true title: description: "Title for the code review comment" required: false @@ -23,10 +26,7 @@ inputs: provider: description: "AI provider shorthand: copilot, github-models, openai, anthropic, openrouter, ollama, xai, zai, google, azure, aws, custom — sets api_url and api_type defaults automatically (azure/aws require api_url). Use github-models to authenticate with GITHUB_TOKEN without needing a personal PAT." required: false - default: "copilot" - api_key: - description: "API key for the selected provider. For copilot: provide a personal GitHub PAT with Copilot subscription (GITHUB_TOKEN will not work — it lacks Copilot access)" - required: false + default: "github-models" api_url: description: "Full API endpoint URL (e.g. https://api.anthropic.com/v1/messages). Overrides provider + api_type defaults when set." required: false @@ -37,11 +37,11 @@ inputs: max_tokens: description: "Maximum number of tokens in the AI response" required: false - default: "4096" + default: "2048" max_diff_chars: description: "Maximum number of characters of diff to send to Copilot" required: false - default: "30000" + default: "10000" language: description: "Language for the code review response (e.g. English, Thai, Japanese)" required: false diff --git a/src/config.ts b/src/config.ts index f159484..2599692 100644 --- a/src/config.ts +++ b/src/config.ts @@ -21,8 +21,8 @@ export const API_TYPE_PATHS: Record = { }; export const PROVIDER_BASES: Partial> = { + "github-models": "https://models.inference.ai.azure.com", copilot: "https://api.githubcopilot.com", - "github-models": "https://models.inference.ai.azure.com", openai: "https://api.openai.com/v1", anthropic: "https://api.anthropic.com/v1", openrouter: "https://openrouter.ai/api/v1", From 1249440d4979ee32037b3241ef72468bd42af578 Mon Sep 17 00:00:00 2001 From: n-devs Date: Fri, 26 Jun 2026 17:42:01 +0700 Subject: [PATCH 11/13] fix: update default model from gpt-5-mini to gpt-4o in environment variable configuration --- dist/index.js | 4 ++-- src/index.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/index.js b/dist/index.js index f958ca6..3054d9c 100644 --- a/dist/index.js +++ b/dist/index.js @@ -20,8 +20,8 @@ exports.API_TYPE_PATHS = { text: "/completions", }; exports.PROVIDER_BASES = { - copilot: "https://api.githubcopilot.com", "github-models": "https://models.inference.ai.azure.com", + copilot: "https://api.githubcopilot.com", openai: "https://api.openai.com/v1", anthropic: "https://api.anthropic.com/v1", openrouter: "https://openrouter.ai/api/v1", @@ -101,7 +101,7 @@ const config_1 = __nccwpck_require__(973); const GH_PAT = process.env.INPUT_GH_PAT ?? process.env.GH_PAT; const PR_NUMBER = process.env.INPUT_PR_NUMBER ?? process.env.PR_NUMBER; const REPO = process.env.INPUT_REPO ?? process.env.REPO; // format: owner/repo -const MODEL = process.env.INPUT_MODEL ?? process.env.INPUT_COPILOT_MODEL ?? process.env.MODEL ?? process.env.COPILOT_MODEL ?? "gpt-5-mini"; +const MODEL = process.env.INPUT_MODEL ?? process.env.INPUT_COPILOT_MODEL ?? process.env.MODEL ?? process.env.COPILOT_MODEL ?? "gpt-4o"; const MAX_DIFF_CHARS = parseInt(process.env.INPUT_MAX_DIFF_CHARS ?? process.env.MAX_DIFF_CHARS ?? "30000", 10); const MAX_TOKENS = parseInt(process.env.INPUT_MAX_TOKENS ?? process.env.MAX_TOKENS ?? "4096", 10); const LANGUAGE = process.env.INPUT_LANGUAGE ?? process.env.LANGUAGE ?? "English"; diff --git a/src/index.ts b/src/index.ts index 8cced0a..e8f5b2b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,7 @@ import { const GH_PAT = process.env.INPUT_GH_PAT ?? process.env.GH_PAT; const PR_NUMBER = process.env.INPUT_PR_NUMBER ?? process.env.PR_NUMBER; const REPO = process.env.INPUT_REPO ?? process.env.REPO; // format: owner/repo -const MODEL = process.env.INPUT_MODEL ?? process.env.INPUT_COPILOT_MODEL ?? process.env.MODEL ?? process.env.COPILOT_MODEL ?? "gpt-5-mini"; +const MODEL = process.env.INPUT_MODEL ?? process.env.INPUT_COPILOT_MODEL ?? process.env.MODEL ?? process.env.COPILOT_MODEL ?? "gpt-4o"; const MAX_DIFF_CHARS = parseInt(process.env.INPUT_MAX_DIFF_CHARS ?? process.env.MAX_DIFF_CHARS ?? "30000", 10); const MAX_TOKENS = parseInt(process.env.INPUT_MAX_TOKENS ?? process.env.MAX_TOKENS ?? "4096", 10); const LANGUAGE = process.env.INPUT_LANGUAGE ?? process.env.LANGUAGE ?? "English"; From 0f7296bd402077e79fca001b4af5390b7bbddda9 Mon Sep 17 00:00:00 2001 From: n-devs Date: Fri, 26 Jun 2026 17:42:30 +0700 Subject: [PATCH 12/13] fix: update default max_tokens value from 2048 to 4096 in action configuration --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 8a73f08..db09d6d 100644 --- a/action.yml +++ b/action.yml @@ -37,7 +37,7 @@ inputs: max_tokens: description: "Maximum number of tokens in the AI response" required: false - default: "2048" + default: "4096" max_diff_chars: description: "Maximum number of characters of diff to send to Copilot" required: false From 18fdb8b1fa5dd0aab4eb39bca4eca3f84086e066 Mon Sep 17 00:00:00 2001 From: n-devs Date: Fri, 26 Jun 2026 17:43:21 +0700 Subject: [PATCH 13/13] fix: update default model from gpt-5-mini to gpt-4o in action configuration --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index db09d6d..9467475 100644 --- a/action.yml +++ b/action.yml @@ -22,7 +22,7 @@ inputs: model: description: "AI model to use (e.g. gpt-4o, claude-3-5-sonnet-20241022, llama3)" required: false - default: "gpt-5-mini" + default: "gpt-4o" provider: description: "AI provider shorthand: copilot, github-models, openai, anthropic, openrouter, ollama, xai, zai, google, azure, aws, custom — sets api_url and api_type defaults automatically (azure/aws require api_url). Use github-models to authenticate with GITHUB_TOKEN without needing a personal PAT." required: false