From d069d7070f83b3b196992df4b573f74fa24a8868 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Thu, 4 Jun 2026 03:12:21 +0530 Subject: [PATCH 1/3] fix(chat): thread UI-selected model into the chat workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chat UI sends the selected model in the POST /api/chat/workflow body, but the schema stripped it and the handler only read the chat's persisted model_id (null for new chats) — so every request billed the default model. Accept `model` in the schema and prefer validated.model ?? chat.model_id ?? DEFAULT_MODEL_ID. Co-Authored-By: Claude Opus 4.8 --- .../handleChatWorkflowStream.test.ts | 40 +++++++++++++++++++ .../__tests__/validateChatWorkflow.test.ts | 16 ++++++++ lib/chat/handleChatWorkflowStream.ts | 6 ++- lib/chat/validateChatWorkflow.ts | 7 ++++ 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/lib/chat/__tests__/handleChatWorkflowStream.test.ts b/lib/chat/__tests__/handleChatWorkflowStream.test.ts index 1af062c8b..407eb0aef 100644 --- a/lib/chat/__tests__/handleChatWorkflowStream.test.ts +++ b/lib/chat/__tests__/handleChatWorkflowStream.test.ts @@ -289,6 +289,46 @@ describe("handleChatWorkflowStream", () => { expect(startArgs.modelId).toBe("anthropic/claude-haiku-4.5"); }); + it("passes the UI-selected validated.model into the workflow", async () => { + vi.mocked(validateChatWorkflow).mockResolvedValue({ + messages: [], + chatId: CHAT_ID, + sessionId: SESSION_ID, + accountId: ACCOUNT_ID, + orgId: null, + authToken: "test-key", + model: "openai/gpt-5.4-mini", + }); + mockStartedRun(); + await handleChatWorkflowStream(makeRequest()); + const startArgs = vi.mocked(start).mock.calls[0]?.[1]?.[0] as { modelId: string }; + expect(startArgs.modelId).toBe("openai/gpt-5.4-mini"); + }); + + it("prefers validated.model over a persisted chat.model_id", async () => { + vi.mocked(validateChatWorkflow).mockResolvedValue({ + messages: [], + chatId: CHAT_ID, + sessionId: SESSION_ID, + accountId: ACCOUNT_ID, + orgId: null, + authToken: "test-key", + model: "openai/gpt-5.4-mini", + }); + vi.mocked(selectChats).mockResolvedValue([ + { + id: CHAT_ID, + session_id: SESSION_ID, + active_stream_id: null, + model_id: "anthropic/claude-opus-4.6", + } as never, + ]); + mockStartedRun(); + await handleChatWorkflowStream(makeRequest()); + const startArgs = vi.mocked(start).mock.calls[0]?.[1]?.[0] as { modelId: string }; + expect(startArgs.modelId).toBe("openai/gpt-5.4-mini"); + }); + // Bundle A.4 — forward the Privy JWT from the validated body into // AgentContext.recoupAccessToken so the sandbox env-builder can // surface it as `RECOUP_ACCESS_TOKEN`. diff --git a/lib/chat/__tests__/validateChatWorkflow.test.ts b/lib/chat/__tests__/validateChatWorkflow.test.ts index 25dd74d20..88e48b9f5 100644 --- a/lib/chat/__tests__/validateChatWorkflow.test.ts +++ b/lib/chat/__tests__/validateChatWorkflow.test.ts @@ -78,6 +78,22 @@ describe("validateChatWorkflow", () => { if (result instanceof NextResponse) return; expect(result.recoupAccessToken).toBe("eyJ.test.jwt"); }); + + it("accepts and surfaces the gateway-format model selected in the UI", async () => { + const result = await validateChatWorkflow( + makeRequest({ ...validBody, model: "openai/gpt-5.4-mini" }), + ); + expect(result).not.toBeInstanceOf(NextResponse); + if (result instanceof NextResponse) return; + expect(result.model).toBe("openai/gpt-5.4-mini"); + }); + + it("leaves model undefined when the body omits it", async () => { + const result = await validateChatWorkflow(makeRequest()); + expect(result).not.toBeInstanceOf(NextResponse); + if (result instanceof NextResponse) return; + expect(result.model).toBeUndefined(); + }); }); describe("invalid body", () => { diff --git a/lib/chat/handleChatWorkflowStream.ts b/lib/chat/handleChatWorkflowStream.ts index 0e7af2f3e..4c86e6287 100644 --- a/lib/chat/handleChatWorkflowStream.ts +++ b/lib/chat/handleChatWorkflowStream.ts @@ -90,7 +90,11 @@ export async function handleChatWorkflowStream(request: NextRequest): Promise; From ea0da8267abaeea904de68f98854ff8ba8d26172 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Thu, 4 Jun 2026 05:12:07 +0530 Subject: [PATCH 2/3] test(chat): trim workflow model tests to one case each Co-Authored-By: Claude Opus 4.8 --- .../__tests__/handleChatWorkflowStream.test.ts | 16 ---------------- lib/chat/__tests__/validateChatWorkflow.test.ts | 7 ------- 2 files changed, 23 deletions(-) diff --git a/lib/chat/__tests__/handleChatWorkflowStream.test.ts b/lib/chat/__tests__/handleChatWorkflowStream.test.ts index 407eb0aef..9e8b396c8 100644 --- a/lib/chat/__tests__/handleChatWorkflowStream.test.ts +++ b/lib/chat/__tests__/handleChatWorkflowStream.test.ts @@ -289,22 +289,6 @@ describe("handleChatWorkflowStream", () => { expect(startArgs.modelId).toBe("anthropic/claude-haiku-4.5"); }); - it("passes the UI-selected validated.model into the workflow", async () => { - vi.mocked(validateChatWorkflow).mockResolvedValue({ - messages: [], - chatId: CHAT_ID, - sessionId: SESSION_ID, - accountId: ACCOUNT_ID, - orgId: null, - authToken: "test-key", - model: "openai/gpt-5.4-mini", - }); - mockStartedRun(); - await handleChatWorkflowStream(makeRequest()); - const startArgs = vi.mocked(start).mock.calls[0]?.[1]?.[0] as { modelId: string }; - expect(startArgs.modelId).toBe("openai/gpt-5.4-mini"); - }); - it("prefers validated.model over a persisted chat.model_id", async () => { vi.mocked(validateChatWorkflow).mockResolvedValue({ messages: [], diff --git a/lib/chat/__tests__/validateChatWorkflow.test.ts b/lib/chat/__tests__/validateChatWorkflow.test.ts index 88e48b9f5..540e7cc83 100644 --- a/lib/chat/__tests__/validateChatWorkflow.test.ts +++ b/lib/chat/__tests__/validateChatWorkflow.test.ts @@ -87,13 +87,6 @@ describe("validateChatWorkflow", () => { if (result instanceof NextResponse) return; expect(result.model).toBe("openai/gpt-5.4-mini"); }); - - it("leaves model undefined when the body omits it", async () => { - const result = await validateChatWorkflow(makeRequest()); - expect(result).not.toBeInstanceOf(NextResponse); - if (result instanceof NextResponse) return; - expect(result.model).toBeUndefined(); - }); }); describe("invalid body", () => { From d5e63a059f913fd92136c7cb084344c6bbfa9f15 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Thu, 4 Jun 2026 05:12:58 +0530 Subject: [PATCH 3/3] docs(chat): trim model-selection comments to one-liners Co-Authored-By: Claude Opus 4.8 --- lib/chat/handleChatWorkflowStream.ts | 5 +---- lib/chat/validateChatWorkflow.ts | 7 +------ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/chat/handleChatWorkflowStream.ts b/lib/chat/handleChatWorkflowStream.ts index 4c86e6287..377b29780 100644 --- a/lib/chat/handleChatWorkflowStream.ts +++ b/lib/chat/handleChatWorkflowStream.ts @@ -90,10 +90,7 @@ export async function handleChatWorkflowStream(request: NextRequest): Promise