Skip to content

fix: render think tags as reasoning blocks + increase MCP MaxListeners#15389

Open
erwinh22 wants to merge 2 commits intoanomalyco:devfrom
erwinh22:fix/think-tag-rendering
Open

fix: render think tags as reasoning blocks + increase MCP MaxListeners#15389
erwinh22 wants to merge 2 commits intoanomalyco:devfrom
erwinh22:fix/think-tag-rendering

Conversation

@erwinh22
Copy link

@erwinh22 erwinh22 commented Feb 27, 2026

Issue for this PR

Closes #11439

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Models like DeepSeek R1, QwQ, and other reasoning models emit <think>/<thinking> tags inline in text parts instead of using the dedicated reasoning content block. These tags leak into rendered output as raw HTML in both the TUI and web UI.

Think-tag fix:

  • Added splitThinkBlocks() / stripThinkTags() utilities in packages/util/src/think.ts and packages/opencode/src/util/format.ts
  • TUI TextPart now extracts reasoning and renders it as a dimmed/bordered block (only when thinking display is enabled)
  • Web UI TextPartDisplay renders extracted reasoning in a reasoning-part div
  • CLI run.ts strips think tags from output; shows reasoning with --thinking flag

MCP MaxListeners fix (related):

  • With 7+ local MCP servers, the default EventEmitter limit of 10 is easily exceeded because each StdioClientTransport adds listeners to stdin/stdout/stderr plus drain listeners for concurrent protocol requests
  • Dynamically calculates the needed limit from the local MCP server count and raises EventEmitter.defaultMaxListeners + per-stream limits before connecting

How did you verify your code works?

Tested locally with DeepSeek R1 and QwQ models that emit <think> tags in streaming responses. Verified tags no longer leak into TUI/web rendered output and reasoning blocks display correctly. MCP fix verified with 8 local MCP servers — no more MaxListeners warnings.

Screenshots / recordings

N/A — TUI text rendering change; reasoning blocks render with dim/italic styling behind a left border.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Models that emit <think>/<thinking> tags (e.g. kimi-k2-thinking via
NVIDIA NIM) currently display raw tag content in the UI instead of
rendering it as collapsible reasoning blocks.

The extractReasoningMiddleware was intentionally removed in PR anomalyco#11270
because stripping tags at the middleware layer prevents them from being
stored in messages, breaking multi-turn LLM context.

This fix takes the rendering-layer approach: tags are preserved in
storage for faithful multi-turn context, but parsed and rendered as
reasoning blocks at display time in all three rendering surfaces:

- TUI: TextPart in session/index.tsx uses splitThinkBlocks() to
  separate reasoning from display text, rendering reasoning with the
  same dim/italic styling as native ReasoningPart
- Web UI: TextPartDisplay in message-part.tsx renders extracted
  reasoning in a reasoning-part div above the text content
- CLI: run.ts strips think tags from text output and shows reasoning
  with dim/italic styling when --thinking flag is set

Shared utility functions (stripThinkTags, splitThinkBlocks) are added
to both @opencode-ai/util/think (for web UI) and the core
packages/opencode/src/util/format.ts (for TUI and CLI).

Fixes anomalyco#15380
Relates to anomalyco#11439
Each StdioClientTransport spawns a child process and adds listeners to
its stdin/stdout/stderr pipes. The MCP protocol layer also sends many
concurrent requests (getPrompt, listTools, etc.) that each add a
'drain' listener on the child process's stdin. With 7+ MCP servers,
the default Node.js EventEmitter limit of ~10 is easily exceeded,
causing MaxListeners overflow warnings and potential crashes.

Dynamically calculate the needed limit based on the number of configured
local (stdio) MCP servers and increase both EventEmitter.defaultMaxListeners
and process stream limits before connecting.
@github-actions github-actions bot added needs:compliance This means the issue will auto-close after 2 hours. and removed needs:compliance This means the issue will auto-close after 2 hours. labels Feb 27, 2026
@github-actions
Copy link
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

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.

Support parsing <think> and <thinking> tags as reasoning blocks

1 participant