Skip to content

feat(web): render the reasoning stream as a collapsible thinking block#394

Merged
Yevanchen merged 2 commits into
mainfrom
feat/web-reasoning-stream
Jul 25, 2026
Merged

feat(web): render the reasoning stream as a collapsible thinking block#394
Yevanchen merged 2 commits into
mainfrom
feat/web-reasoning-stream

Conversation

@Yevanchen

Copy link
Copy Markdown
Collaborator

Summary

  • Render the reasoning stream (REASONING_MESSAGE_*) in the session chat instead of dropping it: a collapsed disclosure showing a shimmering "Thinking" label with a live character count while streaming, a static "Thought · N chars" header once prose/tools follow, expandable to the raw thought text.
  • New reasoning segment kind in SessionLiveState (pkgs/ag-ui-session): REASONING_MESSAGE_START creates an assistant message keyed by the reasoning messageId, REASONING_MESSAGE_CONTENT appends to its reasoning segment; other REASONING_/THINKING_ events remain no-ops. Live-state schema extended to match.
  • Web: convertSessionMessage maps reasoning segments to assistant-ui's native reasoning part; new AssistantReasoning component registered in MessagePrimitive.Parts (apps/web session-chat).

Why

Thinking models can reason for a long time before the first visible token — measured on the runtime catalog's DeepSeek default (deepseek-v4-pro): 148.7s to first visible character with 21.7k chars of hidden reasoning on a ~400-char writing task. The reducer previously no-opped every REASONING_* event, so that whole phase rendered as dead air; a user in YEF-891 replied "你吐字太慢了" mid-turn. The chat now shows continuous thinking progress instead of silence.

Verification

  • Commands: bun test in pkgs/ag-ui-session (40 pass, includes 3 new reducer tests); tsc --noEmit clean for pkgs/ag-ui-session and apps/web; vp fmt / vp lint clean (pre-existing apps/api generated-tsconfig warning unrelated).
  • Manual steps: rendered the real SessionThread + external-store runtime against fabricated live state in a dev harness (streaming and finished turns); verified shimmer + live char count while streaming, collapsed "Thought" header when done, expand/collapse of the thought text (screenshots on the linked issue).
  • Not run: full platform e2e with a live DeepSeek session.

Impact

  • User/API/contract changes: live-view only — a new reasoning segment kind in the SessionLiveState view model and its arktype schema. Persisted message history, GraphQL schema, and DB are untouched (reasoning is not persisted; it exists only during/within the live session view). Rollback consideration: live-state snapshots written with reasoning segments would fail schema parse on an older build.
  • Generated files / GraphQL / DB / lockfile: N/A
  • Env or config changes: N/A
  • Risk and rollback: low; revert the commit. Empty reasoning parts are filtered by assistant-ui, so START-only messages fall back to the existing "Thinking…" empty indicator.

Review

  • Closest review areas: pkgs/ag-ui-session/src/live-state-message-reasoning.reducer.ts (message keyed by reasoning id, ordering vs. tool/text messages); apps/web/.../session-message-parts.tsx (disclosure a11y/styling).
  • Known trade-offs: reasoning is grouped as one block per turn (ACP emits a single thought stream id per turn), so interleaved think→tool→think phases accumulate into the same block rather than splitting; character count is shown rather than token count (token usage only arrives at turn end).

Context: YEF-891 — OpenCode + DeepSeek v4 preview feels silent/slow; companion driver fix langgenius/mosoo-agent-driver#81.

Thinking models (e.g. DeepSeek V4 Pro) can reason for minutes before the
first visible token, and the live state dropped every REASONING_* event,
so the whole phase rendered as dead air in the chat.

Store REASONING_MESSAGE_START/CONTENT deltas as a reasoning segment on an
assistant message keyed by the reasoning messageId, project it to the
assistant-ui reasoning part, and render it as a collapsed disclosure:
shimmering "Thinking" with a live character count while streaming, a
static "Thought" header afterwards, expandable to the raw thought text.
@Yevanchen
Yevanchen force-pushed the feat/web-reasoning-stream branch from a541535 to c384e4b Compare July 24, 2026 16:19
@Yevanchen
Yevanchen merged commit 3580a6c into main Jul 25, 2026
10 checks passed
@Yevanchen
Yevanchen deleted the feat/web-reasoning-stream branch July 25, 2026 05:53
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.

1 participant