2.5 KiB
2.5 KiB
id, title, status, deps, created
| id | title | status | deps | created |
|---|---|---|---|---|
| 9b83b90a | Refactor stream buffering to head/tail model | open | 2026-01-11T08:48:35.444Z |
Goal
Replace the current special-case buffering logic with a unified head/tail model where:
- Tail: Committed StreamItem[] (history)
- Head: Active StreamItem[] (being streamed, same type as tail)
Both use the same reduceStreamUpdate() function. The only difference is that head items get flushed to tail when a stream item completes.
Current Problems
applyStreamEventWithBuffer()has hardcoded assistant_message bufferingStreamingBufferEntryis a different type thanStreamItem- Reasoning chunks go through reducer directly (no buffering)
- Duplicate logic between
appendAssistantMessage()and buffer handling
Design
Types
// Head is just StreamItem[] - no special type needed
// Tail is just StreamItem[] - same as before
interface StreamState {
tail: StreamItem[]; // committed history
head: StreamItem[]; // active streaming items
}
Core Function
function applyStreamEvent(params: {
tail: StreamItem[];
head: StreamItem[];
event: AgentStreamEventPayload;
timestamp: Date;
}): {
tail: StreamItem[];
head: StreamItem[];
changedTail: boolean;
changedHead: boolean;
}
Flush Logic
Head items are flushed to tail when:
- A different item kind arrives (thought → tool_call flushes the thought)
- Turn completion events (turn_completed, turn_failed, turn_canceled)
- User message arrives (new turn starting)
Implementation Steps
- Remove
StreamingBufferEntrytype - Remove
StreamingHeadtype (or repurpose) - Modify
applyStreamEventWithBuffer→applyStreamEventto work with head/tail - Head uses same
reduceStreamUpdateas tail - Add flush logic based on item kind transitions
- Update session-context.tsx to use new API
- Update agent-stream-view.tsx to render head items
- Update session-store.ts types
Flush Conditions
function shouldFlushHead(head: StreamItem[], incomingKind: StreamItemKind): boolean {
if (head.length === 0) return false;
const lastHeadKind = head[head.length - 1].kind;
// Different kind arriving = flush
if (lastHeadKind !== incomingKind) return true;
return false;
}
Rendering
In agent-stream-view.tsx:
- Tail is rendered in FlatList (reversed, virtualized)
- Head is rendered in ListHeaderComponent (always visible, not virtualized since it's small)
This is similar to current approach but head can now contain any StreamItem types.