A2UI · Reference

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:

ComponentMain fields
Texttext, variant
Imageurl, description, fit, variant
Iconname (string or { svgPath })
Videourl
AudioPlayerurl, description
Rowchildren, justify, align
Columnchildren, justify, align
Listchildren, direction, align
Cardchild
Tabstabs (array of { title, child })
Modaltrigger, content
Divideraxis
Buttonchild, variant, action
CheckBoxlabel, value
TextFieldlabel, value, variant, validationRegexp
DateTimeInputvalue, enableDate, enableTime, min, max, label
ChoicePickeroptions (array of { label, value }), value, variant, displayStyle, filterable, label
Slidervalue, 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:

FieldOnAllowed values
variantText'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'caption' | 'body'
variantImage'icon' | 'avatar' | 'smallFeature' | 'mediumFeature' | 'largeFeature' | 'header'
fitImage'contain' | 'cover' | 'fill' | 'none' | 'scaleDown'
variantButton'default' | 'primary' | 'borderless'
variantTextField'shortText' | 'longText' | 'number' | 'obscured'
variantChoicePicker'mutuallyExclusive' | 'multipleSelection'
displayStyleChoicePicker'checkbox' | 'chips'
justifyRow, Column'start' | 'center' | 'end' | 'spaceAround' | 'spaceBetween' | 'spaceEvenly' | 'stretch'
alignRow, Column, List'start' | 'center' | 'end' | 'stretch'
directionList'vertical' | 'horizontal'
axisDivider'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.