Render A2UI surfaces from AG-UI activity snapshots as generative UI, using the community a2ui-surface convention.
A2UI is a declarative generative UI protocol: the agent streams surface operations (create a surface, upsert components, update a data model) and the host renders them from a pre-approved component catalog, with no code over the wire. The AG-UI ecosystem carries A2UI as ACTIVITY_SNAPSHOT events with activityType: "a2ui-surface" and the operations under content.a2ui_operations; this is the convention emitted by @ag-ui/a2ui-middleware.
useAgUiRuntime consumes these snapshots natively. Each surface becomes one tool-call part with toolCallId a2ui:<surfaceId> and toolName "present", whose args are the converted generative UI spec, so surfaces render through the same path as the present frontend tool. Snapshots with any other activityType are ignored, and the MCP Apps activity path is unaffected.
Wire contract
A backend paints or updates a surface by emitting an activity snapshot on the AG-UI stream:
{
"type": "ACTIVITY_SNAPSHOT",
"messageId": "a2ui-surface-call_1",
"activityType": "a2ui-surface",
"replace": true,
"content": {
"a2ui_operations": [
{ "version": "v0.9", "createSurface": { "surfaceId": "s1" } },
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "s1",
"components": [
{ "id": "root", "component": "Card", "title": "Order", "children": ["total"] },
{ "id": "total", "component": "Text", "text": { "path": "/total" } }
]
}
},
{
"version": "v0.9",
"updateDataModel": { "surfaceId": "s1", "path": "/", "contents": { "total": "$42" } }
}
]
}
}- A snapshot with
replace: true(the schema default) rebuilds thatmessageId's surface state from the operations it carries.replace: falsemeans the snapshot is ignored when thatmessageIdhas already been seen, matching the AG-UI event spec. The middleware always emitsreplace: true. - Surfaces are keyed by the
surfaceIdinside the operations, not bymessageId. Multiple snapshots that share amessageIdupdate their surfaces in place, and the synthesized part keeps itstoolCallId, so the rendered surface updates without duplicating parts. - Component trees use the A2UI adjacency-list model: nodes reference children by id, the root node has id
root, and props of shape{ "path": "/x/y" }are JSON Pointer bindings resolved against the surface data model. Both v0.9 and v1.0 operation payloads are accepted, including the v1.0 inlinecomponentsanddataModeloncreateSurface. - Lifecycle snapshots that carry a
status(such as"building") but noa2ui_operationsare tolerated and produce no part until operations arrive. AdeleteSurfaceoperation removes the surface's part. - The
a2ui:tool-call id prefix is reserved for synthesized surface parts: they are client-side render artifacts and are excluded from the history sent back to the agent, so genuine agent tool calls must not use ids starting witha2ui:.
Quick start
Register the present frontend tool from @assistant-ui/react-generative-ui; incoming surfaces then render with the default vocabulary:
import {
JSONGenerativeUI,
createActionRegistry,
defaultGenerativeUILibrary,
} from "@assistant-ui/react-generative-ui";
const generative = new JSONGenerativeUI({
library: defaultGenerativeUILibrary,
actions: createActionRegistry({
"a2ui:action": async ({ payload }) => {
// forward the A2UI action to your backend
},
}),
});
const toolkit = {
present: generative.present({ display: "standalone" }),
};Pass the toolkit to your assistant as on the Generative UI page; no other configuration is needed on useAgUiRuntime.
Component mapping
The converter maps the A2UI basic catalog onto the default generative UI vocabulary: Text becomes Markdown (h1 to h6 variants become Header, caption becomes Caption), Column becomes Col, TextField becomes Input (the control name is derived from the last segment of its binding path), CheckBox becomes Checkbox, and Image, Row, Card, Divider, Button map one to one. A node with template children expands into a ListView over the bound list. Button actions become $action objects with type "a2ui:action" carrying the action name, surfaceId, and sourceComponentId, which is how your action registry receives them. Unknown components and operations are skipped with a debug warning, and conversion is bounded (depth 32, 100 template items, 5000 nodes) so a malformed stream cannot hang the client.
Limitations
- Forwarding an
"a2ui:action"back to the agent is host code today: send it on the next run asforwardedProps.a2uiAction.userAction, the convention@ag-ui/a2ui-middlewareconsumes. A first-class round-trip helper is planned. - Surfaces are not yet rehydrated when a thread is restored from history.
- The upstream middleware currently emits v0.9 operation payloads; the renderer accepts v0.9 and v1.0 shapes.