Skip to content

Collapse reasoning - #32148

Closed
BEEugene wants to merge 4 commits into
anomalyco:devfrom
BEEugene:collapse-reasoning
Closed

Collapse reasoning#32148
BEEugene wants to merge 4 commits into
anomalyco:devfrom
BEEugene:collapse-reasoning

Conversation

@BEEugene

Copy link
Copy Markdown

Summary

Fix TUI rendering of fragmented reasoning from extended-thinking models
(notably MiniMax-M3, but the same pattern hits DeepSeek-R1, GLM-Z1, and
any OpenAI-compatible provider that streams both reasoning_content and
echoes it in content).

Closes #31999

Root cause

Certain reasoning-capable providers (DeepSeek-style, MiniMax-M3,
GLM-Z1) stream the model's reasoning_content as discrete
reasoning-start / reasoning-delta / reasoning-end events
(packages/opencode/src/session/processor.ts:371-425) AND also echo
the same text into the regular content field for back-compat. opencode
correctly persists both as separate parts, but the TUI rendered them
as dozens of "Thought:" boxes plus a duplicate text paragraph in the
same message.

Dedupe at the TUI layer is the right place because:

  • Not every provider does this (don't strip in the provider layer)
  • The echoed text often has the final response appended after the echo
    (we have to preserve that tail, not just drop the text)

Changes (4 commits on collapse-reasoning)

  1. chore: gitignore local build/log artifacts
  2. fix(tui): explicitly register spinner intrinsic to survive bundler tree-shaking
    — Bun.build drops side-effect-only import "opentui-spinner/solid",
    was causing [Reconciler] Unknown component type: spinner at runtime
  3. fix(plugin): warn instead of fail on tui plugin loader errors
    — loader already filters failed plugins downstream; fail() was just noise
  4. fix(tui): collapse fragmented reasoning parts and strip thinking echoes from text
    — main fix for Too many thought messages with MiniMax-M3 #31999, see commit body for full algorithm

Test plan

  • Reproduces on dev before fix (verified locally)
  • Fix verified on local build of all 4 commits
  • Tested with the original repro from Too many thought messages with MiniMax-M3 #31999
  • No regression on non-reasoning responses
    (the "text has new content not in reasoning" branch keeps them as-is)
  • Pre-push bun turbo typecheck (29 packages) passed on local

BEEugene added 4 commits June 12, 2026 23:57
…ee-shaking

Bun's bundler drops the side-effect-only `import "opentui-spinner/solid"`
when compiling the TUI. Confirmed by `extend({spinner` being absent from
the bundled binary while the `dots` (cli-spinners default) string was
present. Result: `[Reconciler] Unknown component type: spinner` at
runtime for any session that renders a Spinner.

Switch to a named import of SpinnerRenderable and an explicit
`extend({spinner})` call. Both bindings are used, so the bundler cannot
drop the registration.
The plugin loader already filters out failed plugins, so the report.error
callback's `fail()` calls were producing noise without blocking anything.
Convert all four stages (install, compatibility, entry, default) to
`warn()` so the TUI surfaces the issue but doesn't make the process
unstartable on a transient plugin error (e.g. a network blip on
`git ls-remote`).
…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
@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label Jun 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@github-actions

Copy link
Copy Markdown
Contributor

Hey! Your PR title Collapse reasoning doesn't follow conventional commit format.

Please update it to start with one of:

  • feat: or feat(scope): new feature
  • fix: or fix(scope): bug fix
  • docs: or docs(scope): documentation changes
  • chore: or chore(scope): maintenance tasks
  • refactor: or refactor(scope): code refactoring
  • test: or test(scope): adding or updating tests

Where scope is the package name (e.g., app, desktop, opencode).

See CONTRIBUTING.md for details.

@BEEugene BEEugene closed this Jun 13, 2026
@BEEugene
BEEugene deleted the collapse-reasoning branch June 13, 2026 05:16
@BEEugene

Copy link
Copy Markdown
Author

Split into 3 smaller focused PRs per CONTRIBUTING.md ("Keep pull requests small and focused"). See #NEW_SPINNER_URL, #NEW_PLUGIN_URL, #NEW_REASONING_URL.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs:compliance This means the issue will auto-close after 2 hours. needs:title

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Too many thought messages with MiniMax-M3

1 participant