Skip to content

fix: handle SDK 0.3.193 stream_event wrapper for real-time streaming / 修复流式输出失效#934

Open
WenhuaXia wants to merge 2 commits into
siteboon:mainfrom
WenhuaXia:fix/streaming-output
Open

fix: handle SDK 0.3.193 stream_event wrapper for real-time streaming / 修复流式输出失效#934
WenhuaXia wants to merge 2 commits into
siteboon:mainfrom
WenhuaXia:fix/streaming-output

Conversation

@WenhuaXia

@WenhuaXia WenhuaXia commented Jun 29, 2026

Copy link
Copy Markdown

Problem / 问题

SDK 0.3.193 changed the message format from bare content_block_delta to a stream_event wrapper. The frontend no longer receives streaming output.

SDK 0.3.193 将消息格式从裸 content_block_delta 改为 stream_event 包装器,前端不再接收流式输出。

Solution / 修复

  • Unwrap stream_event.event before checking internal event types
  • Handle thinking_delta and text_delta
  • Preserve backward compat with older SDK

Changed File / 修改文件

  • server/modules/providers/list/claude/claude-sessions.provider.ts

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of streamed Claude responses by correctly recognizing wrapped SSE event payloads.
    • Enhanced live streaming output to update both text and “thinking” content as it becomes available during a turn.
    • Improved detection of stream completion/end-of-message across differing event formats.
    • Enabled partial streaming behavior so responses render incrementally rather than only after completion.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 562b52f5-25ac-4402-816d-175bf0de7c8e

📥 Commits

Reviewing files that changed from the base of the PR and between 0b2e708 and df2ed75.

📒 Files selected for processing (2)
  • server/claude-sdk.js
  • server/modules/providers/list/claude/claude-sessions.provider.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/modules/providers/list/claude/claude-sessions.provider.ts
  • server/claude-sdk.js

📝 Walkthrough

Walkthrough

mapCliOptionsToSDK now enables partial message streaming, and ClaudeSessionsProvider.normalizeMessage unwraps stream_event-wrapped Anthropic SSE payloads to emit normalized delta and end events for inner content block updates.

Changes

Claude streaming partial-message handling

Layer / File(s) Summary
SDK partial message option and SSE normalization
server/claude-sdk.js, server/modules/providers/list/claude/claude-sessions.provider.ts
sdkOptions.includePartialMessages is enabled, and normalizeMessage unwraps { type: 'stream_event', event: ... } payloads to emit stream_delta for content_block_delta text/thinking events and stream_end for content_block_stop before continuing with bare-event handling.

Possibly Related PRs

Suggested Reviewers

  • viper151

Poem

