fix: handle SDK 0.3.193 stream_event wrapper for real-time streaming / 修复流式输出失效#934
fix: handle SDK 0.3.193 stream_event wrapper for real-time streaming / 修复流式输出失效#934WenhuaXia wants to merge 2 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthrough
ChangesClaude streaming partial-message handling
Possibly Related PRs
Suggested Reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
server/modules/providers/list/claude/claude-sessions.provider.ts
| if (innerEvent.type === 'content_block_stop') { | ||
| return [createNormalizedMessage({ kind: 'stream_end', sessionId, provider: PROVIDER })]; |
There was a problem hiding this comment.
🎯 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:
- 1: https://platform.claude.com/docs/en/build-with-claude/streaming
- 2: https://github.com/anthropics/skills/blob/da20c925/skills/claude-api/curl/examples.md
- 3: https://docs.rs/anthropic-sdk-rust/latest/anthropic_sdk/types/streaming/enum.MessageStreamEvent.html
- 4: https://github.com/anthropics/skills/blob/main/skills/claude-api/python/claude-api/streaming.md
- 5: https://github.com/anthropics/anthropic-sdk-python/blob/main/helpers.md
- 6: https://claude.yourdocs.dev/docs/build-with-claude/streaming
- 7: https://github.com/anthropics/anthropic-sdk-typescript/blob/a0fbd59685f638af9dab2f55192d6b04cbb94f0d/helpers.md
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.
| 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.
0b2e708 to
df2ed75
Compare
Problem / 问题
SDK 0.3.193 changed the message format from bare
content_block_deltato astream_eventwrapper. The frontend no longer receives streaming output.SDK 0.3.193 将消息格式从裸
content_block_delta改为stream_event包装器,前端不再接收流式输出。Solution / 修复
stream_event.eventbefore checking internal event typesthinking_deltaandtext_deltaChanged File / 修改文件
server/modules/providers/list/claude/claude-sessions.provider.tsSummary by CodeRabbit