fix(providers): resolve reasoning through the catalog; add appearance preferences - #161
Merged
Merged
Conversation
Reported in #153 as "DeepSeek shows no thinking levels". The cause is wider than DeepSeek: infer_reasoning_capabilities() was a chain of substring checks kept apart from the catalog, and it had rotted. It recognised claude-sonnet-4-6 while the catalog had moved on to claude-sonnet-5, so on main today these all advertise no reasoning at all: claude-sonnet-5, claude-opus-4-8, claude-sonnet-4-5, claude-haiku-4-5, deepseek-r1, deepseek-reasoner, deepseek-chat, qwen3-max, grok-4 The Desktop effort picker renders reasoningOptions(model), so an empty capability means the control appears with nothing in it — exactly what #153 describes. The provider side was never the problem: deepseek already carries thinking_style="thinking_type", so the wire format was wired all along. Both catalog.py and model_compat.py open by naming `model_id.includes(...)` as the anti-pattern to avoid, and catalog.py already resolves every other per-model fact — context window, output cap, pricing — through a normalize → snapshot → seed → family → default cascade. Reasoning now joins it as a ModelInfo field, so one id yields one answer from one lookup. profiles.py had been calling resolve_model_info() and the name chain on adjacent lines for the same model. Consequences beyond the reported bug: * An unseen deepseek-r2 or gpt-5.9 inherits its family row instead of silently losing its controls. Shipping a model no longer needs a code edit. * Family fallback deliberately advertises less than an exact seed row: claude-sonnet-4-<date> keeps the effort ladder but not summarised thinking, which it predates. Claiming it would route raw trace text through the summary channel. * Dotted aliases (claude-sonnet-4.6) are listed rather than normalised, because the dot is load-bearing elsewhere in the table — gpt-5.4 and kimi-k2.5 are distinct models, not separator noise. A ladder is only claimed where the vendor publishes named levels; the rest get the Auto/Off surface ModelReasoningCapabilities already documents for "reasoning exists, levels unpublished". GLM and MiniMax stay absent: they need verified context/output limits before they earn a seed row, and that is now a table edit rather than a branch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to the catalog move, closing the two real gaps it exposed. Both
lines were resolving to the 128K default with no reasoning at all — a 200K
model trimmed to 128K, and a thinking-capable one advertising nothing.
Limits taken from the vendors, not inferred:
* GLM-4.6 — 200K in / 128K out (docs.z.ai/guides/llm/glm-4.6)
* MiniMax M2 line — 204,800 total; M3 is the 1M tier
(platform.minimax.io/docs/api-reference/api-overview). The published figure
is input+output combined, which is how this table already reads it.
Family rules follow, longest prefix first so ``minimax-m3`` keeps its 1M
window while the rest of the line takes 200K. ``glm-4.7`` and ``glm-5.2``
inherit rather than falling off the table.
Separately: Zhipu enables reasoning with ``thinking: {"type": "enabled"}``,
the same body DeepSeek takes and the shape
``_THINKING_STYLE_BUILDERS["thinking_type"]`` already builds. The spec never
declared it, so every GLM request went out with reasoning silently omitted
whatever effort the user chose. One field; the mechanism was already there.
MiniMax stays on the Auto/Off surface — it publishes no named effort ladder,
and inventing one would put dead options in the picker.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes the display half of #151, #152 and #155. They are one problem wearing three hats — a value the user picks, persisted locally, surfaced to CSS — so they share one mechanism rather than three copies of read/validate/persist. src/app/appearance.ts holds the settings as a table. Each row owns its label, its range, its sanitiser and the custom property it feeds; the machinery around it is generic. Adding a preference is a row, and the settings UI renders from the same table instead of growing another block of JSX. Notes on the three: * Width (#151) — .conversation, its composer and the goal rail each hardcoded `min(820px, 100%)`. They now read one `--conversation-width`, which is the point: three copies of a layout constant cannot stay in step by hand. 100% restores the built-in cap rather than stretching edge to edge, so the default is byte-identical to today. * Theme (#152) — the report says there is no dark theme. There is: tokens.css has carried a full dark palette behind `prefers-color-scheme` all along. What was missing is overriding the OS, which is what this adds. CSS cannot share a declaration block between a media query and a selector, so the dark palette is now written twice; styles/tokens.test.ts fails if the copies ever disagree, because two tables drifting apart is the exact bug this project just spent a day fixing in its model catalog. VS Code theme import is not here — mapping a foreign schema onto these tokens is its own piece of work. * Typography (#155) — a free-text family list rather than a dropdown. A curated menu would be a hardcoded guess about fonts the user's machine may not have; the value is a prefix of the built-in stack, so anything missing falls through instead of leaving the UI unstyled. Size is a slider. Preferences apply immediately and live in localStorage: per-machine display choices, not project configuration. The store is read through useSyncExternalStore so the shell and the settings page share one copy — per-component state would let them disagree, and threading a controller down through the workspace would be prop drilling for something neither server state nor scoped to a subtree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 6, 2026
pull Bot
pushed a commit
to ari1988/DeepCode
that referenced
this pull request
Aug 6, 2026
`ProviderSpec.thinking_style` describes the endpoint's dialect — how this API
spells a thinking toggle. It says nothing about whether the model behind it
has one, and the two were never intersected, so any model routed through a
DeepSeek- or Zhipu-compatible endpoint received:
thinking: {"type": "enabled"}
including gpt-4o and llama3, which have no thinking mode at all. The empty
`reasoning_content` echo rode along with it.
Pre-existing, but HKUDS#161 widened it by giving Zhipu a style, which is why this
is not waiting for a larger cleanup. Both halves of the answer now live in one
place: the catalog knows whether the model thinks, the spec knows how the
endpoint writes it. `model_supports_thinking()` asks the first, and the style
only applies when both agree.
Deliberately not narrowed to a vendor. A capable model reached through an
endpoint that speaks a given dialect should get that dialect — gpt-5.4 behind
a DeepSeek-compatible gateway still gets the DeepSeek body, because that is
the endpoint's contract, not a claim about who made the model.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pull Bot
pushed a commit
to ari1988/DeepCode
that referenced
this pull request
Aug 6, 2026
HKUDS#155 asked for a font dropdown. HKUDS#161 shipped free text instead, on the grounds that a fixed menu is a hardcoded guess about the user's machine. The premise was right and the conclusion was not: the browser can be asked. `document.fonts.check()` answers whether a family resolves here, so the candidate list is filtered to what is actually installed and grouped by Interface / Monospace / CJK. Picking one appends it to the field rather than replacing it, which keeps the CJK case HKUDS#155 raises working — put the CJK face alongside the Latin one and mixed text renders through a single fallback chain. The list stays advisory. Free text remains the source of truth, so a family outside the candidates is still reachable, and the candidates themselves cost nothing when absent because they are filtered out. Where the Font Loading API is missing the picker hides rather than claiming every family exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Addresses #153, #151, #152 and #155.
#153 reports that DeepSeek's thinking selector has no levels. That turned out to be one symptom of a resolver that had rotted, and chasing it surfaced two more gaps. The Desktop half of the other three reports is a separate, self-contained change in the same PR.
#153 — reasoning capabilities resolved through the model catalog
infer_reasoning_capabilities()was a chain of substring checks kept apart from the model catalog. It recognisedclaude-sonnet-4-6while the catalog had moved on toclaude-sonnet-5, so onmaintoday all of these advertise no reasoning at all:The Desktop picker builds its options from
model.reasoning, so an empty capability renders a control with nothing in it — exactly what #153 describes. The provider side was never at fault:deepseekalready carriedthinking_style="thinking_type", so the wire format had been wired all along.Both
catalog.pyandmodel_compat.pyopen by namingmodel_id.includes(...)as the anti-pattern to avoid, andcatalog.pyalready resolves every other per-model fact — context window, output cap, pricing — through anormalize → snapshot → seed → family → defaultcascade. Reasoning now joins it as aModelInfofield.profiles.pyhad been callingresolve_model_info()and the name chain on adjacent lines for the same model.Consequences beyond the report:
deepseek-r2orgpt-5.9inherits its family row instead of silently losing its controls. Shipping a model is a table row, not a code edit.claude-sonnet-4-<date>keeps the effort ladder but not summarised thinking, which it predates. Claiming it would route raw trace text through the summary channel.claude-sonnet-4.6) are listed rather than normalised away — the dot is load-bearing elsewhere in the table, wheregpt-5.4andkimi-k2.5are distinct models.Two gaps this exposed
GLM and MiniMax were resolving to the 128K default — a 200K model trimmed to 128K, and a thinking-capable one advertising nothing. Limits taken from the vendors, not guessed: GLM-4.6 is 200K/128K (docs.z.ai); the MiniMax M2 line is 204,800 with M3 at 1M (platform.minimax.io).
Zhipu never declared a
thinking_style. GLM enables reasoning withthinking: {"type": "enabled"}— the same body DeepSeek takes, and the shape_THINKING_STYLE_BUILDERS["thinking_type"]already builds. Every GLM request had been going out with reasoning silently omitted whatever effort the user picked. One field; the mechanism was already there.What this does not fix
DeepSeek still shows Auto/Off rather than low/medium/high, because its API is a binary switch — there are no named levels to expose, and inventing three would put dead options in the picker.
deepseek-reasonerhas no user-facing choice at all: reasoning is intrinsic and cannot be turned off.The dramatic change is elsewhere: Claude's current line-up goes from a single "Auto" back to the full six-option ladder.
#151 / #152 / #155 — appearance preferences
One mechanism, not three.
desktop/src/app/appearance.tsholds the settings as a table; each row owns its label, range, sanitiser and CSS custom property, and the settings UI renders from the same table..conversation, its composer and the goal rail each hardcodedmin(820px, 100%). They now read one--conversation-width. 100% restores the built-in cap, so the default is byte-identical to today.tokens.csshas carried a full dark palette behindprefers-color-schemeall along. What was missing is overriding the OS. CSS cannot share a declaration block between a media query and a selector, so the dark palette is now written twice — andstyles/tokens.test.tsfails if the copies ever disagree, because two tables drifting apart is the bug this PR's first half exists to fix. VS Code theme import is not here.Testing
tsc --noEmit,eslintandnpm run buildall cleanNot verified: the Desktop changes have not been seen running in Tauri. Unit tests and the build cover syntax and logic, not appearance. Worth a look at the dark toggle and a narrow conversation width before release.
tests/application/test_automation_goal_runs.py::test_legacy_unreserved_turn_is_never_adopted_as_automation_initial_turnfails intermittently — measured at 2/20 on cleanmain, unrelated to this work, and deserving its own issue.Known gaps, deliberately left
thinking_styleis applied per provider without consulting the model, sogpt-4orouted through a thinking-capable gateway still receives a thinking body. Pre-existing, but this PR widens it by giving Zhipu a style. Both halves of the information now exist in one place — the catalog knows the model, the spec knows the wire — so intersecting them is a focused follow-up rather than more surface here.