Skip to content

fix(model): self-heal stale model id on load; drop v3 ids from picker fallbacks#1663

Merged
esengine merged 1 commit into
mainfrom
fix/model-validate-on-load
May 24, 2026
Merged

fix(model): self-heal stale model id on load; drop v3 ids from picker fallbacks#1663
esengine merged 1 commit into
mainfrom
fix/model-validate-on-load

Conversation

@esengine

Copy link
Copy Markdown
Owner

Summary

  • Bug: a stale deepseek-chat / deepseek-reasoner (or any other id no longer accepted by api.deepseek.com) persisted in ~/.reasonix/config.json bricked the next launch with Bad request (DeepSeek 400): The supported API model names are deepseek-v4-pro or deepseek-v4-flash, but you passed …. The 400 string itself is server-side; we just wrap it via loop.badrequest400. The bug was that loadModel() returned the persisted value verbatim with no validation, and the picker/settings fallback lists (used when the live catalog fetch fails) still surfaced the v3 ids as selectable so users could land on a dead id without ever hand-editing config.
  • Fix: loadModel() now keeps the persisted id only when (a) the user has a custom baseUrl set — custom-endpoint owners pick their own model namespace, so we trust them — or (b) the id is in the new SUPPORTED_OFFICIAL_MODELS allowlist (deepseek-v4-pro, deepseek-v4-flash). Otherwise it returns DEFAULT_MODEL, and the next /model or picker write replaces the stale config value. Read-only — we don't rewrite config on load, so tests / read paths are side-effect-free.
  • Cleanup: drops the v3 ids from all six picker / settings fallback lists (CLI ModelPicker, QQ channel, desktop composer + settings, dashboard composer + settings). Inert metadata in src/telemetry/stats.ts and src/loop/thinking.ts is left alone — never user-facing.

Test plan

  • npx vitest run tests/config.test.ts tests/loop.test.ts tests/slash.test.ts tests/ui-model-picker.test.tsx — 290 passed / 2 skipped
  • npx tsc --noEmit
  • New loadModel cases: stale id → DEFAULT, custom baseUrl → pass-through, supported id → kept

… fallbacks

A stale `deepseek-chat` / `deepseek-reasoner` (or any other id no longer
accepted by api.deepseek.com) persisted in config.json bricked the next
launch with "Bad request (DeepSeek 400): supported API model names are
deepseek-v4-pro or deepseek-v4-flash, but you passed …". The picker
fallback list (used when the live catalog fetch fails) still surfaced
the v3 ids as selectable, so users could land there without ever
hand-editing config.

loadModel now keeps the persisted id only when (a) the user has a
custom baseUrl set — in which case we trust their model namespace —
or (b) the id is in the supported v4 allowlist. Otherwise it returns
DEFAULT_MODEL, and the next /model or picker write replaces the stale
config value.

Also drops the v3 ids from all six picker / settings fallback lists
(CLI, QQ, desktop composer + settings, dashboard composer + settings).
@esengine esengine merged commit 4610d54 into main May 24, 2026
3 checks passed
@esengine esengine deleted the fix/model-validate-on-load branch May 24, 2026 06:24
esengine pushed a commit that referenced this pull request May 24, 2026
…moved, persisted usage stats, plan dispatch gate

Headline themes:
- Desktop: bundle the CLI-hosted React dashboard, retire Tauri+Preact duplicate (#1418)
- Config: drop preset abstraction; flash/pro are direct model selections (#1657, #1630)
- Stats: persist cumulative usage to session meta + auto-restore on startup (#1667, #1680, #1643, #1628)
- Plans: editMode="plan" enforced at the ToolRegistry dispatch gate (#1681); step advance fix (#1629)
- Context: fold once at turn start, drop pre-flight + byte-ceiling (#1642, #1646); collapsible compacted card (#1649)
- Subagents: per-skill flash/pro override + Settings UI (#1632)
- Desktop polish: sidebar drag-resize (#1688), responsive collapse (#1585), copy/edit overlay + msg-history nav (#1645), Esc closes modal not turn (#1685), QQ tab isolation (#1672), DiffCard for edits (#1662), theme-aware highlighting (#1655), system events toggle (#1654/#1650), macOS TCC inheritance (#1614), dashboard.enabled (#1612)
- Dashboard polish: persistent session URL (#1586, #1589, #1599), theme-aware highlighting (#1664), IME confirm-enter guard (#1689), code-fence lang fix (#1677), vendor chunk split (#1587), markdown table h-scroll (#1562)
- TUI: Alt+S input stash/recall; static history isolated from input rerenders (#1635); legacy mouse drop (#1637, #1648); multi-edit gated in review (#1647)
- Diff: SplitDiff column border holds under CJK (#1686)
- MCP: workspace roots passed to servers (#1625); codeCommand honors mcpServers (#1603)
- Config plumbing: (baseUrl, apiKey) resolved as a tuple (#1658); stale model id self-heal (#1663)

See CHANGELOG for the full list.
esengine added a commit that referenced this pull request May 24, 2026
…1699)

* fix(api): self-heal stale model id in settings GET

#1663 added `loadModel()` which maps unsupported model ids (e.g. v3
`deepseek-chat` / `deepseek-reasoner` left over in a user's config)
to `DEFAULT_MODEL` when no custom endpoint is set. The CLI uses it,
but the dashboard/desktop `/settings` GET endpoint still returned the
raw `cfg.model`, so users upgrading from a pre-v4 config saw their
desktop landing on the stale id and the model picker rendering it as
"custom" — looks like the app forgot which model it's on.

Route the GET through `loadModel(ctx.configPath)` so the same
self-heal applies on the wire. Custom-baseUrl users still keep their
custom ids unchanged (`loadModel` trusts non-empty `baseUrl`).

* feat(plan): expose plan as a 4th editMode in API + desktop + dashboard UIs

#1681 wired `editMode === "plan"` into the ToolRegistry dispatch gate
so plan mode is now a persisted state, not a slash-only toggle. The
desktop and dashboard surfaces never grew a control to set it though —
the settings API + edit-mode API still rejected "plan" with a 400, the
composer's ModeSwitch hardcoded review/auto/yolo, and the settings
page tri-state only listed the same three. Net: plan mode was
reachable from the CLI's `/plan` slash and direct config edits, but
not from either GUI surface.

Admit "plan" in both validation sets (`src/server/api/settings.ts`,
`src/server/api/edit-mode.ts`) and the `/mode` slash completer
(`src/cli/ui/slash/commands.ts`). Add a "plan" entry to MODE_INFO in
both composers with `I.list` and a planHint string. Add "plan" to the
settings page button maps. Extend the local `EditMode` string unions
in `desktop/src/{protocol,App,ui/composer}.tsx` and the dashboard
equivalents so TS accepts it everywhere.

i18n: add `editMode.plan` + `editMode.planHint` (and `planDesc` on
desktop) in both en + zh-CN for each surface.

---------

Co-authored-by: reasonix <reasonix@deepseek.com>
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