From 8dd9e31e5f7a2e2fa056e2bdbcd469eb7753bbe9 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Wed, 12 Aug 2026 17:19:56 -0500 Subject: [PATCH 1/4] fix(chat): write the resolved model to chats.model_id at provision time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Headless runs (POST /api/chat/runs) resolved the model correctly and threaded it into the workflow, but the chat insert dropped it one call before the row was written — the chats.model_id column default then fabricated 'anthropic/claude-haiku-4.5' for every headless run (recoupable/chat#1956). - createSessionWithInitialChat now requires modelId and writes it on the chat insert; provisionRunSession threads it; handleStartChatRun passes the validated modelId. - Interactive writers (createSessionHandler, createSessionChatHandler) set the default explicitly, so no writer relies on the column default and the database default can be dropped (recoupable/database follow-up). - Single source for the default: DEFAULT_CHAT_MODEL_ID in lib/const; DEFAULT_RUN_MODEL_ID and handleChatWorkflowStream's local constant now alias it instead of repeating the string. Co-Authored-By: Claude Fable 5 --- lib/chat/handleChatWorkflowStream.ts | 3 ++- .../runs/__tests__/handleStartChatRun.test.ts | 7 ++++++- .../runs/__tests__/provisionRunSession.test.ts | 14 ++++++++++++-- lib/chat/runs/handleStartChatRun.ts | 1 + lib/chat/runs/provisionRunSession.ts | 4 ++++ lib/chat/runs/validateChatRunRequest.ts | 5 +++-- lib/const.ts | 7 +++++++ .../createSessionHandler.persistence.test.ts | 3 +++ .../createSessionWithInitialChat.test.ts | 17 ++++++++++++++++- .../__tests__/createSessionChatHandler.test.ts | 3 +++ lib/sessions/chats/createSessionChatHandler.ts | 2 ++ lib/sessions/createSessionHandler.ts | 2 ++ lib/sessions/createSessionWithInitialChat.ts | 10 +++++++++- 13 files changed, 70 insertions(+), 8 deletions(-) diff --git a/lib/chat/handleChatWorkflowStream.ts b/lib/chat/handleChatWorkflowStream.ts index 568e546bc..bb49b7615 100644 --- a/lib/chat/handleChatWorkflowStream.ts +++ b/lib/chat/handleChatWorkflowStream.ts @@ -1,3 +1,4 @@ +import { DEFAULT_CHAT_MODEL_ID } from "@/lib/const"; import { NextRequest, NextResponse } from "next/server"; import { createUIMessageStreamResponse, type UIMessageChunk } from "ai"; import { start, getRun } from "workflow/api"; @@ -22,7 +23,7 @@ import { discoverSkills } from "@/lib/skills/discoverSkills"; import { getSandboxSkillDirectories } from "@/lib/skills/getSandboxSkillDirectories"; import generateUUID from "@/lib/uuid/generateUUID"; -const DEFAULT_MODEL_ID = "anthropic/claude-haiku-4.5"; +const DEFAULT_MODEL_ID = DEFAULT_CHAT_MODEL_ID; /** * Handles POST /api/chat/workflow. diff --git a/lib/chat/runs/__tests__/handleStartChatRun.test.ts b/lib/chat/runs/__tests__/handleStartChatRun.test.ts index 999b12131..d8dcc3b5b 100644 --- a/lib/chat/runs/__tests__/handleStartChatRun.test.ts +++ b/lib/chat/runs/__tests__/handleStartChatRun.test.ts @@ -86,7 +86,12 @@ describe("handleStartChatRun", () => { }); expect(provisionRunSession).toHaveBeenCalledWith( - expect.objectContaining({ accountId: "acc-1", title: "Scheduled generation" }), + expect.objectContaining({ + accountId: "acc-1", + title: "Scheduled generation", + // Provenance (chat#1956): the resolved model reaches the chat insert. + modelId: "anthropic/claude-haiku-4.5", + }), ); // the minted key is injected as recoupAccessToken AND threaded as ephemeralKeyId expect(buildRunAgentInput).toHaveBeenCalledWith( diff --git a/lib/chat/runs/__tests__/provisionRunSession.test.ts b/lib/chat/runs/__tests__/provisionRunSession.test.ts index 3dd3a13b3..3a9d2343d 100644 --- a/lib/chat/runs/__tests__/provisionRunSession.test.ts +++ b/lib/chat/runs/__tests__/provisionRunSession.test.ts @@ -56,7 +56,7 @@ describe("provisionRunSession", () => { }); it("installs global skills into the sandbox before discovering them", async () => { - await provisionRunSession({ accountId: "account-1", title: "t" }); + await provisionRunSession({ accountId: "account-1", title: "t", modelId: "test/model-x" }); // Headless runs must PROVISION skills, not just discover them (chat#1822). expect(installSessionGlobalSkills).toHaveBeenCalledWith({ @@ -69,10 +69,20 @@ describe("provisionRunSession", () => { expect(installOrder).toBeLessThan(discoverOrder); }); + // Provenance thread-through (chat#1956): the resolved model must reach the + // chat insert, not stop at the workflow input. + it("forwards modelId to createSessionWithInitialChat", async () => { + await provisionRunSession({ accountId: "account-1", title: "t", modelId: "test/model-x" }); + + expect(createSessionWithInitialChat).toHaveBeenCalledWith( + expect.objectContaining({ modelId: "test/model-x" }), + ); + }); + it("still completes the run when skill install fails (best-effort)", async () => { vi.mocked(installSessionGlobalSkills).mockRejectedValueOnce(new Error("install boom")); - const result = await provisionRunSession({ accountId: "account-1", title: "t" }); + const result = await provisionRunSession({ accountId: "account-1", title: "t", modelId: "test/model-x" }); expect(result.session).toEqual(updated); expect(discoverSkills).toHaveBeenCalled(); diff --git a/lib/chat/runs/handleStartChatRun.ts b/lib/chat/runs/handleStartChatRun.ts index 4122834c8..5328145c9 100644 --- a/lib/chat/runs/handleStartChatRun.ts +++ b/lib/chat/runs/handleStartChatRun.ts @@ -46,6 +46,7 @@ export async function handleStartChatRun(request: NextRequest): Promise { const created = await createSessionWithInitialChat({ accountId, title, chatTitle: "Scheduled generation", artistId, + modelId, }); if (created.ok === false) { throw new Error( diff --git a/lib/chat/runs/validateChatRunRequest.ts b/lib/chat/runs/validateChatRunRequest.ts index 154f47e6c..e6ae3634d 100644 --- a/lib/chat/runs/validateChatRunRequest.ts +++ b/lib/chat/runs/validateChatRunRequest.ts @@ -6,9 +6,10 @@ import { validateAuthContext } from "@/lib/auth/validateAuthContext"; import { errorResponse } from "@/lib/networking/errorResponse"; import { validationErrorResponse } from "@/lib/zod/validationErrorResponse"; import { generateUUID } from "@/lib/uuid/generateUUID"; +import { DEFAULT_CHAT_MODEL_ID } from "@/lib/const"; -/** Default model for headless generation when the caller omits `model`. */ -export const DEFAULT_RUN_MODEL_ID = "anthropic/claude-haiku-4.5"; +/** Default model for headless generation when the caller omits `model` (alias of the shared default). */ +export const DEFAULT_RUN_MODEL_ID = DEFAULT_CHAT_MODEL_ID; /** * Body schema for `POST /api/chat/runs` (the durable-workflow re-point, diff --git a/lib/const.ts b/lib/const.ts index 86a460432..e6830b1b8 100644 --- a/lib/const.ts +++ b/lib/const.ts @@ -10,6 +10,13 @@ export const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" as Addr export const PAYMASTER_URL = `https://api.developer.coinbase.com/rpc/v1/base/${process.env.PAYMASTER_KEY}`; export const IMAGE_GENERATE_PRICE = "0.15"; export const DEFAULT_MODEL = "openai/gpt-5.4-nano"; +/** + * Default model for chat generation when the caller picks none — the single + * source for interactive chats, headless runs, and the value written to + * `chats.model_id` at provision time (chat#1956). Every chat writer persists + * a model explicitly; nothing relies on a database column default. + */ +export const DEFAULT_CHAT_MODEL_ID = "anthropic/claude-haiku-4.5"; export const LIGHTWEIGHT_MODEL = "openai/gpt-4o-mini"; export const PRIVY_PROJECT_SECRET = process.env.PRIVY_PROJECT_SECRET; /** Base URL for the public API documentation site */ diff --git a/lib/sessions/__tests__/createSessionHandler.persistence.test.ts b/lib/sessions/__tests__/createSessionHandler.persistence.test.ts index 043b65e6e..185126187 100644 --- a/lib/sessions/__tests__/createSessionHandler.persistence.test.ts +++ b/lib/sessions/__tests__/createSessionHandler.persistence.test.ts @@ -68,6 +68,9 @@ describe("createSessionHandler — persistence", () => { const chatArgs = vi.mocked(insertChat).mock.calls[0][0]; expect(chatArgs.session_id).toBe("sess_1"); expect(chatArgs.title).toBe("New chat"); + // Provenance (chat#1956): every writer sets model_id explicitly so the + // chats.model_id column default can be dropped. + expect(chatArgs.model_id).toBe("anthropic/claude-haiku-4.5"); }); it("uses auth.accountId for personal sessions", async () => { diff --git a/lib/sessions/__tests__/createSessionWithInitialChat.test.ts b/lib/sessions/__tests__/createSessionWithInitialChat.test.ts index f30286ffd..723af5797 100644 --- a/lib/sessions/__tests__/createSessionWithInitialChat.test.ts +++ b/lib/sessions/__tests__/createSessionWithInitialChat.test.ts @@ -16,7 +16,13 @@ vi.mock("@/lib/supabase/sessions/insertSession", () => ({ insertSession: vi.fn() vi.mock("@/lib/supabase/sessions/deleteSessionById", () => ({ deleteSessionById: vi.fn() })); vi.mock("@/lib/supabase/chats/insertChat", () => ({ insertChat: vi.fn() })); -const args = { accountId: "acc-1", title: "T", chatTitle: "New chat", artistId: "art-1" }; +const args = { + accountId: "acc-1", + title: "T", + chatTitle: "New chat", + artistId: "art-1", + modelId: "test/model-x", +}; describe("createSessionWithInitialChat", () => { beforeEach(() => { @@ -38,6 +44,15 @@ describe("createSessionWithInitialChat", () => { ); }); + // Provenance: the chat row must record the model that will run (chat#1956) — + // never rely on the chats.model_id column default to fill it in. + it("writes the caller's modelId onto the inserted chat row", async () => { + await createSessionWithInitialChat(args); + expect(insertChat).toHaveBeenCalledWith( + expect.objectContaining({ model_id: "test/model-x" }), + ); + }); + it("uses workspaceAccountId for the repo when provided", async () => { await createSessionWithInitialChat({ ...args, workspaceAccountId: "org-9" }); expect(ensurePersonalRepo).toHaveBeenCalledWith({ accountId: "org-9" }); diff --git a/lib/sessions/chats/__tests__/createSessionChatHandler.test.ts b/lib/sessions/chats/__tests__/createSessionChatHandler.test.ts index efc6797cd..963020f73 100644 --- a/lib/sessions/chats/__tests__/createSessionChatHandler.test.ts +++ b/lib/sessions/chats/__tests__/createSessionChatHandler.test.ts @@ -95,6 +95,9 @@ describe("createSessionChatHandler", () => { expect(insertArgs.id).toBe("chat_requested"); expect(insertArgs.session_id).toBe("sess_1"); expect(insertArgs.title).toBe("New chat"); + // Provenance (chat#1956): every writer sets model_id explicitly so the + // chats.model_id column default can be dropped. + expect(insertArgs.model_id).toBe("anthropic/claude-haiku-4.5"); }); it("creates a chat with a generated id when no id is provided", async () => { diff --git a/lib/sessions/chats/createSessionChatHandler.ts b/lib/sessions/chats/createSessionChatHandler.ts index 868691246..fdfca9141 100644 --- a/lib/sessions/chats/createSessionChatHandler.ts +++ b/lib/sessions/chats/createSessionChatHandler.ts @@ -5,6 +5,7 @@ import { validateCreateSessionChatRequest } from "@/lib/sessions/chats/validateC import { selectChats } from "@/lib/supabase/chats/selectChats"; import { insertChat } from "@/lib/supabase/chats/insertChat"; import { toChatResponse } from "@/lib/sessions/toChatResponse"; +import { DEFAULT_CHAT_MODEL_ID } from "@/lib/const"; const INITIAL_CHAT_TITLE = "New chat"; @@ -53,6 +54,7 @@ export async function createSessionChatHandler( id: requestedChatId ?? generateUUID(), session_id: sessionId, title: INITIAL_CHAT_TITLE, + model_id: DEFAULT_CHAT_MODEL_ID, }); if (!chatRow) { diff --git a/lib/sessions/createSessionHandler.ts b/lib/sessions/createSessionHandler.ts index 9667695cd..e0e8128a2 100644 --- a/lib/sessions/createSessionHandler.ts +++ b/lib/sessions/createSessionHandler.ts @@ -9,6 +9,7 @@ import { import { failedToCreateSession } from "@/lib/sessions/failedToCreateSession"; import { toSessionResponse } from "@/lib/sessions/toSessionResponse"; import { toChatResponse } from "@/lib/sessions/toChatResponse"; +import { DEFAULT_CHAT_MODEL_ID } from "@/lib/const"; const INITIAL_CHAT_TITLE = "New chat"; @@ -49,6 +50,7 @@ export async function createSessionHandler(request: NextRequest): Promise { const cloneUrl = await ensurePersonalRepo({ accountId: workspaceAccountId ?? accountId }); if (!cloneUrl) return { ok: false, reason: "repo" }; @@ -50,7 +53,12 @@ export async function createSessionWithInitialChat({ ); if (!session) return { ok: false, reason: "insert" }; - const chat = await insertChat({ id: generateUUID(), session_id: session.id, title: chatTitle }); + const chat = await insertChat({ + id: generateUUID(), + session_id: session.id, + title: chatTitle, + model_id: modelId, + }); if (!chat) { const rolledBack = await deleteSessionById(session.id); if (!rolledBack) { From 24d5188dfdb9c1ae9feef2c3c6c156a64daa0d44 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Wed, 12 Aug 2026 17:54:37 -0500 Subject: [PATCH 2/4] style: prettier formatting on two test files Co-Authored-By: Claude Fable 5 --- lib/chat/runs/__tests__/provisionRunSession.test.ts | 6 +++++- lib/sessions/__tests__/createSessionWithInitialChat.test.ts | 4 +--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/chat/runs/__tests__/provisionRunSession.test.ts b/lib/chat/runs/__tests__/provisionRunSession.test.ts index 3a9d2343d..404d438f2 100644 --- a/lib/chat/runs/__tests__/provisionRunSession.test.ts +++ b/lib/chat/runs/__tests__/provisionRunSession.test.ts @@ -82,7 +82,11 @@ describe("provisionRunSession", () => { it("still completes the run when skill install fails (best-effort)", async () => { vi.mocked(installSessionGlobalSkills).mockRejectedValueOnce(new Error("install boom")); - const result = await provisionRunSession({ accountId: "account-1", title: "t", modelId: "test/model-x" }); + const result = await provisionRunSession({ + accountId: "account-1", + title: "t", + modelId: "test/model-x", + }); expect(result.session).toEqual(updated); expect(discoverSkills).toHaveBeenCalled(); diff --git a/lib/sessions/__tests__/createSessionWithInitialChat.test.ts b/lib/sessions/__tests__/createSessionWithInitialChat.test.ts index 723af5797..95f9a9a75 100644 --- a/lib/sessions/__tests__/createSessionWithInitialChat.test.ts +++ b/lib/sessions/__tests__/createSessionWithInitialChat.test.ts @@ -48,9 +48,7 @@ describe("createSessionWithInitialChat", () => { // never rely on the chats.model_id column default to fill it in. it("writes the caller's modelId onto the inserted chat row", async () => { await createSessionWithInitialChat(args); - expect(insertChat).toHaveBeenCalledWith( - expect.objectContaining({ model_id: "test/model-x" }), - ); + expect(insertChat).toHaveBeenCalledWith(expect.objectContaining({ model_id: "test/model-x" })); }); it("uses workspaceAccountId for the repo when provided", async () => { From 9bb063bb2629babecdcfe003edcf02103019f21d Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Wed, 12 Aug 2026 19:54:27 -0500 Subject: [PATCH 3/4] fix(chat): default chat model is kimi-k3; single constant, no aliases; normalize empty model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review feedback on #830: - DEFAULT_RUN_MODEL_ID and the local DEFAULT_MODEL_ID alias are deleted; every consumer imports DEFAULT_CHAT_MODEL_ID directly (DRY). - DEFAULT_CHAT_MODEL_ID is moonshotai/kimi-k3 — best recall and lowest cost in the 2026-08-12 4-model A/B on a production roster-brief task. Distinct from DEFAULT_MODEL (internal utility LLM calls). - cubic P2: model: "" / whitespace now normalizes to the default instead of persisting "" as provenance and sending "" to the workflow. Co-Authored-By: Claude Fable 5 --- app/api/chat/runs/route.ts | 2 +- lib/chat/__tests__/handleChatWorkflowStream.test.ts | 2 +- lib/chat/handleChatWorkflowStream.ts | 4 +--- lib/chat/runs/__tests__/handleStartChatRun.test.ts | 4 ++-- .../runs/__tests__/validateChatRunRequest.test.ts | 12 +++++++++++- lib/chat/runs/validateChatRunRequest.ts | 9 +++++---- lib/const.ts | 7 ++++++- .../createSessionHandler.persistence.test.ts | 2 +- .../chats/__tests__/createSessionChatHandler.test.ts | 2 +- 9 files changed, 29 insertions(+), 15 deletions(-) diff --git a/app/api/chat/runs/route.ts b/app/api/chat/runs/route.ts index 0f667665b..1434a07f9 100644 --- a/app/api/chat/runs/route.ts +++ b/app/api/chat/runs/route.ts @@ -32,7 +32,7 @@ export async function OPTIONS() { * - prompt: String prompt (mutually exclusive with messages) * - messages: Array of UIMessages (mutually exclusive with prompt) * - artistId: Optional UUID of the artist account - * - model: Optional model ID override (default anthropic/claude-haiku-4.5) + * - model: Optional model ID override (default moonshotai/kimi-k3) * - topic: Optional session title * - accountId: Optional accountId override (requires org API key) * diff --git a/lib/chat/__tests__/handleChatWorkflowStream.test.ts b/lib/chat/__tests__/handleChatWorkflowStream.test.ts index 1af062c8b..7a37c4a77 100644 --- a/lib/chat/__tests__/handleChatWorkflowStream.test.ts +++ b/lib/chat/__tests__/handleChatWorkflowStream.test.ts @@ -286,7 +286,7 @@ describe("handleChatWorkflowStream", () => { mockStartedRun(); await handleChatWorkflowStream(makeRequest()); const startArgs = vi.mocked(start).mock.calls[0]?.[1]?.[0] as { modelId: string }; - expect(startArgs.modelId).toBe("anthropic/claude-haiku-4.5"); + expect(startArgs.modelId).toBe("moonshotai/kimi-k3"); }); // Bundle A.4 — forward the Privy JWT from the validated body into diff --git a/lib/chat/handleChatWorkflowStream.ts b/lib/chat/handleChatWorkflowStream.ts index bb49b7615..fd4dc9ee3 100644 --- a/lib/chat/handleChatWorkflowStream.ts +++ b/lib/chat/handleChatWorkflowStream.ts @@ -23,8 +23,6 @@ import { discoverSkills } from "@/lib/skills/discoverSkills"; import { getSandboxSkillDirectories } from "@/lib/skills/getSandboxSkillDirectories"; import generateUUID from "@/lib/uuid/generateUUID"; -const DEFAULT_MODEL_ID = DEFAULT_CHAT_MODEL_ID; - /** * Handles POST /api/chat/workflow. * @@ -91,7 +89,7 @@ export async function handleChatWorkflowStream(request: NextRequest): Promise { accountId: "acc-1", title: "Scheduled generation", // Provenance (chat#1956): the resolved model reaches the chat insert. - modelId: "anthropic/claude-haiku-4.5", + modelId: "test/model-y", }), ); // the minted key is injected as recoupAccessToken AND threaded as ephemeralKeyId diff --git a/lib/chat/runs/__tests__/validateChatRunRequest.test.ts b/lib/chat/runs/__tests__/validateChatRunRequest.test.ts index aae8f0903..79c2db924 100644 --- a/lib/chat/runs/__tests__/validateChatRunRequest.test.ts +++ b/lib/chat/runs/__tests__/validateChatRunRequest.test.ts @@ -50,7 +50,17 @@ describe("validateChatRunRequest", () => { const noModel = await validateChatRunRequest(req({ prompt: "hi" })); if (noModel instanceof NextResponse) throw new Error("unexpected error"); - expect(noModel.modelId).toBe("anthropic/claude-haiku-4.5"); + expect(noModel.modelId).toBe("moonshotai/kimi-k3"); + + // cubic P2: an empty or whitespace model must normalize to the default, + // never persist as "" (provenance) or reach the workflow as "". + const emptyModel = await validateChatRunRequest(req({ prompt: "hi", model: "" })); + if (emptyModel instanceof NextResponse) throw new Error("unexpected error"); + expect(emptyModel.modelId).toBe("moonshotai/kimi-k3"); + + const blankModel = await validateChatRunRequest(req({ prompt: "hi", model: " " })); + if (blankModel instanceof NextResponse) throw new Error("unexpected error"); + expect(blankModel.modelId).toBe("moonshotai/kimi-k3"); }); it("rejects when neither prompt nor messages is provided (400)", async () => { diff --git a/lib/chat/runs/validateChatRunRequest.ts b/lib/chat/runs/validateChatRunRequest.ts index e6ae3634d..7a18034c4 100644 --- a/lib/chat/runs/validateChatRunRequest.ts +++ b/lib/chat/runs/validateChatRunRequest.ts @@ -8,9 +8,6 @@ import { validationErrorResponse } from "@/lib/zod/validationErrorResponse"; import { generateUUID } from "@/lib/uuid/generateUUID"; import { DEFAULT_CHAT_MODEL_ID } from "@/lib/const"; -/** Default model for headless generation when the caller omits `model` (alias of the shared default). */ -export const DEFAULT_RUN_MODEL_ID = DEFAULT_CHAT_MODEL_ID; - /** * Body schema for `POST /api/chat/runs` (the durable-workflow re-point, * recoupable/chat#1813). Exactly one of `prompt` / `messages` must be present. @@ -79,11 +76,15 @@ export async function validateChatRunRequest( ? [{ id: generateUUID(), role: "user", parts: [{ type: "text", text: trimmedPrompt }] }] : (messages as UIMessage[]); + // `||` not `??`: an empty/whitespace model must fall back too, or "" would + // be persisted as provenance and sent to the workflow as the model id. + const trimmedModel = typeof model === "string" ? model.trim() : ""; + return { accountId: auth.accountId, orgId: auth.orgId, messages: uiMessages, artistId, - modelId: model ?? DEFAULT_RUN_MODEL_ID, + modelId: trimmedModel || DEFAULT_CHAT_MODEL_ID, }; } diff --git a/lib/const.ts b/lib/const.ts index e6830b1b8..b3842bfa8 100644 --- a/lib/const.ts +++ b/lib/const.ts @@ -15,8 +15,13 @@ export const DEFAULT_MODEL = "openai/gpt-5.4-nano"; * source for interactive chats, headless runs, and the value written to * `chats.model_id` at provision time (chat#1956). Every chat writer persists * a model explicitly; nothing relies on a database column default. + * + * Distinct from DEFAULT_MODEL above, which powers internal utility LLM calls + * (evals, catalog batch analysis, general agent), not user-facing chat. + * kimi-k3 chosen 2026-08-12: best recall and lowest cost in a 4-model A/B on + * a production roster-brief task (api#830 review thread). */ -export const DEFAULT_CHAT_MODEL_ID = "anthropic/claude-haiku-4.5"; +export const DEFAULT_CHAT_MODEL_ID = "moonshotai/kimi-k3"; export const LIGHTWEIGHT_MODEL = "openai/gpt-4o-mini"; export const PRIVY_PROJECT_SECRET = process.env.PRIVY_PROJECT_SECRET; /** Base URL for the public API documentation site */ diff --git a/lib/sessions/__tests__/createSessionHandler.persistence.test.ts b/lib/sessions/__tests__/createSessionHandler.persistence.test.ts index 185126187..898ba58cc 100644 --- a/lib/sessions/__tests__/createSessionHandler.persistence.test.ts +++ b/lib/sessions/__tests__/createSessionHandler.persistence.test.ts @@ -70,7 +70,7 @@ describe("createSessionHandler — persistence", () => { expect(chatArgs.title).toBe("New chat"); // Provenance (chat#1956): every writer sets model_id explicitly so the // chats.model_id column default can be dropped. - expect(chatArgs.model_id).toBe("anthropic/claude-haiku-4.5"); + expect(chatArgs.model_id).toBe("moonshotai/kimi-k3"); }); it("uses auth.accountId for personal sessions", async () => { diff --git a/lib/sessions/chats/__tests__/createSessionChatHandler.test.ts b/lib/sessions/chats/__tests__/createSessionChatHandler.test.ts index 963020f73..2d91e91a4 100644 --- a/lib/sessions/chats/__tests__/createSessionChatHandler.test.ts +++ b/lib/sessions/chats/__tests__/createSessionChatHandler.test.ts @@ -97,7 +97,7 @@ describe("createSessionChatHandler", () => { expect(insertArgs.title).toBe("New chat"); // Provenance (chat#1956): every writer sets model_id explicitly so the // chats.model_id column default can be dropped. - expect(insertArgs.model_id).toBe("anthropic/claude-haiku-4.5"); + expect(insertArgs.model_id).toBe("moonshotai/kimi-k3"); }); it("creates a chat with a generated id when no id is provided", async () => { From bb879e6378f8cb837d20e90a5fe29f95874675a1 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Wed, 12 Aug 2026 21:18:32 -0500 Subject: [PATCH 4/4] refactor(const): one DEFAULT_MODEL for everything, value kimi-k3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review decision on #830: no separate DEFAULT_CHAT_MODEL_ID. DEFAULT_MODEL (now moonshotai/kimi-k3) is the single default everywhere a caller picks none: interactive chats, headless runs (persisted to chats.model_id), the session-chats endpoint's defaultModelId exposed to the UI, the general agent's fallback (incl. inbound email replies), evals, and catalog batch analysis. This closes a previously unnoticed three-way disagreement: the UI's defaultModelId was gpt-5.4-nano (DEFAULT_MODEL), the chats column default was haiku-4.5, and the run default was haiku-4.5 — three different answers to "what model do I get by default". Co-Authored-By: Claude Fable 5 --- .../generalAgent/__tests__/getGeneralAgent.test.ts | 2 +- lib/chat/handleChatWorkflowStream.ts | 4 ++-- lib/chat/runs/validateChatRunRequest.ts | 4 ++-- lib/const.ts | 13 ++++--------- .../chats/__tests__/getSessionChatsHandler.test.ts | 2 +- lib/sessions/chats/createSessionChatHandler.ts | 4 ++-- lib/sessions/createSessionHandler.ts | 4 ++-- 7 files changed, 14 insertions(+), 19 deletions(-) diff --git a/lib/agents/generalAgent/__tests__/getGeneralAgent.test.ts b/lib/agents/generalAgent/__tests__/getGeneralAgent.test.ts index 88fbadf94..7c75797fc 100644 --- a/lib/agents/generalAgent/__tests__/getGeneralAgent.test.ts +++ b/lib/agents/generalAgent/__tests__/getGeneralAgent.test.ts @@ -117,7 +117,7 @@ describe("getGeneralAgent", () => { const result = await getGeneralAgent(body); - expect(result.model).toBe("openai/gpt-5.4-nano"); + expect(result.model).toBe("moonshotai/kimi-k3"); }); it("uses custom model when specified in body", async () => { diff --git a/lib/chat/handleChatWorkflowStream.ts b/lib/chat/handleChatWorkflowStream.ts index fd4dc9ee3..30b2c4086 100644 --- a/lib/chat/handleChatWorkflowStream.ts +++ b/lib/chat/handleChatWorkflowStream.ts @@ -1,4 +1,4 @@ -import { DEFAULT_CHAT_MODEL_ID } from "@/lib/const"; +import { DEFAULT_MODEL } from "@/lib/const"; import { NextRequest, NextResponse } from "next/server"; import { createUIMessageStreamResponse, type UIMessageChunk } from "ai"; import { start, getRun } from "workflow/api"; @@ -89,7 +89,7 @@ export async function handleChatWorkflowStream(request: NextRequest): Promise { defaultModelId: string; }; expect(body.chats).toEqual(summaries); - expect(body.defaultModelId).toBe("openai/gpt-5.4-nano"); + expect(body.defaultModelId).toBe("moonshotai/kimi-k3"); expect(getChatSummaries).toHaveBeenCalledWith({ sessionId: "sess_1", accountId, diff --git a/lib/sessions/chats/createSessionChatHandler.ts b/lib/sessions/chats/createSessionChatHandler.ts index fdfca9141..433bdbc9a 100644 --- a/lib/sessions/chats/createSessionChatHandler.ts +++ b/lib/sessions/chats/createSessionChatHandler.ts @@ -5,7 +5,7 @@ import { validateCreateSessionChatRequest } from "@/lib/sessions/chats/validateC import { selectChats } from "@/lib/supabase/chats/selectChats"; import { insertChat } from "@/lib/supabase/chats/insertChat"; import { toChatResponse } from "@/lib/sessions/toChatResponse"; -import { DEFAULT_CHAT_MODEL_ID } from "@/lib/const"; +import { DEFAULT_MODEL } from "@/lib/const"; const INITIAL_CHAT_TITLE = "New chat"; @@ -54,7 +54,7 @@ export async function createSessionChatHandler( id: requestedChatId ?? generateUUID(), session_id: sessionId, title: INITIAL_CHAT_TITLE, - model_id: DEFAULT_CHAT_MODEL_ID, + model_id: DEFAULT_MODEL, }); if (!chatRow) { diff --git a/lib/sessions/createSessionHandler.ts b/lib/sessions/createSessionHandler.ts index e0e8128a2..4ec02ed9f 100644 --- a/lib/sessions/createSessionHandler.ts +++ b/lib/sessions/createSessionHandler.ts @@ -9,7 +9,7 @@ import { import { failedToCreateSession } from "@/lib/sessions/failedToCreateSession"; import { toSessionResponse } from "@/lib/sessions/toSessionResponse"; import { toChatResponse } from "@/lib/sessions/toChatResponse"; -import { DEFAULT_CHAT_MODEL_ID } from "@/lib/const"; +import { DEFAULT_MODEL } from "@/lib/const"; const INITIAL_CHAT_TITLE = "New chat"; @@ -50,7 +50,7 @@ export async function createSessionHandler(request: NextRequest): Promise