Repository navigation
Description
[Bug] MiniMax-M3 (reasoning model) silently finishes stop and produces no text/tool parts in OpenCode 1.18.18
Summary
When using MiniMax-M3 (provider minimax-cn-coding-plan, a reasoning-capable model via OpenAI-compatible /v1/chat/completions), OpenCode 1.18.18 sometimes saves an assistant message with finish: "stop", tokens counted, only a reasoning part and no text/tool parts at all, and then waits for the next user message. From the UI perspective the model "thinks and stops" with no visible reply and no tool calls.
The same task works perfectly with deepseek-v4-flash (also a reasoning-capable model through the OpenAI-compatible layer) under the same configuration.
This looks like a sibling of #42677 (and #42300, #42287): OpenCode's stream→parts materializer is dropping parts when the upstream model returns reasoning content embedded inside delta.content rather than as a separate delta.reasoning_content field.
Environment
- OpenCode: 1.18.18 (native Tauri build)
- OS: WSL2 (Ubuntu 24.04), Linux x86_64
- Provider:
minimax-cn-coding-plan(OpenAI-compatible,https://api.minimaxi.com/v1/chat/completions) - Model:
MiniMax-M3(advertised as reasoning-capable; OMO bundled snapshot marksreasoning: true) - Plugin:
oh-my-openagent4.19.0 (with PR opentui: fatal: undefined is not an object (evaluating 'color.buffer') #4719 fix already merged) - Agent:
Sisyphus - Ultraworker - Reproducible: yes, on demand (see below)
Reproduction
- In OpenCode Web UI, open a session using
Sisyphus - Ultraworkeragent with default modelminimax-cn-coding-plan/MiniMax-M3. - Send a coding investigation task that requires the agent to (a) reason about findings, (b) decide to call a tool, and (c) write a final text report. Example: ask the agent to check a backend file for an arithmetic bug and produce a diagnosis.
- Observe the agent's first turn: it may emit one
tool_callpart correctly, then on the subsequent turn produce an assistant message with onlyreasoningcontent and notextortoolparts. - The user prompt that reliably triggers it in our setup is "继续,把结论写出来" (continue, write the conclusion) after the agent has already found the relevant code.
- Switch the model dropdown to
deepseek-v4-flashand send the same prompt → the same agent emitsreasoning+text+toolparts normally and the conversation proceeds.
Symptom (DB evidence)
Database: ~/.local/share/opencode/opencode.db
A failing turn produced this single assistant message (excerpted from message + part tables; role/model/tokens come from message.data, part.data):
msg_id: msg_00eb4ce45001G3qHLlgD3hqm0N
time_created: 2026-08-17 15:52:05
data:
role: assistant
agent: Sisyphus - ultraworker
modelID: MiniMax-M3
providerID: minimax-cn-coding-plan
finish: stop
tokens: { total: 430932, input: 430177, output: 627, reasoning: 0,
cache: { write: 0, read: 128 } }
Parts in this message:
part[0] type=step-start text_len=0
part[1] type=reasoning text_len=1741 ← only reasoning, NO text, NO tool
part[2] type=step-finish text_len=0
The 1741-char reasoning content describes the model's internal plan — including a fully-formatted bash invocation it intends to make — but finish_reason is already stop, no tool_calls were emitted, and no assistant text reply exists.
For comparison, the same prompt with deepseek-v4-flash immediately afterwards produced three assistant messages, all of which had [reasoning, text, tool] parts and finish: tool-calls/stop properly sequenced:
[15:57:38] assistant finish=tool-calls out=399 types=[reasoning, text, tool]
[15:58:32] assistant finish=tool-calls out=613 types=[reasoning, text, tool]
[15:58:59] assistant finish=stop out=932 types=[text]
This is the exact same bug signature described in #42677 ("opencode run silently saves an empty assistant stop message (tokens counted, zero parts) and exits 0"). We are seeing it not with ollama but with MiniMax's hosted OpenAI-compatible endpoint, and not on opencode run but inside an interactive Tauri/WebUI session.
Evidence: MiniMax-M3 SSE stream format does not use delta.reasoning_content
Captured by hitting https://api.minimaxi.com/v1/chat/completions directly with a Bearer key from ~/.local/share/opencode/auth.json, model=MiniMax-M3, stream=true, prompt="计算 17*23 等于多少?只输出数字":
HTTP 200
Content-Type: text/event-stream; charset=utf-8
chunk [0]: delta = { role: "assistant" }
chunk [2]: delta.content = "The user is asking me to calculate 17 * 23 and only output the number.\n\n"
chunk [4]: delta.content = "17 * 23 = 17 * 20 + 17 * 3 = "
chunk [6]: delta.content = "340 + 51 = 391\n\nThe user wants only"
chunk [8]: delta.content = "the number as output."
chunk [10]: delta.content = "\n\n391" ← thinking closes, then the answer
chunk [12]: delta = { role: "assistant" }, finish_reason: "stop"
DeepSeek's stream under the same test uses the OpenAI-standard delta.reasoning_content field separately from delta.content (visible in chunked deltas when reasoning_effort is set).
MiniMax-M3 embeds its reasoning inside delta.content wrapped in <think>...</think> tags, with no reasoning_content field anywhere. When the same chunk stream also contains a planned tool_call, both end up serialized into the same delta.content blob with custom ]<minim>...<tool_call> pseudo-tags, which OpenCode 1.18.18 does not appear to parse as separate parts.
Hypothesized root cause
OpenCode's @ai-sdk/openai-compatible provider (or its downstream message materializer) assumes reasoning content arrives in delta.reasoning_content (the OpenAI standard since o1). For MiniMax-M3 the reasoning text arrives inside delta.content together with the answer and any pseudo-tool-call XML, so:
- The provider sees only one big
content stringand never constructs a dedicated reasoning part — but does happen to attach areasoningpart (1741 chars in our example), suggesting some heuristic extraction may be running partially. - The same
contentblob is not recognized as validtextbecause it contains…markers and pseudo-tool-call tags, so OpenCode declines to materialize atextpart. - No native
tool_callsare in the stream because MiniMax's pseudo-tool-call syntax is non-standard, so notoolpart is created either. - The stream ends with
finish_reason: "stop"and OpenCode writes the message with only the (half-recognized) reasoning part — which is exactly what we see.
Net effect from the user: the agent "thinks" for one round, then the UI shows nothing and the session stalls.
Why this is hard to detect automatically
- The response is HTTP 200, complete, and tokens are counted (
out=627in our run). - No error is raised; OpenCode simply believes the turn is finished.
- It only manifests with reasoning-capable models that do not use the
delta.reasoning_contentconvention. DeepSeek and Kimi both use it and are unaffected.
Suggested investigation
- Audit the OpenAI-compatible stream consumer to see how it splits
delta.contentwhen nodelta.reasoning_contentis present. Likely it should at minimum split on<think>...</think>boundaries for providers that use that syntax. - Audit the message-write path when
finish_reason: "stop"arrives with a non-emptycontentblob containing custom XML tags. Currently it appears to drop thetextpart while retaining areasoningpart — which is the inverse of the desired behaviour. - Consider treating any
<think>...</think>substring insidedelta.contentas a reasoning part regardless ofdelta.reasoning_contentpresence, since several providers (MiniMax, possibly Qwen via certain proxies) ship reasoning content inlined incontentrather than in a separate field. - Confirm whether this intersects with opencode run silently saves an empty assistant stop message (tokens counted, zero parts) and exits 0 #42677 (ollama
gpt-oss:20b, qwen3), Session title silently empty when the title model is a reasoning model (returns only a<think>block) #42287 (session-title model returns only ``), and [Bug] v1 path drops variant body fields (e.g. \reasoning_split) for @ai-sdk/openai-compatible provider#42300 (v1 path drops variant body fields).
Workarounds (none are upstream fixes)
Until this is fixed, our options are:
- Switch the agent's primary model to a provider that emits
delta.reasoning_content(e.g.deepseek/deepseek-v4-flash,kimi-for-coding/k2p7). Confirmed working in the same session. - Write a thin local proxy that re-emits MiniMax-M3's SSE stream with reasoning split into
delta.reasoning_contentand answers intodelta.content, then point OpenCode'sbaseURLat the proxy. (We can provide a minimal Python implementation if useful.) - Disable
oh-my-openagent— does not fix this bug (we verified the same DB signature with and without OMO, because the issue is in the OpenAI-compatible provider layer, not the plugin).
Related issues
- opencode run silently saves an empty assistant stop message (tokens counted, zero parts) and exits 0 #42677 — empty assistant stop message, zero parts, tokens counted (ollama / qwen3 / gpt-oss)
- Session title silently empty when the title model is a reasoning model (returns only a
<think>block) #42287 — session title empty when title model returns only a `` block - [Bug] v1 path drops variant body fields (e.g. \reasoning_split
) for @ai-sdk/openai-compatible provider#42300 — v1 path drops variant body fields for@ai-sdk/openai-compatible
Possibly also related to the oh-my-openagent model-capability work: PR #4719 (snapshot > heuristic for supportsThinking), PR #4716 (k2p* thinking fix), PR #3666 (mark minimax and non-thinking kimi as supportsThinking:false). Those are upstream-side fixes for a different symptom, but they confirm MiniMax-M3 is currently classified as thinking-capable in OMO 4.19+ bundled snapshots, so the request side is sending reasoning-capable requests and the model is returning reasoning — OpenCode just fails to materialize it.
Activity
github-actions commentedon Aug 17, 2026
This issue might be a duplicate of existing issues. Please check:
delta.contentrather thandelta.reasoning_content)contentfield and pushed word-by-word, causingThoughtblock spam in TUI #40865: Reasoning content mixed intocontentfield causing rendering issues (same underlying provider non-compliance pattern)mvallebr commentedon Sep 4, 2026
This looks closely related to a workaround I documented here:
https://gist.github.com/mvallebr/877f2dabb5da269091d2e2725a77b218
MiniMax-M3’s OpenAI-compatible endpoint can return reasoning embedded in
delta.contentunlessreasoning_split: trueis included in the request. With that option enabled, the API returns separate reasoning fields and clean answer content. This may prevent OpenCode from saving a turn containing only areasoningpart and notextortoolpart.The configuration also explicitly sets
thinking.typetoadaptive. If this is confirmed, perhaps the same handling could eventually be incorporated into OpenCode’s default MiniMax provider, so users would not need a custom provider configuration.thdxr commentedon Oct 11, 2026
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (
opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