Skip to content

Streamed replies render one token per line with OpenAI-compatible providers that pad empty reasoning fields #2226

Description

@Liewzheng

Description

When using a custom OpenAI-compatible provider (chat completions), streamed assistant replies render in the TUI with one token per line, e.g.:

(
固件更新页
显示
版本
...

Root cause

Some gateways include both content and reasoning_content in every stream chunk, with the inactive one set to an empty string:

{"choices":[{"delta":{"content":"固件","reasoning_content":""}}]}

In packages/kosong, the OpenAI chat-completions provider (openai-legacy) yields a think part whenever the reasoning field is a string — including "". The part stream therefore alternates think("") → text → think("") → text → …. Downstream, mergeInPlace only merges adjacent same-type parts, so the text deltas can no longer merge and the assembled message ends up with one text part per token. The TUI renders each text part as its own block → a line break per token.

Providers whose gateway omits the reasoning field entirely during the content phase (e.g. DeepSeek's official API) are unaffected.

Reproduction

  1. Configure an openai-type provider whose gateway pads "reasoning_content":"" into every chunk (observed with GLM-class models behind an OpenAI-compatible subscription gateway).
  2. Ask anything and watch the streamed reply: every token lands on its own line.

Expected behavior

Empty reasoning strings are ignored, text deltas stay adjacent, and the reply renders as continuous text.

Fix

I have a ready patch: skip empty reasoning strings at both yield sites (stream and non-stream) in the provider, with regression tests. PR incoming.

Metadata

Metadata

Assignees

No one assigned

    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