Skip to content

fix(cli): render thinking blocks above the answer when the model streams them last - #287

Open
li-xiu-qi wants to merge 1 commit into
stepfun-ai:mainfrom
li-xiu-qi:fix/thinking-before-answer
Open

li-xiu-qi wants to merge 1 commit into
stepfun-ai:mainfrom
li-xiu-qi:fix/thinking-before-answer

Conversation

@li-xiu-qi

@li-xiu-qi li-xiu-qi commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #286

Problem

Within one conversation, the collapsed thinking block is painted below the
answer in some turns and above it in others. The transcript renders an
assistant message strictly in the order of message.content, and that array
is assembled by the provider streaming layer in event arrival order
(packages/providers/src/api/openai-responses-shared.ts pushes each thinking
/ text / toolCall block as it arrives). Most turns deliver the reasoning
stream first, but some turns flush it only after the answer text, so the same
conversation ends up with the thinking block on both sides of the answer.

The inconsistent placement was reproduced end-to-end in a live terminal
against real model streams; the repro screenshot is attached to the issue.

Changes

  • Add withThinkingBeforeAnswer() in
    apps/cli/src/ui/view/transcript/assistant-message.ts: when no thinking
    block precedes the first answer block, hoist every thinking block ahead of
    the answer (stable within each group). This is the only normalization
    applied: alternating runs (thinking, answer, thinking, answer) already lead
    with thinking and are returned untouched, preserving segmented reasoning.
  • Use it in AssistantMessageComponent.updateContent() so the pi skin
    renders thinking above the answer.
  • Use it in partitionAssistantContent() in step-message.ts so the step
    skin gets the same placement through its existing run partitioning.

The normalization is a pure function over the content array and runs on every
updateContent call, so it applies to both fresh messages and session
replay.

Behavior and compatibility

  • Only messages whose thinking blocks all arrive after the first answer block
    change appearance; the common shape (thinking first) and the alternating
    shape are byte-for-byte unchanged.
  • message.content itself is not mutated and no provider response shape,
    config key, or CLI surface changes, so history
    sent back to the API keeps its original order.
  • Tool-call layout is unaffected: tool calls are rendered by their own
    components and still act as run boundaries in the step skin.

Validation

  • The inconsistent placement was reproduced in a live terminal against real
    model streams (screenshot attached to issue [Bug] Thinking block position is inconsistent across turns: sometimes above the answer, sometimes below it #286); the fix targets exactly
    that rendering path.
  • New tests: "hoists thinking ahead of the answer when the model streams it
    last" in both apps/cli/test/assistant-message.test.ts and
    apps/cli/test/step-message.test.ts, plus "keeps segmented reasoning in
    place when thinking already leads" in the former.
  • npx vitest run apps/cli/test/assistant-message.test.ts apps/cli/test/step-message.test.ts
    Result: 28/28 pass (25 pre-existing + 3 new).
  • npx tsgo -p tsconfig.build.json --noEmit in apps/cli: clean.
  • npx biome check on the four changed files: clean.
  • Repo guard checks check:ui-layer, check:layer-direction,
    check:ts-relative-imports: pass.
  • Full apps/cli suite: 727 passed, 13 failed. The 13 failures reproduce
    identically on a stashed (unmodified) tree. These are clipboard / suspend / path
    formatting tests that are environment-dependent on Windows and are not
    touched by this change.
  • Full packages/* suites: 5606 passed, 110 failed. The failures are all
    local Windows environment issues (spawnSync of node.exe failing, timeouts,
    path assertions, test-file loading errors) and are flaky (the failure set
    differs between runs). Running the same failing files with the change stashed
    reproduces the identical failure mode, confirming they are unrelated to this
    change. The authoritative signal for these suites is the fork CI (ubuntu).
    Uploading 3d59a6c64fee25d0af53327c9e16afb4.png…

Limitations

Not applicable: the change is a display-only rendering normalization covered
by unit tests on both skins; no unresolved boundary is known.

…ams them last

Provider streaming assembles message.content in arrival order, so turns
that flush the reasoning stream after the answer text rendered the
thinking block below the answer while other turns rendered it above.
Normalize the render path: when no thinking block precedes the first
answer block, hoist thinking ahead of the answer in both the pi and step
skins. Alternating runs are left untouched.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Thinking block position is inconsistent across turns: sometimes above the answer, sometimes below it

1 participant