fix(chat): write the resolved model to chats.model_id at provision time

Merged
SW
2mo ago
+92-19

Closes the api row of recoupable/chat#1956 (model provenance for headless/task runs).

What

Every chat writer now records the model explicitly on the chat row:

  • createSessionWithInitialChat takes a required modelId and writes model_id on the insert (lib/sessions/createSessionWithInitialChat.ts)
  • provisionRunSession → handleStartChatRun thread the validated model from the request, so POST /api/chat/runs with model: X yields a chat row with model_id = X from the moment the 202 returns
  • Interactive writers (createSessionHandler, createSessionChatHandler) pass the default explicitly
  • One default constant for everything: DEFAULT_MODEL in lib/const.ts, value moonshotai/kimi-k3 (review decision). DEFAULT_RUN_MODEL_ID, the local DEFAULT_MODEL_ID alias, and the interim DEFAULT_CHAT_MODEL_ID are all gone. This also fixes a pre-existing three-way disagreement: the UI's defaultModelId (session-chats endpoint) was gpt-5.4-nano while the run default and the DB column default were haiku-4.5

Why

Reproduced 2026-08-12 (table in the issue): four POST /api/chat/runs calls with four explicit models — all four chat rows read anthropic/claude-haiku-4.5 (the column default) while usage_events billed the real models. The interactive path reads chats.model_id as its source of truth (chat#1767), so the column being wrong for headless runs made model debugging three-sources-disagree work.

Merge sequencing

This PR first, then the database PR dropping the chats.model_id column default (hard dependency: dropping the default before every writer is explicit would leave interactive chats with NULL). No docs PR — no response schema documents the resolved model today; checked in the issue.

Tests

  • TDD, all five units RED before GREEN: createSessionWithInitialChat (writes caller's modelId), provisionRunSession (forwards), handleStartChatRun (passes validated model), createSessionHandler persistence + createSessionChatHandler (explicit default pinned to the literal "anthropic/claude-haiku-4.5", not the constant, so a broken import can't false-green)
  • Full lib/sessions + lib/chat suites: 642 passed
  • tsc --noEmit: 202 pre-existing errors on clean main, 202 with this diff — zero introduced. Lint clean on touched files.

Preview verification to follow as a PR comment.

🤖 Generated with Claude Code


Summary by cubic

Writes the resolved chat model to chats.model_id at provision time and unifies the default to DEFAULT_MODEL = "moonshotai/kimi-k3". Previously headless runs used the column default (anthropic/claude-haiku-4.5) and different paths had divergent defaults; now every path persists the requested model or the unified default, and empty/whitespace model values normalize to the default.

  • Review notes

    • createSessionWithInitialChat now requires modelId and writes it; handleStartChatRun → provisionRunSession forwards it to the chat insert.
    • Interactive writers (createSessionHandler, createSessionChatHandler, workflow stream) set model_id explicitly using DEFAULT_MODEL.
    • Deleted per-path defaults (DEFAULT_RUN_MODEL_ID, local constants); all consumers import DEFAULT_MODEL. API route docs reflect the new default.
    • General agent fallback and getSessionChats defaultModelId now use DEFAULT_MODEL.
    • Request validation trims model and falls back when empty/whitespace.
  • Migration and sequencing

    • Callers of createSessionWithInitialChat must pass modelId.
    • Merge this before dropping the chats.model_id column default.

Summary by CodeRabbit

  • New Features
    • New chats and chat runs now consistently record and use the selected AI model.
    • Blank or unspecified model selections automatically use moonshotai/kimi-k3.
  • Bug Fixes
    • Fixed cases where a requested model could be lost during chat run setup.
    • Ensured initial session chats use the configured model instead of relying on an implicit default.
  • Documentation
    • Updated the chat runs API documentation to reflect the new default model.