fix(tui): collapse fragmented reasoning parts and strip thinking echo…#32152
Open
BEEugene wants to merge 1 commit into
Open
fix(tui): collapse fragmented reasoning parts and strip thinking echo…#32152BEEugene wants to merge 1 commit into
BEEugene wants to merge 1 commit into
Conversation
…es from text Some models (notably MiniMax-M3 with extended thinking, but also DeepSeek-R1, GLM-Z1, and other OpenAI-compatible reasoning providers) fragment a single line of reasoning across many short ReasoningPart chunks, sometimes interleaved with TextPart echoes of the same content. The TUI rendered this as dozens of "Thought: Xms" boxes plus a duplicate text paragraph in the assistant message. Root cause: certain providers stream the model's `reasoning_content` field as discrete reasoning events AND also echo the same text into the regular `content` field for back-compat. opencode's `SessionProcessor.handleEvent` correctly persists both as separate parts (`processor.ts:371-425`), but the TUI then renders them as duplicates. Dedupe at the TUI layer is the right place because not every provider does this, and the echoed text often contains the final response appended after the echo (we have to keep that tail). - Aggregate ALL reasoning parts into one block at the top of the message (not just consecutive ones; the model can interleave non-reasoning parts in between). - Strip `<think>`/`</mm:think>` tags individually from text parts. The opening/closing tags often end up split across the reasoning/text boundary, so a paired regex misses them. - Dedup on a normalized fingerprint (lowercase, whitespace + Unicode punctuation stripped) and use substring `includes` (not prefix/suffix only) so 4-part alternating streams A B A' B' are caught. - For text parts that begin with the merged reasoning, strip the echo prefix and keep only the tail (the actual response) using a character-level diff that skips whitespace/punctuation on both sides. A debug logging hook (`DEBUG_DEDUP_LOG = false` by default) writes the part sequence, fingerprints, and merge decisions to displayparts.log when enabled, kept for future regressions. Closes anomalyco#31999
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR #3: reasoning-dedup (Closes #31999, probably, also #20782, #20706, #11439, and some related)
Issue for this PR
Closes #31999
Type of change
What does this PR do?
Some OpenAI-compatible reasoning providers (notably MiniMax-M3, but also DeepSeek-R1, GLM-Z1) stream the model's
reasoning_contentfield as discretereasoning-start/reasoning-delta/reasoning-endevents (packages/opencode/src/session/processor.ts:371-425) and also echo the same text into the regularcontentfield for back-compat. opencode correctly persists both as separate parts, but the TUI rendered them as dozens of "Thought: Xms" boxes plus a duplicate text paragraph in the same message.Dedupe at the TUI layer in
packages/tui/src/routes/session/index.tsx:ReasoningParts into one block at the top of the message.<think>/</think>tags from text parts (opening/closing often split across the boundary, so a paired regex misses them).includes(not prefix/suffix) so 4-part alternating streamsA B A' B'collapse correctly.A debug logging hook (
DEBUG_DEDUP_LOG = falseby default) writes the part sequence and merge decisions todisplayparts.logwhen enabled.How did you verify your code works?
bun typecheckclean onpackages/tuiandpackages/opencode.bun turbo typecheck(29 packages) on the local fork passed for the touched packages. Two unrelated packages (@opencode-ai/stats-appand@opencode-ai/enterprise) have pre-existing typecheck failures ondev(verified by checking out cleanorigin/devand runningbun run typecheckin each — both exit 2 on the unmodified upstream). Unrelated to this PR; flagged here so reviewers aren't surprised.Screenshots / recordings
TUI before fix (two near-duplicate reasoning paragraphs, no collapsed display):
[screenshot the user has, or describe: dozens of "Thought: Xms" boxes for one line of thinking, then the same text echoed in a paragraph below] PR #3: reasoning-dedup (Closes #31999)
Issue for this PR
Closes #31999
Type of change
What does this PR do?
Some OpenAI-compatible reasoning providers (notably MiniMax-M3, but also DeepSeek-R1, GLM-Z1) stream the model's
reasoning_contentfield as discretereasoning-start/reasoning-delta/reasoning-endevents (packages/opencode/src/session/processor.ts:371-425) and also echo the same text into the regularcontentfield for back-compat. opencode correctly persists both as separate parts, but the TUI rendered them as dozens of "Thought: Xms" boxes plus a duplicate text paragraph in the same message.Dedupe at the TUI layer in
packages/tui/src/routes/session/index.tsx:ReasoningParts into one block at the top of the message.<think>/</think>tags from text parts (opening/closing often split across the boundary, so a paired regex misses them).includes(not prefix/suffix) so 4-part alternating streamsA B A' B'collapse correctly.A debug logging hook (
DEBUG_DEDUP_LOG = falseby default) writes the part sequence and merge decisions todisplayparts.logwhen enabled.How did you verify your code works?
bun typecheckclean onpackages/tuiandpackages/opencode.ghbdtnand Englishghbdtn rfr jyj&): reasoning collapsed to one block, no duplicate text paragraph, actual response preserved.bun turbo typecheck(29 packages) on the local fork passed for the touched packages. Two unrelated packages (@opencode-ai/stats-appand@opencode-ai/enterprise) have pre-existing typecheck failures ondev(verified by checking out cleanorigin/devand runningbun run typecheckin each — both exit 2 on the unmodified upstream). Unrelated to this PR; flagged here so reviewers aren't surprised.Screenshots / recordings
TUI before fix (two near-duplicate reasoning paragraphs, no collapsed display):

[screenshot the user has, or describe: dozens of "Thought: Xms" boxes for one line of thinking, then the same text echoed in a paragraph below]
TUI after fix (one merged reasoning block, clean response):

[screenshot: single "Thought: 1.9s" header, single reasoning paragraph, then the response "Hi! How can I help you?" on its own]
Checklist