diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index fb2c838..b32ad09 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -32,7 +32,7 @@ jobs: steps: - name: "Checkout" - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: "Debug event.json" continue-on-error: true diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index c9c3c80..9d20930 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -35,7 +35,7 @@ jobs: steps: - name: "Checkout" - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: "Debug event.json" continue-on-error: true diff --git a/.github/workflows/draft.yaml b/.github/workflows/draft.yaml index 7f93c65..9c43df3 100644 --- a/.github/workflows/draft.yaml +++ b/.github/workflows/draft.yaml @@ -20,7 +20,7 @@ jobs: steps: - name: "Checkout" - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: "Draft Release Action" id: draft diff --git a/.github/workflows/issue.yaml b/.github/workflows/issue.yaml index 1b1d2ae..e0bcf6e 100644 --- a/.github/workflows/issue.yaml +++ b/.github/workflows/issue.yaml @@ -17,7 +17,7 @@ jobs: steps: - name: "Checkout" - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: "Create App Token" id: app diff --git a/.github/workflows/labeler.yaml b/.github/workflows/labeler.yaml index 95030e5..c37a7bc 100644 --- a/.github/workflows/labeler.yaml +++ b/.github/workflows/labeler.yaml @@ -19,7 +19,7 @@ jobs: steps: - name: "Checkout Configs" - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: cssnr/configs ref: master @@ -46,7 +46,7 @@ jobs: file: .configs/labels/labels.yaml - name: "Labeler" - uses: actions/labeler@v6 + uses: actions/labeler@v7 with: sync-labels: true configuration-path: .configs/labels/labeler.yaml diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 334bc85..f12c0eb 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -19,7 +19,7 @@ jobs: steps: - name: "Checkout" - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: "Debug event.json" continue-on-error: true @@ -31,7 +31,7 @@ jobs: run: echo "$GITHUB_CTX" - name: "Setup Node" - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: 24 @@ -66,7 +66,7 @@ jobs: - name: "hadolint" if: ${{ !cancelled() }} - uses: hadolint/hadolint-action@v3.3.0 + uses: hadolint/hadolint-action@v3.4.0 with: dockerfile: Dockerfile ignore: "DL3018" diff --git a/.prettierrc.json b/.prettierrc.json index 577fddf..a1ceab5 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/prettierrc", "semi": false, "singleQuote": true, - "printWidth": 90, + "printWidth": 96, "overrides": [ { "files": ["**/*.vue"], diff --git a/AGENTS.md b/AGENTS.md index bb46bf5..6227489 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,9 +1,10 @@ # Agent Guide -Before answering any question that involves facts about ANYTHING, you MUST output at least one Read, WebFetch, or WebSearch tool call. -If your first output is text instead of a tool call, you have failed. +- [index.ts](src/index.ts) — Single source Express.js AI-SDK server +- [README.md](README.md) — Server documentation -- [index.ts](src/index.ts) — Single source Express.js ai-sdk server with single route `/` +- [AI SDK](https://ai-sdk.dev/docs/reference) +- [Express.js](https://expressjs.com/en/5x/api/) ## Commands @@ -15,3 +16,15 @@ ALWAYS use the `npm run *` command | `npm run lint` | ESLint on `src/` | | `npm run tsc` | TypeScript Check Only `tsc --noEmit` | | `npm run prettier` | ALWAYS RUN AFTER EDITING FILES | + +## Endpoints and Documentation + +| Server Endpoint | Client | +| :-------------- | :-------------------------------------------------------------------------- | +| `/chat` | [useChat](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat) | +| `/completion` | [useCompletion](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-completion) | +| `/object` | [useObject](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-object) | + +- [createUIMessageStream](https://ai-sdk.dev/docs/reference/ai-sdk-ui/create-ui-message-stream) +- [pipeUIMessageStreamToResponse](https://ai-sdk.dev/docs/reference/ai-sdk-ui/pipe-ui-message-stream-to-response) +- [streamText](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text) diff --git a/Dockerfile b/Dockerfile index 9c939f4..84a6176 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,6 +23,7 @@ ARG VERSION="Dockerfile" ENV APP_VERSION="${VERSION}" LABEL org.opencontainers.image.version="${VERSION}" -USER node +#USER node +USER 1000 CMD ["npm", "start"] diff --git a/README.md b/README.md index 47ed6ad..ffcdc48 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ - [Setup](#setup) - [Configure](#configure) - [Client](#client) + - [Endpoints](#endpoints) - [VitePress Plugin](#vitepress-chat-plugin) - [Development](#development) - [Support](#support) @@ -42,24 +43,25 @@ To get started [Setup](#setup) and [Configure](#configure) the server. No API Ke [![View Live Demo](https://img.shields.io/badge/view_live_demo-green?style=for-the-badge&logo=chatbot&logoColor=white)](https://cssnr.github.io/vitepress-chat/) -- Client: https://github.com/cssnr/vitepress-chat -- Server: https://github.com/cssnr/chat-server +- Client: +- Server: ### Features - Works with Claude, OpenAI, Gemini and OpenAI Compatible Providers -- Live Stream Results to Client +- Includes Chat, Completion, and Object Endpoints +- Supports Multiple Clients Simultaneously +- Live Streams the Results to the Client - Automatic Input Token Caching - Automatic Retry on API Failures - Deploy with Docker or Node -- Supports Multiple Clients Simultaneously - Plus all the [Client Features](https://github.com/cssnr/vitepress-chat?tab=readme-ov-file#features) Built with the [AI SDK](https://ai-sdk.dev/). ## Setup -💡 The server works out-of-the box with NO environment variables. +💡 The server works out-of-the-box with NO environment variables. [![Deploy to Render](https://img.shields.io/badge/Deploy_to_Render-4351E8?style=for-the-badge&logo=render)](https://render.com/deploy?repo=https://github.com/cssnr/chat-server) @@ -99,18 +101,24 @@ For a Portainer Deploy workflow see the [.github/workflows/deploy.yaml](https:// 💡 All variables are optional. The default `big-pickle` model works with NO API Key. -Environment Variables. - -| Variable | Default | Description | -| :------------------------------------ | :--------------------------- | :---------------------------------- | -| `MODEL` | `big-pickle` | Model to Use | -| `BASE_URL` | `https://opencode.ai/zen/v1` | OpenAI Compatible Provider Base URL | -| [PROVIDER_OPTIONS](#PROVIDER_OPTIONS) | - | Provider Options JSON String | -| `MAX_TOKENS` | - | Max Output Tokens | -| `INSTRUCTIONS` | - | Fallback System Instructions | -| `AI_SDK_LOG_WARNINGS` | - | Disable SDK Warnings | -| `CORS_ORIGINS` | - | Allowed CORS Origins (supports \*) | -| `PORT` | `3000` | Server Port | +Environment Variables (can be placed in a `settings.env` file). + +| Variable | Default | Description | +| :--------------------------------------- | :--------------------------- | :---------------------------------- | +| `MODEL` | `big-pickle` | Model to Use | +| `BASE_URL` | `https://opencode.ai/zen/v1` | OpenAI Compatible Provider Base URL | +| `MAX_TOKENS` | - | Max Output Tokens | +| [PROVIDER_OPTIONS](#PROVIDER_OPTIONS) | - | Provider Options JSON String | +| [INSTRUCTIONS_CHAT](#INSTRUCTIONS) | - | System Instructions for Chat | +| [INSTRUCTIONS_COMPLETION](#INSTRUCTIONS) | - | System Instructions for Completion | +| [INSTRUCTIONS_OBJECT](#INSTRUCTIONS) | - | System Instructions for Object | +| `DISABLE_CLIENT_INSTRUCTIONS`**¹** | - | Ignore Client System Instructions | +| `AI_SDK_LOG_WARNINGS`**¹** | - | Enable SDK Warnings Logging | +| `CORS_ORIGINS` | - | Allowed CORS Origins (supports \*) | +| `PORT` | `3000` | Server Port | +| `DEBUG` | - | Set to `app` for Debug Logging | + +> **¹** Boolean Variables. **True** values include: `['1', 't', 'true', 'y', 'yes', 'on']` You must also set the API key for the `MODEL` you select. @@ -123,6 +131,15 @@ You must also set the API key for the `MODEL` you select. The `PROVIDER_API_KEY` is optional for free-tier models like `big-pickle`. +#### INSTRUCTIONS + +There are mechanisms to override the instructions per-call for all clients on all endpoints. +These are used as fallback when those instructions are not sent for configurations where this is desired. + +The `INSTRUCTIONS` variable (legacy) also points to the `INSTRUCTIONS_CHAT` variable (recommended). + +To disable the clients ability to send custom instructions set `DISABLE_CLIENT_INSTRUCTIONS=true` + #### PROVIDER_OPTIONS Provider Options: @@ -140,33 +157,98 @@ PROVIDER_OPTIONS='{"openai":{"serviceTier":"flex","reasoningEffort":"low"}}' ``` You are responsible for providing valid options for the chosen model. -The SDK supports providing provider options for multiple provider simultaneously. +The SDK supports providing provider options for multiple providers simultaneously. The value is only checked for valid JSON at startup and will fail at runtime if it contains invalid options. ## Client -To send System Instructions from the client, add them to the body. +### Endpoints + +| Endpoint | Method | Description | +| :------------ | :----: | :------------------------------------------------------------------------------------------------------------------------------------- | +| `/chat` | `POST` | Use with [useChat](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat) and [VitePress Chat](https://cssnr.github.io/vitepress-chat/) | +| `/completion` | `POST` | Use with [useCompletion](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-completion) | +| `/object` | `POST` | Use with [useObject](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-object) | + +Note: The `/` endpoint (legacy) also points to the `/chat` endpoint (recommended). + +#### chat + +Reference: ```typescript -const chat = new Chat({ +import { useChat } from '@ai-sdk/vue' +import { DefaultChatTransport } from 'ai' + +const { messages, sendMessage, status, stop } = useChat({ transport: new DefaultChatTransport({ - api: 'https://chat-server.cssnr.com/', + api: 'https://chat-server.cssnr.com/chat', headers: { Authorization: 'Basic Abc123=' }, - body: { system: 'You are a helpful assistant.' }, + body: { instructions: 'You are a helpful assistant.' }, }), }) ``` +To send System Instructions from the client, add them to the body. + +#### completion + +Reference: + +```typescript +import { useCompletion } from '@ai-sdk/vue' + +const { completion, complete, isLoading, stop } = useCompletion({ + api: 'https://chat-server.cssnr.com/completion', + headers: { Authorization: 'Basic Abc123=' }, + body: { instructions: 'You are a helpful assistant.' }, +}) + +await complete('Explain how to set up cssnr/chat-server') +``` + +To send System Instructions from the client, add them to the body. + +#### object + +Reference: + +```typescript +import { useObject } from '@ai-sdk/vue' +import { z } from 'zod' +import { zodToJsonSchema } from 'zod-to-json-schema' + +const schema = z.object({ name: z.string(), age: z.number() }) + +const { object, submit } = useObject({ + api: 'https://chat-server.cssnr.com/object', + headers: { Authorization: 'Basic Abc123=' }, + schema, +}) + +submit({ + instructions: 'You are a helpful assistant.', + prompt: 'Extract the name and age from: John is 30 years old.', + output: zodToJsonSchema(schema), +}) +``` + +To send System Instructions and Output Schema from the client, add them to the body. + +Note: Both `instructions` and `output` are custom body parameters parsed by the server. + ### VitePress Chat Plugin The client is currently available as a VitePress Plugin. -- https://github.com/cssnr/vitepress-chat +- [![View Documentation](https://img.shields.io/badge/view_documentation-blue?style=for-the-badge&logo=googledocs&logoColor=white)](https://cssnr.github.io/vitepress-chat/) ## Development +To enable debug logs set: `DEBUG=app` + This works with no configuration using the `big-pickle` model. You can set your environment variables in the `settings.env` file. If using `big-pickle` for testing it is much faster to disable reasoning. @@ -224,11 +306,3 @@ and [additional](https://cssnr.com/) open source projects. [![Ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/cssnr) For a full list of current projects visit: [https://cssnr.github.io/](https://cssnr.github.io/) - - - - - - Star History Chart - - diff --git a/package-lock.json b/package-lock.json index f49f933..583cca6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,38 +8,40 @@ "name": "chat-server", "version": "0.0.0", "dependencies": { - "@ai-sdk/anthropic": "^4.0.3", - "@ai-sdk/google": "^4.0.3", - "@ai-sdk/openai": "^4.0.4", - "@ai-sdk/openai-compatible": "^3.0.2", - "ai": "^7.0.8", + "@ai-sdk/anthropic": "^4.0.27", + "@ai-sdk/google": "^4.0.31", + "@ai-sdk/openai": "^4.0.27", + "@ai-sdk/openai-compatible": "^3.0.20", + "ai": "^7.0.48", "cors": "^2.8.6", + "debug": "^4.4.3", "dotenv": "^17.4.2", "express": "^5.2.1", - "picomatch": "^4.0.4" + "picomatch": "^4.0.5" }, "devDependencies": { "@eslint/js": "^10.0.1", "@types/cors": "^2.8.19", + "@types/debug": "^4.1.13", "@types/express": "^5.0.6", - "@types/node": "^26.0.1", + "@types/node": "^26.1.2", "@types/picomatch": "^4.0.3", - "eslint": "^10.6.0", + "eslint": "^10.8.0", "nodemon": "^3.1.14", - "prettier": "^3.9.4", - "tsx": "^4.22.4", + "prettier": "^3.9.6", + "tsx": "^4.23.4", "typescript": "^6.0.3", - "typescript-eslint": "^8.62.1" + "typescript-eslint": "^8.65.0" } }, "node_modules/@ai-sdk/anthropic": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-4.0.3.tgz", - "integrity": "sha512-USlJtQxkpbZy01evFpAOOtkELwDoWFlLrlJDM7B81KTqGVIZ60BMoEfJHZ/G4poK+t7tYFpHgDKalspc4TJKeg==", + "version": "4.0.27", + "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-4.0.27.tgz", + "integrity": "sha512-ecrPWkYf+sHDLHErHAU/yjJfNevUzT0i6TuoQLb58oadnquSHcLQ5NMLLWjbr6d1rxYdzI7ftulIJ03VH9yM3w==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.1", - "@ai-sdk/provider-utils": "5.0.2" + "@ai-sdk/provider": "4.0.4", + "@ai-sdk/provider-utils": "5.0.18" }, "engines": { "node": ">=22" @@ -49,13 +51,13 @@ } }, "node_modules/@ai-sdk/gateway": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.6.tgz", - "integrity": "sha512-8GjssUxXeTd8tst2fYXNOFbtNNZFv3sG7aq7wKnQYrU2eB3JFR7np6o4F3Snp/fy/rJZSeH28LvjSWq5wkdL8Q==", + "version": "4.0.37", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.37.tgz", + "integrity": "sha512-YBCTsSlX0ETVsjo0ihfBOeJ3p3y1wXKXDzF9Ygp9dscUY06dzOGmyWJSGsKg+khKVArzBWUW4UCSAKms20ZDmg==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.1", - "@ai-sdk/provider-utils": "5.0.2", + "@ai-sdk/provider": "4.0.4", + "@ai-sdk/provider-utils": "5.0.18", "@vercel/oidc": "3.2.0" }, "engines": { @@ -66,13 +68,13 @@ } }, "node_modules/@ai-sdk/google": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-4.0.3.tgz", - "integrity": "sha512-mTTQt9/+/traTwrM4+J3Ewea1WgQBHJC+3EVa3dqdOfN1iyOC9P76CpGCZdUyVTASfVCpUTKrgCxHUTl9S7q/A==", + "version": "4.0.31", + "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-4.0.31.tgz", + "integrity": "sha512-AFyO4MuHryrzr/ZEMHm/dEXwqABqoi4yIa9cfZxfI4VYM+dyPeZIXU4/qjvxiD6vzbitQhYf1jutQL/d71bymw==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.1", - "@ai-sdk/provider-utils": "5.0.2" + "@ai-sdk/provider": "4.0.4", + "@ai-sdk/provider-utils": "5.0.18" }, "engines": { "node": ">=22" @@ -82,13 +84,13 @@ } }, "node_modules/@ai-sdk/openai": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-4.0.4.tgz", - "integrity": "sha512-p6VDEzEx52PIzRnFoUpiw6ABn07h4Rt+atL+M51W9SqwhselaZFRnz/pzv7LF9D1Gok5qfzOmR+JwvDDvDWS3w==", + "version": "4.0.27", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-4.0.27.tgz", + "integrity": "sha512-6MsWnbNG0ZjZgzYimNNjnTAlOFXn+jdLMFB47dDVUsRNjxdUoTSduCQPZZeTKoUl0ZNwCjsJ31of49l/t1r9MA==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.1", - "@ai-sdk/provider-utils": "5.0.2" + "@ai-sdk/provider": "4.0.4", + "@ai-sdk/provider-utils": "5.0.18" }, "engines": { "node": ">=22" @@ -98,13 +100,13 @@ } }, "node_modules/@ai-sdk/openai-compatible": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-3.0.2.tgz", - "integrity": "sha512-C6qUMe+qSMn7HVr1a0v5H9BXYKhsT5uO4Q2VYVHYdT7johzx+d1gYgFMhkRIoheBpCl5NPhljR4I4sHMWWfz6w==", + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-3.0.20.tgz", + "integrity": "sha512-RsPg+HilsKc/d+jy4zItG7v7RJQm+caUu+iU0a5bx3eyyX5tkayMGDRklXMMMAPa4l34luzQwT3t4cmeHfdiWw==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.1", - "@ai-sdk/provider-utils": "5.0.2" + "@ai-sdk/provider": "4.0.4", + "@ai-sdk/provider-utils": "5.0.18" }, "engines": { "node": ">=22" @@ -114,9 +116,9 @@ } }, "node_modules/@ai-sdk/provider": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.1.tgz", - "integrity": "sha512-6p3C/vGqVIjcptBu1DnVd/BZJ2wWmV9TUv9192vT6ZvT9KNED8EwRTqyqFpoQZKgSbMDSvBSq3dqR524Nt/Crw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.4.tgz", + "integrity": "sha512-tbHKNLirllUNF3ZlkCsXnwab2ZV1Sl4b1H/Cp9ruCce15IBmskE8Gwkk0yo9xDWY+jho2of7lVXtwSsyrq7cwQ==", "license": "Apache-2.0", "dependencies": { "json-schema": "^0.4.0" @@ -126,15 +128,16 @@ } }, "node_modules/@ai-sdk/provider-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.2.tgz", - "integrity": "sha512-EcmdjJb7yggsZPCbS3MFBpvAUnKaPW+QvanU5GzF00XCq0bqqAmvJ3MN19ejlmOETbW8sJNiq6qam48wTcbUNw==", + "version": "5.0.18", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.18.tgz", + "integrity": "sha512-UBNCrkxS5llgN2/RXLBRkjCFTIuL6YB1Goq5c+yRecnznhtX4XPjTwLHz2hrsTltTVZ0rqVegtnwFXdPBkjDHA==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.1", + "@ai-sdk/provider": "4.0.4", "@standard-schema/spec": "^1.1.0", "@workflow/serde": "4.1.0", - "eventsource-parser": "^3.0.8" + "eventsource-parser": "^3.0.8", + "undici": "^7.28.0" }, "engines": { "node": ">=22" @@ -586,9 +589,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, "license": "MIT", "dependencies": { @@ -643,9 +646,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", - "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -816,6 +819,16 @@ "@types/node": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", @@ -843,9 +856,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", - "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz", + "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==", "dev": true, "license": "MIT", "dependencies": { @@ -869,10 +882,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz", - "integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==", + "version": "26.1.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz", + "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==", "dev": true, "license": "MIT", "dependencies": { @@ -922,17 +942,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz", - "integrity": "sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.62.1", - "@typescript-eslint/type-utils": "8.62.1", - "@typescript-eslint/utils": "8.62.1", - "@typescript-eslint/visitor-keys": "8.62.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -945,15 +965,15 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.62.1", + "@typescript-eslint/parser": "^8.65.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, "license": "MIT", "engines": { @@ -961,16 +981,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz", - "integrity": "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.62.1", - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/typescript-estree": "8.62.1", - "@typescript-eslint/visitor-keys": "8.62.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3" }, "engines": { @@ -986,14 +1006,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.1.tgz", - "integrity": "sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.62.1", - "@typescript-eslint/types": "^8.62.1", + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", "debug": "^4.4.3" }, "engines": { @@ -1008,14 +1028,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz", - "integrity": "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/visitor-keys": "8.62.1" + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1026,9 +1046,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz", - "integrity": "sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", "dev": true, "license": "MIT", "engines": { @@ -1043,15 +1063,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz", - "integrity": "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/typescript-estree": "8.62.1", - "@typescript-eslint/utils": "8.62.1", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -1068,9 +1088,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz", - "integrity": "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", "dev": true, "license": "MIT", "engines": { @@ -1082,16 +1102,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz", - "integrity": "sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.62.1", - "@typescript-eslint/tsconfig-utils": "8.62.1", - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/visitor-keys": "8.62.1", + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1110,16 +1130,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.1.tgz", - "integrity": "sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.62.1", - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/typescript-estree": "8.62.1" + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1134,13 +1154,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz", - "integrity": "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/types": "8.65.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -1180,9 +1200,9 @@ } }, "node_modules/acorn": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", - "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "dev": true, "license": "MIT", "bin": { @@ -1203,14 +1223,14 @@ } }, "node_modules/ai": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/ai/-/ai-7.0.8.tgz", - "integrity": "sha512-vTEKl6fDBZ2IxBXTRaZOajf9W2Ev57Ju8iKtUvqlmDk8Z9BrEP4c22SWJsg1RcWHSFmJMSBa/s5dlUBHUq3YwA==", + "version": "7.0.48", + "resolved": "https://registry.npmjs.org/ai/-/ai-7.0.48.tgz", + "integrity": "sha512-QmqshWFEDdkFFqSgdJ4cogaoDIYaedqbv73q/NXEYNkSIocJCzXnGFVyM9lrtAGTSBfm+5hq3/292ooXJ1jDSw==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/gateway": "4.0.6", - "@ai-sdk/provider": "4.0.1", - "@ai-sdk/provider-utils": "5.0.2" + "@ai-sdk/gateway": "4.0.37", + "@ai-sdk/provider": "4.0.4", + "@ai-sdk/provider-utils": "5.0.18" }, "engines": { "node": ">=22" @@ -1324,16 +1344,16 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/braces": { @@ -1663,9 +1683,9 @@ } }, "node_modules/eslint": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz", - "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", + "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", "dev": true, "license": "MIT", "workspaces": [ @@ -1675,7 +1695,7 @@ "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.6.0", + "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", @@ -1699,7 +1719,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", + "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -1996,9 +2016,9 @@ } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, @@ -2161,9 +2181,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -2354,12 +2374,16 @@ } }, "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "license": "MIT", "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/merge-descriptors": { @@ -2400,13 +2424,13 @@ } }, "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -2608,9 +2632,9 @@ } }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", "engines": { "node": ">=12" @@ -2630,9 +2654,9 @@ } }, "node_modules/prettier": { - "version": "3.9.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.4.tgz", - "integrity": "sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==", + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", "dev": true, "license": "MIT", "bin": { @@ -3024,9 +3048,9 @@ } }, "node_modules/tsx": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", - "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", + "version": "4.23.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.4.tgz", + "integrity": "sha512-ZiUQ8oT/KzN51mJUWPqARYqwFLFJZtGZipRkw1ynHMr9vy3eU77m5yfF3Gzm6meEg/beW+lUu3fHYgskTN2oVQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3101,16 +3125,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.1.tgz", - "integrity": "sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.62.1", - "@typescript-eslint/parser": "8.62.1", - "@typescript-eslint/typescript-estree": "8.62.1", - "@typescript-eslint/utils": "8.62.1" + "@typescript-eslint/eslint-plugin": "8.65.0", + "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3131,6 +3155,15 @@ "dev": true, "license": "MIT" }, + "node_modules/undici": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", diff --git a/package.json b/package.json index 8ab8f5a..277f8f9 100644 --- a/package.json +++ b/package.json @@ -9,34 +9,36 @@ "dev": "nodemon --exec tsx src/index.ts", "lint": "npx eslint src", "prepare": "npm run build", - "prettier:": "npm run prettier:write", + "prettier": "npm run prettier:write", "prettier:check": "npx prettier --check .", "prettier:write": "npx prettier --write .", "start": "node dist/index.js", "tsc": "npx tsc --noEmit" }, "dependencies": { - "@ai-sdk/anthropic": "^4.0.3", - "@ai-sdk/google": "^4.0.3", - "@ai-sdk/openai": "^4.0.4", - "@ai-sdk/openai-compatible": "^3.0.2", - "ai": "^7.0.8", + "@ai-sdk/anthropic": "^4.0.27", + "@ai-sdk/google": "^4.0.31", + "@ai-sdk/openai": "^4.0.27", + "@ai-sdk/openai-compatible": "^3.0.20", + "ai": "^7.0.48", "cors": "^2.8.6", + "debug": "^4.4.3", "dotenv": "^17.4.2", "express": "^5.2.1", - "picomatch": "^4.0.4" + "picomatch": "^4.0.5" }, "devDependencies": { "@eslint/js": "^10.0.1", "@types/cors": "^2.8.19", + "@types/debug": "^4.1.13", "@types/express": "^5.0.6", - "@types/node": "^26.0.1", + "@types/node": "^26.1.2", "@types/picomatch": "^4.0.3", - "eslint": "^10.6.0", + "eslint": "^10.8.0", "nodemon": "^3.1.14", - "prettier": "^3.9.4", - "tsx": "^4.22.4", + "prettier": "^3.9.6", + "tsx": "^4.23.4", "typescript": "^6.0.3", - "typescript-eslint": "^8.62.1" + "typescript-eslint": "^8.65.0" } } diff --git a/src/index.ts b/src/index.ts index 2379805..3361912 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +import createDebug from 'debug' import cors from 'cors' import dotenv from 'dotenv' import pm from 'picomatch' @@ -7,30 +8,42 @@ import { google } from '@ai-sdk/google' import { openai } from '@ai-sdk/openai' import { createOpenAICompatible } from '@ai-sdk/openai-compatible' import { - streamText, + type GenerateTextEndEvent, + Output, + consumeStream, + convertToModelMessages, createUIMessageStream, + jsonSchema, + pipeTextStreamToResponse, pipeUIMessageStreamToResponse, - convertToModelMessages, + streamText, + toTextStream, toUIMessageStream, } from 'ai' dotenv.config({ path: 'settings.env' }) -console.log(`chat-server: ${process.env.APP_VERSION}`) +console.log('chat-server:', process.env.APP_VERSION) -console.log('MODEL:', process.env.MODEL ? 'SET' : undefined) +console.log('DEBUG:', process.env.DEBUG) +createDebug.enable(process.env.DEBUG ?? '') +const debug = createDebug('app') +debug('debug enabled: app') + +console.log('MODEL:', process.env.MODEL) console.log('ANTHROPIC_API_KEY:', process.env.ANTHROPIC_API_KEY ? 'SET' : undefined) console.log('OPENAI_API_KEY:', process.env.OPENAI_API_KEY ? 'SET' : undefined) console.log( 'GOOGLE_GENERATIVE_AI_API_KEY:', process.env.GOOGLE_GENERATIVE_AI_API_KEY ? 'SET' : undefined, ) + console.log('PROVIDER_API_KEY:', process.env.PROVIDER_API_KEY ? 'SET' : undefined) const baseURL = process.env.BASE_URL || 'https://opencode.ai/zen/v1' // NOSONAR console.log('BASE_URL:', baseURL) -if (process.env.AI_SDK_LOG_WARNINGS) globalThis.AI_SDK_LOG_WARNINGS = false -console.log('AI_SDK_LOG_WARNINGS:', process.env.AI_SDK_LOG_WARNINGS) +console.log('AI_SDK_LOG_WARNINGS:', getBool(process.env.AI_SDK_LOG_WARNINGS)) +if (!getBool(process.env.AI_SDK_LOG_WARNINGS)) globalThis.AI_SDK_LOG_WARNINGS = false const corsOrigins = process.env.CORS_ORIGINS?.split(/[, \n\r]+/) .map((s) => s.trim()) @@ -41,11 +54,18 @@ console.log('corsOrigins:', corsOrigins) const maxOutputTokens = process.env.MAX_TOKENS ? Number.parseInt(process.env.MAX_TOKENS) : undefined -console.log(`maxOutputTokens: ${maxOutputTokens}`) -console.log(`INSTRUCTIONS: ${process.env.INSTRUCTIONS}`) +console.log('maxOutputTokens:', maxOutputTokens) + +const disableInstructions = getBool(process.env.DISABLE_CLIENT_INSTRUCTIONS) +console.log('disableInstructions:', disableInstructions) + +process.env.INSTRUCTIONS_CHAT = process.env.INSTRUCTIONS_CHAT || process.env.INSTRUCTIONS // NOSONAR +console.log('INSTRUCTIONS_CHAT:', process.env.INSTRUCTIONS_CHAT) +console.log('INSTRUCTIONS_COMPLETION:', process.env.INSTRUCTIONS_COMPLETION) +console.log('INSTRUCTIONS_OBJECT:', process.env.INSTRUCTIONS_OBJECT) const model = getModel() -console.log(`Loaded modelId: ${model.modelId}`) +console.log('Loaded modelId:', model.modelId) const providerOptions = getProviderOptions() console.log('providerOptions:', providerOptions) @@ -54,36 +74,90 @@ const app = express() const port = process.env.PORT || 3000 // NOSONAR app.use(express.json({ limit: '10mb' })) - app.use(cors({ origin: corsCallback })) - app.listen(port, () => console.log(`Listening on PORT: ${port}`)) // app.get('/app-health-check', (_req, res) => res.sendStatus(200)) -app.post('/', async (req: Request, res: Response) => { - // console.log('req.headers:', req.headers) - // console.log('authorization:', req.headers.authorization) - const { messages, system } = req.body - // if (system) console.log('system:', system.substring(0, 512)) +app.post(['/', '/chat'], async (req: Request, res: Response) => { + // debug('req.headers:', req.headers) + // debug('authorization:', req.headers.authorization) + const { instructions, messages, system } = req.body + debug('instructions:', (instructions || system)?.length) + // debug('instructions:', (instructions || system)?.substring(0, 128)) const modelMessages = await convertToModelMessages(messages) - console.log('modelMessages:', modelMessages.length) + debug('modelMessages:', modelMessages.length) const stream = createUIMessageStream({ execute: ({ writer }) => { const result = streamText({ model: model, messages: modelMessages, - system: system || process.env.INSTRUCTIONS, + instructions: + (!disableInstructions && (instructions || system)) || process.env.INSTRUCTIONS_CHAT, maxOutputTokens, providerOptions, + onError: onStreamError, + onEnd: onStreamEnd, }) writer.merge(toUIMessageStream({ stream: result.stream })) }, }) - // console.log('stream:', stream) pipeUIMessageStreamToResponse({ response: res, stream }) }) +app.post('/completion', async (req: Request, res: Response) => { + const { instructions, prompt, system } = req.body + debug('instructions:', (instructions || system)?.length) + debug('prompt:', prompt?.length) + const result = streamText({ + model: model, + prompt, + instructions: + (!disableInstructions && (instructions || system)) || process.env.INSTRUCTIONS_COMPLETION, + maxOutputTokens, + providerOptions, + onError: onStreamError, + onEnd: onStreamEnd, + }) + const stream = createUIMessageStream({ + execute: ({ writer }) => { + writer.merge(toUIMessageStream({ stream: result.stream })) + }, + }) + pipeUIMessageStreamToResponse({ + response: res, + stream, + consumeSseStream: consumeStream, + }) +}) + +app.post('/object', async (req: Request, res: Response) => { + const { instructions, output, prompt, system } = req.body + debug('instructions:', (instructions || system)?.length) + debug('output:', output?.length) + debug('prompt:', prompt?.length) + const result = streamText({ + model: model, + prompt, + instructions: + (!disableInstructions && (instructions || system)) || process.env.INSTRUCTIONS_OBJECT, + maxOutputTokens, + providerOptions, + output: output ? Output.object({ schema: jsonSchema(output) }) : Output.json(), + onError: onStreamError, + onEnd: onStreamEnd, + }) + pipeTextStreamToResponse({ + response: res, + stream: toTextStream({ stream: result.stream }), + }) +}) + +function getBool(value: string | undefined): boolean { + if (!value) return false + return ['1', 't', 'true', 'y', 'yes', 'on'].includes(value.trim().toLowerCase()) +} + function corsCallback( origin: string | undefined, callback: (err: Error | null, origin?: boolean) => void, @@ -122,7 +196,18 @@ function getProviderOptions() { if (!process.env.PROVIDER_OPTIONS) return try { return JSON.parse(process.env.PROVIDER_OPTIONS) - } catch { - console.error('parsing PROVIDER_OPTIONS as JSON') + } catch (e) { + console.error('error parsing PROVIDER_OPTIONS as JSON:', e) } } + +async function onStreamError({ error }: { error: unknown }) { + console.error('error:', error) +} + +async function onStreamEnd({ finalStep, finishReason, text, usage }: GenerateTextEndEvent) { + debug('reasoning:', finalStep.reasoningText) + debug('response:', text) + debug('usage:', usage) + debug('finishReason:', finishReason) +}