Repository navigation
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
OpenAI Responses reasoning summaries arrive as parts that each start with a bold title, for example
**Assessing word count**\n\nI need to....Streamemitted a single"\n"before each part, andGeneratejoined 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
Streamemits"\n\n"before each summary part, including the first.Generatejoins 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.
Generatereturns each item as its ownReasoningContent, so it still adds no leading blank line.ResponsesReasoningMetadata.Summarystill holds each part verbatim, andtoResponsesPromptdoes not replay reasoning, so requests are unchanged.OpenRouter and Vercel build text from
openai-responsesreasoning 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.gocoversStream(an item with two parts, then an item with one part) andGenerate(an item with two parts). Both tests fail with the old separator.Coder runs this change in its fork as coder#67.