Repository navigation
fix(thinking): generic reasoning_effort for models with no known thinking family (fixes #252) - #253
Conversation
… family (fixes ltmoerdani#252) For any model not matched by a hardcoded family in core/registry.ts (space-bunny-free, big-pickle, hy3, ...), the per-model Thinking Effort picker rendered but its selection was silently discarded -- the request never carried a reasoning field, so the model always answered at its default effort. Two independent defects: - FallbackThinking had no payload path at all: buildPayload returned {} for every unknown model, while schema() already used metadata.reasoning to decide whether to show a picker. The UI promised a control the request ignored. - All four buildPayload call sites called thinkingProviderFor(modelId) without metadata, so this.metadata was always undefined and any metadata-driven gate could never have fired. This is also why patching a single model name could not work -- the model was never the problem, the strategy was. models.dev is now the source of truth with no per-model hardcoding: metadata.reasoning decides whether a model gets a control, its own reasoning_options decide which levels (falling back to a shared off/low/medium/high/xhigh/max ladder), and the pick is sent as a plain OpenAI-compatible reasoning_effort. Family-less models routed to non-chat endpoints (claude-* -> messages, gemini-* -> google, grok-* -> responses) are excluded, since reasoning_effort is meaningless on those wire formats and a picker there would silently do nothing. big-pickle keeps its repetition penalty (ltmoerdani#207) and now composes with the effort. Fixing this exposed a second bug in the same feature: prepareChatRequest resolved thinking config before metadata was available and without passing it, so the generic strategy dropped the per-model pick outright while the workspace setting kept working. metadata is now required on ResolveThinkingConfigInput so the compiler rejects that call shape, which also restores stripSchemaDefaultEcho (ltmoerdani#226) for generic models. Adds the opencodego.thinking.generic setting and surfaces it in the Set Thinking Effort... command. Co-Authored-By: Claude Code <noreply@anthropic.com>
Documents the generic reasoning path in docs/issues/109 (including the live models.dev evidence for which models take each path), adds the 0.7.9 changelog entry, corrects the README thinking table, and refreshes the Thinking row in ARCHITECTURE-MAP.md: fallback.ts 39 -> 93 plus pre-existing drift in glm/mimo/provider and the missing muse.ts entry. The version bump is intentionally left to a separate chore(release) commit, per repo convention. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
Nice one @ashif4ai, this is a clean fix. The two-defect diagnosis (payload returning I ran the verification locally before reviewing:
One small thing I want to keep an eye on (not a blocker): for models that declare reasoning but no Planning to merge with a merge commit to preserve both of your commits. Thanks for the thorough issue doc and changelog entry too. |
Problem
For any model that doesn't match a hardcoded thinking family in
src/core/registry.ts(space-bunny-free,big-pickle,hy3, …), the Thinking Effort control appeared in the Copilot model picker — but selecting a level did nothing. The request never carried a reasoning field, so the model always answered at its default effort.Reproduced on
space-bunny-free(Go and Zen): set the per-model Thinking Effort to a non-offlevel, send a message, and the pick is silently discarded.Root cause — two independent defects
1.
FallbackThinkinghad no payload path.buildPayload()returned{}for every unknown model, so no reasoning field could ever be emitted — whileschema()already usedmetadata.reasoningto decide whether to show a picker. The UI promised a control the request ignored.2.
buildPayloadwas never given metadata. All four production call sites calledthinkingProviderFor(modelId)with no metadata, sothis.metadatawas alwaysundefinedinsideFallbackThinking. Any metadata-driven gate would therefore never have fired, even once #1 was fixed.This is also why patching a single model name can't work — the model was never the problem, the strategy was.
Fix
models.dev is now the source of truth, with no per-model hardcoding:
metadata.reasoningdecides whether a model gets a reasoning control at all.reasoning_optionsdecide which levels; the sharedoff/low/medium/high/xhigh/maxladder only fills the gap — so a model declaring a level this build has never seen works with no code change.reasoning_effort.opencodego.thinking.genericsetting for a workspace-wide default, surfaced in the Set Thinking Effort… command.FallbackThinkingalso serves family-less models on other wire formats (claude-*→ Anthropic messages,gemini-*→ Google,grok-*→ Responses).reasoning_effortis meaningless there, so those are deliberately excluded via the registry'sendpointKindand keep today's exact no-op behaviour rather than gaining a picker that silently does nothing. Thebig-picklerepetition penalty (#207) is preserved and composes with the effort field.Follow-up bug found while testing
Fixing this exposed a second defect in the same feature:
prepareChatRequestcalledresolveThinkingConfig()before metadata was resolved and without passing it, soFallbackThinking.applyOverridediscarded the per-model pick outright. The workspace setting kept working because it flows throughbuildPayload, which does receive metadata — producing a confusing split where the Copilot dropdown did nothing but the same value in settings worked.metadatais now a required field onResolveThinkingConfigInput, so the compiler rejects that call shape and it can't silently return. This also restoresstripSchemaDefaultEcho(#226) for generic models — without metadata the picker schema is unknown, no default is stripped, and VS Code's"off"baseline would clobber the user's workspace pick.Testing
512 unit tests pass (495 before),
npm run lint7/7,npm run compileclean. 14 new cases insrc/test/thinking.test.tscover unknown reasoning-capable models, non-reasoning models, models with no metadata at all, non-chat endpoints, penalty coexistence, and a full known-family regression suite.Verified live against
https://models.dev/api.jsonto confirm which models take each path:reasoning_optionsspace-bunny-freeeffort: low,medium,high,xhigh,maxhy4effort: none,highbig-pickle,nemotron-3-ultra-free,ling-3.0-tiny-freelongcat-2.*,ling-3.1-flash-freetoggleoff/onpath (unchanged)Manually confirmed in the Extension Development Host on
space-bunny-freeandbig-pickle— the latter sends{ repetition_penalty: 1.2, reasoning_effort: "high" }athigh.Notes for review
chore(release): 0.7.xcommits, so I leftpackage.jsonat 0.7.8 and only added the## [0.7.9]CHANGELOG heading — happy to fold the bump in if you'd rather ship it here.ARCHITECTURE-MAP.mdcorrections beyond this fix. The Thinking row had pre-existing drift (glm.ts53→93,mimo.ts72→78,provider.ts78→81) and was missingmuse.tsentirely. Since I was updating the row anyway I made it accurate; happy to split that out if you'd prefer a focused diff.reasoning_effortto Anthropic/Google/Responses would be meaningless or rejected, so those models keep today's behaviour. Covering Claude properly would need an Anthropic-shapedthinkingblock — worth a separate issue if you want it.Full write-up:
docs/issues/109-20261004-issue252-generic-reasoning-effort.md🤖 Generated with Claude Code