跪拜 Guibai
← All articles
Frontend · Backend · Artificial Intelligence

A State Machine That Catches Malformed AI Event Streams Before They Reach Your Agent

By 东方小月 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

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.

Summary

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.

Takeaways
TypeScript can verify the shape of a single event but cannot remember what happened earlier in the stream; a state machine fills that gap.
Three stream phases — idle, streaming, terminal — gate which events are legal at any moment.
Text and thinking blocks share identical start-delta-end structure and can be validated with the same logic.
Tool-call arguments arrive as JSON fragments; parsing must wait until the end event, not happen per-delta.
A `sawArgumentsDelta` boolean flag is necessary because an empty-string delta and no delta at all are different protocol states.
The validator factory creates a fresh closure per stream; a module-level singleton would cause concurrent requests to corrupt each other's state.
`done` requires no active block; `error` can preserve partial text or thinking but must discard an unfinished tool-call block.
`StreamSequenceError` bundles the event type and stream phase so Provider authors can trace exactly where a protocol violation occurred.
Conclusions

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.

Concepts & terms
State Machine (in streaming validation)
A validator that tracks the current phase of an event stream (idle, streaming, terminal) and the active content block, using that memory to reject events that arrive out of protocol order.
StreamSequenceError
A custom error type that bundles the offending event type and the stream phase where it occurred, giving Provider authors precise debugging context instead of a generic failure message.
sawArgumentsDelta flag
A boolean that records whether any tool-call argument delta was received, distinguishing 'no deltas arrived' from 'an empty-string delta arrived' — two states that a length check on the accumulated string cannot tell apart.
JSON deep comparison for stream integrity
Comparing the final message content against the state machine's accumulated content using structural equality, rather than trusting the terminal event's self-reported payload, to catch Provider-side inconsistencies.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