Generative UI (A2UI)

Before you build a custom surface — try the catalog first. Twenty vetted composite surface presets ship as the A2UI catalog, installable with gecx add ui:<name>. Each preset bundles a frame factory, a data-model schema, an action allowlist, a host React renderer, a mock scenario, and a sha256 integrity hash. When one matches your moment (returns, scheduling, confirmation, multi-step form, address confirm, payment-method picker, etc.) you get free a11y, action policy, integrity guarantees, and analytics. Build custom only when no catalog entry fits. See A2UI Catalog and Contributing to the catalog.

What A2UI is

A2UI (Agent-Authored UI) lets the AI agent stream interactive UI surfaces through the chat response. Instead of sending plain text or a fixed card, the agent describes a layout -- buttons, choice pickers, text, columns -- and the SDK renders it with your local React components.

The agent chooses the interaction shape. Your app controls what components are available and how actions are routed.

When to use A2UI

Use caseBest SDK feature
Host-designed product carousel or order summaryTyped rich content part
Host-owned custom visualizationcustom rich payload with custom renderer
Tool side effect or customer API callSchema-backed client tool
Agent-authored interactive layoutA2UI surface

Use A2UI when the agent should decide the layout for a task. Examples: a return workflow with reason chips and a submit button, a troubleshooting form that asks for the exact missing fields, or a product tuner the agent creates on the fly.

Do not use A2UI for every rich part. Typed rich content is simpler when you already know the UI shape at build time.

Package exports

Core A2UI utilities are framework-neutral:

import {
  createA2UISurfaceManager,
  defaultActionPolicy,
  redactA2UIAction,
  redactA2UIDataModel,
  validateA2UIFrame,
  type A2UIActionEnvelope,
  type A2UISurfacePart,
  type A2UIv09Frame,
} from 'gecx-chat/a2ui';

React rendering is under a separate subpath:

import { createA2UIRenderer } from 'gecx-chat/a2ui/react';
import { basicCatalog } from '@a2ui/react/v0_9';

Enabling A2UI

Set capabilities.a2ui when creating the client or hook:

const chat = useChatSession({
  config: {
    transport: createMockTransport({ scenarios }),
    capabilities: {
      a2ui: true,
      a2uiCatalogIds: [basicCatalog.id],
    },
  },
});

Then register a renderer where you render message parts:

import { createA2UIRenderer } from 'gecx-chat/a2ui/react';
import { basicCatalog } from '@a2ui/react/v0_9';

const renderA2UI = createA2UIRenderer({
  catalogs: [basicCatalog],
  onAction: async (action) => {
    console.log(action.name, action.context);
  },
});

function PartRenderer({ part }) {
  if (part.type === 'a2ui-surface') return <>{renderA2UI(part)}</>;
  return null;
}

The renderer requires at least one catalog. A surface whose catalogId is not registered fails closed with an A2UI_CATALOG_REJECTED fallback.

A2UISurfacePart structure

When the SDK processes A2UI frames from the response stream, it produces an A2UISurfacePart:

interface A2UISurfacePart {
  id: string;
  type: 'a2ui-surface';
  surfaceId: string;
  protocolVersion: 'v0.9';
  catalogId: string;
  theme?: Record<string, unknown>;
  components: A2UIComponentFrame[];
  dataModel: Record<string, unknown>;
  status: 'streaming' | 'ready' | 'deleted' | 'error';
  error?: { code: string; message: string };
}

While the response is streaming, status is 'streaming'. When the response completes, it moves to 'ready'.

Surface manager

createA2UISurfaceManager() processes raw frames and maintains surface state. The SDK uses it internally, but you can also use it directly for testing or non-React integrations:

const manager = createA2UISurfaceManager();
const result = manager.applyFrame(frame, { responseId: 'resp_1', timestamp: new Date().toISOString() });
if (result.ok) {
  console.log(result.part.components);
}

The manager accepts four v0.9 frame operations: createSurface, updateComponents, updateDataModel, and deleteSurface.

Action policy

When a user interacts with an A2UI surface (clicks a button, submits a form), the renderer emits an A2UIActionEnvelope:

interface A2UIActionEnvelope {
  name: string;
  surfaceId: string;
  sourceComponentId?: string;
  timestamp: string;
  context?: Record<string, unknown>;
  dataModel?: Record<string, unknown>;
}

The action policy classifies every action as 'allow', 'confirm', or 'block':

  • allow -- action proceeds immediately.
  • confirm -- SDK prompts the user for confirmation before routing.
  • block -- action is silently dropped.

The default policy confirms dangerous action names like delete, cancel, purchase, and transfer_money, and blocks actions where context.userInitiated is false. You can supply a custom policy:

const renderA2UI = createA2UIRenderer({
  catalogs: [basicCatalog],
  actionPolicy: {
    classify(action) {
      if (action.name.startsWith('admin.')) return 'block';
      return defaultActionPolicy().classify(action);
    },
  },
  onAction(action) { /* route action */ },
});

Action policy is a user-experience guard, not authorization. Real authorization must still be enforced server-side.

Security

A2UI frames are untrusted agent-emitted UI. The SDK applies layered safety:

  • Frame validation -- only v0.9 frame shapes are accepted. Malformed frames fail with A2UI_FRAME_INVALID. Unsupported versions fail with A2UI_VERSION_UNSUPPORTED.
  • Catalog gating -- surfaces must reference a catalog ID registered by the host. Unregistered catalogs fail with A2UI_CATALOG_REJECTED.
  • Redaction -- redactA2UIAction and redactA2UIDataModel strip context and dataModel fields from debug and trace payloads by default. Frame and action payloads never leak into debug bundles.
  • Error containment -- renderer failures are caught by a per-surface error boundary. A broken surface does not crash the chat.
  • No remote code -- the SDK does not fetch remote catalogs or execute code from frames.

What's next

  • Custom Renderers -- building renderers for typed rich content parts.
  • Client Tools -- when an action should trigger a tool call instead of UI.
  • Testing -- using mock transport scenarios that include A2UI frames.
Source: docs/guides/generative-ui.md