Skip to content

perf: stream agent message deltas instead of full snapshots (#375) - #382

Open
Naoki326 wants to merge 1 commit into
agegr:mainfrom
Naoki326:main
Open

perf: stream agent message deltas instead of full snapshots (#375)#382
Naoki326 wants to merge 1 commit into
agegr:mainfrom
Naoki326:main

Conversation

@Naoki326

@Naoki326 Naoki326 commented Aug 4, 2026

Copy link
Copy Markdown

Fixes #375 — remote access bandwidth amplification (~150x) caused by the events stream resending the full accumulated message on every message_update.

Root cause

The events route forwarded each SDK message_update (which carries the complete accumulated message plus an assistantMessageEvent delta) almost verbatim — only stripping the delta field. Since streaming emits a chunk roughly every 50 characters, every chunk re-sent everything generated so far → O(n²) transfer (~15 KB response cost ~2.2 MB over the wire).

Change

  • Server (app/api/agent/[id]/events/route.ts): message_update is projected onto a light message_delta event — only the slim delta fields (contentIndex/delta/content/toolCall) are forwarded; partial (the full accumulated message) is stripped. done/error are omitted (their reason already arrives in message_end's full message). Unknown/missing delta types degrade to the existing full-snapshot fallback. On SSE (re)connect, the current partial message is injected as a snapshot so mid-stream page reloads recover immediately instead of waiting for message_end.
  • Client (hooks/useAgentSession.ts + new lib/stream-delta.ts): streamed messages are assembled locally by appending deltas (text/thinking/toolCall), keeping the existing snapshot-overwrite fallback. Rendering is unchanged.
  • toolcall_start carries contentIndex only; id/name are extracted from partial (two fields only, not the whole message) so tool cards show the tool name while streaming.

Verification

  • typecheck + lint clean; full test suite: 261 tests (250 pass; 11 pre-existing environment failures identical to HEAD).
  • New unit tests for delta assembly (lib/stream-delta.test.mjs) plus projection assertions in app/api/agent/events-route.test.mjs.
  • Simulation: ~13.5 KB response previously cost ~1.8 MB over the wire (~139x amplification); now ~55 KB (~4x constant framing overhead — O(n) instead of O(n²)).

The events route forwarded every message_update with the full
accumulated message, so a ~15 KB response cost ~2.2 MB over the wire
(~150x amplification, O(n^2) — especially painful over VPN/remote).

The server now projects message_update onto a light message_delta
event (assistantMessageEvent with the partial stripped) and the web
client assembles the streamed message locally via a new pure helper
(lib/stream-delta.ts). Message snapshots are still sent when a delta
is missing or of unknown type, and on SSE (re)connect the current
partial message is injected as a snapshot so mid-stream page reloads
recover immediately instead of waiting for message_end.
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.

远程访问时带宽放大 ~150 倍:events 流每次更新都重发完整消息

1 participant