a2ui_agent 0.1.0
a2ui_agent: ^0.1.0 copied to clipboard
Helps a Dart agent generate A2UI: negotiates catalogs with the renderer, teaches the model the catalog, and parses its reply into A2UI messages.
a2ui_agent Changelog #
Unreleased #
0.1.0 #
-
Remove
(Agent to UI)from the embedded v0.9server_to_client.jsonand v1.0agent_to_renderer.jsonschema descriptions inDirectJsonPromptGenerator(#1605). -
Add incremental streaming support for the Express inference format via
ExpressResponseStream(ExpressFormat.supportsStreamingandExpressParser.supportsStreamingnow returntrue).ExpressFormatFactory,ExpressFormat, andExpressParseracceptprogressiveKeys(defaulting todefaultExpressProgressiveKeys = {'text'}) to heal unclosed string literals in matching component properties and data path assignments as chunks arrive, while gating initialcreateSurfaceemission onroot = ...for new surfaces, emitting deltaupdateComponentsand per-pathupdateDataModelmessages on subsequent chunks, and holding statements with unresolved value variables until defined (#2896).surface(...)also acceptsupdate=andsendDataModel=keyword arguments to update an existing surface without emittingcreateSurfaceor to request client data model echoing on surface creation. -
Breaking: Use
'1.0'(without a'v'prefix) as theversionfield on v1.0 protocol messages and capabilities inExpressCompiler,ExpressDecompiler,DirectJsonParser,RendererState,resolveCatalogs, and prompt examples (#3097, #3726). -
Support the reserved
extensions=keyword argument onsurface(...)and component calls in v1.0 A2UI Express (ExpressCompilerandExpressDecompiler), compiling to and decompiling frommetadata.extensionswith UAX 31 extension key validation (#3708, #3757). -
Requires
a2ui_core^0.5.0. -
Breaking: In
ExpressCompiler, data path assignments ($/... = ...) in scopes that update a surface (scopes without arootassignment) now emit one targetedupdateDataModelmessage per assigned path at its JSON Pointerpathinstead of folding all assignments into a single rootupdateDataModelwithpath: "/"(which replaced the entire surface data model on the renderer). Surface-creating scopes continue to fold data path assignments into a single root data model and now support list element traversal, canonical numeric-segment list auto-vivification, and full root replacement via$/ = {...}.ExpressDecompilernow writes standaloneupdateDataModelmessages as{path} = {value}($/ = {...}at root) so map replacements round-trip without merging into sibling keys, accepts numeric path segments inupdateDataModel(such as/items/0), and sorts map keys when flattening surface-creation data models into leaf assignments. -
Breaking: when
ResponseStream.feedorclosethrows anA2uiError, the error'spartialResultsholds the parts the call made ready before the failure, and they stay applied to the stream'sRendererState. Before, a chunk that finished a block and then failed in the next one threw without those parts and rolled them back. Showerror.partialResultsbefore ending the stream (#3597). -
An
A2uiErrorthatParser.parseResponsethrows carriespartialResultstoo: the parts before the failing block, or only the text when a tree check fails, since that check spans every block. None of them is applied to the state (#3597). -
Breaking:
ResponseStreamis nowResponseReader<ResponsePart>, and a class that implements it addsisInBlock.ResponsePartextends the new sealedStreamPart. Code that namesResponseStreamorDirectJsonResponseStreamcompiles unchanged (#3597). -
Added
Parser.openContinuingStreamandA2uiRequestProcessor.openContinuingStream, which open aContinuingResponseStream(ResponseReader<StreamPart>;DirectJsonContinuingResponseStreamfor direct JSON andExpressContinuingResponseStreamfor Express) that reads past a rejected block. It returns anErrorPartin place of the block, skips the rest of it, and reads the text and blocks after it; the messages the block emitted before it was rejected stay in the state. A block still open when the response ends is returned as anErrorParttoo (#3597). -
Added
ResponseReader.isInBlock, which tells whether the response read so far ends inside a block, so a client that joins the chunks of a paused and resumed response can tell whether a space between them could break a block (#3597). -
Added
RendererState.of, a state that checks messages by its own rules and applies them to theMessageProcessoran app renders from, for an app that is both agent and renderer. A rollback takes a message back out of the processor's model, acallRendererFunctionreaches the processor only once the outermostapplyor transaction it was applied in succeeds, anddisposeleaves the processor's surfaces to its owner (#3597). -
Added
StreamedAnswer, which reads one streamed answer into a renderer'sMessageProcessorthroughRendererState.of, reading past rejected blocks, and keeps itspartsin the order the model wrote them: text (AnswerTextPart), one part per surface it creates (AnswerSurfacePart), and anAnswerErrorPartper rejected block (#3597). -
Breaking:
Event(...)in Express no longer accepts the event name as aname=keyword argument; pass it positionally, as the TypeScript and Python compilers require. A keywordEventdoes not define now throwsA2uiValidationError. -
ExpressCompileracceptsuserMessage=andcontext=keyword arguments onEvent(...), compilinguserMessageto the v1.0event.userMessagefield, and throwsA2uiValidationErrorforuserMessagebefore v1.0 and for any keywordEventdoes not define, where it used to drop them. A function call in an action slot nested inside a list item or object property (such asitems: [{label, action: openUrl(...)}]) is now compiled as afunctionCallaction.ExpressDecompilerwritesuserMessage=back, andExpressPromptGeneratorteachesEvent(name, context?, userMessage=...)in rule 8 for v1.0+ catalogs (#3598, #3690, #3867). -
DirectJsonParser.unwrap,DirectJsonResponseStream, andExpressParser.unwrapremove the backticks left at the edges of a block when a model writes the tags as inline code (`<a2ui>` ... `</a2ui>`or`<a2ui-json>` ... `</a2ui-json>`) and drop text made of nothing but backticks. In one-shot unwrapping (unwrapandparseResponse), they also recover at most one block closed with a closing tag when the model opened the block with a Markdown code fence or omitted the opening tag;DirectJsonParser.hasFormatContentandExpressParser.hasFormatContentdelegate tounwrapand count recovered blocks as complete format blocks. -
The Direct JSON and Express compilers strip stray unescaped line breaks inside JSON object keys and identifier/reference properties (
id,component,child,childrenitems,surfaceId,catalogId, andcomponentId, leaving user data underupdateDataModelvalues andcreateSurface.dataModeluntouched), at the edges of single-line Express strings, and inside Express data paths and line-wrapped identifiers (outside strings and comments, when the joined name resolves to a declared variable), accept SQL/Lua-style--comments in Express, and map a trailing numeric positional argument across optional enum properties when a component schema declares a single later numeric property. -
Express
IDENTIFIERandCHECKtokens now follow UAX 31 ([\p{XID_Start}_] [\p{XID_Continue}]*) rather than ASCII-only letters, digits, and underscores. InExpressDecompiler, non-identifier component ids before protocol v1.0 (such astitle-heading) and reserved-word component ids in any version (true,false,null,_) are written with a derived variable name andid="..."so they round-trip throughExpressCompiler; from v1.0 onward, component ids that are not valid UAX 31 identifiers throwA2uiValidationError. -
Express number literals follow the grammar of the core
${...}expression parsers: an optional sign, digits with an optional fraction (.5,1.) and an optional exponent (1e6,2.5E-7). A literal outside the double range (1e999) throwsA2uiParseError.ExpressDecompilernow writes doubles that need exponent notation (1e-7,1e+21) instead of throwing, and throwsA2uiValidationErroronly for non-finite values (#3019). -
Breaking:
A2uiGenerator.inferenceFormatFactoryandA2uiRequestProcessor.formatFactorydefault toExpressFormatFactoryinstead ofDirectJsonFormatFactory. -
The Express compiler throws
A2uiValidationErrorfor a check on a component whose schema declares nochecks, such asText("hi", ?required($/name)), rather than writingchecksonto it. -
Add
RendererState, the agent's mirror of the surfaces a renderer holds. It wraps oneMessageProcessorfor its whole life and checks each message against everything applied before it, so an agent that keeps one state for a conversation catches a reference to a component no turn sent, a cycle with components from an earlier turn, or a new component that nothing reaches.applychecks messages and applies them. The call is atomic: if a message is rejected, the state is left as it was. An update to a surface the state never saw created is rejected, as a renderer would reject it.transactionmakes several calls atomic. Transactions nest: a failed inner transaction undoes only what it applied, and the outer one goes on.- Being atomic costs what the messages change, not the size of the surfaces they touch: the state keeps an undo log of what each message replaced instead of copying a surface, so the time to apply n messages to one surface grows with n. A deleted surface is kept aside until the outermost call ends, so a rollback puts the same surface back. A rollback keeps the order of a surface's components and of the keys of its data model, restores the fields its references are read through, and undoes the whole log even if one step of it fails, rethrowing the error that caused the rollback.
checkTopologychecks the trees of the given surfaces: the root exists, every reference resolves, and every component added or replaced since the last check is reachable from the root. A component that an update detached, by rewriting its parent's children, is accepted, since the protocol has no message that removes one component.markCheckedstops holding the components each surface changed against latercheckTopologycalls. A stream that fails after emitting messages, and a block cut off at the end of a response, mark their surfaces checked: the renderer holds those components and no message removes one, so later turns aren't required to make them reachable. Dangling references and a missing root are still reported.disposereleases the surfaces the state holds.
-
Add
validatePayload, which checks one payload with no record of earlier ones, such as the argument of a tool call. It runs on a short-livedRendererStatethat assumes a surface the payload only updates exists, and replaces the processor that payload checks built for every call. -
Breaking:
Parser.parseResponseandA2uiRequestProcessor.parseResponsetake an optionalstateand check every message they compile against it. Without one, a call checks the response against a new, empty state, as a chat turn that shows new UI, so an update to a surface the response does not create is rejected;Parser.parseResponseaccepted it before. A canvas agent keeps one state for the conversation and passes it on every turn. The response is applied to the state all or nothing, and the trees of every surface it touches, including surfaces it only updates, are checked once every block is applied, unless a block is cut off by the end of the response; the components of a cut-off block are marked checked. Express output is checked the same way, which rejects inline child ids that collide with assigned variables and check lists on components that declare none. -
Breaking: a parser holds no state, so one parser reads every response of a request.
Parser.parseChunkis removed: open a stream for each response withparser.openStream(wrapped: ...), callfeed(chunk)for each chunk, andclose()after the last one.Parser.openStreamandA2uiRequestProcessor.openStreamreturn aResponseStream, and the direct JSON parser aDirectJsonResponseStream:- Each message the stream emits is checked against the stream's state and applied to it as it is emitted, so the state mirrors what the renderer received even when the response fails part way.
- When a block closes, the trees of the surfaces it touched are checked, which a streamed response skipped before: a component nothing reaches, a dangling reference or a cycle now throws.
- Only
updateComponentsandupdateDataModelare emitted while they grow; any other message, such ascreateSurface,deleteSurfaceorcallRendererFunction, is held until its object closes, so it is emitted once. A message for a surface that does not exist yet is held back, as a renderer would reject it, and the block throws when it closes. closeemits the text held back as a possible tag, throwsA2uiParseErrorif a block is still open, and checks the tree of an unwrapped stream.- A stream that throws has ended:
feedthen throwsStateError, andclosereturns nothing. The messages it emitted stay in its state, and their components are marked checked.
-
Breaking:
Parser.compileonly translates a block into messages. It reports what the notation gets wrong, butDirectJsonParser.compileno longer checks components, function calls, themes or catalog ids against the catalogs;parseResponse(block, wrapped: false)compiles and checks one block. AParsersubclass implements the newcatalogsgetter. -
The v1.0 schema in the direct JSON prompt no longer says that
createSurfaceneeds later messages for its components and data. They can be sent inline increateSurfaceor in laterupdateComponentsandupdateDataModelmessages. #3678 -
A2uiRequestProcessorreuses one parser, which itsparsergetter returns.createStatecreates an emptyRendererStatefor the active catalogs. A processor can be bound to a state, through itsstateconstructor parameter orA2uiGenerator.createProcessor(..., state: ...), so thatparseResponseandopenStreamuse it when a call passes none. A state for other catalogs throwsA2uiCatalogError.A2uiRequestProcessor.acceptsStatetells whether a state holds the processor's catalogs, so an agent that keeps a state per conversation can tell when to make a new one.A2uiRequestProcessor.parseResponsealso takeswrapped. -
CatalogConfig.transformedCatalogis computed on first access and then reused, so every request sees the same transformed catalog (#3575). -
Implemented protocol v1.0. The SDK reads and writes the protocol version that the active catalogs target, rather than always v0.9:
FileSystemCatalogProviderandInMemoryCatalogProviderload documents that declareprotocolVersion1.0, and takeA2uiProtocolVersion.v1_0.resolveCatalogs, and soA2uiGenerator.createProcessor, negotiate a protocol version with the renderer. The registered catalogs may target several versions; the latest one the renderer declares capabilities for is used, or the next earlier one when the two have no catalog in common there.createProcessorgives each processor the example turns written in its version, and throwsA2uiValidationErrorfor a turn written in a version no registered catalog targets.- Without capabilities,
resolveCatalogsthrowsA2uiCatalogErrorfor registered catalogs of different versions, since nothing says which one the renderer reads. Given capabilities, it throws it when no catalog is registered. - Both formats compile and decompile v1.0 messages, in which a component or function call may name its own catalog.
- In Express, a
catalogIdargument names a catalog, and a function call written as a statement of its own compiles tocallRendererFunction. - The Express prompt explains how to name a catalog, lists the functions
the agent may call, and carries each catalog's
instructions. - A
callRendererFunctionmay call only a function whoseallowedCallerslets the agent call it and that needs no user activation.RendererState,openStream,parseResponseandExpressCompiler.compileall check it througha2ui_core'sMessageProcessor.forAgent, so a streamed call the renderer would reject is never emitted. - The direct JSON parser holds a streamed v1.0 component back until it
closes;
bufferIncompleteComponentsapplies only before v1.0.
-
Exported
ExpressFormat,ExpressPromptGenerator,ExpressParser,ExpressCompilerandExpressDecompiler, which the agent SDK blueprint lists, as the direct JSON classes are.ExpressFormathas a public constructor, asDirectJsonFormatdoes. -
DirectJsonFormatandExpressFormatFactory.createFormatthrowA2uiCatalogErrorfor two catalogs with the same id, since acatalogIdcould not say which of the two it names. -
Express prompts, compilers and decompilers, and direct JSON parsers, throw
A2uiCatalogErrorfor catalogs that target different protocol versions. Catalogs of v0.9 and v0.9.1, which share message shapes, mix as v0.9, there and inresolveCatalogs. -
Payload checks run through
a2ui_core'sMessageProcessor.forAgent. -
DirectJsonResponseStreamreads and checks only what each chunk adds, rather than the whole block so far: a message once it closes, after which it is not read again, a growingupdateComponentsfor its new components, and a growingupdateDataModelfor the lists and maps its value gains, with the value applied once it closes. The time to stream a payload grows with its size rather than with its square: streaming 6.4 KB in 16-byte chunks takes about 0.1 s rather than 1.4 s, and 105 KB about 0.3 s. A message that grows is emitted again with the components it emitted before, as the same objects. -
The Express decompiler rejects a check condition with extra fields rather than dropping them.
-
The Express prompt gives each function's description.
-
A streamed string cut inside an escaped surrogate pair is cut before the pair rather than healed with U+FFFD.
-
The Express compiler rejects an inline component whose id another component has, rather than renaming it.
-
The Express decompiler writes a single check without brackets, a multiline string with backslashes as a raw triple-quoted string, and an
updateDataModelbelow the root as assignments at its path. -
A check's default message has no trailing period, as in the other SDKs.
-
The Direct JSON prompt lists
descriptionaftertypein the agent-to-renderer andcommon_typesschemas it embeds, following the reordered specification files. -
Align
MessageProcessor.modelandPayloadValidatorusage witha2ui_core. -
DirectJsonPromptGeneratorembeds the agent-to-renderer schema, the prunedcommon_typesschema, and each active catalog'sCatalog.toJson()document instead of the deprecatedcatalogSchema, avoiding duplicate inlined common type definitions when multiple catalogs are active. -
allowedMessagesof the Direct JSON format accepts only messages: the definitions the agent-to-renderer schema lists inoneOfand their envelope keys. Other definitions, such as v1.0'sComponentandComponentsList, throwArgumentError. -
Payload validation builds its
MessageProcessorwithdefaultVersion. -
Payload validation gives
MessageProcessorthe catalog throughCatalog.withFunctions, so a catalog read byCatalog.fromJsonkeeps its protocol version, its authored$defs(a recursive#/$defs/nodeno longer fails as an unresolvable reference) and each function'sdescriptionand declaredallowedCallersandrequiresUserActivation. -
The Express format reads component signatures from
Catalog.resolvedComponentSchema, sinceComponentApi.schemanow keeps a component's schema as the catalog document wrote it. -
FileSystemCatalogProviderandInMemoryCatalogProviderpass the provider's id and protocol version toCatalog.fromJsoninstead of writing the id into the document, soload().toJson()writes back a document that omits itscatalogIdwithout one, and the catalog reports the provider's protocol version when the document declares none. -
Include common mixin properties (
accessibility,weight) at the end of Express component signatures (after component-specific properties) so positional arguments map to component properties while supporting common properties via keyword arguments; previously they were omitted. -
A2uiRequestProcessorchecks each example withvalidatePayload, as one render: a surface the example creates may arrive across severalupdateComponentsmessages, and must have a root, resolved references and every component reachable from the root once the whole example is applied. An example may also update a surface it does not create, which is assumed to exist; such an example was rejected before. Undeclared component types, duplicate ids, cycles and malformed data-model paths are still rejected per message. -
DirectJsonParser,DirectJsonFormatandDirectJsonFormatFactorytakebufferIncompleteComponents. When it is true, a streamed component is emitted only once its JSON object closes, as v1.0 requires, and progressive keys heal only values outside components. It is false by default, which keeps the v0.9 behavior of healing a component while it streams. -
DirectJsonParserrejects a number out of the range of a double, such as1e400, withA2uiParseError. A streamed response used to throw aJsonUnsupportedObjectErrorfor it. -
The Direct JSON message reader and the Express decompiler continue to reject a
createSurfacemessage without acatalogIdwithA2uiValidationError. The check moved into the agent SDK now thata2ui_coremakes the field optional for v1.0; previouslya2ui_corerejected the message during parsing. -
pub.dev now recognizes the license. The package
LICENSElinkedhttps://where the Apache text hashttp://, which pana does not match, and carried the root's notice foreval/bin/transcrypt, a file outside this package. Also a longerdescription. -
The Express lexer and parser are generated from
Express.g4with ANTLR instead of written by hand, so the package depends onantlr4. Where the handwritten parser differed from the grammar, Express now follows the grammar: a comment ends at a lone\r,r"""without a closing"""is read asr""followed by a string, andr"followed by a line break is read as the namerfollowed by a string. Syntax errors carry ANTLR's messages, and their columns count Unicode code points. -
ExpressParser.unwrapandhasFormatContentread strings and comments asExpress.g4does when finding the</a2ui>that ends a block, so a close tag after one of the constructs above ends the block instead of being read as part of a string or comment. -
An Express integer too large for
intcompiles to a double instead of throwingFormatException. -
The Direct JSON prompt rule says the JSON part is a JSON array of A2UI messages, each following the schema, instead of "a single, raw JSON object (usually a list of A2UI messages)" that "MUST validate" against it (#3721).
-
Catalogs are typed
CatalogApi, the new name ofa2ui_core'sSchemaCatalog. -
ComponentPruningTransformerandFunctionPruningTransformernow accept an optional or null allowlist, treating it as empty. -
Implemented the rest of the agent SDK blueprint API for protocol v0.9:
CatalogProvider,FileSystemCatalogProvider,InMemoryCatalogProviderandCatalogConfig.fromPath. A document's id and version are settled with the provider's; on the web,FileSystemCatalogProvidercannot read files.- The direct JSON format:
DirectJsonFormatFactory,DirectJsonFormat,DirectJsonPromptGeneratorandDirectJsonParser. The prompt carries the v0.9 message schema pruned toallowedMessages, the common types and the catalogs.parseChunkemits each message once it reads and satisfies the catalogs, healingprogressiveKeyswhile their values stream. - Express decompilation, through
Parser.decompile, and prompt examples in the Express prompt. - Inline catalogs in
resolveCatalogs, whenacceptsInlineCatalogsis true. An inline catalog whose id is already active is dropped.
-
Added
CatalogTransformer,ComponentPruningTransformer,FunctionPruningTransformerandCatalogConfig.transformers. -
Added
resolveCatalogs, whichA2uiGenerator.createProcessornow uses. The active catalogs are the transformed ones. Renderer capabilities of null, for a request that carries none, activate every registered catalog. -
Added
allowedMessagestoExpressFormatFactory. The Express prompt describes only the statements that compile to the allowed message types. -
Added
examplestoA2uiGenerator,A2uiRequestProcessor,ExpressPromptGeneratorandInferenceFormatFactory.createFormat. A processor checks each example against its active catalogs when it is created, and renders its prompt then, so an example the format cannot write fails there. -
A2uiGenerator.createProcessortakes aninferenceFormatFactorythat overrides the generator's. -
Parserhas new members:hasFormatContent,wrap,decompile,supportsStreamingandparseChunk. Only the direct JSON parser implementsparseChunk. -
The Express compiler reads a block that assigns components but no
rootas an update of a surface created earlier, and compiles it toupdateComponentsalone. -
A payload whose
createSurfacenames an inactive catalog is anA2uiValidationErrorrather than anA2uiCatalogError. -
The Express syntax rules no longer show
Card(...), so a prunedCardnever reaches the prompt. -
Breaking: the Express parser reads a direct JSON block (
<a2ui-json>) as text instead of throwingA2uiParseError. A parser reads only its own format's tags. -
Breaking:
A2uiGenerator.inferenceFormatFactoryandA2uiRequestProcessor.formatFactorydefault toDirectJsonFormatFactoryinstead of being required, as in the blueprint. -
Breaking: custom
Parsersubclasses must implement the new abstract members.
0.0.1-wip004 #
- Implement full Express format compiler, parser, syntax lexer, and prompt generator for protocol v0.9:
ExpressCompiler: Compiles Express blocks (components,updateComponents,dataModel,updateDataModel,deleteSurface) into canonical A2UI v0.9 message payloads.ExpressParser: Parses streaming LLM output into structuredResponsePartchunks (TextBlock,ExpressBlock), validating message boundaries and syntax.ExpressPromptGenerator/A2uiRequestProcessor.promptSnippet: Dynamically generates system prompt instructions describing Express syntax, active catalog components, functions, and positional signatures.ExpressSyntax: Grammar, token definitions, keywords, and AST representation for Express blocks.
- Add shared conformance test harness integration running cross-language
conformance/agent/expresstest suites (express_conformance_test.dart). - Add end-to-end integration test runner in
e2e_test/targeting Gemini models (gemini-3.6-flash). - Bump dependency on
a2ui_coreto^0.2.2.
0.0.1-wip003 #
- Added the initial API scaffolding for one agent turn in the Express format, limited
to protocol v0.9:
A2uiGenerator,A2uiRequestProcessor,CatalogConfig,InferenceFormatFactory,ExpressFormatFactoryand theResponseParttypes. InferenceFormatFactory.createFormatbinds a format to the active catalogs as anInferenceFormat, which provides aPromptGeneratorand aParser.- Removed the placeholder
Awesomeclass.
0.0.1-wip002 #
- Requires
a2ui_core^0.2.0, which takes two type parameters onCatalog. - Dropped an unnecessary
library;directive.
0.0.1-wip001 #
- Initial version.