Skip to content

fix(models): per-provider discovery state, Fable 5.1 in the picker, Codex effort from the CLI - #219

Open
bbsngg wants to merge 5 commits into
mainfrom
fix/model-picker-discovery
Open

fix(models): per-provider discovery state, Fable 5.1 in the picker, Codex effort from the CLI#219
bbsngg wants to merge 5 commits into
mainfrom
fix/model-picker-discovery

Conversation

@bbsngg

@bbsngg bbsngg commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #213 / #216 for three problems found while using the picker on main.

1. Provider switch corrupted the saved model (bug)

useHarnessModels set its state asynchronously, so for one render after a provider switch it still held the previous provider's list while provider already named the new one. ChatComposer's rescue effect then saw the Claude model "missing" from Codex's list and replaced it with Codex's default: localStorage['claude-model'] ended up as gpt-5.6-sol, and every new Claude session would have failed.

  • The hook now tags each answer with the provider it was fetched for and only exposes an answer for the current provider.
  • useChatProviderState drops a stored model that cannot belong to its slot (a gpt-* under claude-model, a claude-*/alias under codex-model) and falls back to the default, so anyone already affected recovers on next load. Verified in the browser: the corrupted value was replaced by claude-fable-5.

2. Fable 5.1 was not selectable

The Claude probe runs with settingSources: [] so no hooks fire. That also hides the model configured in ~/.claude/settings.json, and that is exactly where the CLI's own /model menu gets Fable 5.1 from (0.3.226's base menu lists Fable 5, not 5.1). Discovery now reads model from the user settings file (honouring CLAUDE_CONFIG_DIR) and ANTHROPIC_MODEL, and offers it the way the CLI does. claude-fable-5-1 / claude-fable-5-1[1m] are also in the built-in list so they are available even with discovery off. Duplicate CLI display names ("Fable" for both 5 and 5.1) are disambiguated from the description.

Real probe output on this machine after the change: default, opus[1m], claude-fable-5[1m] → Fable, sonnet, haiku, claude-fable-5-1[1m] → Fable 5.1 [1M], then the built-ins.

3. Codex reasoning-effort selector missing for discovered models

Support was a table keyed by exact model name; gpt-5.6-sol, gpt-5.4-mini, gpt-5.3-codex-spark fell to "default only" and the selector disappeared. Codex's model/list reports supportedReasoningEfforts and defaultReasoningEffort per model, so discovery now carries them through and the selector, its gating, and the saved-effort normalisation use that list first, with the table as fallback. The ultra effort the CLI offers (gpt-5.6-sol / -terra) is added to the UI and en/zh-CN/ko locales.

Measured: gpt-5.6-sol low/medium/high/xhigh/max/ultra (default low), gpt-5.6-luna low…max, gpt-5.5/5.4/5.4-mini/5.3-codex-spark low…xhigh.

useHarnessModels moved from ChatComposer up to ChatInterface so composer state (normalisation) and the picker share one answer.

Verification

  • npx vitest run: 172/172 (new tests for label disambiguation, configured-model injection, ANTHROPIC_MODEL, Codex effort mapping, discovered-effort precedence, labelForClaudeModelId).
  • npm run typecheck clean.
  • End-to-end against the real Claude and Codex CLIs through the module.
  • Browser: model slot sanitisation confirmed; the picker/selector UI check was cut short when the browser extension disconnected, so please give the Codex effort selector and the Claude dropdown a quick look after merging.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NBqmTvJRhyysMvMvL6cp7x

…e Codex effort from the CLI

Three problems reported after #213/#216 landed, all in the model picker.

1. Switching provider could write one provider's model into another's slot.
   useHarnessModels updated its state asynchronously, so for one render
   after a switch it still held the previous provider's list while
   `provider` already named the new one. ChatComposer's rescue effect then
   saw a Claude model "missing" from Codex's list and replaced it with
   Codex's default, persisting `claude-model` = `gpt-5.6-sol`. The hook now
   tags every answer with the provider it was fetched for and only ever
   exposes an answer for the current provider. useChatProviderState also
   discards a stored model that cannot belong to its slot, so users already
   hit by this get their default back instead of a failing session.

2. Fable 5.1 was not selectable. The Claude probe disables settings sources
   (so no hooks fire), which also hid the model the user configured in
   ~/.claude/settings.json; that is where the CLI's own /model menu gets
   Fable 5.1 from. Discovery now reads `model` from the user settings file
   (honouring CLAUDE_CONFIG_DIR) and ANTHROPIC_MODEL and offers it, and
   Fable 5.1 is in the built-in list too so it is there even when discovery
   is unavailable. Duplicate CLI display names ("Fable" for both Fable 5 and
   Fable 5.1) are disambiguated from the description.

3. No reasoning-effort selector for discovered Codex models. Support was a
   table keyed by exact model name, so `gpt-5.6-sol` and friends fell to
   "default only". Codex's model/list reports supportedReasoningEfforts and
   defaultReasoningEffort per model; discovery now carries them through and
   the selector, its gating, and the saved-effort normalisation prefer that
   list over the table. The `ultra` effort the CLI offers is added to the
   UI and locales. useHarnessModels moved up to ChatInterface so composer
   state and the picker share one answer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBqmTvJRhyysMvMvL6cp7x
