fix: render think tags as reasoning blocks + increase MCP MaxListeners#15389
Open
erwinh22 wants to merge 2 commits intoanomalyco:devfrom
Open
fix: render think tags as reasoning blocks + increase MCP MaxListeners#15389erwinh22 wants to merge 2 commits intoanomalyco:devfrom
erwinh22 wants to merge 2 commits intoanomalyco:devfrom
Conversation
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.
Contributor
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
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.
Issue for this PR
Closes #11439
Type of change
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:
splitThinkBlocks()/stripThinkTags()utilities inpackages/util/src/think.tsandpackages/opencode/src/util/format.tsTextPartnow extracts reasoning and renders it as a dimmed/bordered block (only when thinking display is enabled)TextPartDisplayrenders extracted reasoning in areasoning-partdivrun.tsstrips think tags from output; shows reasoning with--thinkingflagMCP MaxListeners fix (related):
EventEmitterlimit of 10 is easily exceeded because eachStdioClientTransportadds listeners to stdin/stdout/stderr plusdrainlisteners for concurrent protocol requestsEventEmitter.defaultMaxListeners+ per-stream limits before connectingHow 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