From 5a279cb77438694e512ffb594e8955539c808e6e Mon Sep 17 00:00:00 2001 From: Fabio Carvano Date: Sat, 17 Oct 2026 17:20:10 -0300 Subject: [PATCH 1/2] fix: openai-compatible provider calls /responses, breaking self-hosted chat backends The OpenAICompatibleProvider called the AI SDK client as a bare function (this.client(model)), which routes to the Responses API (/v1/responses). Any self-hosted OpenAI-compatible backend that only implements /v1/chat/completions (Ollama, vLLM, LiteLLM, etc.) 404s on every chat/streaming request. The failure surfaces to end users only as a generic "An error occurred.", with the real cause (unsupported path /v1/responses) visible only in server logs. Switched to this.client.chat(model), which targets /v1/chat/completions as intended. Embeddings were unaffected (already used textEmbeddingModel() correctly). Also add an exports map to the @chatbot/ai package.json generated in apps/workers/Dockerfile, matching the pattern already used for @chatbot/shared and @chatbot/agent-studio in the same file. Without it, subpath imports such as @chatbot/ai/tools/built-in-registry fail to resolve at runtime inside the workers container. This silently broke Claw's web-search, web-fetch and URL-guard tools and caused the claw-scheduler sweep to fail every 30 seconds in any Docker-deployed workers container. Both issues were confirmed by running the full stack in Docker end-to-end (agent create/publish/inference against a real OpenAI-compatible backend), not just by code review. --- apps/workers/Dockerfile | 2 +- libs/ai/src/providers/openai-compatible.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/workers/Dockerfile b/apps/workers/Dockerfile index 52c22f45..90126b6c 100644 --- a/apps/workers/Dockerfile +++ b/apps/workers/Dockerfile @@ -51,7 +51,7 @@ COPY libs/ai/ libs/ai/ RUN cd libs/ai && bunx tsc --project tsconfig.lib.json && \ mkdir -p /app/node_modules/@chatbot/ai && \ cp -rL /app/dist/libs/ai/libs/ai/src /app/node_modules/@chatbot/ai/src && \ - printf '{"name":"@chatbot/ai","version":"0.0.1","type":"module","main":"src/index.js","types":"src/index.d.ts"}\n' > /app/node_modules/@chatbot/ai/package.json + printf '{"name":"@chatbot/ai","version":"0.0.1","type":"module","main":"src/index.js","types":"src/index.d.ts","exports":{".":"./src/index.js","./*":"./src/*.js"}}\n' > /app/node_modules/@chatbot/ai/package.json COPY libs/knowledge-base/ libs/knowledge-base/ RUN cd libs/knowledge-base && bunx tsc --project tsconfig.lib.json && \ diff --git a/libs/ai/src/providers/openai-compatible.ts b/libs/ai/src/providers/openai-compatible.ts index 67a29fe9..fb5fe5d2 100644 --- a/libs/ai/src/providers/openai-compatible.ts +++ b/libs/ai/src/providers/openai-compatible.ts @@ -41,7 +41,7 @@ export class OpenAICompatibleProvider implements LLMProvider { onFinish, } = options; return streamText({ - model: this.client(model ?? this.chatModel), + model: this.client.chat(model ?? this.chatModel), messages, system, temperature, From c466248f541749dae7c5909541ed9e557670edd2 Mon Sep 17 00:00:00 2001 From: Fabio Carvano Date: Sat, 17 Oct 2026 18:32:07 -0300 Subject: [PATCH 2/2] fix: update openai-compatible mock to match the .chat() routing fix streamChat now calls this.client.chat(model) instead of the bare this.client(model) call (see the previous commit on this branch), but this test's mock only exposed the provider as a bare callable, so every streamChat assertion broke with 'this.client.chat is not a function'. Add provider.chat alongside the existing bare callable and textEmbeddingModel, matching the real @ai-sdk/openai client shape. Caught by actually running bun run test for the ai project end to end, which the original fix wasn't validated against. --- libs/ai/src/providers/openai-compatible.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libs/ai/src/providers/openai-compatible.test.ts b/libs/ai/src/providers/openai-compatible.test.ts index 043deb77..bdae5bd9 100644 --- a/libs/ai/src/providers/openai-compatible.test.ts +++ b/libs/ai/src/providers/openai-compatible.test.ts @@ -3,6 +3,12 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; const { mockCreateOpenAI, mockStreamText } = vi.hoisted(() => ({ mockCreateOpenAI: vi.fn(() => { const provider = (model: string) => ({ modelId: model, provider: 'openai-compatible' }); + // streamChat calls `this.client.chat(model)`, not the bare callable — the + // bare call routes to OpenAIResponsesModelId/`/v1/responses`, which most + // OpenAI-compatible gateways (Ollama, vLLM, LiteLLM, ...) never implement. + // `.chat()` routes to OpenAIChatModelId/`/v1/chat/completions`, which they + // all support. See openai-compatible.ts's streamChat for the full story. + provider.chat = (model: string) => ({ modelId: model, provider: 'openai-compatible' }); provider.textEmbeddingModel = (model: string) => ({ modelId: model, type: 'embedding' }); return provider; }),