Skip to content

perf: fix O(N^2) accumulation in MessageStream and BetaMessageStream#997

Open
claygeo wants to merge 2 commits intoanthropics:mainfrom
claygeo:fix/streaming-on2-accumulation
Open

perf: fix O(N^2) accumulation in MessageStream and BetaMessageStream#997
claygeo wants to merge 2 commits intoanthropics:mainfrom
claygeo:fix/streaming-on2-accumulation

Conversation

@claygeo
Copy link
Copy Markdown

@claygeo claygeo commented Apr 12, 2026

Summary

Replaces object spread with direct in-place mutation in #accumulateMessage for all content_block_delta types in both MessageStream and BetaMessageStream. The previous implementation created a new content block object via { ...snapshotContent, prop: newValue } on every delta event, causing O(N^2) work for N deltas.

Delta types fixed:

  • text_delta: { ...obj, text: old + new }obj.text = old + new
  • citations_delta: [...existing, new]Array.push() (O(N^2) → O(N) for array copy)
  • input_json_delta: spread + defineProperty each time → defineProperty once, then direct assignment
  • thinking_delta: { ...obj, thinking: old + new }obj.thinking = old + new
  • signature_delta: { ...obj, signature: val }obj.signature = val
  • compaction_delta (Beta only): { ...obj, content: old + new }obj.content = old + new

Why this is safe: The message_delta case already uses direct mutation (snapshot.stop_reason = event.delta.stop_reason). All content block TypeScript types are mutable interfaces (no readonly modifiers). The snapshot is stored in a private field and events emit references to the same object.

Also fixes: BetaMessageStream's content_block_start was missing the defensive copy ({ ...event.content_block }) that MessageStream has. Added for consistency since we now mutate content blocks in place.

Fixes #995
Fixes #933

Relationship to PR #936

PR #936 addresses only input_json_delta with a lazy getter approach. This PR fixes all 6 delta types in both files using a simpler, consistent mutation pattern.

Test plan

  • All existing tests pass (MessageStream.test.ts, BetaMessageStream.test.ts, streaming.test.ts)
  • Added test: "accumulates large text responses in linear time" — streams 5,000 text deltas and verifies correct accumulation
  • tsc --noEmit passes with no type errors

claygeo added 2 commits April 12, 2026 15:24
Replace object spread with direct in-place mutation in #accumulateMessage
for all content_block_delta types (text, citations, input_json, thinking,
signature, compaction). Object spread copied all properties on every delta
event, forcing O(N^2) work for N deltas. Direct mutation is O(N) total and
is consistent with the existing message_delta handling pattern.

Fixes anthropics#995
Fixes anthropics#933
Add object spread when pushing content blocks in BetaMessageStream to
match MessageStream behavior. Since we now mutate content blocks in
place during delta accumulation, this prevents the original event
object from being modified.
@claygeo claygeo requested a review from a team as a code owner April 12, 2026 19:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant