diff --git a/.env.example b/.env.example deleted file mode 100644 index 671d4eb..0000000 --- a/.env.example +++ /dev/null @@ -1,31 +0,0 @@ -## If you don't want to use multi-model routing -## set ENABLE_ROUTER to false, and define the following variables -## the model needs to support function calling -ENABLE_ROUTER=false -OPENAI_API_KEY="" -OPENAI_BASE_URL="" -OPENAI_MODEL="" - - -## If you want to use multi-model routing, set ENABLE_ROUTER to true -# ENABLE_ROUTER=true - -## Define the model for the tool agent, the model needs to support function calling -# TOOL_AGENT_API_KEY="" -# TOOL_AGENT_BASE_URL="" -# TOOL_AGENT_MODEL="" - -## Define the model for the coder agent -# CODER_AGENT_API_KEY="" -# CODER_AGENT_BASE_URL="" -# CODER_AGENT_MODEL="" - -## Define the model for the thinker agent, using a model that supports reasoning will yield better results -# THINK_AGENT_API_KEY="" -# THINK_AGENT_BASE_URL="" -# THINK_AGENT_MODEL="" - -## Define the model for the router agent, this model is the entry point for each request, it will consume a lot of tokens, please choose a small model to reduce costs -# ROUTER_AGENT_API_KEY="" -# ROUTER_AGENT_BASE_URL="" -# ROUTER_AGENT_MODEL="" diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..b6a2878 --- /dev/null +++ b/.npmignore @@ -0,0 +1,9 @@ +src +node_modules +.claude +CLAUDE.md +screenshoots +.DS_Store +.vscode +.idea +.env \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..50bfac0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,12 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.You need use English to write text. + +## Key Development Commands +- Build: `npm run build` +- Start: `npm start` + +## Architecture +- Uses `express` for routing (see `src/server.ts`) +- Bundles with `esbuild` for CLI distribution +- Plugins are loaded from `$HOME/.claude-code-router/plugins` \ No newline at end of file diff --git a/README.md b/README.md index a17cf44..cb9ace1 100644 --- a/README.md +++ b/README.md @@ -2,44 +2,26 @@ > This is a repository for testing routing Claude Code requests to different models. -![demo.png](https://github.com/musistudio/claude-code-router/blob/main/screenshoots/demo.png) - -## Implemented - -- [x] Support writing custom plugins for rewriting prompts. - -- [x] Support writing custom plugins for implementing routers. - ## Usage -0. Install Claude Code +1. Install Claude Code ```shell npm install -g @anthropic-ai/claude-code ``` -1. Clone this repo and install dependencies +2. Install Claude Code Router ```shell -git clone https://github.com/musistudio/claude-code-router -cd claude-code-router && pnpm i -npm run build +npm install -g @musistudio/claude-code-router ``` -2. Start claude-code-router server +3. Start Claude Code by claude-code-router ```shell -node dist/cli.js +ccr code ``` -3. Set environment variable to start claude code - -```shell -export DISABLE_PROMPT_CACHING=1 -export ANTHROPIC_BASE_URL="http://127.0.0.1:3456" -export API_TIMEOUT_MS=600000 -claude -``` ## Plugin @@ -58,3 +40,8 @@ You need to move them to the `$HOME/.claude-code-router/plugins` directory and c "OPENAI_MODEL": "" } ``` + +## Features +- [x] Plugins +- [] Support change models +- [] Suport scheduled tasks \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..9641bf6 --- /dev/null +++ b/config.json @@ -0,0 +1,7 @@ +{ + "usePlugin": "", + "LOG": true, + "OPENAI_API_KEY": "", + "OPENAI_BASE_URL": "", + "OPENAI_MODEL": "" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 54b732e..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1013 +0,0 @@ -{ - "name": "claude-code-reverse", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "claude-code-reverse", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "dotenv": "^16.4.7", - "express": "^4.21.2", - "openai": "^4.85.4" - } - }, - "node_modules/@types/node": { - "version": "18.19.76", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.76.tgz", - "integrity": "sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" - }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" - }, - "engines": { - "node": ">= 12.20" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/openai": { - "version": "4.85.4", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.85.4.tgz", - "integrity": "sha512-Nki51PBSu+Aryo7WKbdXvfm0X/iKkQS2fq3O0Uqb/O3b4exOZFid2te1BZ52bbO5UwxQZ5eeHJDCTqtrJLPw0w==", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - }, - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.23.8" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "engines": { - "node": ">= 14" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } -} diff --git a/package.json b/package.json index 9b043b3..f0b94ab 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,17 @@ { - "name": "claude-code-router", + "name": "@musistudio/claude-code-router", "version": "1.0.0", "description": "Use Claude Code without an Anthropics account and route it to another LLM provider", "bin": { - "claude-code-router": "./dist/cli.js" + "ccr": "./dist/cli.js" }, "scripts": { - "start": "node dist/cli.js", - "build": "tsc && esbuild src/index.ts --bundle --platform=node --outfile=dist/cli.js" + "build": "esbuild src/cli.ts --bundle --platform=node --outfile=dist/cli.js" }, "keywords": ["claude", "code", "router", "llm", "anthropic"], "author": "musistudio", "license": "MIT", "dependencies": { - "@anthropic-ai/claude-code": "^0.2.53", "@anthropic-ai/sdk": "^0.39.0", "dotenv": "^16.4.7", "express": "^4.21.2", diff --git a/plugins/deepseek.js b/plugins/deepseek.js index 7de7038..7d81ab9 100644 --- a/plugins/deepseek.js +++ b/plugins/deepseek.js @@ -6,7 +6,7 @@ const { const thinkRouter = { name: "think", - description: `This agent is used solely for complex reasoning and thinking tasks. It should not be called for information retrieval or repetitive, frequent requests. Only use this agent for tasks that require deep analysis or problem-solving. If there is an existing result from the Thinker agent, do not call this agent again.你只负责深度思考以拆分任务,不需要进行任何的编码和调用工具。最后讲拆分的步骤按照顺序返回。比如\n1. xxx\n2. xxx\n3. xxx`, + description: `This agent is used solely for complex reasoning and thinking tasks. It should not be called for information retrieval or repetitive, frequent requests. Only use this agent for tasks that require deep analysis or problem-solving. If there is an existing result from the Thinker agent, do not call this agent again. You are only responsible for deep thinking to break down tasks, no coding or tool calls are needed. Finally, return the broken-down steps in order, for example:\n1. xxx\n2. xxx\n3. xxx`, run(args) { const client = createClient({ apiKey: process.env.THINK_AGENT_API_KEY, diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 0000000..bd47cc5 --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,88 @@ +#!/usr/bin/env node +import { run } from "./index"; +import { closeService } from "./utils/close"; +import { showStatus } from "./utils/status"; +import { executeCodeCommand } from "./utils/codeCommand"; +import { isServiceRunning } from "./utils/processCheck"; +import { version } from "../package.json"; + +const command = process.argv[2]; + +const HELP_TEXT = ` +Usage: claude-code [command] + +Commands: + start Start service + stop Stop service + status Show service status + code Execute code command + -v, version Show version information + -h, help Show help information + +Example: + claude-code start + claude-code code "Write a Hello World" +`; + +async function waitForService( + timeout = 10000, + initialDelay = 1000 +): Promise { + // Wait for an initial period to let the service initialize + await new Promise((resolve) => setTimeout(resolve, initialDelay)); + + const startTime = Date.now(); + while (Date.now() - startTime < timeout) { + if (isServiceRunning()) { + // Wait for an additional short period to ensure service is fully ready + await new Promise((resolve) => setTimeout(resolve, 500)); + return true; + } + await new Promise((resolve) => setTimeout(resolve, 100)); + } + return false; +} + +async function main() { + switch (command) { + case "start": + await run({ daemon: true }); + break; + case "stop": + await closeService(); + break; + case "status": + showStatus(); + break; + case "code": + if (!isServiceRunning()) { + console.log("Service not running, starting service..."); + await run({ daemon: true }); + // Wait for service to start, exit with error if timeout + if (await waitForService()) { + executeCodeCommand(process.argv.slice(3)); + } else { + console.error( + "Service startup timeout, please manually run claude-code start to start the service" + ); + process.exit(1); + } + } else { + executeCodeCommand(process.argv.slice(3)); + } + break; + case "-v": + case "version": + console.log(`claude-code version: ${version}`); + break; + case "-h": + case "help": + console.log(HELP_TEXT); + break; + default: + console.log(HELP_TEXT); + process.exit(1); + } +} + +main().catch(console.error); diff --git a/src/constants.ts b/src/constants.ts index 644bf6f..c143533 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -7,9 +7,12 @@ export const CONFIG_FILE = `${HOME_DIR}/config.json`; export const PLUGINS_DIR = `${HOME_DIR}/plugins`; +export const PID_FILE = path.join(HOME_DIR, '.claude-code-router.pid'); + + export const DEFAULT_CONFIG = { log: false, OPENAI_API_KEY: "", - OPENAI_BASE_URL: "https://openrouter.ai/api/v1", - OPENAI_MODEL: "openai/o3-mini", + OPENAI_BASE_URL: "", + OPENAI_MODEL: "", }; diff --git a/src/index.ts b/src/index.ts index 6fd70c9..498d0b8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,8 @@ import { formatRequest } from "./middlewares/formatRequest"; import { rewriteBody } from "./middlewares/rewriteBody"; import OpenAI from "openai"; import { streamOpenAIResponse } from "./utils/stream"; +import { isServiceRunning, savePid } from "./utils/processCheck"; +import { fork } from "child_process"; async function initializeClaudeConfig() { const homeDir = process.env.HOME; @@ -20,18 +22,40 @@ async function initializeClaudeConfig() { autoUpdaterStatus: "enabled", userID, hasCompletedOnboarding: true, - lastOnboardingVersion: "0.2.9", + lastOnboardingVersion: "1.0.17", projects: {}, }; await writeFile(configPath, JSON.stringify(configContent, null, 2)); } } -async function run() { +interface RunOptions { + port?: number; + daemon?: boolean; +} + +async function run(options: RunOptions = {}) { + const port = options.port || 3456; + + // Check if service is already running + if (isServiceRunning()) { + console.log("✅ Service is already running in the background."); + return; + } + await initializeClaudeConfig(); await initDir(); await initConfig(); - const server = createServer(3456); + + // Save the PID of the background process + savePid(process.pid); + + // Use port from environment variable if set (for background process) + const servicePort = process.env.SERVICE_PORT + ? parseInt(process.env.SERVICE_PORT) + : port; + + const server = createServer(servicePort); server.useMiddleware(formatRequest); server.useMiddleware(rewriteBody); @@ -46,11 +70,13 @@ async function run() { req.body.model = process.env.OPENAI_MODEL; } const completion: any = await openai.chat.completions.create(req.body); - await streamOpenAIResponse(res, completion, req.body.model); + await streamOpenAIResponse(res, completion, req.body.model, req.body); } catch (e) { console.error("Error in OpenAI API call:", e); } }); server.start(); + console.log(`🚀 Claude Code Router is running on port ${servicePort}`); } -run(); + +export { run }; diff --git a/src/middlewares/formatRequest.ts b/src/middlewares/formatRequest.ts index e5ab184..bbb5177 100644 --- a/src/middlewares/formatRequest.ts +++ b/src/middlewares/formatRequest.ts @@ -3,6 +3,7 @@ import { ContentBlockParam } from "@anthropic-ai/sdk/resources"; import { MessageCreateParamsBase } from "@anthropic-ai/sdk/resources/messages"; import OpenAI from "openai"; import { streamOpenAIResponse } from "../utils/stream"; +import { log } from "../utils/log"; export const formatRequest = async ( req: Request, @@ -17,33 +18,138 @@ export const formatRequest = async ( temperature, metadata, tools, + stream, }: MessageCreateParamsBase = req.body; + log("formatRequest: ", req.body); try { - const openAIMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = - messages.map((item) => { - if (item.content instanceof Array) { - return { - role: item.role, - content: item.content - .map((it: ContentBlockParam) => { - if (it.type === "text") { - return typeof it.text === "string" - ? it.text - : JSON.stringify(it); - } - return JSON.stringify(it); - }) - .join(""), - } as OpenAI.Chat.Completions.ChatCompletionMessageParam; - } - return { - role: item.role, - content: - typeof item.content === "string" - ? item.content - : JSON.stringify(item.content), - }; - }); + // @ts-ignore + const openAIMessages = Array.isArray(messages) + ? messages.flatMap((anthropicMessage) => { + const openAiMessagesFromThisAnthropicMessage = []; + + if (!Array.isArray(anthropicMessage.content)) { + // Handle simple string content + if (typeof anthropicMessage.content === "string") { + openAiMessagesFromThisAnthropicMessage.push({ + role: anthropicMessage.role, + content: anthropicMessage.content, + }); + } + // If content is not string and not array (e.g. null/undefined), it will result in an empty array, effectively skipping this message. + return openAiMessagesFromThisAnthropicMessage; + } + + // Handle array content + if (anthropicMessage.role === "assistant") { + const assistantMessage = { + role: "assistant", + content: null, // Will be populated if text parts exist + }; + let textContent = ""; + // @ts-ignore + const toolCalls = []; // Corrected type here + + anthropicMessage.content.forEach((contentPart) => { + if (contentPart.type === "text") { + textContent += + (typeof contentPart.text === "string" + ? contentPart.text + : JSON.stringify(contentPart.text)) + "\\n"; + } else if (contentPart.type === "tool_use") { + toolCalls.push({ + id: contentPart.id, + type: "function", + function: { + name: contentPart.name, + arguments: JSON.stringify(contentPart.input), + }, + }); + } + }); + + const trimmedTextContent = textContent.trim(); + if (trimmedTextContent.length > 0) { + // @ts-ignore + assistantMessage.content = trimmedTextContent; + } + if (toolCalls.length > 0) { + // @ts-ignore + assistantMessage.tool_calls = toolCalls; + } + // @ts-ignore + if ( + assistantMessage.content || + // @ts-ignore + (assistantMessage.tool_calls && + // @ts-ignore + assistantMessage.tool_calls.length > 0) + ) { + openAiMessagesFromThisAnthropicMessage.push(assistantMessage); + } + } else if (anthropicMessage.role === "user") { + // For user messages, text parts are combined into one message. + // Tool results are transformed into subsequent, separate 'tool' role messages. + let userTextMessageContent = ""; + // @ts-ignore + const subsequentToolMessages = []; + + anthropicMessage.content.forEach((contentPart) => { + if (contentPart.type === "text") { + userTextMessageContent += + (typeof contentPart.text === "string" + ? contentPart.text + : JSON.stringify(contentPart.text)) + "\\n"; + } else if (contentPart.type === "tool_result") { + // Each tool_result becomes a separate 'tool' message + subsequentToolMessages.push({ + role: "tool", + tool_call_id: contentPart.tool_use_id, + content: + typeof contentPart.content === "string" + ? contentPart.content + : JSON.stringify(contentPart.content), + }); + } + }); + + const trimmedUserText = userTextMessageContent.trim(); + if (trimmedUserText.length > 0) { + openAiMessagesFromThisAnthropicMessage.push({ + role: "user", + content: trimmedUserText, + }); + } + // @ts-ignore + openAiMessagesFromThisAnthropicMessage.push( + // @ts-ignore + ...subsequentToolMessages + ); + } else { + // Fallback for other roles (e.g. system, or custom roles if they were to appear here with array content) + // This will combine all text parts into a single message for that role. + let combinedContent = ""; + anthropicMessage.content.forEach((contentPart) => { + if (contentPart.type === "text") { + combinedContent += + (typeof contentPart.text === "string" + ? contentPart.text + : JSON.stringify(contentPart.text)) + "\\n"; + } else { + // For non-text parts in other roles, stringify them or handle as appropriate + combinedContent += JSON.stringify(contentPart) + "\\n"; + } + }); + const trimmedCombinedContent = combinedContent.trim(); + if (trimmedCombinedContent.length > 0) { + openAiMessagesFromThisAnthropicMessage.push({ + role: anthropicMessage.role, // Cast needed as role could be other than 'user'/'assistant' + content: trimmedCombinedContent, + }); + } + } + return openAiMessagesFromThisAnthropicMessage; + }) + : []; const systemMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = Array.isArray(system) ? system.map((item) => ({ @@ -51,11 +157,11 @@ export const formatRequest = async ( content: item.text, })) : [{ role: "system", content: system }]; - const data: OpenAI.Chat.Completions.ChatCompletionCreateParams = { + const data: any = { model, messages: [...systemMessages, ...openAIMessages], temperature, - stream: true, + stream, }; if (tools) { data.tools = tools @@ -69,7 +175,9 @@ export const formatRequest = async ( }, })); } - res.setHeader("Content-Type", "text/event-stream"); + if (stream) { + res.setHeader("Content-Type", "text/event-stream"); + } res.setHeader("Cache-Control", "no-cache"); res.setHeader("Connection", "keep-alive"); req.body = data; @@ -95,7 +203,7 @@ export const formatRequest = async ( }; }, }; - await streamOpenAIResponse(res, errorCompletion, model); + await streamOpenAIResponse(res, errorCompletion, model, req.body); } next(); }; diff --git a/src/utils/close.ts b/src/utils/close.ts new file mode 100644 index 0000000..b0368da --- /dev/null +++ b/src/utils/close.ts @@ -0,0 +1,23 @@ +import { isServiceRunning, cleanupPidFile } from './processCheck'; +import { existsSync, readFileSync } from 'fs'; +import { homedir } from 'os'; +import { join } from 'path'; + +export async function closeService() { + const PID_FILE = join(homedir(), '.claude-code-router.pid'); + + if (!isServiceRunning()) { + console.log("No service is currently running."); + return; + } + + try { + const pid = parseInt(readFileSync(PID_FILE, 'utf-8')); + process.kill(pid); + cleanupPidFile(); + console.log("Service has been successfully stopped."); + } catch (e) { + console.log("Failed to stop the service. It may have already been stopped."); + cleanupPidFile(); + } +} diff --git a/src/utils/codeCommand.ts b/src/utils/codeCommand.ts new file mode 100644 index 0000000..5cd9564 --- /dev/null +++ b/src/utils/codeCommand.ts @@ -0,0 +1,31 @@ +import { spawn } from 'child_process'; +import { isServiceRunning } from './processCheck'; + +export async function executeCodeCommand(args: string[] = []) { + // Service check is now handled in cli.ts + + // Set environment variables + const env = { + ...process.env, + DISABLE_PROMPT_CACHING: '1', + ANTHROPIC_BASE_URL: 'http://127.0.0.1:3456', + API_TIMEOUT_MS: '600000' + }; + + // Execute claude command + const claudeProcess = spawn('claude', args, { + env, + stdio: 'inherit', + shell: true + }); + + claudeProcess.on('error', (error) => { + console.error('Failed to start claude command:', error.message); + console.log('Make sure Claude Code is installed: npm install -g @anthropic-ai/claude-code'); + process.exit(1); + }); + + claudeProcess.on('close', (code) => { + process.exit(code || 0); + }); +} diff --git a/src/utils/index.ts b/src/utils/index.ts index d218bb6..12367b3 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -57,28 +57,21 @@ export const readConfigFile = async () => { const config = await fs.readFile(CONFIG_FILE, "utf-8"); return JSON.parse(config); } catch { - const useRouter = await confirm( - "No config file found. Enable router mode? (Y/n)" - ); - if (!useRouter) { - const apiKey = await question("Enter OPENAI_API_KEY: "); - const baseUrl = await question("Enter OPENAI_BASE_URL: "); - const model = await question("Enter OPENAI_MODEL: "); - const config = Object.assign({}, DEFAULT_CONFIG, { - OPENAI_API_KEY: apiKey, - OPENAI_BASE_URL: baseUrl, - OPENAI_MODEL: model, - }); - await writeConfigFile(config); - return config; - } else { - const router = await question("Enter OPENAI_API_KEY: "); - return DEFAULT_CONFIG; - } + const apiKey = await question("Enter OPENAI_API_KEY: "); + const baseUrl = await question("Enter OPENAI_BASE_URL: "); + const model = await question("Enter OPENAI_MODEL: "); + const config = Object.assign({}, DEFAULT_CONFIG, { + OPENAI_API_KEY: apiKey, + OPENAI_BASE_URL: baseUrl, + OPENAI_MODEL: model, + }); + await writeConfigFile(config); + return config; } }; export const writeConfigFile = async (config: any) => { + await ensureDir(HOME_DIR); await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2)); }; diff --git a/src/utils/log.ts b/src/utils/log.ts index 8f9f271..6999726 100644 --- a/src/utils/log.ts +++ b/src/utils/log.ts @@ -1,8 +1,8 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { HOME_DIR } from '../constants'; +import fs from "node:fs"; +import path from "node:path"; +import { HOME_DIR } from "../constants"; -const LOG_FILE = path.join(HOME_DIR, 'claude-code-router.log'); +const LOG_FILE = path.join(HOME_DIR, "claude-code-router.log"); // Ensure log directory exists if (!fs.existsSync(HOME_DIR)) { @@ -11,17 +11,23 @@ if (!fs.existsSync(HOME_DIR)) { export function log(...args: any[]) { // Check if logging is enabled via environment variable - const isLogEnabled = process.env.LOG === 'true'; - + const isLogEnabled = process.env.LOG === "true"; + if (!isLogEnabled) { return; } const timestamp = new Date().toISOString(); - const logMessage = `[${timestamp}] ${args.map(arg => - typeof arg === 'object' ? JSON.stringify(arg) : String(arg) - ).join(' ')}\n`; + const logMessage = `[${timestamp}] ${ + Array.isArray(args) + ? args + .map((arg) => + typeof arg === "object" ? JSON.stringify(arg) : String(arg) + ) + .join(" ") + : "" + }\n`; // Append to log file - fs.appendFileSync(LOG_FILE, logMessage, 'utf8'); + fs.appendFileSync(LOG_FILE, logMessage, "utf8"); } diff --git a/src/utils/processCheck.ts b/src/utils/processCheck.ts new file mode 100644 index 0000000..152acf1 --- /dev/null +++ b/src/utils/processCheck.ts @@ -0,0 +1,60 @@ +import { existsSync, readFileSync, writeFileSync } from 'fs'; +import { PID_FILE } from '../constants'; + + +export function isServiceRunning(): boolean { + if (!existsSync(PID_FILE)) { + return false; + } + + try { + const pid = parseInt(readFileSync(PID_FILE, 'utf-8')); + process.kill(pid, 0); + return true; + } catch (e) { + // Process not running, clean up pid file + cleanupPidFile(); + return false; + } +} + +export function savePid(pid: number) { + writeFileSync(PID_FILE, pid.toString()); +} + +export function cleanupPidFile() { + if (existsSync(PID_FILE)) { + try { + const fs = require('fs'); + fs.unlinkSync(PID_FILE); + } catch (e) { + // Ignore cleanup errors + } + } +} + +export function getServicePid(): number | null { + if (!existsSync(PID_FILE)) { + return null; + } + + try { + const pid = parseInt(readFileSync(PID_FILE, 'utf-8')); + return isNaN(pid) ? null : pid; + } catch (e) { + return null; + } +} + +export function getServiceInfo() { + const pid = getServicePid(); + const running = isServiceRunning(); + + return { + running, + pid, + port: 3456, + endpoint: 'http://127.0.0.1:3456', + pidFile: PID_FILE + }; +} diff --git a/src/utils/status.ts b/src/utils/status.ts new file mode 100644 index 0000000..d58d0b1 --- /dev/null +++ b/src/utils/status.ts @@ -0,0 +1,27 @@ +import { getServiceInfo } from './processCheck'; + +export function showStatus() { + const info = getServiceInfo(); + + console.log('\n📊 Claude Code Router Status'); + console.log('═'.repeat(40)); + + if (info.running) { + console.log('✅ Status: Running'); + console.log(`🆔 Process ID: ${info.pid}`); + console.log(`🌐 Port: ${info.port}`); + console.log(`📡 API Endpoint: ${info.endpoint}`); + console.log(`📄 PID File: ${info.pidFile}`); + console.log(''); + console.log('🚀 Ready to use! Run the following commands:'); + console.log(' claude-code-router code # Start coding with Claude'); + console.log(' claude-code-router close # Stop the service'); + } else { + console.log('❌ Status: Not Running'); + console.log(''); + console.log('💡 To start the service:'); + console.log(' claude-code-router start'); + } + + console.log(''); +} diff --git a/src/utils/stream.ts b/src/utils/stream.ts index b71ec50..7f3c4e7 100644 --- a/src/utils/stream.ts +++ b/src/utils/stream.ts @@ -1,5 +1,6 @@ import { Response } from "express"; import { OpenAI } from "openai"; +import { log } from "./log"; interface ContentBlock { type: string; @@ -42,10 +43,40 @@ interface MessageEvent { export async function streamOpenAIResponse( res: Response, - completion: AsyncIterable, - model: string + completion: any, + model: string, + body: any ) { + const write = (data: string) => { + log("response: ", data); + res.write(data); + }; const messageId = "msg_" + Date.now(); + if (!body.stream) { + res.json({ + id: messageId, + type: "message", + role: "assistant", + // @ts-ignore + content: completion.choices[0].message.content || completion.choices[0].message.tool_calls?.map((item) => { + return { + type: 'tool_use', + id: item.id, + name: item.function?.name, + input: item.function?.arguments ? JSON.parse(item.function.arguments) : {}, + }; + }) || '', + stop_reason: completion.choices[0].finish_reason === 'tool_calls' ? "tool_use" : "end_turn", + stop_sequence: null, + usage: { + input_tokens: 100, + output_tokens: 50, + }, + }); + res.end(); + return; + } + let contentBlockIndex = 0; let currentContentBlocks: ContentBlock[] = []; @@ -63,7 +94,7 @@ export async function streamOpenAIResponse( usage: { input_tokens: 1, output_tokens: 1 }, }, }; - res.write(`event: message_start\ndata: ${JSON.stringify(messageStart)}\n\n`); + write(`event: message_start\ndata: ${JSON.stringify(messageStart)}\n\n`); let isToolUse = false; let toolUseJson = ""; @@ -71,6 +102,7 @@ export async function streamOpenAIResponse( try { for await (const chunk of completion) { + log("Processing chunk:", chunk); const delta = chunk.choices[0].delta; if (delta.tool_calls && delta.tool_calls.length > 0) { @@ -94,7 +126,7 @@ export async function streamOpenAIResponse( currentContentBlocks.push(toolBlock); - res.write( + write( `event: content_block_start\ndata: ${JSON.stringify( toolBlockStart )}\n\n` @@ -119,23 +151,25 @@ export async function streamOpenAIResponse( const parsedJson = JSON.parse(toolUseJson); currentContentBlocks[contentBlockIndex].input = parsedJson; } catch (e) { + log(e); // JSON not yet complete, continue accumulating } - res.write( + write( `event: content_block_delta\ndata: ${JSON.stringify(jsonDelta)}\n\n` ); } } else if (delta.content) { // Handle regular text content if (isToolUse) { + log("Tool call ended here:", delta); // End previous tool call block const contentBlockStop: MessageEvent = { type: "content_block_stop", index: contentBlockIndex, }; - res.write( + write( `event: content_block_stop\ndata: ${JSON.stringify( contentBlockStop )}\n\n` @@ -161,7 +195,7 @@ export async function streamOpenAIResponse( currentContentBlocks.push(textBlock); - res.write( + write( `event: content_block_start\ndata: ${JSON.stringify( textBlockStart )}\n\n` @@ -184,7 +218,7 @@ export async function streamOpenAIResponse( currentContentBlocks[contentBlockIndex].text += delta.content; } - res.write( + write( `event: content_block_delta\ndata: ${JSON.stringify( contentDelta )}\n\n` @@ -207,7 +241,7 @@ export async function streamOpenAIResponse( currentContentBlocks.push(textBlock); - res.write( + write( `event: content_block_start\ndata: ${JSON.stringify( textBlockStart )}\n\n` @@ -230,7 +264,7 @@ export async function streamOpenAIResponse( currentContentBlocks[contentBlockIndex].text += JSON.stringify(e); } - res.write( + write( `event: content_block_delta\ndata: ${JSON.stringify(contentDelta)}\n\n` ); } @@ -241,7 +275,7 @@ export async function streamOpenAIResponse( index: contentBlockIndex, }; - res.write( + write( `event: content_block_stop\ndata: ${JSON.stringify(contentBlockStop)}\n\n` ); @@ -255,14 +289,17 @@ export async function streamOpenAIResponse( }, usage: { input_tokens: 100, output_tokens: 150 }, }; + if (!isToolUse) { + log("body: ", body, "messageDelta: ", messageDelta); + } - res.write(`event: message_delta\ndata: ${JSON.stringify(messageDelta)}\n\n`); + write(`event: message_delta\ndata: ${JSON.stringify(messageDelta)}\n\n`); // Send message_stop event const messageStop: MessageEvent = { type: "message_stop", }; - res.write(`event: message_stop\ndata: ${JSON.stringify(messageStop)}\n\n`); + write(`event: message_stop\ndata: ${JSON.stringify(messageStop)}\n\n`); res.end(); }