Expand description
Client-side protocol verification.
The TypeScript SDK puts its verifier on the client, and that is the right instinct for a consumer: the events arrive from someone else’s process, and a stream that breaks the rules should produce one clear error rather than a confused UI. This module is that check, as an ordering state machine.
crate::client::Session runs it by default. Turn it off with
SessionBuilder::verify when talking to a
producer whose quirks you have decided to live with.
use ag_ui::client::verify::Verifier;
use ag_ui::Event;
let mut verifier = Verifier::new();
verifier.verify(&Event::run_started("thread-1", "run-1"))?;
// Content for a message that was never opened.
let orphan = Event::text_message_content("msg-1", "Hello");
assert!(verifier.verify(&orphan).is_err());§The rules
RUN_STARTEDopens the stream, and does so exactly once. OnlyRAWandCUSTOMmay precede it.RUN_FINISHEDandRUN_ERRORclose it. Nothing may follow.TEXT_MESSAGE_CONTENTandTEXT_MESSAGE_ENDrequire an open message with the same id, andTEXT_MESSAGE_STARTmay not re-open an id that is already open.- The same, for
TOOL_CALL_*and forREASONING_MESSAGE_*. TOOL_CALL_RESULTmay not answer a call that has not ended.STEP_FINISHEDrequires a matchingSTEP_STARTED, and step names do not nest with themselves.- Everything open must be closed before
RUN_FINISHED. - An
interruptoutcome must carry at least one interrupt — the one rule the type system cannot express, checked byRunOutcome::validate. - A continuation, terminator or re-open that names a subagent must name
the one that opened the entity — a message, a reasoning block or the
message inside it, a tool call, an activity, or whatever a
REASONING_ENCRYPTED_VALUEattaches to. One that names none is accepted: attribution is optional per event, and a bare continuation is what a pre-subagent producer sends. It does not hand the entity to the parent either: the first writer stays the owner. - A tool call belongs to the message its
parentMessageIdnames, so aTOOL_CALL_STARTtagged with one subagent while that message belongs to another is rejected; an untagged one inherits the message’s owner. - Steps are scoped to the agent that opened them: a subagent cannot close the parent’s step, or a sibling’s, and the same name may be open under two owners at once.
SUBAGENT_STARTEDnames an invocation that is neither active nor already finished in this run, and aparentSubagentRunIdthat was started.SUBAGENT_FINISHEDandSUBAGENT_ERRORname an active one.- Every started subagent is closed before
RUN_FINISHED— not beforeRUN_ERROR, where an unclosed subagent is the expected shape.
What is deliberately not a rule: that one stream must close before the
next opens. Everything here is keyed by id, exactly as the TypeScript
verifier keys its activeMessages / activeToolCalls maps. Two messages
may stream at once, two tool calls may stream at once, and a tool call may
open inside the message that narrates it — which is what every provider
doing parallel tool calls actually sends. Nor must an attributing
subagentRunId have been announced: attribution without lifecycle events
is a supported mode. Events outside these families (state, activity, raw,
custom) are unordered and never close anything. A MESSAGES_SNAPSHOT
seeds ownership from the messages it carries and is authoritative; the
RUN_STARTED input echo seeds it too, for ids not yet recorded; and a
TOOL_CALL_RESULT mints the tool message it names under its own
attribution.
Structs§
- Verifier
- An ordering state machine for one run’s event stream.
Functions§
- verify_
all - Verifies a whole run in one call.