Skip to content

fix: share one model resolver between the daemon and the web client - #316

Merged
saucam merged 1 commit into
mainfrom
fix/share-model-resolver
Sep 3, 2026
Merged

fix: share one model resolver between the daemon and the web client#316
saucam merged 1 commit into
mainfrom
fix/share-model-resolver

Conversation

@saucam

@saucam saucam commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Symptom

/model opus is rejected in the web UI as an unknown model, while the daemon behind it resolves the same input fine.

Cause

Model-value resolution existed twice:

Copy Where
resolveAgainstList src/daemon/models.ts
resolveModelInput web/src/state/models.ts — a hand-maintained mirror

They drifted. #315 taught the daemon copy to match a bare alias against a variant-suffixed value — the backend reports {"value": "opus[1m]", "displayName": "Opus (1M context)"}, so a typed opus matches neither field verbatim. The mirror kept the old rule, so the client returned null and reported an unknown model before the request was ever sent.

Change

Patching the mirror would leave two copies and the same drift. The resolver moves into @highflame/codeoid-core, which already exists for daemon/frontend shared logic — the re-export shims in web/src/lib/{approvals,identity,format}.ts are the established pattern.

packages/core/src/models.ts   ← the one implementation
src/daemon/models.ts          ← re-exports it (37 lines of duplicate deleted)
web/src/state/models.ts       ← resolveModelInput delegates to it

Daemon behaviour is unchanged; the client now matches it. The web client keeps its own pre-catalog passthrough — with an empty list the trimmed input goes through untouched so typing isn't blocked before the fetch lands, and the daemon remains the backstop validator either way.

Verification

  • packages/core + daemon model tests — 52 pass
  • web suite — 434 pass across 41 files
  • bun run typecheck (root + protocol + core) and cd web && tsc --noEmit — clean
  • bun run lint — clean

Coverage moves to packages/core alongside the code, using the live supportedModels() payload from claude-agent-sdk 0.3.258 as the fixture, plus a web-side regression test that drives the real symptom end-to-end through fetchModelsresolveModelInput.

Notes for the reviewer

  • The picker already renders opt.value beneath the display name (SessionControls.tsx, unchanged since July), so Fable 5 vs 5.1 is already distinguishable there — the backend's display name is just "Fable" for both. No UI change was needed and none is included.
  • Unrelated, noticed while working: web/dist is served at /ui and is built separately, so a daemon restart does not pick up frontend changes — this fix needs bun run build:web to reach a browser.

🤖 Generated with Claude Code

`/model opus` was rejected in the web UI as an unknown model while the daemon
behind it resolved the very same input fine.

Model-value resolution existed twice: `resolveAgainstList` in
src/daemon/models.ts and `resolveModelInput` in web/src/state/models.ts, the
latter a hand-maintained mirror. They drifted. #315 taught the daemon copy to
match a bare alias against a variant-suffixed value (the backend reports
`opus[1m]` / "Opus (1M context)", so a typed `opus` matches neither field
verbatim) and the mirror kept the old rule — so the client rejected the input
before the request was ever sent.

Patching the mirror would leave two copies and the same drift. The resolver
moves to @highflame/codeoid-core instead, which already exists for exactly
this (see the re-export shims in web/src/lib/{approvals,identity,format}.ts):

  packages/core/src/models.ts  — the one implementation
  src/daemon/models.ts         — re-exports it, 37 lines of duplicate deleted
  web/src/state/models.ts      — resolveModelInput delegates to it

Behaviour is unchanged on the daemon side and now matches it on the client.
The web client keeps its own pre-catalog passthrough: with an empty list the
trimmed input goes through untouched so typing isn't blocked before the
fetch lands, and the daemon stays the backstop validator.

Coverage moves to packages/core alongside the code, with the live payload from
claude-agent-sdk 0.3.258 as the fixture, plus a web-side regression test that
drives the real symptom through fetchModels -> resolveModelInput.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@highflame-oracle highflame-oracle Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔮 Oracle Review

🎯 Start Here

packages/core/src/index.ts (~15 min) — Logic changes in index.ts


📋 PR Summary

What this PR does: fix: share one model resolver between the daemon and the web client


🔍 Code Review

Review completed.

Review Stats: suggestion:1


Generated by Oracle - Highflame's AI Code Reviewer

Comment thread web/src/state/models.ts
@saucam
saucam merged commit 18ea00f into main Sep 3, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants