Summary
With a custom OpenAI-compatible provider (local mlx-serve) and a user-defined variants entry that sets reasoningEffort, the variant is selected and sent (variant:"high" appears on every assistant message, reasoning_effort is in the request body), and the server returns reasoning — but no reasoning part ever reaches the session/TUI. Model: compatibility.reasoningField: "reasoning_content" from opencode.json appears not to be applied to the model definition, so reasoning streamed by the provider is silently dropped.
Environment
- opencode version: 0.0.0-beta-19378 (
opencode2 --version)
- OS: Darwin 25.6.0 (macOS, arm64)
- Terminal: iTerm.app (TERM=xterm-256color, COLORTERM=truecolor)
- Shell: /bin/zsh
- Install/channel: beta (
@opencode/cli npm bundle, opencode2)
- Active plugins: none found in config
- Provider: custom
mlx-serve (package: "@opencode/ai/providers/openai-compatible", baseURL http://127.0.0.1:11234/v1), model Qwen reasoning hybrid model, server exposes capabilities: ["chat","tool_use","streaming","vision","reasoning","json_schema"]
Reproduction
- Configure a custom OpenAI-compatible provider in
~/.config/opencode/opencode.json with a model that defines compatibility: { "reasoningField": "reasoning_content" } and variants: [{ "id": "high", "settings": { "reasoningEffort": "high" } }].
- Select the model in the TUI and set the variant to
high (Ctrl+T).
- Ask a question that triggers reasoning, e.g. a simple multi-step arithmetic word problem.
- Observe: no thinking block is rendered in the TUI.
- Inspect the session DB (
session_message table): every assistant message has model.variant: "high" but contains only text/tool content parts — zero reasoning parts (0 occurrences across 538 assistant messages in the session).
- Directly against the same server with the equivalent request (
{"reasoning_effort":"high","stream":true}): SSE deltas contain delta.reasoning_content (≈500 chars streamed), and non-streaming responses return message.reasoning_content with usage.completion_tokens_details.reasoning_tokens populated (203/293/223 for low/medium/high; 0 for none/absent).
Expected Behavior
When the variant is applied, the request sends reasoning_effort, and the server streams reasoning_content deltas, OpenCode should render/emit a reasoning part on the assistant message (as it does for built-in providers).
Actual Behavior
Reasoning output is silently dropped: no thinking appears in the TUI and no reasoning parts are stored in the session. No error is logged.
Additional Context
Config snippet (redacted, structurally verbatim):
Investigation notes from the bundle (opencode2.exe strings, openai-chat protocol): the stream step reads reasoning via a helper that checks a set of fields including the configured reasoningField ({[key]: val} — an object literal, so the value reasoning_content is the checked key, undefined is never in the set, and the loop takes the first match). The non-streaming path likewise falls back to message.reasoning_content ?? message.reasoning. That implies dropping compatibility from the config would not break the reasoning_content case, so something else in the beta-19378 pipeline loses either model.compatibility from user config or the reasoning events between provider stream and session storage — I could not pinpoint the exact line statically.
Related issues (same family, not identical): #35283 (delta schema strips unknown reasoning fields — but that is about the field name reasoning, not user config), #41294/#20815 (variant reasoningEffort dropped from request body — closed; here the request body is correct), #42876 (variant body-level fields dropped before the request).
Frequency: consistent, every message, across all sessions using this custom provider.
Workarounds tried: explicitly setting compatibility.reasoningField: "reasoning_content" (no effect observed); toggling variant via Ctrl+T (variant metadata confirms high on every message). Untested lead: the model card advertises "capabilities": { "tools": true, "input": [...], "output": [...] } without a reasoning capability — the built-in catalog's variant generator (DQ) keys off capabilities.supports.reasoning_effort, so it is plausible that user-config models with variants are handled by a different code path than catalog models.
Summary
With a custom OpenAI-compatible provider (local mlx-serve) and a user-defined
variantsentry that setsreasoningEffort, the variant is selected and sent (variant:"high"appears on every assistant message,reasoning_effortis in the request body), and the server returns reasoning — but noreasoningpart ever reaches the session/TUI. Model:compatibility.reasoningField: "reasoning_content"fromopencode.jsonappears not to be applied to the model definition, so reasoning streamed by the provider is silently dropped.Environment
opencode2 --version)@opencode/clinpm bundle,opencode2)mlx-serve(package: "@opencode/ai/providers/openai-compatible", baseURLhttp://127.0.0.1:11234/v1), model Qwen reasoning hybrid model, server exposescapabilities: ["chat","tool_use","streaming","vision","reasoning","json_schema"]Reproduction
~/.config/opencode/opencode.jsonwith a model that definescompatibility: { "reasoningField": "reasoning_content" }andvariants: [{ "id": "high", "settings": { "reasoningEffort": "high" } }].high(Ctrl+T).session_messagetable): every assistant message hasmodel.variant: "high"but contains onlytext/toolcontent parts — zeroreasoningparts (0 occurrences across 538 assistant messages in the session).{"reasoning_effort":"high","stream":true}): SSE deltas containdelta.reasoning_content(≈500 chars streamed), and non-streaming responses returnmessage.reasoning_contentwithusage.completion_tokens_details.reasoning_tokenspopulated (203/293/223 for low/medium/high; 0 for none/absent).Expected Behavior
When the variant is applied, the request sends
reasoning_effort, and the server streamsreasoning_contentdeltas, OpenCode should render/emit areasoningpart on the assistant message (as it does for built-in providers).Actual Behavior
Reasoning output is silently dropped: no thinking appears in the TUI and no
reasoningparts are stored in the session. No error is logged.Additional Context
Config snippet (redacted, structurally verbatim):
{ "model": "mlx-serve/Qwen3.8-Flash-Next-Uncensored-MLX-Serve-4bit", "providers": { "mlx-serve": { "package": "@opencode/ai/providers/openai-compatible", "settings": { "baseURL": "http://127.0.0.1:11234/v1" }, "models": { "Qwen3.8-Flash-Next-Uncensored-MLX-Serve-4bit": { "compatibility": { "reasoningField": "reasoning_content" }, "variants": [ { "id": "none", "settings": { "reasoningEffort": "none" } }, { "id": "low", "settings": { "reasoningEffort": "low" } }, { "id": "medium", "settings": { "reasoningEffort": "medium" } }, { "id": "high", "settings": { "reasoningEffort": "high" } } ] } } } } }Investigation notes from the bundle (
opencode2.exestrings,openai-chatprotocol): the stream step reads reasoning via a helper that checks a set of fields including the configuredreasoningField({[key]: val}— an object literal, so the valuereasoning_contentis the checked key,undefinedis never in the set, and the loop takes the first match). The non-streaming path likewise falls back tomessage.reasoning_content ?? message.reasoning. That implies droppingcompatibilityfrom the config would not break thereasoning_contentcase, so something else in the beta-19378 pipeline loses eithermodel.compatibilityfrom user config or the reasoning events between provider stream and session storage — I could not pinpoint the exact line statically.Related issues (same family, not identical): #35283 (delta schema strips unknown reasoning fields — but that is about the field name
reasoning, not user config), #41294/#20815 (variantreasoningEffortdropped from request body — closed; here the request body is correct), #42876 (variant body-level fields dropped before the request).Frequency: consistent, every message, across all sessions using this custom provider.
Workarounds tried: explicitly setting
compatibility.reasoningField: "reasoning_content"(no effect observed); toggling variant via Ctrl+T (variant metadata confirmshighon every message). Untested lead: the model card advertises"capabilities": { "tools": true, "input": [...], "output": [...] }without areasoningcapability — the built-in catalog's variant generator (DQ) keys offcapabilities.supports.reasoning_effort, so it is plausible that user-config models with variants are handled by a different code path than catalog models.