Skip to content

[Bug] Reasoning content mixed into content field and pushed word-by-word, causing Thought block spam in TUI #40865

Closed as not planned

Description

@Jsliu28

Environment

  • opencode version: 1.18.x (Windows 11, PowerShell terminal)
  • Model: deepseek-v4-flash (accessed via an OpenAI-compatible interface)
  • Access method: third-party relay/proxy service (OpenAI-compatible protocol)

Problem description

When using a relay/proxy, the model's reasoning content is not placed in the standard reasoning_content field. Instead, it is mixed directly into the content field and pushed with extremely fine granularity (1-4 characters per chunk), word by word.

opencode renders the beginning of the content stream (the reasoning text before the actual answer) as a Thought block. Because the granularity is so fine, this produces a spam of one Thought line per chunk, causing the terminal to scroll uncontrollably.

Steps to reproduce

  1. Configure an OpenAI-compatible relay provider and connect a reasoning-capable model (e.g. deepseek-v4-flash)
  2. Send a complex question that triggers deep reasoning
  3. Observe the TUI: during the reasoning phase, a large number of single-line Thought indicators scroll by rapidly, spamming the screen

SSE evidence (raw captured data)

1. reasoning_content is empty in every chunk

Out of 2081 streamed chunks captured from the same request, 0 chunks carried a non-empty reasoning_content field:

data: {"id":"xxx-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":"","reasoning_content":"","function_call":null,"refusal":"","tool_calls":[]},"finish_reason":""}],"usage":null}

2. Reasoning text is pushed word-by-word inside content

2079 non-empty content chunks, with the reasoning text cut into 1-4 character fragments:

data: {"id":"xxx-...","choices":[{"index":0,"delta":{"content":"这个","reasoning_content":"","..." }},"finish_reason":""}]}
data: {"id":"xxx-...","choices":[{"index":0,"delta":{"content":"问题","reasoning_content":"","..." }},"finish_reason":""}]}
data: {"id":"xxx-...","choices":[{"index":0,"delta":{"content":"涉及","reasoning_content":"","..." }},"finish_reason":""}]}
data: {"id":"xxx-...","choices":[{"index":0,"delta":{"content":"分布式","reasoning_content":"","..." }},"finish_reason":""}]}
...

3. Chunk length statistics

  • Total chunks: 2081
  • Chunks with non-empty reasoning_content: 0
  • Non-empty content chunks: 2079
  • Total content characters: 4438
  • Average chunk length: 2.1 characters
  • 93% of chunks are ≤ 4 characters long

4. Structure of the concatenated content

After concatenating all 2079 chunks, the content reads as: a reasoning preamble ("I will build the solution via ...") followed by the full answer. There is no protocol-level separator between the reasoning section and the actual answer (no reasoning_content, no thinking field), so opencode cannot distinguish between them.

Expected behavior

Reasoning content should be delivered via a standard field (e.g. OpenAI's reasoning_content / Anthropic's thinking), or there should be a recognizable boundary between the reasoning text and the answer inside the content stream. opencode should not misclassify the leading text of an ordinary content stream as a reasoning block and render it per chunk.

Impact

  • Terminal spam, making the conversation unreadable
  • The /thinking toggle cannot hide these lines (it can only hide proper reasoning blocks; this content arrives via the content field, so hiding has no effect)
  • No way to keep the model's reasoning ability while removing the visual noise

Notes

The root cause is likely the relay service's non-compliant passthrough of the upstream reasoning stream. At the same time, opencode's recognition/rendering strategy for reasoning text inside the content stream could be more robust (e.g. coalescing rendering when detecting a burst of consecutive high-frequency short chunks). Responsibility attribution is up to the opencode maintainers.

Attachments

  • Full SSE capture file: wb_stream2.txt (681KB, 2081 chunks)
  • Analysis script: analyze_stream.py (paths contain no service-provider information)

Activity

  1. github-actions commented on Aug 6, 2026

    @github-actions
    Contributor

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

  2. Jsliu28 commented on Aug 7, 2026

    @Jsliu28
    Author

    补充证据:opencode 1.18.15 未合并 reasoning delta,每个 chunk 创建独立 part

    在 issue #40865 基础上补充实测证据(来源:本地 opencode 1.18.15 + 数据库 opencode.db + 抓包 + 二进制分析)。

    1. 数据库铁证:每条消息的 reasoning 被拆成几百个独立 part

    会话 ses_028f25532ffel29lZXVCp1Z69W(普通对话)共 8412 个 type: "reasoning" part,每条助手消息数百个:

    msg=msg_fd70f83d0001: 454 parts, 其中 451 个 reasoning
    msg=msg_fd71593f4001: 718 parts, 其中 714 个 reasoning
    msg=msg_fd70ddaa3001: 13 parts, 其中 10 个 reasoning
    

    每个 part 只有 1-3 个字符,且为独立 part(各自 id、各自 time.start/end):

    {"type": "reasoning", "text": "用户", "time": {"start": ..., "end": ...}}
    {"type": "reasoning", "text": "的", "time": {"start": ..., "end": ...}}
    {"type": "reasoning", "text": "的是", "time": {"start": ..., "end": ...}}
    {"type": "reasoning", "text": "为什么", "time": {"start": ..., "end": ...}}

    2. 抓包证据:流本身是干净的

    对 workbuddy 中转(OpenAI 兼容)抓取的 SSE 流:

    • 1740 个 reasoning_content chunk 连续出现,随后 660 个 content chunk
    • reasoning 与 content 仅交替 1 次,流结构完全正常
    • 但每个 reasoning chunk 只有 1-4 字符(83% ≤ 4 字符),粒度极细

    3. 二进制证据:1.18.15 缺少 reasoning 合并逻辑

    对 opencode.exe(1.18.15,178MB)二进制搜索:

    • 含 reasoning-delta / reasoning-start / reasoning_content / reasoning_text
    • 不含 isActiveReasoning(AI SDK v6 中用于"同一 reasoning 区间只发一次 start"的合并标志)

    对照 AI SDK v6 源码 packages/openai-compatible/src/chat/openai-compatible-chat-language-model.ts:515-539:

    const reasoningContent = delta.reasoning_content ?? delta.reasoning;
    if (reasoningContent) {
      if (!isActiveReasoning) {        // ← 有这个标志才能合并
        controller.enqueue({ type: 'reasoning-start', id: 'reasoning-0' });
        isActiveReasoning = true;
      }
      controller.enqueue({ type: 'reasoning-delta', id: 'reasoning-0', delta: reasoningContent });
    }

    结论:opencode 1.18.15 打包的 AI SDK 为旧版本,缺少 isActiveReasoning 去重。当上游(中转服务)以极细粒度(1-4 字符/chunk)推送 reasoning_content 时,每个 chunk 都被当作独立 reasoning 区间 → 创建独立 reasoning part → TUI 渲染为独立 + Thought 块 → 刷屏。

    预期行为

    无论上游 chunk 粒度多细,同一个流内的所有 reasoning_content delta 都应合并为一个 reasoning part(对应一个 Thought 块)。

    影响

    • 所有经 OpenAI 兼容接口接入、返回 reasoning_content 的细粒度流都会刷屏
    • 用户无法通过配置缓解(/thinking 折叠无效,因为每个块都是独立 part)

    建议修复

    1. 将 opencode 依赖的 AI SDK 升级到含 isActiveReasoning 合并逻辑的版本(≥ 2.0.41)
    2. 或在 opencode 消费侧(packages/opencode/src/session/processor.ts 的 reasoningMap)按 reasoning 区间合并,而非每个 delta 新建 part
  3. github-actions commented on Oct 8, 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.

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