Uh oh!
There was an error while loading. Please reload this page.
fix: render think tags as reasoning blocks + fix MCP MaxListeners overflow - #15382
fix: render think tags as reasoning blocks + fix MCP MaxListeners overflow#15382erwinh22 wants to merge 2 commits into
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). Fixesanomalyco#15380 Relates to anomalyco#11439
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
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. |
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.
bb4e9f4 to
c92846cCompareThis pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window. Feel free to open a new pull request that follows our guidelines. |
It would be great to get this landed, it's been a pretty annoying UX issue when using EDIT: I found a workaround in LM Studio at least: |
Summary
Two fixes in this PR:
1. Render
<think>tags as reasoning blocks at display layerModels like Kimi K2 Thinking embed reasoning in raw
<think>...</think>tags within text parts. Previously these were displayed as raw HTML to the user.Approach: Parse and render
<think>/<thinking>tags at the display layer (TUI, Web UI, CLI) rather than stripping them from the message storage. This preserves the full model output for multi-turn context while giving users clean, styled reasoning blocks.Changes:
packages/util/src/think.ts-- Shared utility:stripThinkTags(),splitThinkBlocks()with regex matching both<think>and<thinking>variantspackages/opencode/src/util/format.ts-- Same utilities for the core package (@/util/formatpath alias)packages/opencode/src/cli/cmd/tui/routes/session/index.tsx--TextPartrenders think blocks with dim/italic/bordered styling matchingReasoningPartpackages/ui/src/components/message-part.tsx--TextPartDisplayrenders think blocks in<div data-component="reasoning-part">packages/opencode/src/cli/cmd/run.ts-- CLI strips think tags from output, shows reasoning with--thinkingflag2. Fix MCP MaxListeners overflow with many servers
When 7+ stdio-based MCP servers are configured, the default Node.js EventEmitter limit of ~10 is exceeded. Each
StdioClientTransportspawns 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 adrainlistener.Fix: Dynamically calculate the needed listener limit based on the number of configured local MCP servers and increase both
EventEmitter.defaultMaxListenersand process stream limits before connecting.Changes:
packages/opencode/src/mcp/index.ts-- Count local MCP servers, setEventEmitter.defaultMaxListenersand process stream limits proportionallyRelated Issues
<think>and<thinking>tags as reasoning blocks #11439 (think tag rendering regression from removal ofextractReasoningMiddleware)Testing
bun turbo typecheck-- 0 errors in opencode and ui packages