🐇 I hop through streams with a twitch of my nose,
Partial messages in a warm breeze flow.
Wrapped events unwrap, then gently ignite,
stream_delta sparkles in the middle of the night.
stream_end lands softly; the bunny says “done!”
With a little byte-hopping, the stream is begun.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing SDK 0.3.193 stream_event wrapping to restore real-time streaming.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/modules/providers/list/claude/claude-sessions.provider.ts`:
- Around line 314-315: The stream handling in claude-sessions.provider.ts is
treating innerEvent.type === 'content_block_stop' as the end of the entire
response, which can terminate streaming too early. Update the event mapping
logic in the relevant parser/normalization path so only message_stop produces
createNormalizedMessage({ kind: 'stream_end', ... }), and ensure both the
wrapped and bare fallback branches use message_stop rather than
content_block_stop. Keep the change localized around the existing
innerEvent.type checks and the stream_end normalization.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 725bbd41-b000-4c99-9958-6d9801d2204b

📥 Commits

Reviewing files that changed from the base of the PR and between b6cf333 and 432e2c6.

📒 Files selected for processing (1)
  • server/modules/providers/list/claude/claude-sessions.provider.ts

Comment on lines +314 to +315
if (innerEvent.type === 'content_block_stop') {
return [createNormalizedMessage({ kind: 'stream_end', sessionId, provider: PROVIDER })];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant provider implementation and nearby branches.
sed -n '280,360p' server/modules/providers/list/claude/claude-sessions.provider.ts

# Find all uses of the relevant stream event types in the repo.
rg -n "content_block_stop|message_stop|stream_end|stream_delta|stream_event" server -g '!**/node_modules/**'

Repository: siteboon/claudecodeui

Length of output: 7724


🌐 Web query:

Anthropic Messages API streaming docs content_block_stop message_stop content block stop end of stream

💡 Result:

In the Anthropic Messages API, streaming responses are delivered as a series of Server-Sent Events (SSE) that track the lifecycle of a message and its individual content blocks [1][2]. The lifecycle events for streaming are as follows: 1. message_start: Emitted once at the beginning to provide initial message metadata [1][3]. 2. content_block_start: Emitted when a new content block (such as text, tool use, or thinking) begins [1][3]. 3. content_block_delta: Emitted for each incremental update (e.g., text tokens or JSON fragments) within a content block [1][3]. 4. content_block_stop: Emitted when a specific content block has finished generating [4][1]. This event signifies that no further deltas will be sent for that particular block [5]. 5. message_delta: Emitted to provide updates on top-level message properties, such as stop reasons or token usage [1][3]. 6. message_stop: Emitted once at the very end of the stream, signifying that the entire message generation is complete [1][3]. Key differences between these events are: content_block_stop: Marks the completion of a specific content block (identified by its index) [1][3]. It is useful for final processing of a block, such as parsing accumulated JSON or finalizing a tool call [1][6]. message_stop: Marks the total end of the stream [1]. After receiving this event, you can be certain that no further events will be sent for the current request [1]. When using official Anthropic SDKs, these events are often abstracted into higher-level helpers. For instance, the SDKs allow you to listen for specific completion events, such as a.on('contentBlock',...) callback corresponding to content_block_stop or.on('message',...) corresponding to message_stop, and provide methods like.get_final_message or.done to wait for the entire stream to conclude [5][7].

Citations:


Don't treat content_block_stop as end-of-stream. Anthropic uses content_block_stop to close one block; message_stop is the actual end of the streamed message. Mapping either branch to stream_end can cut off later text/tool blocks, so switch both the wrapped and bare fallbacks to message_stop.

Suggested fix
-    if (innerEvent.type === 'content_block_stop') {
+    if (innerEvent.type === 'message_stop') {
       return [createNormalizedMessage({ kind: 'stream_end', sessionId, provider: PROVIDER })];
     }
@@
-    if (raw.type === 'content_block_stop') {
+    if (raw.type === 'message_stop') {
       return [createNormalizedMessage({ kind: 'stream_end', sessionId, provider: PROVIDER })];
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (innerEvent.type === 'content_block_stop') {
return [createNormalizedMessage({ kind: 'stream_end', sessionId, provider: PROVIDER })];
if (innerEvent.type === 'message_stop') {
return [createNormalizedMessage({ kind: 'stream_end', sessionId, provider: PROVIDER })];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/modules/providers/list/claude/claude-sessions.provider.ts` around
lines 314 - 315, The stream handling in claude-sessions.provider.ts is treating
innerEvent.type === 'content_block_stop' as the end of the entire response,
which can terminate streaming too early. Update the event mapping logic in the
relevant parser/normalization path so only message_stop produces
createNormalizedMessage({ kind: 'stream_end', ... }), and ensure both the
wrapped and bare fallback branches use message_stop rather than
content_block_stop. Keep the change localized around the existing
innerEvent.type checks and the stream_end normalization.

SDK 0.3.193 wraps Anthropic SSE events inside type: 'stream_event' with
an inner event object. normalizeMessage only checked bare content_block_delta
at the top level, so stream_delta frames were never produced — the frontend
showed no streaming output.

- Unwrap stream_event.event before checking content_block_delta/stop
- Handle thinking_delta in addition to text_delta
- Preserve backward compat with bare events from older SDK
SDK 0.3.193 requires includePartialMessages: true to emit
content_block_delta events for real-time streaming. Without this
flag, the SDK only sends complete assistant messages at the end
of each turn, causing the frontend to show no streaming output.
@WenhuaXia WenhuaXia force-pushed the fix/streaming-output branch from 0b2e708 to df2ed75 Compare June 30, 2026 16:02
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