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

Open
opened 2026-02-16 18:09:13 -05:00 by yindo · 5 comments
Owner

Originally created by @DevSissi on GitHub (Jan 31, 2026).

Originally assigned to: @thdxr on GitHub.

Description

In v1.1.45, opencode supported parsing <think> tags in model responses and displaying them as special reasoning blocks in the UI. This was useful for adding thinking capability to models that don't have native extended thinking support (e.g., via system prompts or custom providers).

However, since v1.1.46, this functionality no longer works. Both <think> and <thinking> tags are now rendered as plain text mixed into the response content.

Root Cause

PR #11270 removed the extractReasoningMiddleware from packages/opencode/src/session/llm.ts:

// packages/opencode/src/session/llm.ts
- extractReasoningMiddleware({ tagName: "think", startWithReasoning: false }),

Use Case

Many users add thinking capability to models via proxy services or custom providers. Typical implementation flow:

Model emits reasoning event
       ↓
Detect thinking content, mark isThinking=true
       ↓
Output in different formats based on config:
  - reasoning_content field (DeepSeek native)
  - <think>...</think> tag (OpenAI style)
  - <thinking>...</thinking> tag (Claude style)
       ↓
Client parses and displays as reasoning block

Core logic:

// 1. Detect thinking content
if (reasoning.text) {
  onChunk(reasoning.text, undefined, true)  // isThinking=true
}

// 2. Output based on format config
if (isThinking) {
  if (format === 'think') {
    send({ content: `<think>${text}</think>` })
  } else if (format === 'thinking') {
    send({ content: `<thinking>${text}</thinking>` })
  } else {
    send({ reasoning_content: text })  // DeepSeek format
  }
}

In v1.1.45, extractReasoningMiddleware correctly parsed <think> tags. After removal, these tags can only be displayed as plain text.

Suggested Solution

Add a configuration option to re-enable tag-based reasoning extraction:

extractReasoningFromTags: {
  enabled: true,
  tagNames: ["think", "thinking"]
}

Plugins

omo

OpenCode version

  • Broken in: v1.1.46+
  • Working in: v1.1.45

Operating System

Windows 11

Terminal

Windows Terminal

Originally created by @DevSissi on GitHub (Jan 31, 2026). Originally assigned to: @thdxr on GitHub. ## Description In v1.1.45, opencode supported parsing `<think>` tags in model responses and displaying them as special reasoning blocks in the UI. This was useful for adding thinking capability to models that don't have native extended thinking support (e.g., via system prompts or custom providers). However, since v1.1.46, this functionality no longer works. Both `<think>` and `<thinking>` tags are now rendered as plain text mixed into the response content. ## Root Cause PR #11270 removed the `extractReasoningMiddleware` from `packages/opencode/src/session/llm.ts`: ```diff // packages/opencode/src/session/llm.ts - extractReasoningMiddleware({ tagName: "think", startWithReasoning: false }), ``` ## Use Case Many users add thinking capability to models via proxy services or custom providers. Typical implementation flow: ``` Model emits reasoning event ↓ Detect thinking content, mark isThinking=true ↓ Output in different formats based on config: - reasoning_content field (DeepSeek native) - <think>...</think> tag (OpenAI style) - <thinking>...</thinking> tag (Claude style) ↓ Client parses and displays as reasoning block ``` **Core logic:** ```typescript // 1. Detect thinking content if (reasoning.text) { onChunk(reasoning.text, undefined, true) // isThinking=true } // 2. Output based on format config if (isThinking) { if (format === 'think') { send({ content: `<think>${text}</think>` }) } else if (format === 'thinking') { send({ content: `<thinking>${text}</thinking>` }) } else { send({ reasoning_content: text }) // DeepSeek format } } ``` In v1.1.45, `extractReasoningMiddleware` correctly parsed `<think>` tags. After removal, these tags can only be displayed as plain text. ## Suggested Solution Add a configuration option to re-enable tag-based reasoning extraction: ```typescript extractReasoningFromTags: { enabled: true, tagNames: ["think", "thinking"] } ``` ## Plugins omo ## OpenCode version - **Broken in**: v1.1.46+ - **Working in**: v1.1.45 ## Operating System Windows 11 ## Terminal Windows Terminal
Author
Owner

@github-actions[bot] commented on GitHub (Jan 31, 2026):

This issue might be a duplicate of existing issues. Please check:

  • #6337: [FEATURE]: support extracting reasoning XML - requests support for extractReasoningMiddleware() to parse <think> tags, the exact same missing functionality
  • #4952: <think> tags not properly parsed into block - reports that <thinking> tags aren't being properly parsed into reasoning blocks
  • #4033: MiniMax M2 <think> tags - earlier request for rendering <think> tags in the UI
  • #7779: GLM 4.7 thinking process not properly formatted - shows thinking content display issues related to tag extraction
  • #5630: Amazon Bedrock Kimi2 Thinking output - thinking content rendered as raw text instead of proper formatting
  • #10996: Kimi for Coding (k2p5) fails with tool calls - missing reasoning_content handling due to extraction issues
  • #7866: WebUI thinking/reasoning blocks hidden after response completion - users unable to view thinking blocks

Feel free to ignore if none of these address your specific case.

