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..922b063 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 diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 334bc85..5ea343e 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 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/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..987787f 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.16", + "@ai-sdk/google": "^4.0.18", + "@ai-sdk/openai": "^4.0.16", + "@ai-sdk/openai-compatible": "^3.0.12", + "ai": "^7.0.31", "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.1", "@types/picomatch": "^4.0.3", - "eslint": "^10.6.0", + "eslint": "^10.7.0", "nodemon": "^3.1.14", - "prettier": "^3.9.4", - "tsx": "^4.22.4", + "prettier": "^3.9.5", + "tsx": "^4.23.1", "typescript": "^6.0.3", - "typescript-eslint": "^8.62.1" + "typescript-eslint": "^8.64.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.16", + "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-4.0.16.tgz", + "integrity": "sha512-vyH4D6Auih5H2xvVzzh2ep5pbdWiaV7JDC+jHUE7zZJ5Kyv0TteLav4DrOgHzRuyv8ptfUSqFF6Y8//f/Ec0fQ==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.1", - "@ai-sdk/provider-utils": "5.0.2" + "@ai-sdk/provider": "4.0.3", + "@ai-sdk/provider-utils": "5.0.11" }, "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.23", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.23.tgz", + "integrity": "sha512-f85diFdPMXYJpxCjOYZchMQkRH8h3r6lhK4Q2xmzJ7UA2OQ80L3W7tFu61742xGQK7zHWm5AhxYhNuc50H9SGQ==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.1", - "@ai-sdk/provider-utils": "5.0.2", + "@ai-sdk/provider": "4.0.3", + "@ai-sdk/provider-utils": "5.0.11", "@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.18", + "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-4.0.18.tgz", + "integrity": "sha512-NRbXRAasXLgFLiZDTsDUiTUuQtEUDVZoqruB5Px3geltTEqOzOo2eHN5WnDp0/OgxwnrNH4olV/TVetza0PzmQ==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.1", - "@ai-sdk/provider-utils": "5.0.2" + "@ai-sdk/provider": "4.0.3", + "@ai-sdk/provider-utils": "5.0.11" }, "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.16", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-4.0.16.tgz", + "integrity": "sha512-Yh+PsXaf9NbN7oA3oKwOuyjTiHMPD75phf3SqGbDNNKQ3Yj3oTntp/WhO3nCHIPA6gr5/1lyridQokmOpPf9oQ==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.1", - "@ai-sdk/provider-utils": "5.0.2" + "@ai-sdk/provider": "4.0.3", + "@ai-sdk/provider-utils": "5.0.11" }, "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.12", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-3.0.12.tgz", + "integrity": "sha512-tN9BUb4jUGjqtbRPUsziLxASfogLNICcq/Qrwr55vpnzKvprV6Ukl8ynED2lZaNrAHU5U4zlGnyU/iuYrjQK6g==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.1", - "@ai-sdk/provider-utils": "5.0.2" + "@ai-sdk/provider": "4.0.3", + "@ai-sdk/provider-utils": "5.0.11" }, "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.3", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.3.tgz", + "integrity": "sha512-e0CpNWJUY7OxAFAnCZkw+ri9QOHWwTs1tXP42782KFGCU07qt8NiXCrCVowyCB5dP2r5/Uls+g2oPd8kOJn9dw==", "license": "Apache-2.0", "dependencies": { "json-schema": "^0.4.0" @@ -126,12 +128,12 @@ } }, "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.11", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.11.tgz", + "integrity": "sha512-7/96wE+ZsKB35iS9ASyllrE4Ym/EolXEB7AkuJ5FI++fmS85BVTAs77890C+1Z2jwHfBKjBQSBmsliOsAh0iFQ==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.1", + "@ai-sdk/provider": "4.0.3", "@standard-schema/spec": "^1.1.0", "@workflow/serde": "4.1.0", "eventsource-parser": "^3.0.8" @@ -816,6 +818,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 +855,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.2", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.2.tgz", + "integrity": "sha512-d3KvEXBSo/lOAMc2u6fkyDHBvetBHeqD7wm/AcXfLpSOQwlmG9D/aQ0SFswVjv05p7ullQS7Mjohj6/VdbZuTg==", "dev": true, "license": "MIT", "dependencies": { @@ -869,10 +881,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.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", + "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", "dev": true, "license": "MIT", "dependencies": { @@ -922,17 +941,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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", + "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==", "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.64.0", + "@typescript-eslint/type-utils": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -945,15 +964,15 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.62.1", + "@typescript-eslint/parser": "^8.64.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 +980,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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", + "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", "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.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", "debug": "^4.4.3" }, "engines": { @@ -986,14 +1005,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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", + "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.62.1", - "@typescript-eslint/types": "^8.62.1", + "@typescript-eslint/tsconfig-utils": "^8.64.0", + "@typescript-eslint/types": "^8.64.0", "debug": "^4.4.3" }, "engines": { @@ -1008,14 +1027,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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", + "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/visitor-keys": "8.62.1" + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1026,9 +1045,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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", + "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", "dev": true, "license": "MIT", "engines": { @@ -1043,15 +1062,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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz", + "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==", "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.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -1068,9 +1087,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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", + "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", "dev": true, "license": "MIT", "engines": { @@ -1082,16 +1101,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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", + "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", "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.64.0", + "@typescript-eslint/tsconfig-utils": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1110,16 +1129,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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz", + "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==", "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.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1134,13 +1153,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.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", + "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/types": "8.64.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -1203,14 +1222,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.31", + "resolved": "https://registry.npmjs.org/ai/-/ai-7.0.31.tgz", + "integrity": "sha512-pJfwKXjF5kw0rKRTePwYo60EfWb8wfzJAgf3ojln/YkOsVVKttzZAJVcRPsg37Z3a06ZdKkxX+DSrMAFlPm5Mw==", "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.23", + "@ai-sdk/provider": "4.0.3", + "@ai-sdk/provider-utils": "5.0.11" }, "engines": { "node": ">=22" @@ -1663,9 +1682,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.7.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.7.0.tgz", + "integrity": "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==", "dev": true, "license": "MIT", "workspaces": [ @@ -2161,9 +2180,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" @@ -2608,9 +2627,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 +2649,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.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz", + "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==", "dev": true, "license": "MIT", "bin": { @@ -3024,9 +3043,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.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz", + "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3101,16 +3120,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.64.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.64.0.tgz", + "integrity": "sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==", "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.64.0", + "@typescript-eslint/parser": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/package.json b/package.json index 8ab8f5a..5bb4550 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.16", + "@ai-sdk/google": "^4.0.18", + "@ai-sdk/openai": "^4.0.16", + "@ai-sdk/openai-compatible": "^3.0.12", + "ai": "^7.0.31", "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.1", "@types/picomatch": "^4.0.3", - "eslint": "^10.6.0", + "eslint": "^10.7.0", "nodemon": "^3.1.14", - "prettier": "^3.9.4", - "tsx": "^4.22.4", + "prettier": "^3.9.5", + "tsx": "^4.23.1", "typescript": "^6.0.3", - "typescript-eslint": "^8.62.1" + "typescript-eslint": "^8.64.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) +}