A2UI Schema
The @threadplane/a2ui schema is a TypeScript model of the A2UI v0.9 protocol shapes the framework uses. It's a contract for agent output and custom integrations, but it's not a runtime validator.
Protocol constants
const A2UI_WIRE_VERSION = 'v0.9';
const A2UI_MIME_TYPE = 'application/a2ui+json';
const A2UI_BASIC_CATALOG_ID =
'https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json';A2UI_WIRE_VERSION is stamped on every envelope. A2UI_MIME_TYPE is the payload MIME type standardized in the v0.9.1 release. A2UI_BASIC_CATALOG_ID identifies the standard basic component catalog referenced by createSurface.catalogId.
Dynamic values
Dynamic values are bare literals, JSON-pointer bindings, or client-side function calls. There are no literal wrapper objects in v0.9.
interface A2uiPathRef {
path: string;
}
interface A2uiFunctionCall {
call: string;
args?: Record<string, unknown>;
returnType?: 'string' | 'number' | 'boolean' | 'array' | 'object' | 'any' | 'void';
}
type DynamicString = string | A2uiPathRef | A2uiFunctionCall;
type DynamicNumber = number | A2uiPathRef | A2uiFunctionCall;
type DynamicBoolean = boolean | A2uiPathRef | A2uiFunctionCall;
type DynamicStringList = string[] | A2uiPathRef | A2uiFunctionCall;Absolute paths start with / and are resolved from the model root. Relative paths are resolved from an optional A2uiScope (used inside children templates). Function calls execute through an A2uiFunctionRegistry (see createA2uiFunctionRegistry); resolveDynamic returns undefined for them only when no registry is supplied or the name is unknown.
Children
Container components use either a static child-id list or a template declaration.
type A2uiChildren =
| string[]
| { path: string; componentId: string };The template form stamps componentId once per element of the data-model array at path. The protocol layer only types this shape; template expansion is renderer behavior.
Actions
An action either dispatches a named event to the agent or executes a client-side function.
interface A2uiEventAction {
event: {
name: string;
context?: Record<string, DynamicValue>;
};
}
interface A2uiFunctionAction {
functionCall: A2uiFunctionCall;
}
type A2uiAction = A2uiEventAction | A2uiFunctionAction;context is a plain object; values can be literals or { path } bindings resolved at interaction time. @threadplane/a2ui does not execute actions. It only describes the payload that chat/render code can turn into an outbound A2uiActionMessage.
Components
Components are flat objects discriminated by the component string. Props sit at the top level of the same object — there are no type-keyed wrappers.
interface A2uiComponentBase {
id: string;
component: string;
catalogId?: string; // per-component catalog override
weight?: number; // flex-grow-like; only inside Row/Column
accessibility?: Record<string, unknown>;
}Input components additionally mix in A2uiCheckable (checks?: A2uiCheck[]). Each rule is the spec CheckRule shape — { condition: DynamicBoolean, message: string } — where condition is typically a validator call (required, regex, length, numeric, email) or a logic combination. Renderers evaluate rules against the live data model and block event actions while any rule fails.
The basic-catalog component shapes are:
| Component | Main fields |
|---|---|
Text | text, variant |
Image | url, description, fit, variant |
Icon | name (string or { svgPath }) |
Video | url |
AudioPlayer | url, description |
Row | children, justify, align |
Column | children, justify, align |
List | children, direction, align |
Card | child |
Tabs | tabs (array of { title, child }) |
Modal | trigger, content |
Divider | axis |
Button | child, variant, action |
CheckBox | label, value |
TextField | label, value, variant, validationRegexp |
DateTimeInput | value, enableDate, enableTime, min, max, label |
ChoicePicker | options (array of { label, value }), value, variant, displayStyle, filterable, label |
Slider | value, max, min, label |
Note that Button has no text prop — its label is a child Text component referenced by id. ChoicePicker replaces the pre-v0.9 MultipleChoice component.
Several fields are constrained to a fixed enum. Emit one of the listed values — an unknown value isn't validated at the protocol layer, but a renderer may ignore it or fall back to a default:
| Field | On | Allowed values |
|---|---|---|
variant | Text | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'caption' | 'body' |
variant | Image | 'icon' | 'avatar' | 'smallFeature' | 'mediumFeature' | 'largeFeature' | 'header' |
fit | Image | 'contain' | 'cover' | 'fill' | 'none' | 'scaleDown' |
variant | Button | 'default' | 'primary' | 'borderless' |
variant | TextField | 'shortText' | 'longText' | 'number' | 'obscured' |
variant | ChoicePicker | 'mutuallyExclusive' | 'multipleSelection' |
displayStyle | ChoicePicker | 'checkbox' | 'chips' |
justify | Row, Column | 'start' | 'center' | 'end' | 'spaceAround' | 'spaceBetween' | 'spaceEvenly' | 'stretch' |
align | Row, Column, List | 'start' | 'center' | 'end' | 'stretch' |
direction | List | 'vertical' | 'horizontal' |
axis | Divider | 'horizontal' | 'vertical' |
Tabs is the one container that doesn't use A2uiChildren. Its tabs field is an array of { title: DynamicString; child: string } pairs.
The union of the basic-catalog shapes is exported as A2uiCatalogComponent. The broader A2uiComponent also admits non-basic-catalog components (A2uiComponentBase & Record<string, unknown>) — renderers treat unknown component strings as unrenderable and fall back gracefully.
The schema exposes validationRegexp on TextField and checks on input components; this package supplies the validator functions in createA2uiFunctionRegistry(), and @threadplane/chat's surface renderer enforces the rules (inline messages, blocked event actions, VALIDATION_FAILED error messages).
Message envelopes
Every envelope carries a version ('v0.9' on the current wire) plus exactly one envelope key:
type A2uiMessage = { version: string } &
(
| { createSurface: A2uiCreateSurface }
| { updateComponents: A2uiUpdateComponents }
| { updateDataModel: A2uiUpdateDataModel }
| { deleteSurface: A2uiDeleteSurface }
);createSurface
Creates a surface and declares its component catalog. Must be the first envelope for a surface.
{
"version": "v0.9",
"createSurface": {
"surfaceId": "checkout",
"catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json",
"theme": { "primaryColor": "#2563eb" },
"sendDataModel": true
}
}theme and sendDataModel are optional. When sendDataModel is true, the client attaches the surface's full data model to every outbound message.
updateComponents
Adds or replaces components for a surface, merged incrementally by id. Exactly one component across the surface must have id: "root"; rendering starts once it is defined.
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "checkout",
"components": [
{ "id": "root", "component": "Card", "child": "title" },
{ "id": "title", "component": "Text", "text": "Checkout" }
]
}
}updateDataModel
Sets or deletes data at an optional JSON-pointer path (omitted or "/" targets the whole model). value is plain JSON; omitting it deletes the key at path.
{
"version": "v0.9",
"updateDataModel": {
"surfaceId": "checkout",
"path": "/customer",
"value": { "name": "Ada", "active": true }
}
}deleteSurface
Removes a surface by ID.
{ "version": "v0.9", "deleteSurface": { "surfaceId": "checkout" } }Internal surface model
A2uiSurface is an internal model used after messages are applied:
interface A2uiSurface {
surfaceId: string;
catalogId: string;
theme?: A2uiTheme;
sendDataModel?: boolean;
components: Map<string, A2uiComponent>;
dataModel: Record<string, unknown>;
}A2uiTheme carries the optional, agent-supplied presentation hints from createSurface.theme:
interface A2uiTheme {
primaryColor?: string;
iconUrl?: string;
agentDisplayName?: string;
}The A2uiSurface shape is not the wire format. Do not assume an agent sends it directly.
Outbound action messages
When a rendered surface sends an action back to the agent, the typed outbound shape is:
interface A2uiActionMessage {
version: string; // 'v0.9'
action: {
name: string;
surfaceId: string;
sourceComponentId: string;
timestamp: string;
context?: Record<string, unknown>;
label?: string;
};
metadata?: {
a2uiClientDataModel?: A2uiClientDataModel;
};
}context holds the action's context values already resolved against the current data model. The optional label is a Threadplane extension — a human-readable label derived from the source component's text (for example, a Button's child Text), used to describe the action in transcripts; backends may ignore it.
A2uiClientDataModel is { surfaces: Record<surfaceId, Record<string, unknown>> } — the per-surface data model snapshot, attached only when the surface's createSurface set sendDataModel: true.
Two further client → agent shapes are typed: A2uiErrorMessage ({ version, error: { code, surfaceId?, path?, message? } }) for reporting client-side protocol errors, and A2uiClientCapabilities ({ supportedCatalogIds, inlineCatalogs? }) for advertising which catalogs the client can render.