Skip to content

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

Closed as not planned

Description

@DevSissi

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

Activity

  1. github-actions commented on Jan 31, 2026

    @github-actions
    Contributor

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

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

  2. DevSissi commented on Jan 31, 2026

    @DevSissi
    Author

    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.

  3. ghost91- commented on Jan 31, 2026

    @ghost91-
    Contributor

    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.

  4. rekram1-node commented on Jan 31, 2026

    @rekram1-node
    Collaborator

    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

  5. DevSissi commented on Jan 31, 2026

    @DevSissi
    Author

    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!

  6. erwinh22 commented on Feb 27, 2026

    @erwinh22

    I've submitted PR #15382 implementing the rendering-layer approach suggested here:

    Shared utility functions (splitThinkBlocks, stripThinkTags) handle both and tag variants with non-greedy regex matching.

  7. meglio commented on Mar 7, 2026

    @meglio
    1. Besides cosmetics, will the lack of parsing degrade the operation/function of OpenCode? Does it introduce any risks? (for example Tool call parser scans inside <think> blocks, creating false-positive parse attempts from reasoning content lmstudio-ai/lmstudio-bug-tracker#1592)
    2. What's the current workaround, if any?

    P.S. I connected qwen3-next-80b-a3b-thinking-mlx through LMStudio, and it of course spills out <think> tags.

  8. JDNdeveloper commented on Mar 7, 2026

    @JDNdeveloper
    1. Besides cosmetics, will the lack of parsing degrade the operation/function of OpenCode? Does it introduce any risks?
    2. What's the current workaround, if any?

    P.S. I connected qwen3-next-80b-a3b-thinking-mlx through LMStudio, and it of course spills out <think> tags.

    In LM Studio, have you tried enabling: Settings -> Developer -> Experimental Settings -> "When applicable, separate reasoning_content and content in API responses"?

  9. meglio commented on Mar 7, 2026

    @meglio

    Tried just now, did not help:

    Image
  10. emerzon commented on Mar 8, 2026

    @emerzon

    In LM Studio, have you tried enabling: Settings -> Developer -> Experimental Settings -> "When applicable, separate reasoning_content and content in API responses"?

    Thanks, that worked for me!

  11. TzYuiro commented on Mar 8, 2026

    @TzYuiro
    1. Besides cosmetics, will the lack of parsing degrade the operation/function of OpenCode? Does it introduce any risks?
    2. What's the current workaround, if any?

    P.S. I connected qwen3-next-80b-a3b-thinking-mlx through LMStudio, and it of course spills out <think> tags.

    In LM Studio, have you tried enabling: Settings -> Developer -> Experimental Settings -> "When applicable, separate reasoning_content and content in API responses"?

    Telling LM Studio to enable its separation of reasoning_content and content can cause random running agent terminations. There's extensive work about this problem:

    https://www.reddit.com/r/LocalLLaMA/comments/1riwhcf/psa_lm_studios_parser_silently_breaks_qwen35_tool/

    If I'm not mistaken, disabling that option prevents the harness from sometimes suddenly stopping when using LM Studio. It's a parsing problem on LM Studio's part that causes our OpenCode to receive empty content fields in the response, with everything having been placed into the thinking's reasoning_content instead by LM Studio's messaging. The result is the OpenCode harness believing there's nothing left to do and so any running agent work just suddenly stops, ending on OpenCode's aesthetic thinking block (instead of a normal, non-thinking assistant message section).

    It's not pretty, but currently not trusting the LM Studio parser to correctly separate thinking and non-thinking message parts is the safe and function option. Until either LM Studio addresses its parser or OpenCode uses a heuristic on its end to separate thinking and non-thinking message parts itself.

    I'm thinking a simple atomic logic operation is all that's needed to determine when to start and stop OpenCode's aesthetic thinking block at the beginning of each message? Like, begin the aesthetic thinking block in the UI whenever <think> is at the message beginning and end the block when </think> is reached? Because currently OpenCode relies on logic that delegates through inversion of control, instead of, basically, having some sort of node within its message parsing process that gives it some responsibility for message formatting. Right now, I mean, it's just a simple if statement whose predicate is one function call result, through an interface contract that we should now be suspicious of.

    EDIT:

    Wow, I just noticed that there's a commit that possibly does fix this already:

    erwinh22@8cd9f45

    I'm not on the bleeding edge, I have OpenCode installed through Arch Linux's Pacman, so I probably don't have that update yet.

  12. added a commit that references this issue on Apr 10, 2026
    fc36b7c
  13. mishl-dev commented on Apr 14, 2026

    @mishl-dev
    Image

    minimax-m2.7 uses tag

  14. assigned and unassigned on Apr 26, 2026
  15. zuigzm commented on May 18, 2026

    @zuigzm

    Please optimize this issue, which is not fatal but is still annoying. Thank you.

  16. github-actions commented on Jul 18, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

  17. psi00 commented on Aug 9, 2026

    @psi00

    I would like to bump this as I've started using a different custom provider that leaks content in tags as plaintext in Opencode.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions