Skip to content

desktop: model picker shows "No reasoning" for all V2 models — capabilities.reasoning is never set #50257

Closed

Description

@zhengkaics

Description

The desktop model picker tooltip shows "No reasoning" for every V2 model — including models that clearly support reasoning (e.g. opencode-go/deepseek-v4.1-flash, opencode-go/kimi-k3, opencode-go/glm-5.3, …).

Root cause

The tooltip decides based on model.capabilities.reasoning (desktop bundle, app.asar):

model.capabilities
  ? (model.capabilities.reasoning ? t(`model.tooltip.reasoning.allowed`) : t(`model.tooltip.reasoning.none`))
  : (model.reasoning ? t(`model.tooltip.reasoning.allowed`) : t(`model.tooltip.reasoning.none`))

However, the V2 model API (GET /api/model) never sets capabilities.reasoning:

  • 82 / 82 models have a capabilities object (tools, input, output)
  • 0 / 82 have capabilities.reasoning
  • 0 / 82 have a top-level reasoning field

Because capabilities is always present, the model.reasoning fallback is unreachable, so the tooltip always reports "No reasoning".

Evidence

opencode api GET /api/model → opencode-go/deepseek-v4.1-flash:

{
  "id": "deepseek-v4.1-flash",
  "providerID": "opencode-go",
  "family": "deepseek-flash",
  "capabilities": { "tools": true, "input": ["text", "image"], "output": ["text"] },
  "variants": [
    { "id": "low", "settings": { "reasoningEffort": "low" } },
    { "id": "high", "settings": { "reasoningEffort": "high" } },
    { "id": "max", "settings": { "reasoningEffort": "max" } }
  ],
  "compatibility": { "reasoningField": "reasoning_content" }
}

The corresponding models.dev catalog entry has "reasoning": true, but the V2 model transform does not carry it into capabilities.

Impact

Display-only — reasoning itself works. Requests are sent with reasoningMode: "effort" / reasoningEffort: "max" and responses contain reasoning tokens; only the tooltip is wrong. It affects all V2 models, not just DeepSeek.

Environment

  • OpenCode Desktop 2.0.11, macOS (Apple Silicon)
  • Server-side check: GET /api/model (all 82 models missing the field)

Suggested fix

Either populate capabilities.reasoning from the catalog reasoning field in the V2 model transform, or fall back to the presence of reasoning variants in the tooltip logic.

