A State Machine That Catches Malformed AI Event Streams Before They Reach Your Agent
TypeScript type-checks individual event objects but cannot enforce temporal rules like "a text_delta must follow a text_start." Without a stateful validator, a Provider that emits events in the wrong order produces silent corruption downstream. This pattern is reusable for any streaming protocol where sequence matters more than shape.
A generic EventStream transports data but has no memory of what came before. A state machine layered in front of it tracks the stream phase, active content block, and next expected index, rejecting events that arrive in the wrong order. Text, thinking, and tool-call blocks each get their own validation rules: deltas must match the active block's type and index, and the end event's accumulated content must equal the sum of all deltas.
Tool calls add extra complexity because arguments arrive as fragmented JSON strings. The validator concatenates fragments and parses only once at the end, then deep-compares the result against the final tool-call object. A `sawArgumentsDelta` flag distinguishes "no deltas received" from "an empty delta was received," two states that a simple length check would conflate.
The validator is not a singleton. Each call to the factory creates an independent closure, so concurrent model requests never share phase, index, or content state. When an event is illegal, a `StreamSequenceError` carries the event type and the phase where it failed, giving Provider authors enough context to debug protocol violations.
Most streaming validation stops at per-message type-checking. Adding temporal rules — what must precede what — catches a class of bugs that type systems alone cannot express.
The `sawArgumentsDelta` flag is a small but sharp design choice. Using `argumentsJson.length > 0` would conflate two distinct states and silently accept a Provider that skips deltas entirely.
Deep-comparing the final message content against the state machine's observed content, rather than trusting the terminal event's self-reported payload, closes a gap where a Provider could emit a `done` event that contradicts the stream it just sent.
Making the validator a factory rather than a singleton is a concurrency-safe pattern that applies to any stateful middleware in a multi-request server.