Skip to content

fix: stop OpenCode reasoning leaking into response text#514

Merged
alari76 merged 1 commit into
mainfrom
fix/opencode-reasoning-leak
Jun 14, 2026
Merged

fix: stop OpenCode reasoning leaking into response text#514
alari76 merged 1 commit into
mainfrom
fix/opencode-reasoning-leak

Conversation

@alari76

@alari76 alari76 commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Summary

When using the OpenCode provider with models that stream reasoning as field=text deltas (e.g. ollama-cloud/kimi-k2.7-code), the model's chain-of-thought was being rendered concatenated with the actual answer.

Root cause (verified against a live OpenCode 1.15 SSE capture): for this provider the turn streams as message.part.delta events only — no message.part.updated/message.updated ever arrive. Both the hidden reasoning part and the visible answer part come through as field: "text" deltas, distinguishable only by partID. The previous logic set its inReasoningPhase flag exclusively from a part.updated type=reasoning event, which never fires here, so reasoning fell through to emit('text').

Changes

  • Classify each partID as reasoning/text/other via a cached REST lookup (GET …/message/{mid}/part/{pid}).
  • Buffer a part's field=text deltas until its kind is resolved, then route: reasoning → hidden thinking summary, text → transcript.
  • message.part.updated now also populates the cache (recordPartKind) so providers/versions that send it avoid the REST round-trip.
  • New per-turn state reset; extracted emitTextDelta / appendReasoningDelta helpers.
  • On any lookup failure, defaults to showing content (never silently hides the answer).

Parts stream sequentially, so this only adds a one-localhost-round-trip (a few ms) delay to the first delta of each part, and it correctly handles agentic flows where reasoning/text/tool parts interleave.

Test plan

  • New regression test simulating the Kimi flow (field=text deltas keyed only by partID, resolved via mocked REST) — reasoning hidden, answer shown
  • tsc -b clean, eslint clean (0 errors)
  • Full opencode-process.test.ts suite green (97 tests)

🤖 Generated with Claude Code

Some providers (e.g. Kimi via ollama-cloud) stream both reasoning and
the visible answer as field=text deltas distinguished only by partID,
and never emit message.part.updated. The old inReasoningPhase flag was
only set from part.updated type=reasoning, so it never fired and the
model's chain-of-thought was emitted as visible response text.

Classify each partID via a cached REST lookup, buffering a part's deltas
until its kind is known, then routing reasoning to a hidden thinking
summary and text to the transcript. message.part.updated now also
populates the cache so providers that send it skip the lookup.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
@alari76
alari76 merged commit 4486d73 into main Jun 14, 2026
2 checks passed
@alari76
alari76 deleted the fix/opencode-reasoning-leak branch June 14, 2026 08:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant