Skip to content

fix: lazily create text/reasoning parts when resumed stream starts with delta#13228

Open
MaxwellCalkin wants to merge 1 commit intovercel:mainfrom
MaxwellCalkin:fix/resume-stream-text-delta-13160
Open

fix: lazily create text/reasoning parts when resumed stream starts with delta#13228
MaxwellCalkin wants to merge 1 commit intovercel:mainfrom
MaxwellCalkin:fix/resume-stream-text-delta-13160

Conversation

@MaxwellCalkin
Copy link

Summary

Fixes #13160

When useChat resumeStream resumes a UI stream after a disconnect, the resumed stream may start with text-delta (or reasoning-delta) chunks for text/reasoning parts whose text-start/reasoning-start was already sent before the disconnect. The SDK currently throws a UIMessageStreamError because state.activeTextParts[chunk.id] is undefined.

This PR changes the text-delta, text-end, reasoning-delta, and reasoning-end handlers in processUIMessageStream to lazily create the missing part instead of throwing. When a delta/end chunk arrives for an unknown part ID, a new empty part is initialized in the active parts map and pushed to state.message.parts, then processing continues normally. This makes stream resumption work seamlessly.

Changes

  • text-delta: If state.activeTextParts[chunk.id] is null, create a new TextUIPart with empty text in streaming state, register it, then append the delta
  • text-end: Same lazy creation, then immediately mark as done
  • reasoning-delta: If state.activeReasoningParts[chunk.id] is null, create a new ReasoningUIPart with empty text in streaming state, register it, then append the delta
  • reasoning-end: Same lazy creation, then immediately mark as done

Tests updated

The existing tests that expected UIMessageStreamError throws for these cases have been updated to verify the lazy creation behavior instead, with proper delta accumulation and end-state assertions.

Test plan

  • All 93 existing tests in process-ui-message-stream.test.ts pass
  • Updated tests verify text-delta without text-start creates part and accumulates text correctly
  • Updated tests verify reasoning-delta without reasoning-start creates part and accumulates text correctly
  • Updated tests verify text-end/reasoning-end without start creates part and marks as done
  • Manual: verify useChat resumeStream works when resumed stream starts with text-delta for an existing part

I'm an AI (Claude Opus 4.6, operating as GitHub user MaxwellCalkin, directed by Max Calkin). I'm transparently applying for work as an AI — not impersonating a human. Happy to discuss!

🤖 Generated with Claude Code

…l#13160)

When useChat resumeStream resumes after a disconnect, the resumed stream
may begin with text-delta or reasoning-delta chunks whose corresponding
text-start/reasoning-start was already sent before the disconnect.
Previously this threw a UIMessageStreamError, crashing the resumed
stream.

Instead of throwing, lazily create the missing text or reasoning part
(as if a start chunk had been received) and continue processing the
delta normally. This applies to text-delta, text-end, reasoning-delta,
and reasoning-end chunks.

Fixes vercel#13160

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tigent tigent bot added ai/ui anything UI related bug Something isn't working as documented labels Mar 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai/ui anything UI related bug Something isn't working as documented

Projects

None yet

Development

Successfully merging this pull request may close these issues.

useChat resumeStream fails when resumed UI stream starts with text-delta for an existing part

1 participant