@bbsngg

bbsngg commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

Browser verification completed after the extension reconnected (dev server running from this branch against the real database):

  • On load, the corrupted claude-model (gpt-5.6-sol) was replaced by the default; the Claude dropdown showed the discovered menu plus Fable 5.1 [1M] and Fable 5.1; selecting Fable 5.1 [1M] persisted.
  • Switching to Codex left claude-model untouched (claude-fable-5-1[1m]), which is the race this PR fixes.
  • With gpt-5.6-sol selected, the reasoning-effort selector is present and lists Default / Low / Medium / High / Extra High / Max / Ultra, i.e. exactly what codex app-server reports. Selecting Ultra persisted and was not normalised back to Default.

- Codex built-in list uses the ids codex-cli actually serves: the retired
  `gpt-5.6` (labelled "GPT-5.6 (Sol)") is replaced by `gpt-5.6-sol`, and
  `gpt-5.4-mini` / `gpt-5.3-codex-spark` are added; default follows.
- The picker no longer lists a built-in entry the harness has retired
  unless it is the selected value. The API still returns such entries
  flagged deprecated, and the picker labels a selected one "not in CLI
  list", so a saved preference is neither hidden nor mistaken for a live
  model.
- Claude menu labels are expanded from the CLI's description so they stand
  apart from the built-in entries: "Fable" becomes "Fable 5 [1M]",
  "Sonnet" becomes "Sonnet 5", "Default (recommended)" becomes
  "Default (Opus 5 with 1M context)". Descriptions show as tooltips.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBqmTvJRhyysMvMvL6cp7x
@bbsngg

bbsngg commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Second commit (0be3cf11) addresses the duplicate entries reported after the first round ("GPT-5.6 (Sol)" next to "GPT-5.6-Sol", "Fable" next to "Fable 5", …):

  • Codex built-in list now uses the ids codex-cli serves (gpt-5.6-sol replaces the retired gpt-5.6; gpt-5.4-mini, gpt-5.3-codex-spark added; default follows).
  • The picker hides a built-in entry the harness has retired unless it is the selected value (then labelled "not in CLI list"). The API payload is unchanged (deprecated: true).
  • Claude menu labels are expanded from the CLI description: "Default (Opus 5 with 1M context)", "Opus 5 with 1M context", "Fable 5 [1M]", "Sonnet 5", "Haiku 4.5". Descriptions show as tooltips in both pickers.

Verified in the browser on this branch: Codex dropdown shows exactly the six CLI models, no duplicates; Claude dropdown shows the labels above followed by the built-ins (Fable 5.1 [1M], Fable 5.1, Opus 5, Fable 5, Opus, Opus (Plan Mode Only), Sonnet [1M], Opus 4.8/4.7/4.6), every label distinct. 173/173 tests, typecheck clean, CI green.

…as answered

With the CLI menu and the compiled-in table shown side by side, the same
Claude model appeared twice ("Fable 5 [1M]" from the CLI next to "Fable 5"
from the table, likewise Fable 5.1). Mark table entries builtIn in the
discovery payload and have the picker show, like Claude Code's own /model
menu, only what the harness reports plus the configured and selected
models; the rest sit behind a "show N more built-in models" row so an
explicit version such as Opus 4.6 is still one click away.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBqmTvJRhyysMvMvL6cp7x
@bbsngg

bbsngg commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Third commit (92114313): the remaining Fable duplicates ("Fable 5 [1M]" from the CLI next to "Fable 5" from the built-in table, same for 5.1) came from showing the compiled-in table alongside the CLI menu. Discovery now marks table entries builtIn: true, and once the harness has answered the picker reads like Claude Code's own /model menu: only what the CLI reports, plus the configured model and whatever is currently selected. The rest sit behind a "Show N more built-in models" row, so an explicit version such as Opus 4.6 is still one click away.

Verified in the browser: the Claude dropdown shows Default (Opus 5 with 1M context), Opus 5 with 1M context, Fable 5 [1M], Sonnet 5, Haiku 4.5, Fable 5.1 [1M] and a "Show 9 more built-in models" toggle; Codex shows its six CLI models with the retired ones folded. 173/173 tests, typecheck clean, CI green.

The built-in table is a fallback for when discovery is unavailable, not a
second list to browse: drop the "show more built-in models" toggle and
list only what the harness reported, plus the currently selected value so
a saved preference never vanishes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBqmTvJRhyysMvMvL6cp7x
@bbsngg

bbsngg commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Fourth commit (751c125c), per review: once the harness has answered, its list is the source of truth. The "show more built-in models" toggle is gone; the picker lists only what the CLI reported plus the currently selected value (so a saved preference the CLI did not list never vanishes). The compiled-in table is now purely the fallback for when discovery is unavailable. 173/173 tests, typecheck clean, CI green.

An open picker that ignores Escape stays in the way of the next click,
which is how a stray selection can land on the wrong row.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBqmTvJRhyysMvMvL6cp7x
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.

1 participant