Wire format
a2tea targets the real A2UI protocol (v0.9) via
github.com/tmc/a2ui. It defines no
component types of its own.
:::warning Not an a2tea invention.
Earlier revisions used a provisional flat-object shape with a kind
discriminator. That has been removed — the wire format is A2UI.
:::
What arrives
A2UI is message-oriented. An agent emits a stream of ServerMessages —
createSurface, updateComponents, updateDataModel, deleteSurface —
usually interleaved with conversational text and wrapped in <a2ui-json> tags.
<a2ui-json>
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "trip",
"components": [
{ "component": "Card", "id": "root", "child": "col" },
{ "component": "Column", "id": "col",
"children": ["title", "book"] },
{ "component": "Text", "id": "title",
"text": "Kyoto · Autumn Weekend" },
{ "component": "Button", "id": "book", "child": "book-label" },
{ "component": "Text", "id": "book-label", "text": "Book it" }
]
}
}
</a2ui-json>
Components live in a flat set that references children by ID (an adjacency
list); the renderer resolves the tree from its root. Note the shape rules the
catalog enforces: the type discriminator is component, a Card wraps exactly
one child, and a Button has no label field — its label is a child
Text component.
Parsing & rendering
Contains / Scan wrap
a2uistream to split a
reply into ordered Parts of text and typed messages. Render applies the
messages to build surface state and returns an embeddable model — resolving the
component tree from the root (the component nothing else references as a child).
Implemented vs. not yet
✓ Implemented
- Scan A2UI from LLM text (
<a2ui-json>tags or bare JSON) Textvariants,Card,Column/Row/List,Divider- Focusable, styled
Buttons - Editable inputs — all five:
TextField,CheckBox,ChoicePicker,Slider, andDateTimeInput. Edited values round-trip to the agent inActionEvent.Context ChildListtemplates: the dynamic form expands one template instance per element of the bound data-model list, with bindings inside each instance resolving against that element firstModal:Enteropens (content renders as a bordered in-flow block — the honest terminal equivalent of an overlay),Esccloses the innermost open modalcreateSurfaceas a documented no-op: a surface is established by its firstupdateComponents; theme hints andcatalogIdare deliberately ignored so the host theme wins and the compiled-in catalog stays authoritative- Lifecycle:
updateComponents/updateDataModel/deleteSurface - Events:
event.ButtonClicked+ nativeClientMessage,InputSubmitted(Enter on aTextField/DateTimeInput), andChoiceSelected(aChoicePickerselection change, carryingValues []string) Tabs: the tab bar joins the focus ring;←/→(orh/l) switch the active tab, and components inside inactive tabs leave the focus ring
✗ Not yet
The original catalog gaps are closed. What remains is deliberate scope, not
backlog: media components (Image, Icon, Video, AudioPlayer) draw
compact one-line placeholders — a terminal renderer has nothing honest to
play them with. Future work is tracked in the
issue backlog.