NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
pub.dev · #2525 most downloaded on pub.dev
Dart SDK for AG-UI protocol - standardizing agent-user interactions through event-based communication
Last release 3 months ago
24 Jun 2026
Too new to tell
only 2 dated releases
Nearly every release is documented
notes for 2 of 2 stable releases
Nothing withdrawn
no release was ever pulled
1 years old
2 releases · first in 2025
One column per month.
The bare name is preserved as a deprecated typedef alias for backward compat and will be removed in 1.0.0. Internal call sites in AgUiClient throw the…
StateDeltaEvent.delta and ActivityDeltaEvent.patch are now
List<Map<String, dynamic>> instead of List<dynamic>. RFC 6902 JSON
Patch operations are always objects. Using requireListField<Map<String, dynamic>> surfaces non-object elements as AGUIValidationError at the
decoder boundary with a field: 'delta[$i]' / field: 'patch[$i]' index,
rather than leaking a downstream TypeError at the first op['op'] access.
Direct consumers of event.delta[i] who are already casting to Map are
unaffected; consumers storing the list as List<dynamic> will need a type
annotation update.
Migration: change List<dynamic> type annotations on event.delta /
event.patch to List<Map<String, dynamic>>. Code that already accesses
op['op'] / op['path'] without an explicit cast is already correct.SseParser.maxDataBytes renamed to maxDataCodeUnits. The field
already measured UTF-16 code units, not bytes — the rename corrects the
misleading name. SseParser(maxDataBytes: ...) call sites must be updated
to SseParser(maxDataCodeUnits: ...).ActivityMessage.fromJson now silently strips encryptedValue /
encrypted_value instead of throwing AGUIValidationError. ActivityMessage
is not a BaseMessage extension in the canonical protocol, so the field
does not apply. Dart was the only SDK that tore down the stream on encountering
the field; TS strips silently (zod default) and Python preserves it. The
change restores forward compatibility when a proxy emits the field.ReasoningEncryptedValueEvent.fromJson no longer stores the cipher
payload in BaseEvent.rawEvent. Previously rawEvent: _readRawEvent(json)
stored the full wire JSON (including encryptedValue) in the inherited
rawEvent field, undoing the cipher-data scrubbing in every error path.
rawEvent is now always null for this event type; proxies that need the
raw wire form should retain it before calling fromJson.RunStartedEvent.fromJson no longer attaches the offending payload to
AGUIValidationError.json on rethrow. The full outer payload (and the inner
RunAgentInput, which can carry encryptedValue via input.messages[*])
are both omitted, so cipher data cannot leak through validation errors —
matching the existing scrub in MessagesSnapshotEvent.fromJson.MessagesSnapshotEvent.fromJson rethrow now drops json: entirely.
Forwarding e.json previously exposed the inner Message map on the outer
error; for Tool/Reasoning subtypes that map can carry encryptedValue. Drops
json: to match AssistantMessage.fromJson's tool-call IIFE, which already
uses the cautious default.JsonDecoder.requireEitherField now distinguishes "key present but null"
from "key absent". Previously both cases produced the same
"Missing required field 'X' (or 'Y')" message, misleading consumers into
thinking the snake_case alias might work when the camelCase key was
explicitly null. Now: key-present-but-null produces "Required field 'X' is
present but null"; both-absent still produces the dual-key error.copyWith sentinel sweep completed. ThinkingStartEvent.title,
ToolCallResultEvent.role, StateSnapshotEvent.snapshot, and
RunErrorEvent.code now use the kUnsetSentinel pattern so callers can
clear these nullable fields via copyWith(field: null). The "Known parity
gaps" list is now empty for payload fields.EventEncoder.acceptsProtobuf and EventDecoder.decodeBinary now carry
explicit dartdoc warnings that protobuf is not yet implemented end-to-end.
A client negotiating application/vnd.ag-ui.event+proto would receive a
misleading "Invalid UTF-8 data" error; the docs now direct consumers to use
SSE transport until protobuf support lands.groupRelatedEvents dartdoc now documents the ReasoningStart /
ReasoningEnd asymmetry. Phase-level reasoning events are emitted as
standalone singletons; only message-level REASONING_MESSAGE_* events are
grouped. Consumers that need to associate phase-level markers with message
groups must track phase boundaries in their own state.processChunk resets errorRoutedInChunk after the for-loop. The flag
was previously only set inside the loop; future throw sites after the loop
body could have silently swallowed unrelated errors.SseParser error message corrected. The OOM-guard error now says
"code-unit limit" (not "byte limit") to match what the cap actually measures.SseParser._processField now uses write('\\n') instead of writeln()
for the inter-data: separator. writeln() is equivalent on all Dart
platforms but the explicit form removes any ambiguity about whether a
platform line terminator is emitted.EventType.fromString dartdoc strengthened with an explicit contract
note: callers must not change the throw type from ArgumentError, because
BaseEvent.fromJson uses a narrow on ArgumentError catch to distinguish
unknown event types from factory bugs.encryptedValue is now plumbed through every BaseMessage subtype
(DeveloperMessage, SystemMessage, AssistantMessage,
UserMessage) on the base Message class. Mirrors canonical TS
BaseMessageSchema.encryptedValue: z.string().optional() and Python
BaseMessage.encrypted_value: Optional[str]. Previously the field
was only present on ToolMessage and ReasoningMessage, so a Dart
proxy decoding a MESSAGES_SNAPSHOT whose assistant or user message
carried encryptedValue from a TS or Python server silently dropped
the value at decode and could not re-emit it on the next hop. Decode
accepts both encryptedValue (TS-canonical) and encrypted_value
(Python-canonical); toJson emits camelCase; each subtype's
copyWith accepts an explicit-null clear via the sentinel pattern.
The ToolMessage and ReasoningMessage field declarations were
removed in favor of inheriting from the base — the wire shape is
unchanged.raw_event (snake_case) is now preserved on every event factory.
All ~30 BaseEvent subclasses now read rawEvent via a centralized
_readRawEvent helper that uses containsKey precedence: the
camelCase key wins when present (even when explicitly null), and
the snake_case key is consulted only when camelCase is absent.
Previously every factory read json['rawEvent'] directly, silently
dropping Python-style raw_event payloads. toJson continues to
emit camelCase only.REASONING_ENCRYPTED_VALUE no longer rejects empty
entityId / encryptedValue strings. Canonical TS uses
z.string() and Python uses str — neither imposes a minimum
length. The Dart-only empty-string rejection (in both
ReasoningEncryptedValueEvent.fromJson and EventDecoder.validate)
was over-strict and would reject payloads that the canonical SDKs
accept. The strict subtype discriminator stays — unknown subtypes
still throw.SseParser._processField now matches the WHATWG SSE spec for
empty leading data: lines and repeated event: lines. The
data: case used _dataBuffer.isNotEmpty as a "have we written
data yet?" heuristic, which collapsed data:\ndata: x\n\n to "x"
instead of the spec-correct "\nx". Now uses the _hasDataField
flag (mirroring the inDataBlock pattern in
EventStreamAdapter.appendDataLine). The event: case appended on
every event: line; per spec it must REPLACE.EventStreamAdapter.fromRawSseStream now propagates downstream
cancellation, pause, and resume to the upstream raw SSE
subscription. Previously the upstream rawStream.listen(...)
subscription was fire-and-forget — a consumer that cancelled the
adapted stream early left the upstream draining indefinitely
(a real resource leak on long-lived agent streams).SseParser.parseBytes now flushes any final unterminated event
on stream close. Routed through parseLines so the final
_dispatchEvent() flush in parseLines fires for byte-stream
sources too. A byte source that ended without a trailing blank line
previously lost its last buffered message.copyWith sentinel sweep. RawEvent.source,
RunAgentInput.state, RunAgentInput.forwardedProps, and
Run.result previously used the standard ?? this.field pattern,
so a caller could not clear them via copyWith(field: null). They
now use the existing sentinel pattern. The "Known parity gaps"
list below has been updated.JsonDecoder.optionalEitherField now resolves on KEY presence,
not value-non-null. A payload carrying both camelKey: null and
snake_key: <value> previously fell through to the snake_case
value; the documented contract on requireEitherField is that
camelCase wins when its key is present (even when explicitly
null). The implementation now matches the dartdoc. The inline
forwardedProps decode in context.dart was migrated to the same
containsKey rule for consistency.ToolMessage.fromJson and ToolResult.fromJson now use
requireEitherField instead of the older
optionalEitherField + manual null-check + custom throw pattern,
matching the migration already done for RunAgentInput.fromJson
and Run.fromJson.Validators.validateMessageContent is now String-only. The
pre-0.2.0 permissive Map/List branches were dead code (no caller
in the SDK passed those types) and disagreed with canonical
BaseMessage.content: Optional[str]. Multimodal UserMessage.content
remains a tracked parity gap.Validators.validateUrl now rejects URLs containing C0 control
characters or DEL (\x00–\x1f, \x7f). Uri.parse is
permissive with embedded \n / \r / \t, which can flow into
HTTP request lines as a header-injection vector.JsonDecoder.requireField and optionalField transform-failure
paths now preserve cause: e when wrapping an inner exception
in AGUIValidationError. The structured cause was previously
flattened into the message via '$e' interpolation only.AGUIValidationError.json dartdoc now carries an explicit
sensitive-data warning: the field captures the entire wire payload
including cipher fields. toString() does not emit it (safe by
default), but reflection-based serializers used by some logging
frameworks will leak. Prefer .field and .value on log lines
shipped to external sinks.EventDecoder.validate dartdoc now documents the dual-source
error class asymmetry: validate() raises
client/errors.dart's ValidationError; fromJson-side eager
rejections raise types/base.dart's AGUIValidationError. Both
surface uniformly as DecodingError through the public
decode / decodeJson boundary; both extend AGUIError.BaseEvent.rawEvent dartdoc now notes the round-trip emission
consequence — anything assigned to this field WILL be re-emitted
on the next encode. Set rawEvent: null on the in-flight event
if a proxy doesn't want the upstream payload echoed downstream.fromJson but always emits camelCase on toJson. The Error
Handling section is refreshed to use the current error-hierarchy
class names (TransportError, DecodingError, ValidationError,
CancellationError, all under AGUIError).AgUiClient.runAgent dartdoc Throws: list refreshed to match
the current error hierarchy.EventStreamAdapter.groupRelatedEvents dartdoc now carries an
explicit unbounded-state warning — open groups (where *Start was
received but *End has not arrived) are held in memory until the
matching end event or stream completion. Same caveat applies to
accumulateTextMessages.Tool.copyWith(parameters: null) now correctly clears parameters.
The previous parameters ?? this.parameters pattern silently kept the
existing value when null was passed; the field now uses the _unsetTool
sentinel pattern, consistent with ToolCall.encryptedValue and every
other nullable field in the SDK. This gap was omitted from the 0.2.0
"Known parity gaps" list — it has been corrected here.EventStreamAdapter.fromRawSseStream now subscribes to the upstream
lazily (inside controller.onListen) rather than eagerly at call time.
A caller that obtained the returned stream but never subscribed would
previously leak the upstream SSE connection until the server closed it.
The cancellation, pause, and resume propagation added in the prior
review pass is preserved; subscription lifecycle callbacks now use
null-safe ?. calls since the subscription is no longer late final.Message.fromJson now preserves the wire JSON payload in
AGUIValidationError when MessageRole.fromString fails. Previously
the error was thrown without json: set, making it impossible to
identify which message in a MESSAGES_SNAPSHOT had the unrecognized
role. The re-thrown error carries the originating json map so the
decoder pipeline can surface it as a DecodingError with full context.TimeoutError renamed to AGUITimeoutError to avoid shadowing the
built-in dart:async.TimeoutError (raised by Future.timeout(...) /
Stream.timeout(...)). The bare name is preserved as a deprecated
typedef alias for backward compat and will be removed in 1.0.0.
Internal call sites in AgUiClient throw the new name directly. The
README "Errors" recipe and "Migrating from 0.1.0" section call out
the rename so consumers using both package:ag_ui/ag_ui.dart and
dart:async can avoid the symbol collision.delta is now accepted on TEXT_MESSAGE_CONTENT,
TOOL_CALL_ARGS, and REASONING_MESSAGE_CONTENT, and empty
content is accepted on TOOL_CALL_RESULT, to match the canonical
TS/Python schemas (z.string() / str with no min(1) constraint).
Previously the Dart SDK rejected empty values at both the fromJson
factory and the EventDecoder.validate pipeline; a Python or TS
server that legitimately emitted a deliberate empty chunk (e.g. a
noop content refresh) would fail decode in Dart but pass in the
canonical SDKs. Empty cipher payloads on REASONING_ENCRYPTED_VALUE
(entityId, encryptedValue) continue to be rejected — the "no
graceful default for cipher payloads" contract stays.ToolCall now carries the optional encryptedValue field for parity
with canonical TS (ToolCallSchema.encryptedValue: z.string().optional())
and Python (ToolCall.encrypted_value: Optional[str]). Previously a
message arriving with toolCalls: [{..., encryptedValue: "..."}]
silently dropped the value at decode and could not re-emit it on a
proxy hop. Decode accepts both encryptedValue and encrypted_value;
toJson emits the camelCase key when present; copyWith uses the
sentinel pattern so callers can explicitly clear it via
copyWith(encryptedValue: null).RunAgentInput now carries the optional parentRunId field for
parity with canonical TS (RunAgentInputSchema.parentRunId: z.string().optional()) and Python (RunAgentInput.parent_run_id).
Previously a RUN_STARTED payload with input.parentRunId: '...'
decoded with the field silently dropped, even though
RunStartedEvent.parentRunId itself was preserved. Decode accepts
both parentRunId and parent_run_id; toJson emits camelCase when
present; copyWith uses the sentinel pattern.EventStreamAdapter.fromRawSseStream now handles CRLF (\r\n) line
terminators, not just LF. Previously a CRLF-emitting SSE server
produced "\r" lines that never matched the empty-line event-boundary
signal, so events buffered until stream close. The line splitter now
strips a trailing \r after splitting on \n. The same fix is
applied to EventDecoder.decodeSSE, which now uses LineSplitter
(handling \n, \r, and \r\n per the WHATWG SSE spec).JsonDecoder.optionalListField and requireListField now eagerly
type-check elements (raising AGUIValidationError(field: '$field[$i]')
on the first wrong-typed element) instead of returning a lazy
cast<T>() view that surfaced as a raw TypeError at access time and
was flattened to field: 'json' by the decoder catch-all.AssistantMessage.fromJson now uses JsonDecoder.optionalEitherField
on the toolCalls / tool_calls key itself, instead of a ?? chain
on the post-.map(...).toList() value. The previous chain only fired
on null, so an empty toolCalls: [] short-circuited the snake_case
fallback even when tool_calls: [...] was populated.AssistantMessage.toJson now emits toolCalls whenever the in-memory
field is non-null (including empty lists), so the round-trip
fromJson(m.toJson()) == m is symmetric.EncoderError / DecodeError /
EncodeError unchanged instead of re-wrapping them as a generic
"Failed to decode event" via the catch-all.EventEncoder.encodeSSE no longer strips fields whose value is null.
The blanket json.removeWhere((k, v) => v == null) was silently
dropping fields that intentionally serialize as null
(ActivitySnapshotEvent.content, RawEvent.event, CustomEvent.value,
StateSnapshotEvent.snapshot), breaking the encode→decode round-trip
because the matching factories require the key to be present and reject
it with AGUIValidationError. Each toJson() already uses
if (field != null) 'field': field for fields that opt in to omission,
so the strip pass was redundant in addition to harmful. Pinned by a
new round-trip test in fixtures_integration_test.dart.EventStreamAdapter.fromRawSseStream now handles WHATWG-spec lone-\r
line terminators in addition to \n and \r\n. The previous chunk
scanner only split on \n, so a producer using bare \r (rare in
practice but spec-valid) buffered indefinitely. The new multi-terminator
scanner defers a trailing \r at chunk boundaries to disambiguate from
a chunk-spanning \r\n and consumes it on stream close. Steady-state
emission for CRLF-encoded streams is unchanged.EventStreamAdapter.fromSseStream and fromRawSseStream now preserve
any AGUIError subtype (AgUiError, AGUIValidationError,
EncoderError) raised by the decoder instead of re-wrapping the
encoder-family errors as a generic DecodingError. Mirrors the
unified-error-surface contract that EventDecoder.decode/decodeJson
already honor.TestHelpers.findToolCalls (test-only helper) now uses the typed
AssistantMessage.toolCalls accessor. Previously it round-tripped
through toJson and read the snake_case key tool_calls, but
AssistantMessage.toJson emits camelCase toolCalls — the helper
silently always returned an empty list. Currently unreferenced by the
test suite, so this is a latent-bug fix.JsonDecoder.optionalEitherListField<T> helper combining the dual-key
resolution rule from optionalEitherField with the index-aware
element-type validation from requireListField / optionalListField.
AssistantMessage.fromJson now uses it so a malformed nested
toolCalls[i] raises AGUIValidationError(field: 'toolCalls[$i]')
instead of leaking a raw TypeError from the per-element cast.Message subclass copyWith methods (DeveloperMessage,
SystemMessage, UserMessage, AssistantMessage, ToolMessage,
ReasoningMessage) now use the _unsetMessage sentinel pattern for
nullable fields, matching the event-class discipline. Callers can
explicitly clear a nullable field via copyWith(field: null) —
previously ?? this.field could not distinguish "argument omitted"
from "argument explicitly null".JsonDecoder.optionalIntField (new helper) accepts int or num
and coerces via .toInt(). Every event factory now reads
timestamp via this helper, so a TS server emitting a fractional
number (e.g. Date.now() / 1000) no longer fails decode with
AGUIValidationError(field: 'timestamp').AgUiError now extends AGUIError,
and AGUIValidationError now extends AGUIError instead of bare
implements Exception. Callers can on AGUIError catch (e) to
cover the entire SDK error surface (including direct-factory
validation, encoder-side failures, runtime/transport, and decoder
errors). on AgUiError still scopes to runtime/transport/decoding
as before. Added an "Errors" section to the README documenting the
recommended catch recipe.AGUIValidationError gained an optional cause parameter so the
transform-rethrow path in JsonDecoder can preserve structured
error info instead of flattening to 'Failed to transform field: $e'.SseParser documented its per-connection state semantics (sticky
_lastEventId); a new reset() method clears all parser state for
callers that explicitly want to reuse an instance across independent
streams.Validators.maxTimeout exposed as static const Duration so callers
can introspect the limit (10 minutes). The cap value is unchanged;
raising it is deferred to a future release.RunAgentInput.fromJson and Run.fromJson migrated to
JsonDecoder.requireEitherField for consistency with every other
factory in the SDK. Behavior preserved; the
"Missing required field 'X' (or 'Y')" wording shifts slightly to match
the helper's standard error message.@Deprecated messages on the THINKING_* enum values and event
classes hoisted into top-level const strings (event_type.dart,
events.dart). Surfaces the planned-removal version in one place per
context and reduces drift risk if it ever changes. No behavior change.UserMessage documented as a known parity gap with the canonical
multimodal schema (TS Union[string, InputContent[]], Python
Union[str, List[InputContent]]); the Dart SDK currently only
supports the string variant.Message.id documented as nullable-by-type but required-by-convention
(every concrete subtype constructor declares it required); a future
major version may tighten the type to non-nullable for parity with
canonical BaseMessageSchema.id: z.string().EventDecoder.validate's Thinking* deprecated cases gained
comments explaining why they don't validate messageId (the
deprecated wire shape has no such field; the migration target
REASONING_* does).EventDecoder.validate's ActivityDeltaEvent case gained a comment
noting that an empty patch is intentional per the canonical
TS/Python schemas (z.array(...).min(0) / list with no length floor).BaseEvent.rawEvent field gained a dartdoc note clarifying that the
field is unvalidated (typed dynamic because the protocol does not
constrain the shape).ToolCallResultEvent.role, StateSnapshotEvent.snapshot, and
RunErrorEvent.code field declarations gained a dartdoc note that
copyWith(field: null) does NOT clear the field (these three are the
remaining cases listed in "Known parity gaps"). Construct a new
instance directly to drop.MessageRole.activity and MessageRole.reasoning enum values gained
wire-spelling-pinning dartdoc, mirroring the
ReasoningEncryptedValueSubtype.toolCall style.EventDecoder.validate's ThinkingTextMessageContentEvent case gained
a clarified rationale comment: the deprecated path keeps the pre-0.2.0
stricter "non-empty delta" contract intentionally — sibling content
events (TextMessageContentEvent, ToolCallArgsEvent,
ToolCallResultEvent, ReasoningMessageContentEvent) were RELAXED
to accept empty strings in 0.2.0 for canonical TS/Python parity, but
loosening a deprecated contract retroactively serves no one.ReasoningEncryptedValueEvent.fromJson empty-string rejection comment
updated to reflect the post-0.2.0 sibling state — it is intentionally
stricter than the relaxed sibling content events because cipher
payloads have no defensible "empty" semantic.BaseEvent.fromJson and Message.fromJson switches gained an explicit
trailing comment stating the analyzer-enforced exhaustiveness so future
contributors don't add a default clause "to be safe."EventStreamAdapter adopted an internal _appendDataLine /
flushDataBlock decomposition to share the per-line and onDone
flush paths in fromRawSseStream. No behavior change.TimeoutError → AGUITimeoutError
section gained a paragraph clarifying the symmetric case: consumers
who previously meant dart:async.TimeoutError and were accidentally
catching SDK instances will see different runtime behavior after they
fix the import.requireNonEmpty on messageId, threadId, and runId fields is
stricter than the canonical z.string() / str schemas (which allow
empty strings). EventDecoder.validate() rejects empty ID strings;
a TS or Python server that legitimately emits an empty messageId would
fail decode in Dart. The strict behavior is intentional (empty IDs have
no valid semantic in the current protocol) and is tracked for review at
1.0.0 alignment.BaseEvent.toJson always emits rawEvent in camelCase even when the
original wire payload used raw_event (snake_case). Proxies that must
forward the exact wire spelling should read the value before calling
fromJson and re-attach it to the outbound payload manually, or
preserve the original byte stream instead of round-tripping through the
Dart event model.ActivityDeltaEvent.patch decodes as List<Map<String, dynamic>>
and rejects non-object patch elements at the decoder boundary. Canonical TS
(z.array(z.any())) and Python (List[Any]) accept any element type and
defer validation to downstream RFC-6902 consumers. Producers emitting
non-object patch elements (legal per canonical schemas, illegal per RFC 6902)
will be rejected by the Dart decoder.AssistantMessage.toJson emits toolCalls: [] when the in-memory list
is non-null but empty. The canonical TS/Python SDKs omit the key when the
list is empty. This ensures round-trip symmetry
(fromJson(m.toJson()) == m) but diverges from canonical wire output for
messages whose toolCalls field was decoded from an absent key. Consumers
producing wire output for external TS/Python clients should treat an empty
list and an absent key as equivalent.ToolCallResultEvent.role is now typed ToolCallResultRole? instead of
String?. Callers constructing the event directly must use the enum
(e.g. ToolCallResultRole.tool) instead of a raw string. Wire decoding
is unaffected: an unknown role string on the wire is absorbed via
ToolCallResultRole.fromString and falls back to ToolCallResultRole.tool
(forward-compatible with future canonical roles). The new role enum
exists for parity with the Python Literal["tool"] / TypeScript
z.literal("tool") canonical role surface.ActivitySnapshotEvent (ACTIVITY_SNAPSHOT)ActivityDeltaEvent (ACTIVITY_DELTA)ReasoningStartEvent (REASONING_START)ReasoningMessageStartEvent (REASONING_MESSAGE_START)ReasoningMessageContentEvent (REASONING_MESSAGE_CONTENT)ReasoningMessageEndEvent (REASONING_MESSAGE_END)ReasoningMessageChunkEvent (REASONING_MESSAGE_CHUNK)ReasoningEndEvent (REASONING_END)ReasoningEncryptedValueEvent (REASONING_ENCRYPTED_VALUE)ReasoningMessageRole, ReasoningEncryptedValueSubtype.ActivityMessage and ReasoningMessage Message subtypes (with
MessageRole.activity / MessageRole.reasoning) so MESSAGES_SNAPSHOT
payloads carrying those roles decode in Dart with the same schema as the
canonical TypeScript and Python SDKs. The activityType /
activity_type and encryptedValue / encrypted_value keys both
decode for camelCase/snake_case parity with the wider protocol.TextMessageStartEvent.name, TextMessageChunkEvent.name,
RunStartedEvent.parentRunId, and RunStartedEvent.input are now decoded
and re-emitted by toJson so a Dart proxy preserves upstream metadata.fromJson factories now accept both camelCase (TypeScript
server) and snake_case (Python server) field keys, including the
pre-existing TextMessage* and ToolCall* events that were previously
camelCase-only.ToolCallArgsEvent,
ToolCallEndEvent, ToolCallResultEvent, RunFinishedEvent,
StepStartedEvent, StepFinishedEvent, StateSnapshotEvent, RawEvent,
and CustomEvent so wire payloads with empty required identifiers or
missing required content fail at EventDecoder.decodeJson instead of
reaching consumer code as a null/empty value.REASONING_MESSAGE_START.role is now required during decoding to match
the canonical TypeScript and Python schemas. A payload missing role
now raises AGUIValidationError (wrapped as DecodingError through
EventDecoder); an unknown role string still falls back to
ReasoningMessageRole.reasoning for forward-compatibility.TextMessageRole.fromString now throws ArgumentError on unknown
values, mirroring ReasoningMessageRole.fromString. Wire decoding is
unaffected: TextMessageStartEvent.fromJson and
TextMessageChunkEvent.fromJson absorb the throw and fall back to
TextMessageRole.assistant for forward compatibility — only direct
callers of TextMessageRole.fromString see the new visible failure
mode.ReasoningEncryptedValueEvent.fromJson now wraps an unknown subtype
as AGUIValidationError (matching the class-level dartdoc contract),
instead of leaking the raw ArgumentError from
ReasoningEncryptedValueSubtype.fromString. The EventDecoder
pipeline still surfaces it as DecodingError.ActivitySnapshotEvent.copyWith (content), RawEvent.copyWith
(event), CustomEvent.copyWith (value), and
RunFinishedEvent.copyWith (result) now use an internal sentinel
parameter so callers can intentionally clear the field to null
(matching each factory contract that already accepted explicit-null
payloads). Other copyWith methods retain the standard
?? this.field pattern (see Known parity gaps).EventDecoder.decodeJson now wraps AGUIValidationError (thrown by
fromJson factories) explicitly so the resulting DecodingError
preserves the original failing field — role, messageId,
subtype, etc. — instead of flattening to field: 'json'. Pre-fix,
the wrapper relied on the AgUiError-based catch path, which
AGUIValidationError (which only implements Exception) bypassed.EventDecoder.validate now rejects an empty messageId on
TextMessageEndEvent, restoring symmetry with TextMessageStartEvent
and TextMessageContentEvent (and the new reasoning-end events).EventType.thinkingContent and ThinkingContentEvent — not part of the
canonical AG-UI protocol. Use EventType.reasoningMessageContent /
ReasoningMessageContentEvent instead. Decoding remains supported for
backward compatibility; scheduled for removal in 1.0.0.EventType.thinkingTextMessageStart /
EventType.thinkingTextMessageContent /
EventType.thinkingTextMessageEnd (and their event classes:
ThinkingTextMessageStartEvent, ThinkingTextMessageContentEvent,
ThinkingTextMessageEndEvent). Mirrors the canonical TypeScript SDK's
deprecation of THINKING_TEXT_MESSAGE_* in favor of REASONING_*. Use
ReasoningMessageStartEvent / ReasoningMessageContentEvent /
ReasoningMessageEndEvent instead. Decoding remains supported for
backward compatibility; scheduled for removal in 1.0.0.copyWith sentinel sweep is now complete for all nullable payload fields.
The sentinel pattern (kUnsetSentinel / identical check) is in place for
ActivitySnapshotEvent.content, RawEvent.event, RawEvent.source,
CustomEvent.value, RunFinishedEvent.result, the optional fields of
TextMessageStartEvent / TextMessageChunkEvent,
ToolCallStartEvent.parentMessageId, the optional fields of
ToolCallChunkEvent and ReasoningMessageChunkEvent,
RunStartedEvent.parentRunId / RunStartedEvent.input,
RunAgentInput.parentRunId / RunAgentInput.state /
RunAgentInput.forwardedProps, Run.result, the message-class nullables
(name, content, toolCalls, error, encryptedValue),
ThinkingStartEvent.title, ToolCallResultEvent.role,
StateSnapshotEvent.snapshot, and RunErrorEvent.code.RunFinishedEvent.result is dropped from toJson() when null: an
inbound explicit-null 'result': null does not survive a Dart→Dart
re-serialization round-trip. This matches the canonical TS/Python schemas
(z.any().optional() / Optional[Any] = None), so cross-SDK forwarding
is unaffected. Consumers relying on byte-for-byte round-trip fidelity
should read rawEvent instead of re-serializing.Initial release of the AG-UI Dart SDK
AgUiClient - Main client for AG-UI server interactionsSimpleRunAgentInput - Simplified input structure for common use casesYour coding agent can read these notes before it upgrades. Set up the MCP server →