@github-actions[bot] commented on GitHub (Jan 31, 2026): This issue might be a duplicate of existing issues. Please check: - #6337: [FEATURE]: support extracting reasoning XML - requests support for extractReasoningMiddleware() to parse `<think>` tags, the exact same missing functionality - #4952: `<think>` tags not properly parsed into block - reports that `<thinking>` tags aren't being properly parsed into reasoning blocks - #4033: MiniMax M2 `<think>` tags - earlier request for rendering `<think>` tags in the UI - #7779: GLM 4.7 thinking process not properly formatted - shows thinking content display issues related to tag extraction - #5630: Amazon Bedrock Kimi2 Thinking output - thinking content rendered as raw text instead of proper formatting - #10996: Kimi for Coding (k2p5) fails with tool calls - missing `reasoning_content` handling due to extraction issues - #7866: WebUI thinking/reasoning blocks hidden after response completion - users unable to view thinking blocks Feel free to ignore if none of these address your specific case.
Author
Owner

@DevSissi commented on GitHub (Jan 31, 2026):

Additional Finding

After further investigation, I found that opencode has its own thinking mechanism:

1. opencode expects <think> tags in responses

In prompt.ts, there's code that strips <think> tags in certain scenarios (e.g., title generation):

const cleaned = text
  .replace(/<think>[\s\S]*?<\/think>\s*/g, "")

2. The PR #11270 intent

The PR title says the middleware was "preventing <think> blocks from being sent back as assistant message content".

This means opencode intentionally wants <think> tags to remain as plain text in the response, rather than being intercepted and converted to reasoning events.

3. Current behavior with different formats

Format Behavior in v1.1.46+
<think> tag Displayed as plain text (not as reasoning block)
<thinking> tag Displayed as plain text (not as reasoning block)
reasoning_content field Completely ignored - no content shown at all

4. Potential conflict

If users add thinking capability to models via proxy (like outputting <think> tags), and opencode's own prompts (e.g., beast.txt) also trigger thinking output, there could be:

  • Duplicate thinking content
  • Inconsistent formatting
  • Confusion about which thinking content is from the model vs the proxy

Suggestion

Consider providing a configuration option that allows users to choose how <think>/<thinking> tags should be handled:

  1. Display as plain text (current behavior) - for models with native reasoning
  2. Parse as reasoning blocks (v1.1.45 behavior) - for proxy-based thinking extensions
  3. Strip completely - for users who don't want to see thinking content

This would give users flexibility based on their setup.

@DevSissi commented on GitHub (Jan 31, 2026): ## Additional Finding After further investigation, I found that opencode has its own thinking mechanism: ### 1. opencode expects `<think>` tags in responses In `prompt.ts`, there's code that strips `<think>` tags in certain scenarios (e.g., title generation): ```typescript const cleaned = text .replace(/<think>[\s\S]*?<\/think>\s*/g, "") ``` ### 2. The PR #11270 intent The PR title says the middleware was **"preventing `<think>` blocks from being sent back as assistant message content"**. This means opencode **intentionally** wants `<think>` tags to remain as plain text in the response, rather than being intercepted and converted to reasoning events. ### 3. Current behavior with different formats | Format | Behavior in v1.1.46+ | |--------|---------------------| | `<think>` tag | Displayed as plain text (not as reasoning block) | | `<thinking>` tag | Displayed as plain text (not as reasoning block) | | `reasoning_content` field | **Completely ignored** - no content shown at all | ### 4. Potential conflict If users add thinking capability to models via proxy (like outputting `<think>` tags), and opencode's own prompts (e.g., `beast.txt`) also trigger thinking output, there could be: - Duplicate thinking content - Inconsistent formatting - Confusion about which thinking content is from the model vs the proxy ### Suggestion Consider providing a configuration option that allows users to choose how `<think>`/`<thinking>` tags should be handled: 1. **Display as plain text** (current behavior) - for models with native reasoning 2. **Parse as reasoning blocks** (v1.1.45 behavior) - for proxy-based thinking extensions 3. **Strip completely** - for users who don't want to see thinking content This would give users flexibility based on their setup.
Author
Owner

@ghost91- commented on GitHub (Jan 31, 2026):

Also running into this. Our use case is that we have our own LLM inference which does not put reasoning in a separate reasoning_content field, it just outputs <think>...</think> tags around the reasoning output. It would be very nice to get this functionality back.

@ghost91- commented on GitHub (Jan 31, 2026): Also running into this. Our use case is that we have our own LLM inference which does not put reasoning in a separate `reasoning_content` field, it just outputs `<think>...</think>` tags around the reasoning output. It would be very nice to get this functionality back.
Author
Owner

@rekram1-node commented on GitHub (Jan 31, 2026):

The reason I did this is because you need to send those think tags back to the llm in the assistant message, so yeah we should prolly render them as reasoning but they actually are part of assistant message and need to be sent as such

@rekram1-node commented on GitHub (Jan 31, 2026): The reason I did this is because you need to send those think tags back to the llm in the assistant message, so yeah we should prolly render them as reasoning but they actually are part of assistant message and need to be sent as such
Author
Owner

@DevSissi commented on GitHub (Jan 31, 2026):

Thanks for the explanation! Makes sense that tags need to stay for LLM context.
Would it be possible to handle this at the rendering layer — keep raw tags in storage, render as reasoning blocks in UI.
Thanks for your work!

@DevSissi commented on GitHub (Jan 31, 2026): Thanks for the explanation! Makes sense that tags need to stay for LLM context. Would it be possible to handle this at the rendering layer — keep raw tags in storage, render as reasoning blocks in UI. Thanks for your work!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#8121