Skip to content

fix(openai): blank line between reasoning summary parts - #436

Open
mafredri wants to merge 1 commit into
charmbracelet:mainfrom
coder:mafredri/upstream-reasoning-summary-paragraphs
Open

mafredri wants to merge 1 commit into
charmbracelet:mainfrom
coder:mafredri/upstream-reasoning-summary-paragraphs

Conversation

@mafredri

Copy link
Copy Markdown

OpenAI Responses reasoning summaries arrive as parts that each start with a bold title, for example **Assessing word count**\n\nI need to.... Stream emitted a single "\n" before each part, and Generate joined the parts with "\n". Markdown treats a single newline as a soft break, so every title after the first renders at the end of the previous paragraph: ...before finalizing! **Assessing word count**.

Change

  • Stream emits "\n\n" before each summary part, including the first.
  • Generate joins the parts with "\n\n".

The blank line before the first part is for consumers that concatenate reasoning deltas across reasoning items. A response can contain several reasoning items, and the deltas do not mark where one ends, so without it the next item's title follows the previous sentence with no space. Generate returns each item as its own ReasoningContent, so it still adds no leading blank line.

ResponsesReasoningMetadata.Summary still holds each part verbatim, and toResponsesPrompt does not replay reasoning, so requests are unchanged.

OpenRouter and Vercel build text from openai-responses reasoning details the same way (providers/openrouter/language_model_hooks.go, providers/vercel/language_model_hooks.go). This PR leaves them unchanged.

Tests

responses_reasoning_summary_test.go covers Stream (an item with two parts, then an item with one part) and Generate (an item with two parts). Both tests fail with the old separator.

Coder runs this change in its fork as coder#67.

🤖 This PR was created with the help of Coder Agents.

OpenAI Responses reasoning summary parts each open with a bold title
line. Stream separated them with a single newline, which Markdown treats
as a soft break, so every title after the first rendered at the end of
the previous paragraph. Stream now sends a blank line before each part,
and Generate joins the parts with one.

Stream also puts the blank line before the first part. Its deltas do not
mark where one reasoning item ends, so a consumer that joins consecutive
items would otherwise glue the next item's title onto the previous
sentence. Generate returns each item as its own ReasoningContent and
adds no leading blank line.

ResponsesReasoningMetadata.Summary still holds each part verbatim.
mafredri added a commit to coder/coder that referenced this pull request Oct 27, 2026
OpenAI reasoning summaries stream as parts that each open with a bold
title. Fantasy sent a single newline before each part, which Markdown
renders as a space, so the chat UI showed every title after the first
at the end of the previous paragraph. #30551 stopped the UI from
dropping that newline, but one newline is still not a paragraph break.

The fantasy pin moves to coder/fantasy#67, which sends a blank line
before every summary part. The first part gets one too, because the UI
joins adjacent reasoning items into one thinking block. Messages saved
before this keep the single newline. Upstream as
charmbracelet/fantasy#436.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant