From 16b59b3e51257bdc1d36d87c50e047ff4df95857 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Tue, 14 Jul 2026 18:22:15 -0700 Subject: [PATCH 01/23] Add Completion Endpoint --- src/index.ts | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 2379805..b0c9e02 100644 --- a/src/index.ts +++ b/src/index.ts @@ -61,7 +61,7 @@ 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) => { +app.post(['/', '/chat'], async (req: Request, res: Response) => { // console.log('req.headers:', req.headers) // console.log('authorization:', req.headers.authorization) const { messages, system } = req.body @@ -84,6 +84,30 @@ app.post('/', async (req: Request, res: Response) => { pipeUIMessageStreamToResponse({ response: res, stream }) }) +app.post('/completion', async (req: Request, res: Response) => { + const { prompt, system } = req.body + console.log('prompt:', prompt.length, 'system:', system.length) + const result = streamText({ + model: model, + prompt, + system: system || process.env.COMPLETION_INSTRUCTIONS, + maxOutputTokens, + providerOptions, + onEnd({ finishReason, finalStep, text, usage }) { + console.log('finishReason:', finishReason) + console.log('usage:', usage) + console.log('reasoning:', finalStep.reasoningText) + console.log('response:', text) + }, + }) + const stream = createUIMessageStream({ + execute: ({ writer }) => { + writer.merge(toUIMessageStream({ stream: result.stream })) + }, + }) + pipeUIMessageStreamToResponse({ response: res, stream }) +}) + function corsCallback( origin: string | undefined, callback: (err: Error | null, origin?: boolean) => void, From 5161b933dd16ecd4b30be084d0f7f24dd255069c Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Tue, 14 Jul 2026 20:36:49 -0700 Subject: [PATCH 02/23] Cleanup --- src/index.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index b0c9e02..b7c8ac4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import { google } from '@ai-sdk/google' import { openai } from '@ai-sdk/openai' import { createOpenAICompatible } from '@ai-sdk/openai-compatible' import { + consumeStream, streamText, createUIMessageStream, pipeUIMessageStreamToResponse, @@ -85,19 +86,23 @@ app.post(['/', '/chat'], async (req: Request, res: Response) => { }) app.post('/completion', async (req: Request, res: Response) => { - const { prompt, system } = req.body - console.log('prompt:', prompt.length, 'system:', system.length) + const { prompt, system, signal } = req.body + console.log('prompt:', prompt?.length, 'system:', system?.length) const result = streamText({ model: model, prompt, system: system || process.env.COMPLETION_INSTRUCTIONS, maxOutputTokens, providerOptions, - onEnd({ finishReason, finalStep, text, usage }) { - console.log('finishReason:', finishReason) - console.log('usage:', usage) + abortSignal: signal, + onError({ error }) { + console.log('error:', error) + }, + onEnd({ finalStep, finishReason, text, usage }) { console.log('reasoning:', finalStep.reasoningText) console.log('response:', text) + console.log('usage:', usage) + console.log('finishReason:', finishReason) }, }) const stream = createUIMessageStream({ @@ -105,7 +110,11 @@ app.post('/completion', async (req: Request, res: Response) => { writer.merge(toUIMessageStream({ stream: result.stream })) }, }) - pipeUIMessageStreamToResponse({ response: res, stream }) + pipeUIMessageStreamToResponse({ + response: res, + stream, + consumeSseStream: consumeStream, + }) }) function corsCallback( From 091be2300fa2e01606e58a86f24065e48c2cf7db Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Tue, 14 Jul 2026 21:57:42 -0700 Subject: [PATCH 03/23] Add /object --- src/index.ts | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index b7c8ac4..7bd2e39 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,11 +7,14 @@ import { google } from '@ai-sdk/google' import { openai } from '@ai-sdk/openai' import { createOpenAICompatible } from '@ai-sdk/openai-compatible' import { + Output, consumeStream, + pipeTextStreamToResponse, streamText, createUIMessageStream, pipeUIMessageStreamToResponse, convertToModelMessages, + toTextStream, toUIMessageStream, } from 'ai' @@ -43,7 +46,11 @@ 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( + `CHAT_INSTRUCTIONS: ${process.env.INSTRUCTIONS || process.env.CHAT_INSTRUCTIONS}`, // NOSONAR +) +console.log(`COMPLETION_INSTRUCTIONS: ${process.env.COMPLETION_INSTRUCTIONS}`) +console.log(`OBJECT_INSTRUCTIONS: ${process.env.OBJECT_INSTRUCTIONS}`) const model = getModel() console.log(`Loaded modelId: ${model.modelId}`) @@ -74,7 +81,7 @@ app.post(['/', '/chat'], async (req: Request, res: Response) => { const result = streamText({ model: model, messages: modelMessages, - system: system || process.env.INSTRUCTIONS, + system: system || process.env.INSTRUCTIONS || process.env.CHAT_INSTRUCTIONS, maxOutputTokens, providerOptions, }) @@ -86,7 +93,7 @@ app.post(['/', '/chat'], async (req: Request, res: Response) => { }) app.post('/completion', async (req: Request, res: Response) => { - const { prompt, system, signal } = req.body + const { prompt, system } = req.body console.log('prompt:', prompt?.length, 'system:', system?.length) const result = streamText({ model: model, @@ -94,8 +101,7 @@ app.post('/completion', async (req: Request, res: Response) => { system: system || process.env.COMPLETION_INSTRUCTIONS, maxOutputTokens, providerOptions, - abortSignal: signal, - onError({ error }) { + onError(error) { console.log('error:', error) }, onEnd({ finalStep, finishReason, text, usage }) { @@ -117,6 +123,32 @@ app.post('/completion', async (req: Request, res: Response) => { }) }) +app.post('/object', async (req: Request, res: Response) => { + const { outputSchema, prompt, system } = req.body + console.log('prompt:', prompt?.length, 'system:', system?.length) + const result = streamText({ + model: model, + prompt, + system: system || process.env.OBJECT_INSTRUCTIONS, + maxOutputTokens, + providerOptions, + output: outputSchema ? Output.object({ schema: outputSchema }) : Output.json(), + onError(error) { + console.log('error:', error) + }, + onEnd({ finalStep, finishReason, text, usage }) { + console.log('reasoning:', finalStep.reasoningText) + console.log('response:', text) + console.log('usage:', usage) + console.log('finishReason:', finishReason) + }, + }) + pipeTextStreamToResponse({ + response: res, + stream: toTextStream({ stream: result.stream }), + }) +}) + function corsCallback( origin: string | undefined, callback: (err: Error | null, origin?: boolean) => void, From 23f9abb19470270fd95bcef0176eb61ae183bbd4 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Wed, 15 Jul 2026 16:47:40 -0700 Subject: [PATCH 04/23] Update README.md --- README.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++---- src/index.ts | 9 ++++++--- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 47ed6ad..0578466 100644 --- a/README.md +++ b/README.md @@ -42,12 +42,13 @@ 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 +- Chat, Completion, and Object Endpoints - Live Stream Results to Client - Automatic Input Token Caching - Automatic Retry on API Failures @@ -107,7 +108,9 @@ Environment Variables. | `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 | +| `CHAT_INSTRUCTIONS` | - | System Instructions for Chat | +| `COMPLETION_INSTRUCTIONS` | - | System Instructions for Completion | +| `OBJECT_INSTRUCTIONS` | - | System Instructions for Object | | `AI_SDK_LOG_WARNINGS` | - | Disable SDK Warnings | | `CORS_ORIGINS` | - | Allowed CORS Origins (supports \*) | | `PORT` | `3000` | Server Port | @@ -145,6 +148,16 @@ The value is only checked for valid JSON at startup and will fail at runtime if ## Client +### 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) | + +### Chat + To send System Instructions from the client, add them to the body. ```typescript @@ -157,11 +170,46 @@ const chat = new Chat({ }) ``` +Reference: + +### Completion + +```typescript +import { useCompletion } from '@ai-sdk/react' + +const { completion, complete, isLoading, stop } = useCompletion({ + api: 'https://chat-server.cssnr.com/completion', + headers: { Authorization: 'Basic Abc123=' }, + body: { system: 'You are a helpful assistant.' }, +}) +``` + +Reference: + +### Object + +```typescript +import { useObject } from '@ai-sdk/react' + +const { object, submit } = useObject({ + api: 'https://chat-server.cssnr.com/object', + schema: z.object({ name: z.string(), age: z.number() }), + headers: { Authorization: 'Basic Abc123=' }, +}) + +submit({ + system: 'You are a helpful assistant.', + prompt: 'Extract the name and age from: John is 30 years old.', +}) +``` + +Reference: + ### 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/) diff --git a/src/index.ts b/src/index.ts index 7bd2e39..7ea1dc1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -73,7 +73,7 @@ app.post(['/', '/chat'], 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)) + // console.log('system:', system?.substring(0, 512)) const modelMessages = await convertToModelMessages(messages) console.log('modelMessages:', modelMessages.length) const stream = createUIMessageStream({ @@ -94,7 +94,8 @@ app.post(['/', '/chat'], async (req: Request, res: Response) => { app.post('/completion', async (req: Request, res: Response) => { const { prompt, system } = req.body - console.log('prompt:', prompt?.length, 'system:', system?.length) + console.log('prompt:', prompt?.length) + console.log('system:', system?.length) const result = streamText({ model: model, prompt, @@ -125,7 +126,9 @@ app.post('/completion', async (req: Request, res: Response) => { app.post('/object', async (req: Request, res: Response) => { const { outputSchema, prompt, system } = req.body - console.log('prompt:', prompt?.length, 'system:', system?.length) + console.log('outputSchema:', outputSchema?.length) + console.log('prompt:', prompt?.length) + console.log('system:', system?.length) const result = streamText({ model: model, prompt, From d63e304a1e4ec14775751595f9ac5c33548e19a2 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Wed, 15 Jul 2026 18:45:52 -0700 Subject: [PATCH 05/23] Updates --- README.md | 6 +++++- src/index.ts | 7 ++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0578466..7d0df3b 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ To send System Instructions from the client, add them to the body. ```typescript const chat = new Chat({ 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.' }, }), @@ -200,6 +200,10 @@ const { object, submit } = useObject({ submit({ system: 'You are a helpful assistant.', prompt: 'Extract the name and age from: John is 30 years old.', + output: { + type: 'object', + properties: { name: { type: 'string' }, age: { type: 'number' } }, + }, }) ``` diff --git a/src/index.ts b/src/index.ts index 7ea1dc1..fe505b8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,7 @@ import { createOpenAICompatible } from '@ai-sdk/openai-compatible' import { Output, consumeStream, + jsonSchema, pipeTextStreamToResponse, streamText, createUIMessageStream, @@ -125,8 +126,8 @@ app.post('/completion', async (req: Request, res: Response) => { }) app.post('/object', async (req: Request, res: Response) => { - const { outputSchema, prompt, system } = req.body - console.log('outputSchema:', outputSchema?.length) + const { output, prompt, system } = req.body + console.log('output:', output ? 'SET' : undefined) console.log('prompt:', prompt?.length) console.log('system:', system?.length) const result = streamText({ @@ -135,7 +136,7 @@ app.post('/object', async (req: Request, res: Response) => { system: system || process.env.OBJECT_INSTRUCTIONS, maxOutputTokens, providerOptions, - output: outputSchema ? Output.object({ schema: outputSchema }) : Output.json(), + output: output ? Output.object({ schema: jsonSchema(output) }) : Output.json(), onError(error) { console.log('error:', error) }, From e641616498074ef0ae70a8ff99111f4ce304ca90 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Wed, 15 Jul 2026 22:33:01 -0700 Subject: [PATCH 06/23] Cleanup --- src/index.ts | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/index.ts b/src/index.ts index fe505b8..6e16701 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,14 +7,15 @@ import { google } from '@ai-sdk/google' import { openai } from '@ai-sdk/openai' import { createOpenAICompatible } from '@ai-sdk/openai-compatible' import { + type GenerateTextEndEvent, Output, consumeStream, + convertToModelMessages, + createUIMessageStream, jsonSchema, pipeTextStreamToResponse, - streamText, - createUIMessageStream, pipeUIMessageStreamToResponse, - convertToModelMessages, + streamText, toTextStream, toUIMessageStream, } from 'ai' @@ -76,7 +77,7 @@ app.post(['/', '/chat'], async (req: Request, res: Response) => { const { messages, system } = req.body // console.log('system:', system?.substring(0, 512)) const modelMessages = await convertToModelMessages(messages) - console.log('modelMessages:', modelMessages.length) + // console.log('modelMessages:', modelMessages.length) const stream = createUIMessageStream({ execute: ({ writer }) => { const result = streamText({ @@ -85,6 +86,8 @@ app.post(['/', '/chat'], async (req: Request, res: Response) => { system: system || process.env.INSTRUCTIONS || process.env.CHAT_INSTRUCTIONS, maxOutputTokens, providerOptions, + onError: onStreamError, + onEnd: onStreamEnd, }) writer.merge(toUIMessageStream({ stream: result.stream })) }, @@ -103,15 +106,8 @@ app.post('/completion', async (req: Request, res: Response) => { system: system || process.env.COMPLETION_INSTRUCTIONS, maxOutputTokens, providerOptions, - onError(error) { - console.log('error:', error) - }, - onEnd({ finalStep, finishReason, text, usage }) { - console.log('reasoning:', finalStep.reasoningText) - console.log('response:', text) - console.log('usage:', usage) - console.log('finishReason:', finishReason) - }, + onError: onStreamError, + onEnd: onStreamEnd, }) const stream = createUIMessageStream({ execute: ({ writer }) => { @@ -137,15 +133,8 @@ app.post('/object', async (req: Request, res: Response) => { maxOutputTokens, providerOptions, output: output ? Output.object({ schema: jsonSchema(output) }) : Output.json(), - onError(error) { - console.log('error:', error) - }, - onEnd({ finalStep, finishReason, text, usage }) { - console.log('reasoning:', finalStep.reasoningText) - console.log('response:', text) - console.log('usage:', usage) - console.log('finishReason:', finishReason) - }, + onError: onStreamError, + onEnd: onStreamEnd, }) pipeTextStreamToResponse({ response: res, @@ -195,3 +184,14 @@ function getProviderOptions() { console.error('parsing PROVIDER_OPTIONS as JSON') } } + +function onStreamError(error: unknown) { + console.log('error:', error) +} + +function onStreamEnd({ finalStep, finishReason, text, usage }: GenerateTextEndEvent) { + console.log('reasoning:', finalStep.reasoningText) + console.log('response:', text) + console.log('usage:', usage) + console.log('finishReason:', finishReason) +} From 218b7f4cf9bb7bacbeaefee7cd6f20faf469ca51 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:45:33 -0700 Subject: [PATCH 07/23] Add debug --- README.md | 1 + package-lock.json | 19 +++++++++++++++++++ package.json | 2 ++ src/index.ts | 37 +++++++++++++++++++++---------------- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 7d0df3b..8d6e7cf 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ Environment Variables. | `AI_SDK_LOG_WARNINGS` | - | Disable SDK Warnings | | `CORS_ORIGINS` | - | Allowed CORS Origins (supports \*) | | `PORT` | `3000` | Server Port | +| `DEBUG` | - | Set to `app` for debug logs | You must also set the API key for the `MODEL` you select. diff --git a/package-lock.json b/package-lock.json index f49f933..6a33da0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@ai-sdk/openai-compatible": "^3.0.2", "ai": "^7.0.8", "cors": "^2.8.6", + "debug": "^4.4.3", "dotenv": "^17.4.2", "express": "^5.2.1", "picomatch": "^4.0.4" @@ -21,6 +22,7 @@ "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/picomatch": "^4.0.3", @@ -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", @@ -869,6 +881,13 @@ "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", diff --git a/package.json b/package.json index 8ab8f5a..6a81fcc 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@ai-sdk/openai-compatible": "^3.0.2", "ai": "^7.0.8", "cors": "^2.8.6", + "debug": "^4.4.3", "dotenv": "^17.4.2", "express": "^5.2.1", "picomatch": "^4.0.4" @@ -29,6 +30,7 @@ "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/picomatch": "^4.0.3", diff --git a/src/index.ts b/src/index.ts index 6e16701..feb0cad 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' @@ -22,9 +23,14 @@ import { dotenv.config({ path: 'settings.env' }) +console.log(`DEBUG: ${process.env.DEBUG}`) +createDebug.enable(process.env.DEBUG ?? '') +const debug = createDebug('app') +debug('debug enabled: app') + console.log(`chat-server: ${process.env.APP_VERSION}`) -console.log('MODEL:', process.env.MODEL ? 'SET' : undefined) +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( @@ -72,12 +78,12 @@ app.listen(port, () => console.log(`Listening on PORT: ${port}`)) // app.get('/app-health-check', (_req, res) => res.sendStatus(200)) app.post(['/', '/chat'], async (req: Request, res: Response) => { - // console.log('req.headers:', req.headers) - // console.log('authorization:', req.headers.authorization) + // debug('req.headers:', req.headers) + // debug('authorization:', req.headers.authorization) const { messages, system } = req.body - // console.log('system:', system?.substring(0, 512)) + // debug('system:', 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({ @@ -92,14 +98,13 @@ app.post(['/', '/chat'], async (req: Request, res: Response) => { writer.merge(toUIMessageStream({ stream: result.stream })) }, }) - // console.log('stream:', stream) pipeUIMessageStreamToResponse({ response: res, stream }) }) app.post('/completion', async (req: Request, res: Response) => { const { prompt, system } = req.body - console.log('prompt:', prompt?.length) - console.log('system:', system?.length) + debug('prompt:', prompt?.length) + debug('system:', system?.length) const result = streamText({ model: model, prompt, @@ -123,9 +128,9 @@ app.post('/completion', async (req: Request, res: Response) => { app.post('/object', async (req: Request, res: Response) => { const { output, prompt, system } = req.body - console.log('output:', output ? 'SET' : undefined) - console.log('prompt:', prompt?.length) - console.log('system:', system?.length) + debug('output:', output?.length) + debug('prompt:', prompt?.length) + debug('system:', system?.length) const result = streamText({ model: model, prompt, @@ -186,12 +191,12 @@ function getProviderOptions() { } function onStreamError(error: unknown) { - console.log('error:', error) + console.error('error:', error) } function onStreamEnd({ finalStep, finishReason, text, usage }: GenerateTextEndEvent) { - console.log('reasoning:', finalStep.reasoningText) - console.log('response:', text) - console.log('usage:', usage) - console.log('finishReason:', finishReason) + debug('reasoning:', finalStep.reasoningText) + debug('response:', text) + debug('usage:', usage) + debug('finishReason:', finishReason) } From 078e13c5d9dd0d68acba727c83064d46a93861e8 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:26:42 -0700 Subject: [PATCH 08/23] Update README.md --- README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8d6e7cf..903b09f 100644 --- a/README.md +++ b/README.md @@ -48,12 +48,12 @@ To get started [Setup](#setup) and [Configure](#configure) the server. No API Ke ### Features - Works with Claude, OpenAI, Gemini and OpenAI Compatible Providers -- Chat, Completion, and Object Endpoints -- 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/). @@ -116,6 +116,8 @@ Environment Variables. | `PORT` | `3000` | Server Port | | `DEBUG` | - | Set to `app` for debug logs | +Note: The `INSTRUCTIONS` variable also points to the `CHAT_INSTRUCTIONS` variable (recommended). + You must also set the API key for the `MODEL` you select. | Variable | Description | @@ -157,12 +159,17 @@ The value is only checked for valid JSON at startup and will fail at runtime if | `/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 also points to the `/chat` endpoint (recommended). + ### Chat To send System Instructions from the client, add them to the body. ```typescript -const chat = new Chat({ +import { useChat } from '@ai-sdk/vue' +import { DefaultChatTransport } from 'ai' + +const { messages, input, handleSubmit } = useChat({ transport: new DefaultChatTransport({ api: 'https://chat-server.cssnr.com/chat', headers: { Authorization: 'Basic Abc123=' }, @@ -176,7 +183,7 @@ Reference: ### Completion ```typescript -import { useCompletion } from '@ai-sdk/react' +import { useCompletion } from '@ai-sdk/vue' const { completion, complete, isLoading, stop } = useCompletion({ api: 'https://chat-server.cssnr.com/completion', @@ -190,7 +197,7 @@ Reference: ### Object ```typescript -import { useObject } from '@ai-sdk/react' +import { useObject } from '@ai-sdk/vue' const { object, submit } = useObject({ api: 'https://chat-server.cssnr.com/object', From 9cbea121307eecd30d7bd4e2adc1cec071a8c270 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:43:32 -0700 Subject: [PATCH 09/23] Update README.md --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 903b09f..73ee0b8 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ 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. +Environment Variables (can be placed in a `settings.env` file). | Variable | Default | Description | | :------------------------------------ | :--------------------------- | :---------------------------------- | @@ -161,7 +161,7 @@ The value is only checked for valid JSON at startup and will fail at runtime if Note: The `/` endpoint also points to the `/chat` endpoint (recommended). -### Chat +#### chat To send System Instructions from the client, add them to the body. @@ -180,7 +180,7 @@ const { messages, input, handleSubmit } = useChat({ Reference: -### Completion +#### completion ```typescript import { useCompletion } from '@ai-sdk/vue' @@ -194,7 +194,7 @@ const { completion, complete, isLoading, stop } = useCompletion({ Reference: -### Object +#### object ```typescript import { useObject } from '@ai-sdk/vue' @@ -215,6 +215,8 @@ submit({ }) ``` +Note: Both `system` and `output` are custom body parameters parsed by the server allowing the client to send these items. + Reference: ### VitePress Chat Plugin @@ -227,6 +229,8 @@ The client is currently available as a VitePress Plugin. ## 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. From 248c88bb325b59873b90a40ab33e5bed3c9fa71f Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Thu, 16 Jul 2026 14:45:10 -0700 Subject: [PATCH 10/23] Update Dependencies and Workflows --- .github/workflows/build.yaml | 2 +- .github/workflows/deploy.yaml | 2 +- .github/workflows/draft.yaml | 2 +- .github/workflows/issue.yaml | 2 +- .github/workflows/labeler.yaml | 2 +- .github/workflows/lint.yaml | 4 +- package-lock.json | 268 ++++++++++++++++----------------- package.json | 22 +-- src/index.ts | 1 + 9 files changed, 153 insertions(+), 152 deletions(-) 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/package-lock.json b/package-lock.json index 6a33da0..2d7de4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,40 +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.15", + "@ai-sdk/google": "^4.0.17", + "@ai-sdk/openai": "^4.0.15", + "@ai-sdk/openai-compatible": "^3.0.11", + "ai": "^7.0.29", "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.15", + "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-4.0.15.tgz", + "integrity": "sha512-6iVlzqZjqqoHTmgO1WU9id/33pYykIRasWJFAMf1+d+nhju6d7566VRFsOGZ2W2GzgNtzKs8DGsYN7X5/wOGuw==", "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.10" }, "engines": { "node": ">=22" @@ -51,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.21", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.21.tgz", + "integrity": "sha512-GafyeyHFDKJjdtbyhCQ0aC2FORdyE56IxK45EZ1JLO1iVdg8XqEU3bwnRzI0+5S1XHV7W2qQsxkZnGYPbsFiXA==", "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.10", "@vercel/oidc": "3.2.0" }, "engines": { @@ -68,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.17", + "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-4.0.17.tgz", + "integrity": "sha512-90YMrWWIz7HVqrtthaP60fqGhsTT8OXHskxJ9sPmD/bFBHqdbOBrtS9oKBWjO0XBAY8eo2Rd7Ai8zGKCTvPn9Q==", "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.10" }, "engines": { "node": ">=22" @@ -84,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.15", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-4.0.15.tgz", + "integrity": "sha512-JpTLQp5RUbRcs5nOyPEu5NRdxZLUnD/uCyT3qzy26D+iunCeL7KJV58ER9kwisAKnTjWravfNblaQNiWr20M9A==", "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.10" }, "engines": { "node": ">=22" @@ -100,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.11", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-3.0.11.tgz", + "integrity": "sha512-PKLyt5PfjV2maWJPZpm3W29vCc+BbyqSPAcghXhl+iIxzFNjpGPa6UD/8J0b9l7w/lJCZKuhcef9q+Gp5WqUvw==", "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.10" }, "engines": { "node": ">=22" @@ -116,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" @@ -128,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.10", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.10.tgz", + "integrity": "sha512-uPyec0+85dwxZYXtb8qe8gCjhjDfxP4LCDo/uRQS/iG+FIgYbHPRhr/ys281udG90bTaE18+5cxWraYaf8oHCw==", "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" @@ -855,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": { @@ -889,9 +889,9 @@ "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": { @@ -941,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" @@ -964,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": { @@ -980,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": { @@ -1005,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": { @@ -1027,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" @@ -1045,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": { @@ -1062,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" }, @@ -1087,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": { @@ -1101,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", @@ -1129,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" @@ -1153,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": { @@ -1222,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.29", + "resolved": "https://registry.npmjs.org/ai/-/ai-7.0.29.tgz", + "integrity": "sha512-q+A+skhl6SyjWliU6W7zNYgDUrEk5SbNrCc5vt4S9n6+n6e7jSorDmu51hlhjDOsS7VwzWGCgbWFvvT3iomtvg==", "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.21", + "@ai-sdk/provider": "4.0.3", + "@ai-sdk/provider-utils": "5.0.10" }, "engines": { "node": ">=22" @@ -1682,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": [ @@ -2180,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" @@ -2627,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" @@ -2649,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": { @@ -3043,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": { @@ -3120,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 6a81fcc..771f95e 100644 --- a/package.json +++ b/package.json @@ -16,29 +16,29 @@ "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.15", + "@ai-sdk/google": "^4.0.17", + "@ai-sdk/openai": "^4.0.15", + "@ai-sdk/openai-compatible": "^3.0.11", + "ai": "^7.0.29", "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 feb0cad..bb805e7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -81,6 +81,7 @@ app.post(['/', '/chat'], async (req: Request, res: Response) => { // debug('req.headers:', req.headers) // debug('authorization:', req.headers.authorization) const { messages, system } = req.body + debug('system:', system?.length) // debug('system:', system?.substring(0, 128)) const modelMessages = await convertToModelMessages(messages) debug('modelMessages:', modelMessages.length) From f8d1d69f0189b98c452283662006654d5eeb7279 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Thu, 16 Jul 2026 19:59:48 -0700 Subject: [PATCH 11/23] Add Disable Endpoint Options --- AGENTS.md | 8 +++ README.md | 11 ++-- src/index.ts | 150 +++++++++++++++++++++++++++------------------------ 3 files changed, 95 insertions(+), 74 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index bb46bf5..c8cbb07 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,3 +15,11 @@ 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 + +| Endpoint | Links | +| :------------ | :-------------------------------------------------------------------------------------- | +| `/chat` | Client Docs [useChat](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat) | +| `/completion` | Client Docs [useCompletion](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-completion) | +| `/object` | Client Docs [useObject](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-object) | diff --git a/README.md b/README.md index 73ee0b8..7789a17 100644 --- a/README.md +++ b/README.md @@ -108,15 +108,18 @@ Environment Variables (can be placed in a `settings.env` file). | `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 | -| `CHAT_INSTRUCTIONS` | - | System Instructions for Chat | -| `COMPLETION_INSTRUCTIONS` | - | System Instructions for Completion | -| `OBJECT_INSTRUCTIONS` | - | System Instructions for Object | +| `INSTRUCTIONS_CHAT` | - | System Instructions for Chat | +| `INSTRUCTIONS_COMPLETION` | - | System Instructions for Completion | +| `INSTRUCTIONS_OBJECT` | - | System Instructions for Object | +| `DISABLE_CHAT` | - | Disable the `/chat` endpoint | +| `DISABLE_COMPLETION` | - | Disable the `/completion` endpoint | +| `DISABLE_OBJECT` | - | Disable the `/object` endpoint | | `AI_SDK_LOG_WARNINGS` | - | Disable SDK Warnings | | `CORS_ORIGINS` | - | Allowed CORS Origins (supports \*) | | `PORT` | `3000` | Server Port | | `DEBUG` | - | Set to `app` for debug logs | -Note: The `INSTRUCTIONS` variable also points to the `CHAT_INSTRUCTIONS` variable (recommended). +Note: The `INSTRUCTIONS` variable also points to the `INSTRUCTIONS_CHAT` variable (recommended). You must also set the API key for the `MODEL` you select. diff --git a/src/index.ts b/src/index.ts index bb805e7..5d080d0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,6 +30,10 @@ debug('debug enabled: app') console.log(`chat-server: ${process.env.APP_VERSION}`) +console.log('DISABLE_CHAT:', process.env.DISABLE_CHAT ? 'YES' : 'NO') +console.log('DISABLE_COMPLETION:', process.env.DISABLE_COMPLETION ? 'YES' : 'NO') +console.log('DISABLE_OBJECT:', process.env.DISABLE_OBJECT ? 'YES' : 'NO') + 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) @@ -55,10 +59,10 @@ const maxOutputTokens = process.env.MAX_TOKENS : undefined console.log(`maxOutputTokens: ${maxOutputTokens}`) console.log( - `CHAT_INSTRUCTIONS: ${process.env.INSTRUCTIONS || process.env.CHAT_INSTRUCTIONS}`, // NOSONAR + `INSTRUCTIONS_CHAT: ${process.env.INSTRUCTIONS || process.env.INSTRUCTIONS_CHAT}`, // NOSONAR ) -console.log(`COMPLETION_INSTRUCTIONS: ${process.env.COMPLETION_INSTRUCTIONS}`) -console.log(`OBJECT_INSTRUCTIONS: ${process.env.OBJECT_INSTRUCTIONS}`) +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}`) @@ -77,76 +81,82 @@ app.listen(port, () => console.log(`Listening on PORT: ${port}`)) // app.get('/app-health-check', (_req, res) => res.sendStatus(200)) -app.post(['/', '/chat'], async (req: Request, res: Response) => { - // debug('req.headers:', req.headers) - // debug('authorization:', req.headers.authorization) - const { messages, system } = req.body - debug('system:', system?.length) - // debug('system:', system?.substring(0, 128)) - const modelMessages = await convertToModelMessages(messages) - debug('modelMessages:', modelMessages.length) - const stream = createUIMessageStream({ - execute: ({ writer }) => { - const result = streamText({ - model: model, - messages: modelMessages, - system: system || process.env.INSTRUCTIONS || process.env.CHAT_INSTRUCTIONS, - maxOutputTokens, - providerOptions, - onError: onStreamError, - onEnd: onStreamEnd, - }) - writer.merge(toUIMessageStream({ stream: result.stream })) - }, - }) - pipeUIMessageStreamToResponse({ response: res, stream }) -}) - -app.post('/completion', async (req: Request, res: Response) => { - const { prompt, system } = req.body - debug('prompt:', prompt?.length) - debug('system:', system?.length) - const result = streamText({ - model: model, - prompt, - system: system || process.env.COMPLETION_INSTRUCTIONS, - maxOutputTokens, - providerOptions, - onError: onStreamError, - onEnd: onStreamEnd, - }) - const stream = createUIMessageStream({ - execute: ({ writer }) => { - writer.merge(toUIMessageStream({ stream: result.stream })) - }, - }) - pipeUIMessageStreamToResponse({ - response: res, - stream, - consumeSseStream: consumeStream, +if (!process.env.DISABLE_CHAT) { + app.post(['/', '/chat'], async (req: Request, res: Response) => { + // debug('req.headers:', req.headers) + // debug('authorization:', req.headers.authorization) + const { messages, system } = req.body + debug('system:', system?.length) + // debug('system:', system?.substring(0, 128)) + const modelMessages = await convertToModelMessages(messages) + debug('modelMessages:', modelMessages.length) + const stream = createUIMessageStream({ + execute: ({ writer }) => { + const result = streamText({ + model: model, + messages: modelMessages, + system: system || process.env.INSTRUCTIONS || process.env.INSTRUCTIONS_CHAT, + maxOutputTokens, + providerOptions, + onError: onStreamError, + onEnd: onStreamEnd, + }) + writer.merge(toUIMessageStream({ stream: result.stream })) + }, + }) + pipeUIMessageStreamToResponse({ response: res, stream }) }) -}) - -app.post('/object', async (req: Request, res: Response) => { - const { output, prompt, system } = req.body - debug('output:', output?.length) - debug('prompt:', prompt?.length) - debug('system:', system?.length) - const result = streamText({ - model: model, - prompt, - system: system || process.env.OBJECT_INSTRUCTIONS, - maxOutputTokens, - providerOptions, - output: output ? Output.object({ schema: jsonSchema(output) }) : Output.json(), - onError: onStreamError, - onEnd: onStreamEnd, +} + +if (!process.env.DISABLE_COMPLETION) { + app.post('/completion', async (req: Request, res: Response) => { + const { prompt, system } = req.body + debug('prompt:', prompt?.length) + debug('system:', system?.length) + const result = streamText({ + model: model, + prompt, + system: 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, + }) }) - pipeTextStreamToResponse({ - response: res, - stream: toTextStream({ stream: result.stream }), +} + +if (!process.env.DISABLE_OBJECT) { + app.post('/object', async (req: Request, res: Response) => { + const { output, prompt, system } = req.body + debug('output:', output?.length) + debug('prompt:', prompt?.length) + debug('system:', system?.length) + const result = streamText({ + model: model, + prompt, + system: 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 corsCallback( origin: string | undefined, From 888226e574d8b3363762c670ba00f1fe01603c4f Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Thu, 16 Jul 2026 20:48:22 -0700 Subject: [PATCH 12/23] Update README.md --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7789a17..bd79901 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ To send System Instructions from the client, add them to the body. import { useChat } from '@ai-sdk/vue' import { DefaultChatTransport } from 'ai' -const { messages, input, handleSubmit } = useChat({ +const { messages, sendMessage, status, stop } = useChat({ transport: new DefaultChatTransport({ api: 'https://chat-server.cssnr.com/chat', headers: { Authorization: 'Basic Abc123=' }, @@ -193,6 +193,8 @@ const { completion, complete, isLoading, stop } = useCompletion({ headers: { Authorization: 'Basic Abc123=' }, body: { system: 'You are a helpful assistant.' }, }) + +await complete('Explain how to setup cssnr/chat-server') ``` Reference: @@ -201,20 +203,21 @@ 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', - schema: z.object({ name: z.string(), age: z.number() }), headers: { Authorization: 'Basic Abc123=' }, + schema, }) submit({ system: 'You are a helpful assistant.', prompt: 'Extract the name and age from: John is 30 years old.', - output: { - type: 'object', - properties: { name: { type: 'string' }, age: { type: 'number' } }, - }, + output: zodToJsonSchema(schema), }) ``` From f69fec3b126ee05fbaab64deed8ae7893c3a9e6e Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Thu, 16 Jul 2026 20:54:18 -0700 Subject: [PATCH 13/23] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bd79901..509a776 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) From 8c080f0c290db4cc6a2f203c42fa6e2af4d5bc1e Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Thu, 16 Jul 2026 21:09:54 -0700 Subject: [PATCH 14/23] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 509a776..0e6947a 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,8 @@ Note: The `/` endpoint also points to the `/chat` endpoint (recommended). #### chat +Reference: + To send System Instructions from the client, add them to the body. ```typescript @@ -182,10 +184,10 @@ const { messages, sendMessage, status, stop } = useChat({ }) ``` -Reference: - #### completion +Reference: + ```typescript import { useCompletion } from '@ai-sdk/vue' @@ -198,10 +200,10 @@ const { completion, complete, isLoading, stop } = useCompletion({ await complete('Explain how to setup cssnr/chat-server') ``` -Reference: - #### object +Reference: + ```typescript import { useObject } from '@ai-sdk/vue' import { z } from 'zod' @@ -224,8 +226,6 @@ submit({ Note: Both `system` and `output` are custom body parameters parsed by the server allowing the client to send these items. -Reference: - ### VitePress Chat Plugin The client is currently available as a VitePress Plugin. From fefc394cbcd9d21e8cf61b4355988def187b1487 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Thu, 16 Jul 2026 21:50:46 -0700 Subject: [PATCH 15/23] Updates --- AGENTS.md | 15 ++++++--------- README.md | 6 +++--- package-lock.json | 16 ++++++++-------- package.json | 2 +- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c8cbb07..c861aaf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,9 +1,6 @@ # 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 with single route `/` +- [index.ts](src/index.ts) — Single source Express.js AI-SDK server ## Commands @@ -18,8 +15,8 @@ ALWAYS use the `npm run *` command ## Endpoints -| Endpoint | Links | -| :------------ | :-------------------------------------------------------------------------------------- | -| `/chat` | Client Docs [useChat](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat) | -| `/completion` | Client Docs [useCompletion](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-completion) | -| `/object` | Client Docs [useObject](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-object) | +| Server Endpoint | Client | +| :-------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- | +| [/chat](https://ai-sdk.dev/docs/reference/ai-sdk-ui/create-ui-message-stream) | [useChat](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat) | +| [/completion](https://ai-sdk.dev/docs/reference/ai-sdk-ui/pipe-ui-message-stream-to-response) | [useCompletion](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-completion) | +| [/object](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text) | [useObject](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-object) | diff --git a/README.md b/README.md index 0e6947a..e017d54 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ 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) @@ -150,7 +150,7 @@ 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 @@ -197,7 +197,7 @@ const { completion, complete, isLoading, stop } = useCompletion({ body: { system: 'You are a helpful assistant.' }, }) -await complete('Explain how to setup cssnr/chat-server') +await complete('Explain how to set up cssnr/chat-server') ``` #### object diff --git a/package-lock.json b/package-lock.json index 2d7de4f..4f06126 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@ai-sdk/google": "^4.0.17", "@ai-sdk/openai": "^4.0.15", "@ai-sdk/openai-compatible": "^3.0.11", - "ai": "^7.0.29", + "ai": "^7.0.30", "cors": "^2.8.6", "debug": "^4.4.3", "dotenv": "^17.4.2", @@ -51,9 +51,9 @@ } }, "node_modules/@ai-sdk/gateway": { - "version": "4.0.21", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.21.tgz", - "integrity": "sha512-GafyeyHFDKJjdtbyhCQ0aC2FORdyE56IxK45EZ1JLO1iVdg8XqEU3bwnRzI0+5S1XHV7W2qQsxkZnGYPbsFiXA==", + "version": "4.0.22", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.22.tgz", + "integrity": "sha512-KdXJLa6O25fltJC/Lh8gnB1VaYMdX2mEKIalkohuItS+Ig523XxwK9e/Uuc66AvtrLIf7G51cDg3rDpP9mrxwQ==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "4.0.3", @@ -1222,12 +1222,12 @@ } }, "node_modules/ai": { - "version": "7.0.29", - "resolved": "https://registry.npmjs.org/ai/-/ai-7.0.29.tgz", - "integrity": "sha512-q+A+skhl6SyjWliU6W7zNYgDUrEk5SbNrCc5vt4S9n6+n6e7jSorDmu51hlhjDOsS7VwzWGCgbWFvvT3iomtvg==", + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/ai/-/ai-7.0.30.tgz", + "integrity": "sha512-tm0gTAHSWBdDfW4P2mj+LlglGCUQTSA9ktyS2PsPgVhxNO9gYWTSnRAehiJ3h1lYsJBp1Zgbz3RH2lezJXagiw==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/gateway": "4.0.21", + "@ai-sdk/gateway": "4.0.22", "@ai-sdk/provider": "4.0.3", "@ai-sdk/provider-utils": "5.0.10" }, diff --git a/package.json b/package.json index 771f95e..6f6ee19 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@ai-sdk/google": "^4.0.17", "@ai-sdk/openai": "^4.0.15", "@ai-sdk/openai-compatible": "^3.0.11", - "ai": "^7.0.29", + "ai": "^7.0.30", "cors": "^2.8.6", "debug": "^4.4.3", "dotenv": "^17.4.2", From 22cd15444c95595e529162f7b5847304cc233393 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Thu, 16 Jul 2026 21:53:21 -0700 Subject: [PATCH 16/23] Remove star-history --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index e017d54..bf90d6e 100644 --- a/README.md +++ b/README.md @@ -295,11 +295,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 - - From 538e5e91e20eb5147ad885b6256e3c8125aa3917 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Fri, 17 Jul 2026 12:34:45 -0700 Subject: [PATCH 17/23] Add getBool --- .prettierrc.json | 2 +- README.md | 12 +++++---- package-lock.json | 66 +++++++++++++++++++++++------------------------ package.json | 12 ++++----- src/index.ts | 47 +++++++++++++++++---------------- 5 files changed, 72 insertions(+), 67 deletions(-) diff --git a/.prettierrc.json b/.prettierrc.json index 577fddf..d7dd7f6 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/prettierrc", "semi": false, "singleQuote": true, - "printWidth": 90, + "printWidth": 94, "overrides": [ { "files": ["**/*.vue"], diff --git a/README.md b/README.md index bf90d6e..701595d 100644 --- a/README.md +++ b/README.md @@ -112,15 +112,17 @@ Environment Variables (can be placed in a `settings.env` file). | `INSTRUCTIONS_CHAT` | - | System Instructions for Chat | | `INSTRUCTIONS_COMPLETION` | - | System Instructions for Completion | | `INSTRUCTIONS_OBJECT` | - | System Instructions for Object | -| `DISABLE_CHAT` | - | Disable the `/chat` endpoint | -| `DISABLE_COMPLETION` | - | Disable the `/completion` endpoint | -| `DISABLE_OBJECT` | - | Disable the `/object` endpoint | -| `AI_SDK_LOG_WARNINGS` | - | Disable SDK Warnings | +| `DISABLE_CHAT`**¹** | - | Disable the `/chat` endpoint | +| `DISABLE_COMPLETION`**¹** | - | Disable the `/completion` endpoint | +| `DISABLE_OBJECT`**¹** | - | Disable the `/object` endpoint | +| `AI_SDK_LOG_WARNINGS`**¹** | - | Disable SDK Warnings | | `CORS_ORIGINS` | - | Allowed CORS Origins (supports \*) | | `PORT` | `3000` | Server Port | | `DEBUG` | - | Set to `app` for debug logs | -Note: The `INSTRUCTIONS` variable also points to the `INSTRUCTIONS_CHAT` variable (recommended). +> **¹** Boolean Variables. **True** values include: `['1', 't', 'true', 'y', 'yes', 'on']` + +The `INSTRUCTIONS` variable also points to the `INSTRUCTIONS_CHAT` variable (recommended). You must also set the API key for the `MODEL` you select. diff --git a/package-lock.json b/package-lock.json index 4f06126..987787f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,11 @@ "name": "chat-server", "version": "0.0.0", "dependencies": { - "@ai-sdk/anthropic": "^4.0.15", - "@ai-sdk/google": "^4.0.17", - "@ai-sdk/openai": "^4.0.15", - "@ai-sdk/openai-compatible": "^3.0.11", - "ai": "^7.0.30", + "@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", @@ -35,13 +35,13 @@ } }, "node_modules/@ai-sdk/anthropic": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-4.0.15.tgz", - "integrity": "sha512-6iVlzqZjqqoHTmgO1WU9id/33pYykIRasWJFAMf1+d+nhju6d7566VRFsOGZ2W2GzgNtzKs8DGsYN7X5/wOGuw==", + "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.3", - "@ai-sdk/provider-utils": "5.0.10" + "@ai-sdk/provider-utils": "5.0.11" }, "engines": { "node": ">=22" @@ -51,13 +51,13 @@ } }, "node_modules/@ai-sdk/gateway": { - "version": "4.0.22", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.22.tgz", - "integrity": "sha512-KdXJLa6O25fltJC/Lh8gnB1VaYMdX2mEKIalkohuItS+Ig523XxwK9e/Uuc66AvtrLIf7G51cDg3rDpP9mrxwQ==", + "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.3", - "@ai-sdk/provider-utils": "5.0.10", + "@ai-sdk/provider-utils": "5.0.11", "@vercel/oidc": "3.2.0" }, "engines": { @@ -68,13 +68,13 @@ } }, "node_modules/@ai-sdk/google": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-4.0.17.tgz", - "integrity": "sha512-90YMrWWIz7HVqrtthaP60fqGhsTT8OXHskxJ9sPmD/bFBHqdbOBrtS9oKBWjO0XBAY8eo2Rd7Ai8zGKCTvPn9Q==", + "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.3", - "@ai-sdk/provider-utils": "5.0.10" + "@ai-sdk/provider-utils": "5.0.11" }, "engines": { "node": ">=22" @@ -84,13 +84,13 @@ } }, "node_modules/@ai-sdk/openai": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-4.0.15.tgz", - "integrity": "sha512-JpTLQp5RUbRcs5nOyPEu5NRdxZLUnD/uCyT3qzy26D+iunCeL7KJV58ER9kwisAKnTjWravfNblaQNiWr20M9A==", + "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.3", - "@ai-sdk/provider-utils": "5.0.10" + "@ai-sdk/provider-utils": "5.0.11" }, "engines": { "node": ">=22" @@ -100,13 +100,13 @@ } }, "node_modules/@ai-sdk/openai-compatible": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-3.0.11.tgz", - "integrity": "sha512-PKLyt5PfjV2maWJPZpm3W29vCc+BbyqSPAcghXhl+iIxzFNjpGPa6UD/8J0b9l7w/lJCZKuhcef9q+Gp5WqUvw==", + "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.3", - "@ai-sdk/provider-utils": "5.0.10" + "@ai-sdk/provider-utils": "5.0.11" }, "engines": { "node": ">=22" @@ -128,9 +128,9 @@ } }, "node_modules/@ai-sdk/provider-utils": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.10.tgz", - "integrity": "sha512-uPyec0+85dwxZYXtb8qe8gCjhjDfxP4LCDo/uRQS/iG+FIgYbHPRhr/ys281udG90bTaE18+5cxWraYaf8oHCw==", + "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.3", @@ -1222,14 +1222,14 @@ } }, "node_modules/ai": { - "version": "7.0.30", - "resolved": "https://registry.npmjs.org/ai/-/ai-7.0.30.tgz", - "integrity": "sha512-tm0gTAHSWBdDfW4P2mj+LlglGCUQTSA9ktyS2PsPgVhxNO9gYWTSnRAehiJ3h1lYsJBp1Zgbz3RH2lezJXagiw==", + "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.22", + "@ai-sdk/gateway": "4.0.23", "@ai-sdk/provider": "4.0.3", - "@ai-sdk/provider-utils": "5.0.10" + "@ai-sdk/provider-utils": "5.0.11" }, "engines": { "node": ">=22" diff --git a/package.json b/package.json index 6f6ee19..5bb4550 100644 --- a/package.json +++ b/package.json @@ -9,18 +9,18 @@ "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.15", - "@ai-sdk/google": "^4.0.17", - "@ai-sdk/openai": "^4.0.15", - "@ai-sdk/openai-compatible": "^3.0.11", - "ai": "^7.0.30", + "@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", diff --git a/src/index.ts b/src/index.ts index 5d080d0..167f550 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,16 +23,16 @@ import { dotenv.config({ path: 'settings.env' }) -console.log(`DEBUG: ${process.env.DEBUG}`) +console.log('chat-server:', process.env.APP_VERSION) + +console.log('DEBUG:', process.env.DEBUG) createDebug.enable(process.env.DEBUG ?? '') const debug = createDebug('app') debug('debug enabled: app') -console.log(`chat-server: ${process.env.APP_VERSION}`) - -console.log('DISABLE_CHAT:', process.env.DISABLE_CHAT ? 'YES' : 'NO') -console.log('DISABLE_COMPLETION:', process.env.DISABLE_COMPLETION ? 'YES' : 'NO') -console.log('DISABLE_OBJECT:', process.env.DISABLE_OBJECT ? 'YES' : 'NO') +console.log('DISABLE_CHAT:', getBool(process.env.DISABLE_CHAT)) +console.log('DISABLE_COMPLETION:', getBool(process.env.DISABLE_COMPLETION)) +console.log('DISABLE_OBJECT:', getBool(process.env.DISABLE_OBJECT)) console.log('MODEL:', process.env.MODEL) console.log('ANTHROPIC_API_KEY:', process.env.ANTHROPIC_API_KEY ? 'SET' : undefined) @@ -41,12 +41,13 @@ 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()) @@ -57,15 +58,14 @@ console.log('corsOrigins:', corsOrigins) const maxOutputTokens = process.env.MAX_TOKENS ? Number.parseInt(process.env.MAX_TOKENS) : undefined -console.log(`maxOutputTokens: ${maxOutputTokens}`) -console.log( - `INSTRUCTIONS_CHAT: ${process.env.INSTRUCTIONS || process.env.INSTRUCTIONS_CHAT}`, // NOSONAR -) -console.log(`INSTRUCTIONS_COMPLETION: ${process.env.INSTRUCTIONS_COMPLETION}`) -console.log(`INSTRUCTIONS_OBJECT: ${process.env.INSTRUCTIONS_OBJECT}`) +console.log('maxOutputTokens:', maxOutputTokens) + +console.log('INSTRUCTIONS_CHAT:', process.env.INSTRUCTIONS || process.env.INSTRUCTIONS_CHAT) // NOSONAR +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) @@ -74,14 +74,12 @@ 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)) -if (!process.env.DISABLE_CHAT) { +if (!getBool(process.env.DISABLE_CHAT)) { app.post(['/', '/chat'], async (req: Request, res: Response) => { // debug('req.headers:', req.headers) // debug('authorization:', req.headers.authorization) @@ -108,7 +106,7 @@ if (!process.env.DISABLE_CHAT) { }) } -if (!process.env.DISABLE_COMPLETION) { +if (!getBool(process.env.DISABLE_COMPLETION)) { app.post('/completion', async (req: Request, res: Response) => { const { prompt, system } = req.body debug('prompt:', prompt?.length) @@ -135,7 +133,7 @@ if (!process.env.DISABLE_COMPLETION) { }) } -if (!process.env.DISABLE_OBJECT) { +if (!getBool(process.env.DISABLE_OBJECT)) { app.post('/object', async (req: Request, res: Response) => { const { output, prompt, system } = req.body debug('output:', output?.length) @@ -158,6 +156,11 @@ if (!process.env.DISABLE_OBJECT) { }) } +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, @@ -196,8 +199,8 @@ 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) } } From 21ae90fee940179487c8d8fe4deb6718aced4a71 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:10:23 -0700 Subject: [PATCH 18/23] Update README.md --- README.md | 53 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 701595d..5301149 100644 --- a/README.md +++ b/README.md @@ -103,27 +103,25 @@ For a Portainer Deploy workflow see the [.github/workflows/deploy.yaml](https:// 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 | -| [PROVIDER_OPTIONS](#PROVIDER_OPTIONS) | - | Provider Options JSON String | -| `MAX_TOKENS` | - | Max Output Tokens | -| `INSTRUCTIONS_CHAT` | - | System Instructions for Chat | -| `INSTRUCTIONS_COMPLETION` | - | System Instructions for Completion | -| `INSTRUCTIONS_OBJECT` | - | System Instructions for Object | -| `DISABLE_CHAT`**¹** | - | Disable the `/chat` endpoint | -| `DISABLE_COMPLETION`**¹** | - | Disable the `/completion` endpoint | -| `DISABLE_OBJECT`**¹** | - | Disable the `/object` endpoint | -| `AI_SDK_LOG_WARNINGS`**¹** | - | Disable SDK Warnings | -| `CORS_ORIGINS` | - | Allowed CORS Origins (supports \*) | -| `PORT` | `3000` | Server Port | -| `DEBUG` | - | Set to `app` for debug logs | +| 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_CHAT](#INSTRUCTIONS) | - | System Instructions for Chat | +| [INSTRUCTIONS_COMPLETION](#INSTRUCTIONS) | - | System Instructions for Completion | +| [INSTRUCTIONS_OBJECT](#INSTRUCTIONS) | - | System Instructions for Object | +| `DISABLE_CHAT`**¹** | - | Disable the `/chat` Endpoint | +| `DISABLE_COMPLETION`**¹** | - | Disable the `/completion` Endpoint | +| `DISABLE_OBJECT`**¹** | - | Disable the `/object` Endpoint | +| `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']` -The `INSTRUCTIONS` variable also points to the `INSTRUCTIONS_CHAT` variable (recommended). - You must also set the API key for the `MODEL` you select. | Variable | Description | @@ -135,6 +133,13 @@ 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). + #### PROVIDER_OPTIONS Provider Options: @@ -165,14 +170,12 @@ The value is only checked for valid JSON at startup and will fail at runtime if | `/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 also points to the `/chat` endpoint (recommended). +Note: The `/` endpoint (legacy) also points to the `/chat` endpoint (recommended). #### chat Reference: -To send System Instructions from the client, add them to the body. - ```typescript import { useChat } from '@ai-sdk/vue' import { DefaultChatTransport } from 'ai' @@ -186,6 +189,8 @@ const { messages, sendMessage, status, stop } = useChat({ }) ``` +To send System Instructions from the client, add them to the body. + #### completion Reference: @@ -202,6 +207,8 @@ const { completion, complete, isLoading, stop } = useCompletion({ await complete('Explain how to set up cssnr/chat-server') ``` +To send System Instructions from the client, add them to the body. + #### object Reference: @@ -226,7 +233,9 @@ submit({ }) ``` -Note: Both `system` and `output` are custom body parameters parsed by the server allowing the client to send these items. +To send System Instructions and Output Schema from the client, add them to the body. + +Note: Both `system` and `output` are custom body parameters parsed by the server. ### VitePress Chat Plugin From 64a9ae0595588cf385e679b83e792457dc1299aa Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Fri, 17 Jul 2026 16:20:30 -0700 Subject: [PATCH 19/23] Add DISABLE_CLIENT_INSTRUCTIONS --- README.md | 5 ++++- src/index.ts | 12 ++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5301149..09071aa 100644 --- a/README.md +++ b/README.md @@ -107,14 +107,15 @@ Environment Variables (can be placed in a `settings.env` file). | :--------------------------------------- | :--------------------------- | :---------------------------------- | | `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 | +| [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_CHAT`**¹** | - | Disable the `/chat` Endpoint | | `DISABLE_COMPLETION`**¹** | - | Disable the `/completion` Endpoint | | `DISABLE_OBJECT`**¹** | - | Disable the `/object` Endpoint | +| `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 | @@ -140,6 +141,8 @@ These are used as fallback when those instructions are not sent for configuratio 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: diff --git a/src/index.ts b/src/index.ts index 167f550..0ddbfcb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -60,7 +60,11 @@ const maxOutputTokens = process.env.MAX_TOKENS : undefined console.log('maxOutputTokens:', maxOutputTokens) -console.log('INSTRUCTIONS_CHAT:', process.env.INSTRUCTIONS || process.env.INSTRUCTIONS_CHAT) // NOSONAR +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) @@ -93,7 +97,7 @@ if (!getBool(process.env.DISABLE_CHAT)) { const result = streamText({ model: model, messages: modelMessages, - system: system || process.env.INSTRUCTIONS || process.env.INSTRUCTIONS_CHAT, + system: (!disableInstructions && system) || process.env.INSTRUCTIONS_CHAT, maxOutputTokens, providerOptions, onError: onStreamError, @@ -114,7 +118,7 @@ if (!getBool(process.env.DISABLE_COMPLETION)) { const result = streamText({ model: model, prompt, - system: system || process.env.INSTRUCTIONS_COMPLETION, + system: (!disableInstructions && system) || process.env.INSTRUCTIONS_COMPLETION, maxOutputTokens, providerOptions, onError: onStreamError, @@ -142,7 +146,7 @@ if (!getBool(process.env.DISABLE_OBJECT)) { const result = streamText({ model: model, prompt, - system: system || process.env.INSTRUCTIONS_OBJECT, + system: (!disableInstructions && system) || process.env.INSTRUCTIONS_OBJECT, maxOutputTokens, providerOptions, output: output ? Output.object({ schema: jsonSchema(output) }) : Output.json(), From 295fc7c7de56bb67cfd76c93b2d34c99e1e4fd41 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Fri, 17 Jul 2026 19:13:13 -0700 Subject: [PATCH 20/23] Remove Disable Options --- README.md | 3 -- src/index.ts | 144 ++++++++++++++++++++++++--------------------------- 2 files changed, 67 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 09071aa..1273187 100644 --- a/README.md +++ b/README.md @@ -112,9 +112,6 @@ Environment Variables (can be placed in a `settings.env` file). | [INSTRUCTIONS_CHAT](#INSTRUCTIONS) | - | System Instructions for Chat | | [INSTRUCTIONS_COMPLETION](#INSTRUCTIONS) | - | System Instructions for Completion | | [INSTRUCTIONS_OBJECT](#INSTRUCTIONS) | - | System Instructions for Object | -| `DISABLE_CHAT`**¹** | - | Disable the `/chat` Endpoint | -| `DISABLE_COMPLETION`**¹** | - | Disable the `/completion` Endpoint | -| `DISABLE_OBJECT`**¹** | - | Disable the `/object` Endpoint | | `DISABLE_CLIENT_INSTRUCTIONS`**¹** | - | Ignore Client System Instructions | | `AI_SDK_LOG_WARNINGS`**¹** | - | Enable SDK Warnings Logging | | `CORS_ORIGINS` | - | Allowed CORS Origins (supports \*) | diff --git a/src/index.ts b/src/index.ts index 0ddbfcb..39f328b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,10 +30,6 @@ createDebug.enable(process.env.DEBUG ?? '') const debug = createDebug('app') debug('debug enabled: app') -console.log('DISABLE_CHAT:', getBool(process.env.DISABLE_CHAT)) -console.log('DISABLE_COMPLETION:', getBool(process.env.DISABLE_COMPLETION)) -console.log('DISABLE_OBJECT:', getBool(process.env.DISABLE_OBJECT)) - 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) @@ -83,82 +79,76 @@ app.listen(port, () => console.log(`Listening on PORT: ${port}`)) // app.get('/app-health-check', (_req, res) => res.sendStatus(200)) -if (!getBool(process.env.DISABLE_CHAT)) { - app.post(['/', '/chat'], async (req: Request, res: Response) => { - // debug('req.headers:', req.headers) - // debug('authorization:', req.headers.authorization) - const { messages, system } = req.body - debug('system:', system?.length) - // debug('system:', system?.substring(0, 128)) - const modelMessages = await convertToModelMessages(messages) - debug('modelMessages:', modelMessages.length) - const stream = createUIMessageStream({ - execute: ({ writer }) => { - const result = streamText({ - model: model, - messages: modelMessages, - system: (!disableInstructions && system) || process.env.INSTRUCTIONS_CHAT, - maxOutputTokens, - providerOptions, - onError: onStreamError, - onEnd: onStreamEnd, - }) - writer.merge(toUIMessageStream({ stream: result.stream })) - }, - }) - pipeUIMessageStreamToResponse({ response: res, stream }) +app.post(['/', '/chat'], async (req: Request, res: Response) => { + // debug('req.headers:', req.headers) + // debug('authorization:', req.headers.authorization) + const { messages, system } = req.body + debug('system:', system?.length) + // debug('system:', system?.substring(0, 128)) + const modelMessages = await convertToModelMessages(messages) + debug('modelMessages:', modelMessages.length) + const stream = createUIMessageStream({ + execute: ({ writer }) => { + const result = streamText({ + model: model, + messages: modelMessages, + system: (!disableInstructions && system) || process.env.INSTRUCTIONS_CHAT, + maxOutputTokens, + providerOptions, + onError: onStreamError, + onEnd: onStreamEnd, + }) + writer.merge(toUIMessageStream({ stream: result.stream })) + }, }) -} - -if (!getBool(process.env.DISABLE_COMPLETION)) { - app.post('/completion', async (req: Request, res: Response) => { - const { prompt, system } = req.body - debug('prompt:', prompt?.length) - debug('system:', system?.length) - const result = streamText({ - model: model, - prompt, - system: (!disableInstructions && 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, - }) + pipeUIMessageStreamToResponse({ response: res, stream }) +}) + +app.post('/completion', async (req: Request, res: Response) => { + const { prompt, system } = req.body + debug('prompt:', prompt?.length) + debug('system:', system?.length) + const result = streamText({ + model: model, + prompt, + system: (!disableInstructions && system) || process.env.INSTRUCTIONS_COMPLETION, + maxOutputTokens, + providerOptions, + onError: onStreamError, + onEnd: onStreamEnd, }) -} - -if (!getBool(process.env.DISABLE_OBJECT)) { - app.post('/object', async (req: Request, res: Response) => { - const { output, prompt, system } = req.body - debug('output:', output?.length) - debug('prompt:', prompt?.length) - debug('system:', system?.length) - const result = streamText({ - model: model, - prompt, - system: (!disableInstructions && 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 }), - }) + 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 { output, prompt, system } = req.body + debug('output:', output?.length) + debug('prompt:', prompt?.length) + debug('system:', system?.length) + const result = streamText({ + model: model, + prompt, + system: (!disableInstructions && 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 From 512527c954d6de4ff6d952e67f79d1b5ea4009d1 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Fri, 17 Jul 2026 21:40:41 -0700 Subject: [PATCH 21/23] Update system to instructions --- src/index.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index 39f328b..ea26d82 100644 --- a/src/index.ts +++ b/src/index.ts @@ -92,7 +92,7 @@ app.post(['/', '/chat'], async (req: Request, res: Response) => { const result = streamText({ model: model, messages: modelMessages, - system: (!disableInstructions && system) || process.env.INSTRUCTIONS_CHAT, + instructions: (!disableInstructions && system) || process.env.INSTRUCTIONS_CHAT, maxOutputTokens, providerOptions, onError: onStreamError, @@ -111,7 +111,7 @@ app.post('/completion', async (req: Request, res: Response) => { const result = streamText({ model: model, prompt, - system: (!disableInstructions && system) || process.env.INSTRUCTIONS_COMPLETION, + instructions: (!disableInstructions && system) || process.env.INSTRUCTIONS_COMPLETION, maxOutputTokens, providerOptions, onError: onStreamError, @@ -137,7 +137,7 @@ app.post('/object', async (req: Request, res: Response) => { const result = streamText({ model: model, prompt, - system: (!disableInstructions && system) || process.env.INSTRUCTIONS_OBJECT, + instructions: (!disableInstructions && system) || process.env.INSTRUCTIONS_OBJECT, maxOutputTokens, providerOptions, output: output ? Output.object({ schema: jsonSchema(output) }) : Output.json(), @@ -198,11 +198,11 @@ function getProviderOptions() { } } -function onStreamError(error: unknown) { +async function onStreamError({ error }: { error: unknown }) { console.error('error:', error) } -function onStreamEnd({ finalStep, finishReason, text, usage }: GenerateTextEndEvent) { +async function onStreamEnd({ finalStep, finishReason, text, usage }: GenerateTextEndEvent) { debug('reasoning:', finalStep.reasoningText) debug('response:', text) debug('usage:', usage) From e4c38ab11b53d6ef02c33c39f8e288257cc8f00d Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Fri, 17 Jul 2026 23:14:15 -0700 Subject: [PATCH 22/23] Update Client system to instructions --- .prettierrc.json | 2 +- README.md | 8 ++++---- src/index.ts | 23 +++++++++++++---------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/.prettierrc.json b/.prettierrc.json index d7dd7f6..a1ceab5 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/prettierrc", "semi": false, "singleQuote": true, - "printWidth": 94, + "printWidth": 96, "overrides": [ { "files": ["**/*.vue"], diff --git a/README.md b/README.md index 1273187..ffcdc48 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ const { messages, sendMessage, status, stop } = useChat({ transport: new DefaultChatTransport({ 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.' }, }), }) ``` @@ -201,7 +201,7 @@ import { useCompletion } from '@ai-sdk/vue' const { completion, complete, isLoading, stop } = useCompletion({ api: 'https://chat-server.cssnr.com/completion', headers: { Authorization: 'Basic Abc123=' }, - body: { system: 'You are a helpful assistant.' }, + body: { instructions: 'You are a helpful assistant.' }, }) await complete('Explain how to set up cssnr/chat-server') @@ -227,7 +227,7 @@ const { object, submit } = useObject({ }) submit({ - system: 'You are a helpful assistant.', + instructions: 'You are a helpful assistant.', prompt: 'Extract the name and age from: John is 30 years old.', output: zodToJsonSchema(schema), }) @@ -235,7 +235,7 @@ submit({ To send System Instructions and Output Schema from the client, add them to the body. -Note: Both `system` and `output` are custom body parameters parsed by the server. +Note: Both `instructions` and `output` are custom body parameters parsed by the server. ### VitePress Chat Plugin diff --git a/src/index.ts b/src/index.ts index ea26d82..3361912 100644 --- a/src/index.ts +++ b/src/index.ts @@ -82,9 +82,9 @@ app.listen(port, () => console.log(`Listening on PORT: ${port}`)) app.post(['/', '/chat'], async (req: Request, res: Response) => { // debug('req.headers:', req.headers) // debug('authorization:', req.headers.authorization) - const { messages, system } = req.body - debug('system:', system?.length) - // debug('system:', system?.substring(0, 128)) + const { instructions, messages, system } = req.body + debug('instructions:', (instructions || system)?.length) + // debug('instructions:', (instructions || system)?.substring(0, 128)) const modelMessages = await convertToModelMessages(messages) debug('modelMessages:', modelMessages.length) const stream = createUIMessageStream({ @@ -92,7 +92,8 @@ app.post(['/', '/chat'], async (req: Request, res: Response) => { const result = streamText({ model: model, messages: modelMessages, - instructions: (!disableInstructions && system) || process.env.INSTRUCTIONS_CHAT, + instructions: + (!disableInstructions && (instructions || system)) || process.env.INSTRUCTIONS_CHAT, maxOutputTokens, providerOptions, onError: onStreamError, @@ -105,13 +106,14 @@ app.post(['/', '/chat'], async (req: Request, res: Response) => { }) app.post('/completion', async (req: Request, res: Response) => { - const { prompt, system } = req.body + const { instructions, prompt, system } = req.body + debug('instructions:', (instructions || system)?.length) debug('prompt:', prompt?.length) - debug('system:', system?.length) const result = streamText({ model: model, prompt, - instructions: (!disableInstructions && system) || process.env.INSTRUCTIONS_COMPLETION, + instructions: + (!disableInstructions && (instructions || system)) || process.env.INSTRUCTIONS_COMPLETION, maxOutputTokens, providerOptions, onError: onStreamError, @@ -130,14 +132,15 @@ app.post('/completion', async (req: Request, res: Response) => { }) app.post('/object', async (req: Request, res: Response) => { - const { output, prompt, system } = req.body + const { instructions, output, prompt, system } = req.body + debug('instructions:', (instructions || system)?.length) debug('output:', output?.length) debug('prompt:', prompt?.length) - debug('system:', system?.length) const result = streamText({ model: model, prompt, - instructions: (!disableInstructions && system) || process.env.INSTRUCTIONS_OBJECT, + instructions: + (!disableInstructions && (instructions || system)) || process.env.INSTRUCTIONS_OBJECT, maxOutputTokens, providerOptions, output: output ? Output.object({ schema: jsonSchema(output) }) : Output.json(), From e6775d190f4a9988e4efe5f36cf047c495ff29ee Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sat, 18 Jul 2026 08:22:25 -0700 Subject: [PATCH 23/23] Update AGENTS.md --- AGENTS.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c861aaf..6227489 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,10 @@ # Agent Guide - [index.ts](src/index.ts) — Single source Express.js AI-SDK server +- [README.md](README.md) — Server documentation + +- [AI SDK](https://ai-sdk.dev/docs/reference) +- [Express.js](https://expressjs.com/en/5x/api/) ## Commands @@ -13,10 +17,14 @@ ALWAYS use the `npm run *` command | `npm run tsc` | TypeScript Check Only `tsc --noEmit` | | `npm run prettier` | ALWAYS RUN AFTER EDITING FILES | -## Endpoints +## 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) | -| Server Endpoint | Client | -| :-------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- | -| [/chat](https://ai-sdk.dev/docs/reference/ai-sdk-ui/create-ui-message-stream) | [useChat](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat) | -| [/completion](https://ai-sdk.dev/docs/reference/ai-sdk-ui/pipe-ui-message-stream-to-response) | [useCompletion](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-completion) | -| [/object](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text) | [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)