Activity

  1. zhengkaics commented on Sep 21, 2026

    @zhengkaics
    Author

    修复已提交:#50283。根因不只是 tooltip 读取的字段——models.dev 目录里的 reasoning 标志在构造 V2 Model.Capabilities 时被丢掉,桌面端 mapper 又硬编码了 reasoning: false。PR 把这个标志从目录一路传到应用 store,tooltip 本身不用改。

  2. 8mdphgg5yn-eng commented on Sep 30, 2026

    @8mdphgg5yn-eng

    Additional data point — still reproducible on the latest release (2.0.20).

    • OpenCode Desktop 2.0.20 (macOS, Apple Silicon), CLI @opencode/cli 2.0.20, latest channel — the updater reports 2.0.20 is the newest build
    • GET /api/model on this machine: 160 models, 0 with capabilities.reasoning; 72 have reasoning variants
    • e.g. deepseek/deepseek-flash: capabilities: { tools: true, input: ["text","image"], output: ["text"] }, variants: none / low / high / max, compatibility.reasoningField: "reasoning_content" → tooltip shows 推理: 不支持推理
    • Also affects built-in catalog models such as minimax-cn/MiniMax-M3 — not only custom providers
    • Confirmed display-only: reasoning works through the variant selector, reasoning tokens are counted in context stats, and requests carry reasoningEffort
    • Re-confirmed there is no config escape hatch: a model-level reasoning: true is dropped by the V2 schema, canonical inherits family/variants/cost but not the flag, and capabilities.output: ["text","reasoning"] is ignored
    • 👍 for fix(core): expose model reasoning capability #50283 — the catalog reasoning flag is dropped when V2 Model.Capabilities is built, and the app mapper hardcodes reasoning: false
  3. junhuwh commented on Oct 9, 2026

    @junhuwh

    Following up from #54195 (closed as a duplicate of this issue): the same desktop adapter that hardcodes reasoning: false also hardcodes two other capability flags in the same object:

    capabilities:{
      temperature:!1,   // always false — never derived
      reasoning:!1,     // always false (this issue)
      attachment: e.capabilities.input.some((e) => e !== `text`), // derived correctly
      toolcall: e.capabilities.tools,                             // derived correctly
      interleaved:!1,   // always false — never derived
      ...
    }

    So beyond the "No reasoning" tooltip, any UI reading capabilities.temperature or capabilities.interleaved is also wrong for every model.

    Both are derivable from data the server already has:

    • temperature — the models.dev catalog entry has temperature: true for these models (e.g. nvidia/deepseek-ai/deepseek-v4.1-flash, opencode/mimo-v2.6-flash-free).
    • interleaved — the catalog carries interleaved: { field: "reasoning_content" }, and the server already maps this into compatibility.reasoningField on Model.Info.

    If PR #50283 is threading the reasoning flag through from catalog to app capabilities, it may be worth deriving these two in the same pass so the capability set doesn't stay half-wrong.

    Verified on Desktop 2.0.26 (Windows 11): the effort/variant selector works correctly (it reads the separate variants array), so this remains display-only.

  4. AirMonkeys-Kun commented on Oct 11, 2026

    @AirMonkeys-Kun

    Adding an update for anyone landing here: this is still unfixed on the current dev (default branch), and the existing fix PRs were closed by automation, not by technical review.

    Still broken in latest source (checked against ref=dev)

    • packages/schema/src/model.ts (L90-94): Model.Capabilities is still only { tools, input, output } — no reasoning field.
    • packages/app/src/runtime/server/global-sync/utils.ts:84: the app mapper still hardcodes reasoning: false.

    Reproducible on the latest desktop release 2.0.26 (current latest per the official update manifest), same as #54195 / #54229.

    The fix PRs were closed without review

    PR approach closed by
    #48417 derive the legacy UI flag from variants automation: "no linked issue" within the 2h window (2026-09-11), never reviewed
    #50283 add optional reasoning to Capabilities, populate it from the models.dev catalog, map it in the app automation: V1→V2 dev promotion sweep (2026-10-10 18:30:41Z), after ~3 weeks waiting for first review
    #53683 preserve reasoning in the v1→v2 config migration same sweep (2026-10-10 18:53:49Z), verified on Windows

    All three were closed by the v1-pr-dev-promoted-v2 auto-close template ("…port it over and open it for V2"), not by a maintainer deciding the change was wrong.

    Scope note

    Purely a tooltip/label bug: V2 exposes reasoning through variants (thinking-level selector) and compatibility.reasoningField (supportsEffortUpdates), and the intensity selector works in-session — only the model-picker tooltip ("No reasoning") is misleading.

    Maintainers: would a fresh PR against dev implementing either approach (thread capabilities.reasoning through the chain as in #50283, or derive the flag from variants as in #48417) be welcome? Happy to open one.

  5. zhengkaics commented on Oct 13, 2026

    @zhengkaics
    Author

    Update: the fix has been ported to the current V2 line and re-opened as #55100 (it is a port of #50283, which the V1→V2 promotion sweep closed). Same minimal approach: optional reasoning on Model.Capabilities, populated from the models.dev catalog materialization, threaded through mergeCapabilities, and the app mapper derives the flag instead of hardcoding false. Typecheck 36/36 and the affected app/core tests pass against dev.

  6. tiftazani commented on Oct 15, 2026

    @tiftazani

    Same issue here. Additional data points in case useful:

    Environment

    • opencode desktop 2.0.29 (bundled opencode-cli v2.0.29), channel: latest
    • macOS Darwin 27.0.0, arm64
    • Custom provider via @ai-sdk/openai-compatible (local router), no plugins

    Observations

    • Every model from the custom provider shows Reasoning: No reasoning in the model picker tooltip, while Context correctly reflects the configured limit.context (1,000,000).
    • In the desktop bundle (app.asar), the V2 → legacy model adapter builds capabilities with reasoning:!1 hardcoded, so the tooltip can never show "Allows reasoning" regardless of config.
    • Setting "reasoning": true on the model in opencode.jsonc has no effect on 2.0.29; the server log drops it:
    level=WARN message="configuration normalization diagnostic" source=~/.config/opencode/opencode.jsonc path=$.provider.<provider>.models.<model>.reasoning kind=unsupported action="omitted unsupported legacy setting"
    
    • Restarting the app does not change anything.
    • The bundled TUI in 2.0.29 derives reasoning from variants (reasoning:(model.variants?.length??0)!==0), so a similar derivation in the desktop adapter (or exposing a config field for it) would fix the label.

    Reasoning itself does work at request time for models that emit it; only the tooltip label is wrong.

  7. yukunmeng commented on Oct 15, 2026

    @yukunmeng

    Additional data point: the hardcoded reasoning: false is not only in the desktop app.asar — the Web UI bundle served by the CLI itself has it too, and I can show the tooltip value cannot be influenced by server data or configuration.

    Environment

    • opencode 2.0.29 (@opencode/cli, npm global install), Windows 11 (NT 10.0.26200)
    • Shell cmd.exe, no plugins
    • Web UI served by opencode serve --port 4096

    Where the hardcode lives (CLI-served web bundle)

    GET /_assets/runtime-*.js contains the v1 → app model adapter with three flags hardcoded:

    capabilities:{
      temperature:!1,
      reasoning:!1,
      attachment:e.capabilities.input.some(e=>e!==`text`),
      toolcall:e.capabilities.tools,
      interleaved:!1,
      ...
    }

    The tooltip component (/_assets/incompatible-server-panel-*.js) reads exactly that value, and its model.reasoning branch is unreachable because the adapter always attaches a capabilities object:

    e.model.capabilities
      ? (e.model.capabilities.reasoning ? t(`model.tooltip.reasoning.allowed`) : t(`model.tooltip.reasoning.none`))
      : (e.model.reasoning ? t(`model.tooltip.reasoning.allowed`) : t(`model.tooltip.reasoning.none`))

    A/B proof — same browser, same config, only the bundle differs

    Entry point opencode/nemotron-3.5-lightning-free → 推理 row
    http://127.0.0.1:4096 (stock bundle) 不支持推理
    http://127.0.0.1:8789 (same server, runtime-*.js rewritten to reasoning:!!e.compatibility?.reasoningField) 支持推理

    Everything else in the same tooltip is already correct (提供商 OpenCode, 输入 文本, 上下文 262,144), and that context value matches GET /api/model for the same model:

    {
      "id": "nemotron-3.5-lightning-free",
      "providerID": "opencode",
      "capabilities": { "tools": true, "input": ["text"], "output": ["text"] },
      "compatibility": { "reasoningField": "reasoning_content" },
      "limit": { "context": 262144, "output": 262144 }
    }

    So the data needed for the row is present on the client; only capabilities.reasoning is wrong. No server-side change alone can fix the label, which also means a config workaround cannot exist until the adapter maps the flag (as in #50283) or the tooltip derives it from compatibility.reasoningField / variants.

    Related

  8. zhengkaics commented on Oct 15, 2026

    @zhengkaics
    Author

    Thanks both — two useful confirmations on 2.0.29.

    1. CLI-served web UI: that bundle is built from the same source as the desktop adapter — packages/app (assetsDir: "_assets"), served by packages/cli/src/services/web-ui.ts — so this is one hardcode in one source location, not two. The open PR fix(core): expose model reasoning capability #55100 fixes it for both the desktop app.asar and the CLI's /_assets/runtime-*.js.

    2. Config declaration: a bare models.<id>.reasoning in a V2-style file is V1 syntax, which normalizes as "unsupported legacy setting" (expected, exactly as your log line shows). Once fix(core): expose model reasoning capability #55100 lands, the supported declaration is capabilities: { reasoning: true } — the config overlay schema previously mapped only tools/input/output and silently stripped it; genuine V1 files are migrated and keep reasoning instead of dropping it.

    To be clear: #55100 is under review and not released yet, so the tooltip will keep showing "No reasoning" on 2.0.29 until it ships.

    (The variants-derived display the TUI uses is the alternative approach from #48417; #55100 is the capabilities-threading one from #50283.)

  9. wangzishi commented on Oct 15, 2026

    @wangzishi

    Fresh repro on Desktop 2.0.29 (Windows 11, bundled opencode-cli 2.0.29) plus a derivation caveat for #55100 that hasn't been quantified yet.

    Repro data (4 provider types, 83 models)

    • opencode (44), opencode-go (29), xiaomi + xiaomi-token-plan-cn (6+4, @opencode/ai/providers/openai-compatible)
    • 83/83: capabilities is {tools, input, output} only — 0 have capabilities.reasoning, 0 have top-level reasoning
    • tooltip shows 推理: 不支持推理 for every model

    Caveat: neither single derivation signal is sufficient

    Splitting the same 83-model inventory by the two reasoning signals that ARE present in GET /api/model:

    variants (reasoningEffort/thinkingConfig) compatibility.reasoningField count
    variants only ✓ – 40
    reasoningField only – ✓ 24
    both ✓ ✓ 19
    neither – – 0
    • The TUI's variants.length !== 0 derivation (noted in this thread) would correctly label 59 models but still show "No reasoning" for the 24 reasoningField-only models — including every xiaomi*/mimo-* and */longcat-2.5-preview-free, which do stream thinking via reasoning_content.
    • The A/B patch from @yukunmeng (reasoning: !!e.compatibility?.reasoningField) covers 43 but misses the 40 variants-only models.

    So the client mapper should derive capabilities.reasoning ||= !!compatibility.reasoningField || variants.length > 0 — on this inventory that is 83/83 correct with zero false positives, using data the server already sends. (Once #55100 threads the catalog flag through, this OR also acts as the fallback for models absent from models.dev.)

  10. benjohnbarnes commented on Oct 16, 2026

    @benjohnbarnes

    Independent repro on Desktop 2.0.31 — BYOK deepseek provider, where reasoning is demonstrably working

    Adding a data point for a provider type not covered above: the built-in deepseek provider (BYOK, package @opencode/ai/providers/deepseek), model deepseek/deepseek-flash.

    GET /api/model → deepseek/deepseek-flash:

    {
      "id": "deepseek-flash",
      "providerID": "deepseek",
      "package": "@opencode/ai/providers/deepseek",
      "compatibility": { "reasoningField": "reasoning_content" },
      "capabilities": { "tools": true, "input": ["text", "image"], "output": ["text"], "temperature": true },
      "variants": [
        { "id": "none", "body": { "thinking": { "type": "disabled" } } },
        { "id": "low",  "settings": { "reasoningEffort": "low"  }, "body": { "thinking": { "type": "enabled" } } },
        { "id": "high", "settings": { "reasoningEffort": "high" }, "body": { "thinking": { "type": "enabled" } } },
        { "id": "max",  "settings": { "reasoningEffort": "max"  }, "body": { "thinking": { "type": "enabled" } } }
      ]
    }
    • capabilities is { tools, input, output, temperature } — no reasoning key, matching the pattern reported above for other providers.
    • Model picker tooltip shows "No reasoning".

    This model is not merely declared as reasoning — it is observably reasoning

    With the variant recorded as default for the entire session (i.e. no user-applied variant, so this is the out-of-the-box path), every assistant message in session_message (opencode.db) carries a reasoning content part — including the first message, which predates any local config file:

    seq time (local) variant reasoning part present
    6 13:21:10 default yes
    21 13:23:58 default yes
    ... ... default yes
    242 13:32:07 default yes

    The empty default variant sends neither thinking nor reasoning_effort, so DeepSeek's own defaults apply (thinking enabled, effort high), reasoning_content is streamed back via compatibility.reasoningField, and OpenCode stores it as a reasoning part.

    So for this model the tooltip is not merely conservative — it asserts the opposite of the actual behaviour.

    Environment

    • opencode v2.0.31 (desktop app, bundled opencode-cli 2.0.31), channel: latest
    • macOS Darwin 24.6.0 (arm64)
    • Shell: bash; no config plugins
    • Reproduced consistently
  11. added 2 commits that reference this issue on Oct 17, 2026
    5c59ada
    956b6ee
  12. xuyongroger commented on Oct 17, 2026

    @xuyongroger

    Confirming on 2.0.31 with a scenario that is not covered by the models.dev catalog path — a custom OpenAI-compatible provider whose model is declared entirely in opencode.json.

    Model config (redacted):

    {
      "providers": {
        "acme": {
          "package": "@opencode/ai/providers/openai-compatible",
          "settings": { "baseURL": "https://llm.internal.example/v1" },
          "models": {
            "qwen-flash": {
              "settings": { "reasoningEffort": "xhigh" },
              "capabilities": {
                "reasoning": { "supported": true, "effort": ["low", "medium", "xhigh", "none"] },
                "tools": true
              }
            }
          }
        }
      }
    }

    GET /api/model returns the reasoning controls as generated variants, and capabilities still has no reasoning field at all:

    {
      "id": "qwen-flash",
      "providerID": "acme",
      "settings": { "reasoningEffort": "xhigh" },
      "capabilities": { "tools": true, "input": ["text", "image"], "output": ["text"] },
      "variants": [
        { "id": "low", "settings": { "reasoningEffort": "low" } },
        { "id": "medium", "settings": { "reasoningEffort": "medium" } },
        { "id": "xhigh", "settings": { "reasoningEffort": "xhigh" } },
        { "id": "none", "settings": { "reasoningEffort": "none" } }
      ]
    }

    So the picker shows model.tooltip.reasoning.none ("No reasoning" / 不支持推理) even though reasoning works end to end for this model — the same session has 38 type: "reasoning" parts and usage like "tokens": { "input": 575, "output": 116, "reasoning": 147 }. The TUI is unaffected because it derives support from model.variants.length > 0; only the app mapper in packages/app/src/runtime/server/global-sync/utils.ts hard-codes reasoning: false while mapping variants correctly one line below.

    Two notes for #55100:

    1. Materializing reasoning from the models.dev catalog fixes the catalog case, but models that only exist via user config (self-hosted / gateway providers, and the new "generate reasoning variants from reasoning options" behavior) have no catalog entry to read from. Would it be worth deriving the flag as capabilities.reasoning === true || model.variants.length > 0 in the app mapper as a belt-and-braces fallback, so config-declared reasoning controls map to true as well?
    2. If Model.Capabilities.reasoning is going to be an explicit field, it would be good if the server also reported it for config-declared reasoning controls, not just for catalog flags — that is the difference between "listed in models.dev" and "actually able to reason" for a lot of local / proxied models.
  13. cookiecathub commented on Oct 17, 2026

    @cookiecathub

    Confirmed on macOS (desktop app 2.0.31, Darwin 27.0.0 arm64) with the NVIDIA provider (integrate.api.nvidia.com, @opencode/ai/providers/openai-compatible).

    • GET /api/model returns 41 NVIDIA models; 0/41 include capabilities.reasoning, but they do include variants (e.g. none/thinking).
    • Example: nvidia/z-ai/glm-5.3 → capabilities:{tools,input,output,temperature}, variants:[none,thinking]. The tooltip shows "No reasoning".
    • The model does reason: opencode run -m "nvidia/nvidia/nemotron-3.5-lightning-30b-a3b#thinking" returned a reasoning block (reasoningField: reasoning_content), and a session with nvidia/z-ai/glm-5.3 stored 61 reasoning tokens.
    • Root cause re-confirmed in the bundle: the V2→legacy adapter (app.asar, out/renderer/assets/main-*.js) builds capabilities:{temperature:!1, reasoning:!1, ...} hard-coded, so the tooltip (capabilities.reasoning) always reports "No reasoning". Suggested fix: derive reasoning support from variants.

    Environment: opencode 2.0.31, Darwin 27.0.0 (darwin arm64), web/desktop client, shell /bin/zsh, no relevant local plugins.

  14. 1 remaining item

  15. yukunmeng commented on Oct 20, 2026

    @yukunmeng

    Same root cause, still reproducible on 2.0.32 (Windows x64, Chinese UI). Additional measurements that may help to close this out:

    • GET /api/model on a live 2.0.32 server returns 47 models: 0 / 47 have capabilities.reasoning, 0 / 47 have a top-level reasoning field, and all 47 have capabilities present — so the tooltip's model.reasoning fallback is unreachable.
    • In packages/core/src/models-dev.ts, modelInfo() builds capabilities from tools / input / output / temperature only; models.dev's top-level reasoning: true and reasoning_options are only converted into variants by supports(), never copied into capabilities.
    • Reasoning execution is unaffected: sessions on opencode/space-bunny-free#max and opencode/minimax-m3.1-flash-free#max report tokens.reasoning of 71263 and 16437 respectively.

    One config-related detail, since the issue mentions the config path: Config.Model.Capabilities.reasoning in the config schema is the v2 object shape (supported / effort / budget / toggle) and is only used to build reasoning variants, while the runtime Model.Capabilities type has no reasoning field at all. Defining providers.<id>.models.<model>.capabilities.reasoning therefore cannot change what the tooltip reads. It also replaces (rather than merges with) the models.dev record: the model name falls back to the raw id, limit falls back to {context: 200000, output: 32000}, the video modality is lost and compatibility.reasoningField is dropped.

    Full details in the comment on #53520.

  16. zhengkaics commented on Oct 20, 2026

    @zhengkaics
    Author

    Thanks for measuring this on 2.0.32 — the 0/47 result matches the root cause. #55100 carries the catalog's reasoning flag through to Model.Capabilities, and the app falls back to compatibility.reasoningField / non-none variants when the catalog is silent.

    One correction on the config path: in #55100 the config's rich capabilities.reasoning is reduced to that runtime boolean in mergeCapabilities, so { "supported": true } does reach the tooltip. The metadata loss you saw (name / limit / modalities / compatibility falling back to defaults) is separate from the reasoning flag and probably worth its own issue.

  17. cripperzprodigy commented on Oct 23, 2026

    @cripperzprodigy

    Independent repro on v2.1.3 (Linux, self-hosted server) — and a config-path data point

    Confirming this is still present on v2.1.3 (latest as of 2026-10-23), with a server where the models are declared in opencode.json (custom providers) and the config path is exercised:

    • opencode-go: 28 models → 0/28 have capabilities.reasoning; 27/28 have variants (e.g. deepseek-v4.1-flash → variants: [low, high, max]).
    • GET /api/model for opencode-go/deepseek-v4.1-flash returns capabilities: {tools,input,output,temperature} — no reasoning, no top-level reasoning.
    • The Config.Model.Capabilities schema does have reasoning (config side), and providers.<id>.models.<id>.capabilities.reasoning validates and is retained in opencode debug config — but Model.Capabilities (API response) has no such field, so it is stripped before the client sees it.
    • Adding explicit variants to models does not change the tooltip (the tooltip reads capabilities.reasoning).

    So the client has no signal at all, regardless of config. This matches the modelInfo()/mergeCapabilities analysis above; #55100 ("fix(core): expose model reasoning capability", still OPEN targeting dev) looks like the correct fix. Adding this data point so the config path is covered by the test matrix: after #55100, {"capabilities":{"reasoning":{"supported":true}}} from opencode.json should reach the tooltip.

    Repro environment: opencode v2.1.3, opencode serve --service, 149 models across 10 providers, 116 with variants.

  18. MercuryOoO commented on Oct 25, 2026

    @MercuryOoO

    Fresh repro on Desktop 2.1.4 (latest channel, Windows 11 x64, 2026-10-25) — still broken on the newest build.

    Measurements (GET /api/model, live server):

    • 71 models total: 0/71 have capabilities.reasoning, 0/71 have a top-level reasoning; 71/71 have capabilities present → the tooltip's model.reasoning fallback stays unreachable
    • 31/71 expose compatibility.reasoningField, 28/71 have effort variants (low/medium/high/…) — reasoning works at runtime, only the capability flag is missing

    Config-path data point (custom providers declared in opencode.jsonc):

    "my_local_proxy_litellm_pii": {
      "compatibility": { "reasoningField": "reasoning_content" },
      "models": {
        "ds-flash-free": {
          "capabilities": {
            "reasoning": { "supported": true, "effort": ["low", "medium", "high"] }
          }
        }
      }
    }
    • Provider-level compatibility.reasoningField merges correctly → visible for 14/14 models in /api/model
    • capabilities.reasoning never reaches the store: the config capabilities merge keeps only tools/input/output/temperature, so the declared effort is dropped and variants remain whatever the package fallback generated

    Bundle check confirms the picker still branches on model.capabilities.reasoning (app.asar, model.tooltip.reasoning.*).

    Hoping #55100 lands — the config path above should be covered by it.

  19. added 2 commits that reference this issue on Oct 25, 2026
    79c23b3
    9f75069
  20. maksiorlo commented on Oct 26, 2026

    @maksiorlo

    Still reproducible on OpenCode Desktop 2.1.4 (macOS 26 / Darwin 25.6.0, Apple Silicon), and it affects the free OpenCode Zen model too, not just V2 paid ones.

    What the UI shows

    Model picker → free model opencode/deepseek-v4.1-flash-free → tooltip:

    • Model: DeepSeek V4.1 Flash
    • Provider: OpenCode
    • Input: text, image
    • Reasoning: "No reasoning" (RU: «Без рассуждений»)
    • Context limit: 200,000

    Reasoning actually works on the free tier

    The catalog says reasoning: true with toggle + effort [low, high, max]:

    • providers/opencode/models/deepseek-v4.1-flash-free.toml on models.dev
    • local catalog cache (kv → models-dev:catalog)

    Empirical check through the bundled CLI (same endpoint, --format json --thinking), one prompt: "Do not use any tools. Compute 87341 * 92837 in your head and reply with just the number."

    Variant reasoning block
    #low 564 chars of CoT
    #high 733 chars
    #max 616 chars
    default (no variant) 699 chars

    The stream contains a real reasoning part with metadata.reasoningField: "reasoning_content", e.g. on #max the model decomposed the multiplication, cross-checked it two ways, and answered 8,108,476,417. No "variant unavailable" error for any variant.

    Same root cause in current dev

    packages/app/src/runtime/server/global-sync/utils.ts still hardcodes:

    capabilities: {
      temperature: false,
      reasoning: false,
      ...
    }

    So this is display-only: the request carries reasoningEffort, the response carries reasoning_content, only the tooltip lies. Deriving reasoning from the catalog flag (or from variants.length > 0) would fix the free model as a side effect.

  21. opencodejr commented on Oct 28, 2026

    @opencodejr

    Thanks for the detailed report. I was able to confirm this.

    On 2.0.11, I started opencode serve and queried GET /api/model?all=true. No model in the catalog has capabilities.reasoning (or a top-level reasoning). That includes models that offer reasoningEffort variants and are marked reasoning: true in models.dev, such as DeepSeek Flash. On top of that, the app's model mapper (packages/app/src/runtime/server/global-sync/utils.ts) hard-codes capabilities.reasoning: false, so the tooltip can only ever show "No reasoning". I didn't open the desktop app itself; the API output and that code already settle it.

    It still happens on the latest dev (fa1260d). The script below reproduces the API side.

    Open PR #55100 addresses this. The later duplicates (#53520, #54229, #56317, #58901) were closed in favour of this issue.

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Reproduces #50257: no V2 model carries a reasoning capability, so the app's
    # model tooltip ("Reasoning") reports "No reasoning" for every model.
    # Usage: ./repro.sh            -> uses `opencode` from PATH
    #        OC_DEV=/path/to/repo ./repro.sh -> runs the CLI from that checkout
    set -euo pipefail
    PORT=${PORT:-4111}
    WORK=$(mktemp -d)
    export HOME="$WORK/home"; mkdir -p "$HOME" "$WORK/proj"; cd "$WORK/proj"
    if [ -n "${OC_DEV:-}" ]; then
      OC=(bun --conditions=browser --preload="$OC_DEV/packages/tui/node_modules/@opentui/solid/scripts/preload.js" "$OC_DEV/packages/cli/src/index.ts")
    else
      OC=(opencode)
    fi
    echo "version: $("${OC[@]}" --version)"
    "${OC[@]}" serve --port "$PORT" --hostname 127.0.0.1 > "$WORK/serve.log" 2>&1 &
    PID=$!; trap 'kill $PID 2>/dev/null || true' EXIT
    for _ in $(seq 60); do grep -q "server password" "$WORK/serve.log" 2>/dev/null && break; sleep 1; done
    PASS=$(grep "server password" "$WORK/serve.log" | awk '{print $3}')
    # wait until the models.dev catalog has loaded
    for _ in $(seq 30); do
      curl -s -u "opencode:$PASS" "http://127.0.0.1:$PORT/api/model?all=true" > "$WORK/models.json"
      grep -q '"capabilities"' "$WORK/models.json" && break; sleep 1
    done
    python3 - "$WORK/models.json" <<'EOF'
    import json, sys
    d = json.load(open(sys.argv[1]))["data"]
    print("models:", len(d))
    print("with capabilities.reasoning:", sum("reasoning" in m["capabilities"] for m in d))
    print("with top-level reasoning:", sum("reasoning" in m for m in d))
    print("with reasoningEffort variants:", sum(any("reasoningEffort" in (v.get("settings") or {}) for v in m.get("variants") or []) for m in d))
    m = next((m for m in d if m["providerID"] == "deepseek"), None) or next(m for m in d if m.get("variants"))
    print("example:", json.dumps({k: m.get(k) for k in ("id", "providerID", "capabilities", "variants")})[:600])
    EOF

    Run it: bash repro.sh

  22. added a commit that references this issue on Oct 28, 2026
    32a123a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions