From dca1fb304af0bdb17c388efb41b4aceed4814ab8 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Sat, 5 Sep 2026 23:24:08 -0700 Subject: [PATCH 1/3] docs(a2ui): the A2UI introduction leaves the pending list Co-Authored-By: Claude Fable 5.1 --- apps/website/src/lib/docs-example-code.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/website/src/lib/docs-example-code.spec.ts b/apps/website/src/lib/docs-example-code.spec.ts index 251c3e47f..ae4b128a0 100644 --- a/apps/website/src/lib/docs-example-code.spec.ts +++ b/apps/website/src/lib/docs-example-code.spec.ts @@ -21,7 +21,6 @@ import { * comments on any docs page, or it counts as an include. */ const PENDING_PAGES = new Set([ - '/docs/a2ui/getting-started/introduction', '/docs/chat/a2ui/overview', '/docs/chat/components/chat-debug', '/docs/chat/components/chat-input', From 3befe8cb3e8c790a066fe73a9f2c84df60eecf0a Mon Sep 17 00:00:00 2001 From: Brian Love Date: Sat, 5 Sep 2026 23:31:08 -0700 Subject: [PATCH 2/3] docs(a2ui): the introduction teaches through the running example Co-Authored-By: Claude Fable 5.1 --- .../a2ui/getting-started/introduction.mdx | 125 ++++++++++++++++- cockpit/ag-ui/a2ui/python/docs/guide.md | 127 ------------------ cockpit/ag-ui/a2ui/python/src/graph.py | 12 ++ deployments/ag-ui-dev/deps/a2ui/docs/guide.md | 127 ------------------ deployments/ag-ui-dev/deps/a2ui/src/graph.py | 12 ++ 5 files changed, 147 insertions(+), 256 deletions(-) delete mode 100644 cockpit/ag-ui/a2ui/python/docs/guide.md delete mode 100644 deployments/ag-ui-dev/deps/a2ui/docs/guide.md diff --git a/apps/website/content/docs/a2ui/getting-started/introduction.mdx b/apps/website/content/docs/a2ui/getting-started/introduction.mdx index 4f03d1fee..cd32845b4 100644 --- a/apps/website/content/docs/a2ui/getting-started/introduction.mdx +++ b/apps/website/content/docs/a2ui/getting-started/introduction.mdx @@ -1,16 +1,120 @@ +--- +description: The protocol layer for A2UI messages, taught through the flight-booking demo whose agent authors every surface and streams it as v0.9 JSONL. +--- + # Introduction `@threadplane/a2ui` is the protocol layer for A2UI messages. It gives the rest of the framework a shared TypeScript vocabulary for agent-built surfaces, streamed JSONL messages, dynamic values, and outbound action payloads. It does not render Angular components. It does not register handler functions. It does not decide how an agent should respond to a button click. Those jobs sit in `@threadplane/chat` and `@threadplane/render`. +The running example is a flight booking flow. The agent authors each screen as an A2UI surface, and this page walks the files that produce it. + +## What the demo does + +The Run tab shows the prebuilt `` composition with the A2UI catalog registered on it. Choose the welcome suggestion "Book LAX → JFK" and the agent replies with a booking form rather than with prose: origin and destination pickers already set to those two airports, a departure date, a passenger count, and a fare class. + +Fill the form and press "Search flights". The button does not post a chat message; it sends a structured A2UI action back to the agent, which searches the flight fixtures and answers with a second surface listing the matching flights. Selecting one produces a third surface, the booking confirmation, whose "Modify search" button returns to the form with the earlier values already filled in. + +The second suggestion, "Book SFO → SEA", runs the same pattern on a different route, which is worth trying because nothing about the form is hardcoded on the client. + +## How it is built + +Four files carry the feature: a LangGraph graph that authors the surfaces, a FastAPI server that exposes it over AG-UI, an application config that registers the agent, and a component that hands the A2UI catalog to ``. Open the Code tab to read them in full. + +### The component shape the model must satisfy + +A2UI v0.9 components are flat. Each entry carries an `id`, a `component` name from the catalog, and its props at the same level of the same object. The example models that as a Pydantic class and uses it as the structured-output schema for the LLM, so the model authors the component list under a validator instead of free-typing JSON. + + + +The validator is the safety gate: an unknown `component` name renders nothing visible, so the schema rejects it and the model is re-prompted with the error. + +### The three parts of a surface + +Every surface in this demo is the same triple: an id, an initial data model, and a flat list of components. Three subclasses give each node its own schema name and description without changing the shape. + + + +The `data_model` is what path bindings such as `{"path": "/origin"}` resolve against. + +### Wrapping a surface in v0.9 envelopes + +The model authors the components; the code writes the wire format. `_wrap_envelopes` emits the sentinel prefix and then one JSON envelope per line, each stamped `"version": "v0.9"`. + + + +Order matters, and the comment above the function states it: `createSurface` first, then `updateComponents`, then `updateDataModel`. + + +`A2UI_PREFIX` is `---a2ui_JSON---`. The content classifier in `@threadplane/chat` looks for exactly that string at the start of assistant content and routes the rest of the message into the A2UI pipeline instead of the markdown renderer. + + +### The node that authors the form + +`build_form` runs on the first turn and again on a "Modify search" turn. It recovers any prior submission from the message history, seeds the origin and destination from a phrase such as "I want to fly LAX to JFK", substitutes those values into the system prompt as the form defaults, and asks the model for a `BookingFormSpec`. + + + +The node returns an ordinary `AIMessage` whose content is the wrapped JSONL, which is why no custom event type is needed to carry a surface. + + +`_emit_with_retry` re-prompts the model with the validation error up to three attempts in total, and `build_form` falls back to a hand-written sentinel form when they all fail. A surface is the whole response here, so a validation failure with no fallback is a blank turn. + + +### Routing an action message back into the graph + +When the user presses a button, the surface sends an A2UI action message back to the agent as the next user message, and its content is JSON rather than prose. The entry node reads that last message and dispatches on the action name. + + + +`_is_submit_event` and `_is_flight_select_event` each parse the content and compare `action.name` against `bookingSubmit` and `flightSelect`, so anything that is not one of those two is treated as a fresh request for the form. + +### The graph and its checkpointer + +The wiring is a fan-out from `route` into the three surface-authoring nodes, each of which ends through background title generation. + + + + +`ag-ui-langgraph` reads thread state through `graph.aget_state`, so a graph served this way must compile a checkpointer, and the example uses `MemorySaver` for development. That is the opposite of a graph served by `langgraph dev` or LangGraph Platform, where the platform supplies persistence and compiling your own saver is an error. + + +### Serving the graph over AG-UI + +The server is the standard `ag-ui-langgraph` mount: wrap the compiled graph in a `LangGraphAgent` and attach it to a FastAPI application at a path. + + + +Nothing in this file is specific to A2UI; the surfaces travel as assistant message content over the same event stream as any other reply. + +### Registering the AG-UI agent + +`provideAgent()` from `@threadplane/ag-ui` needs the URL of that endpoint. The example passes a factory because it resolves the URL at runtime from the host that serves the demo; an application of your own passes `url` directly. + + + +`provideChat({})` registers the chat composition defaults alongside it. + +### Giving the chat composition a catalog + +The client side is one input. `a2uiBasicCatalog()` from `@threadplane/chat` returns a view registry covering all eighteen components of the A2UI basic catalog, and passing it as `[views]` is what lets `` mount a surface. + + + +No handler wiring appears here: `` builds the action message from the surface and submits it to the agent for you. + + +`` keeps a live store of the current data-model values, so the `{"path": "/origin"}` bindings inside the `action.event.context` on the submit button resolve to what the user typed, not to the values the agent seeded. `buildA2uiActionMessage()` stamps the surface id, the source component id, and a timestamp onto the result. + + ## How it fits ## What the package owns -The public entry point exports four groups of tools: +The demo shows the protocol from the agent side. The package is the same protocol expressed as TypeScript, and its public entry point exports four groups of tools: | Area | Exports | |------|---------| @@ -20,7 +124,7 @@ The public entry point exports four groups of tools: | Data access | `getByPointer()`, `setByPointer()`, `deleteByPointer()` | | Dynamic values | `resolveDynamic()`, `A2uiScope`, `isPathRef` / `isFunctionCall` guards | -Use this package when you are building an adapter, validating an agent stream, testing A2UI payloads, or integrating a custom renderer with the same protocol surface that `@threadplane/chat` uses. +Use this package when you are building an adapter, validating an agent stream, testing A2UI payloads, or integrating a custom renderer with the same protocol surface that `@threadplane/chat` uses. Rendering the surfaces inside ``, as the demo does, needs none of it directly. ## Message flow @@ -78,3 +182,20 @@ npm install @threadplane/a2ui ``` The package has no peer dependencies. + +## What's Next + + + + Surfaces, flat components, dynamic values, the four envelopes, and how actions travel back. + + + Pointer helpers, applying updateDataModel envelopes, and resolving dynamic values in scope. + + + Consume a streaming response, narrow values, build test payloads, and write a custom renderer. + + + Every protocol type, component prop interface, and envelope shape in one reference. + + diff --git a/cockpit/ag-ui/a2ui/python/docs/guide.md b/cockpit/ag-ui/a2ui/python/docs/guide.md deleted file mode 100644 index caf420c75..000000000 --- a/cockpit/ag-ui/a2ui/python/docs/guide.md +++ /dev/null @@ -1,127 +0,0 @@ -# A2UI Surfaces with @threadplane/chat - - -Render agent-driven interactive UI using the A2UI (Agent-to-UI) protocol. -The agent streams JSONL messages that build surfaces from the built-in -18-component catalog - no custom view components needed. - - - -Add A2UI surface rendering to your chat interface using `a2uiBasicCatalog()` -from `@threadplane/chat`. Pass it to `ChatComponent` via the `[views]` input -to enable A2UI surface rendering with automatic event routing. - - - - - -Configure `provideAgent()` in your app config: - -```typescript -// app.config.ts -import { provideAgent } from '@threadplane/ag-ui'; -import { provideChat } from '@threadplane/chat'; - -export const appConfig: ApplicationConfig = { - providers: [ - provideAgent({ url: '/agent' }), - provideChat({}), - ], -}; -``` - -Import `a2uiBasicCatalog()` and pass it via the `[views]` input, then retrieve -the agent with `injectAgent()`: - -```typescript -// a2ui.component.ts -import { ChatComponent, a2uiBasicCatalog } from '@threadplane/chat'; -import { injectAgent } from '@threadplane/ag-ui'; - -@Component({ - selector: 'app-a2ui', - standalone: true, - imports: [ChatComponent], - template: ``, -}) -export class A2uiComponent { - protected readonly agentRef = injectAgent(); - protected readonly catalog = a2uiBasicCatalog(); -} -``` - -No event handler wiring needed - A2UI button events route back to the -agent automatically. - - - - -The agent response must start with `---a2ui_JSON---` followed by -newline-delimited JSON envelopes, each stamped `"version": "v0.9"`: - -``` ----a2ui_JSON--- -{"version":"v0.9","createSurface":{"surfaceId":"s1","catalogId":"https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json"}} -{"version":"v0.9","updateDataModel":{"surfaceId":"s1","value":{"name":""}}} -{"version":"v0.9","updateComponents":{"surfaceId":"s1","components":[...]}} -``` - -Three envelope kinds build a surface: -1. `createSurface` - initializes the surface (must come first) -2. `updateDataModel` - sets the initial data model state -3. `updateComponents` - defines the flat component tree; exactly one - component has `id: "root"`, and rendering starts once it is defined - - - - -When tokens stream in, `ContentClassifier` detects the `---a2ui_JSON---` -prefix and routes content to the A2UI pipeline: - -1. `A2uiMessageParser` extracts complete JSON messages from the stream -2. `A2uiSurfaceStore` applies messages to build `A2uiSurface` objects -3. `A2uiSurfaceComponent` converts each surface to a json-render `Spec` -4. `RenderSpecComponent` renders the spec using the catalog components - - - - -Components bind to the data model using path references: - -```json -{"id": "name_field", "component": "TextField", - "label": "Name", "value": {"path": "/name"}} -``` - -The `surfaceToSpec` function auto-detects path references and populates -`_bindings` for each input component - agents do not write `_bindings` -directly. When the user changes a bound input, the component emits a -data model update event. - -**Known limitation:** Data model updates from user input do not currently -reflect to other components in real time. The agent can refresh state by -sending a new `updateDataModel` message. - - - - -Button `event` actions automatically route back to the agent as a -structured `A2uiActionMessage`: - -```json -{"version": "v0.9", "action": {"name": "formSubmit", "surfaceId": "s1", "sourceComponentId": "submit", "timestamp": "...", "context": {"formId": "contact"}}} -``` - -The agent receives this and can respond with a new surface, markdown, or -any other content. - - - - - -The 18-component A2UI catalog covers layout (Column, Row, Card), display -(Text, Image, Icon, Divider, List), input (TextField, CheckBox, -ChoicePicker, DateTimeInput, Slider), media (Video, AudioPlayer), -interactive (Button, Tabs, Modal). No custom components are needed for -standard forms and dashboards. - diff --git a/cockpit/ag-ui/a2ui/python/src/graph.py b/cockpit/ag-ui/a2ui/python/src/graph.py index ed90d8991..2ea24e60f 100644 --- a/cockpit/ag-ui/a2ui/python/src/graph.py +++ b/cockpit/ag-ui/a2ui/python/src/graph.py @@ -94,6 +94,7 @@ def _lookup_flight_impl(flight_number: str) -> dict | None: # ── Pydantic schemas ──────────────────────────────────────────────────────── +# region component-schema class A2uiComponent(BaseModel): """Single A2UI v0.9 updateComponents entry. @@ -136,8 +137,10 @@ def _known_component(cls, v: str) -> str: f"component '{v}' not in catalog. Allowed: {sorted(ALLOWED_COMPONENTS)}" ) return v +# endregion +# region surface-spec class _SurfaceSpec(BaseModel): """Common shape — both booking and results surfaces produce the same triple (surface_id, data_model, components).""" @@ -157,10 +160,12 @@ class FlightResultsSpec(_SurfaceSpec): class ConfirmationSpec(_SurfaceSpec): """Booking confirmation surface — selected flight + prior party context.""" pass +# endregion # ── Envelope wrapping ─────────────────────────────────────────────────────── +# region envelope-wrapping # A2UI v0.9 wire format (a2ui.org server_to_client.json): every envelope # carries "version": "v0.9". Order matters: createSurface first (surfaceId + # catalogId), then updateComponents (flat components; exactly one has id @@ -189,6 +194,7 @@ def _wrap_envelopes(spec: _SurfaceSpec) -> str: "value": spec.data_model, }})) return A2UI_PREFIX + "\n" + "\n".join(lines) + "\n" +# endregion # ── LLM + retry ───────────────────────────────────────────────────────────── @@ -386,6 +392,7 @@ def _build_sentinel_booking_form(defaults: dict[str, Any]) -> BookingFormSpec: ) +# region build-form-node async def build_form(state: MessagesState) -> dict: """First-turn AND Modify-search node: LLM authors the booking form. @@ -417,6 +424,7 @@ async def build_form(state: MessagesState) -> dict: _logger.error("Falling back to sentinel booking form: %s", err) spec = _build_sentinel_booking_form(defaults) return {"messages": [AIMessage(content=_wrap_envelopes(spec))]} +# endregion # ── search_flights node ───────────────────────────────────────────────────── @@ -766,6 +774,7 @@ def _format_party(prior: dict[str, Any]) -> str: return " • ".join(parts) if parts else "(party details unavailable)" +# region route def route(state: MessagesState) -> Command[Literal["build_form", "search_flights", "confirm_booking"]]: """Inspect the last message — submit event → search_flights, flight-select event → confirm_booking, else build_form.""" @@ -775,6 +784,7 @@ def route(state: MessagesState) -> Command[Literal["build_form", "search_flights if _is_flight_select_event(last_content): return Command(goto="confirm_booking") return Command(goto="build_form") +# endregion # ── generate_title node (inline; matches Pattern D from spec @@ -833,6 +843,7 @@ async def generate_title(state: MessagesState, config) -> dict: return {} +# region graph-wiring _builder = StateGraph(MessagesState) _builder.add_node("route", route) _builder.add_node("build_form", build_form) @@ -846,3 +857,4 @@ async def generate_title(state: MessagesState, config) -> dict: _builder.add_edge("generate_title", END) graph = _builder.compile(checkpointer=MemorySaver()) +# endregion diff --git a/deployments/ag-ui-dev/deps/a2ui/docs/guide.md b/deployments/ag-ui-dev/deps/a2ui/docs/guide.md deleted file mode 100644 index caf420c75..000000000 --- a/deployments/ag-ui-dev/deps/a2ui/docs/guide.md +++ /dev/null @@ -1,127 +0,0 @@ -# A2UI Surfaces with @threadplane/chat - - -Render agent-driven interactive UI using the A2UI (Agent-to-UI) protocol. -The agent streams JSONL messages that build surfaces from the built-in -18-component catalog - no custom view components needed. - - - -Add A2UI surface rendering to your chat interface using `a2uiBasicCatalog()` -from `@threadplane/chat`. Pass it to `ChatComponent` via the `[views]` input -to enable A2UI surface rendering with automatic event routing. - - - - - -Configure `provideAgent()` in your app config: - -```typescript -// app.config.ts -import { provideAgent } from '@threadplane/ag-ui'; -import { provideChat } from '@threadplane/chat'; - -export const appConfig: ApplicationConfig = { - providers: [ - provideAgent({ url: '/agent' }), - provideChat({}), - ], -}; -``` - -Import `a2uiBasicCatalog()` and pass it via the `[views]` input, then retrieve -the agent with `injectAgent()`: - -```typescript -// a2ui.component.ts -import { ChatComponent, a2uiBasicCatalog } from '@threadplane/chat'; -import { injectAgent } from '@threadplane/ag-ui'; - -@Component({ - selector: 'app-a2ui', - standalone: true, - imports: [ChatComponent], - template: ``, -}) -export class A2uiComponent { - protected readonly agentRef = injectAgent(); - protected readonly catalog = a2uiBasicCatalog(); -} -``` - -No event handler wiring needed - A2UI button events route back to the -agent automatically. - - - - -The agent response must start with `---a2ui_JSON---` followed by -newline-delimited JSON envelopes, each stamped `"version": "v0.9"`: - -``` ----a2ui_JSON--- -{"version":"v0.9","createSurface":{"surfaceId":"s1","catalogId":"https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json"}} -{"version":"v0.9","updateDataModel":{"surfaceId":"s1","value":{"name":""}}} -{"version":"v0.9","updateComponents":{"surfaceId":"s1","components":[...]}} -``` - -Three envelope kinds build a surface: -1. `createSurface` - initializes the surface (must come first) -2. `updateDataModel` - sets the initial data model state -3. `updateComponents` - defines the flat component tree; exactly one - component has `id: "root"`, and rendering starts once it is defined - - - - -When tokens stream in, `ContentClassifier` detects the `---a2ui_JSON---` -prefix and routes content to the A2UI pipeline: - -1. `A2uiMessageParser` extracts complete JSON messages from the stream -2. `A2uiSurfaceStore` applies messages to build `A2uiSurface` objects -3. `A2uiSurfaceComponent` converts each surface to a json-render `Spec` -4. `RenderSpecComponent` renders the spec using the catalog components - - - - -Components bind to the data model using path references: - -```json -{"id": "name_field", "component": "TextField", - "label": "Name", "value": {"path": "/name"}} -``` - -The `surfaceToSpec` function auto-detects path references and populates -`_bindings` for each input component - agents do not write `_bindings` -directly. When the user changes a bound input, the component emits a -data model update event. - -**Known limitation:** Data model updates from user input do not currently -reflect to other components in real time. The agent can refresh state by -sending a new `updateDataModel` message. - - - - -Button `event` actions automatically route back to the agent as a -structured `A2uiActionMessage`: - -```json -{"version": "v0.9", "action": {"name": "formSubmit", "surfaceId": "s1", "sourceComponentId": "submit", "timestamp": "...", "context": {"formId": "contact"}}} -``` - -The agent receives this and can respond with a new surface, markdown, or -any other content. - - - - - -The 18-component A2UI catalog covers layout (Column, Row, Card), display -(Text, Image, Icon, Divider, List), input (TextField, CheckBox, -ChoicePicker, DateTimeInput, Slider), media (Video, AudioPlayer), -interactive (Button, Tabs, Modal). No custom components are needed for -standard forms and dashboards. - diff --git a/deployments/ag-ui-dev/deps/a2ui/src/graph.py b/deployments/ag-ui-dev/deps/a2ui/src/graph.py index ed90d8991..2ea24e60f 100644 --- a/deployments/ag-ui-dev/deps/a2ui/src/graph.py +++ b/deployments/ag-ui-dev/deps/a2ui/src/graph.py @@ -94,6 +94,7 @@ def _lookup_flight_impl(flight_number: str) -> dict | None: # ── Pydantic schemas ──────────────────────────────────────────────────────── +# region component-schema class A2uiComponent(BaseModel): """Single A2UI v0.9 updateComponents entry. @@ -136,8 +137,10 @@ def _known_component(cls, v: str) -> str: f"component '{v}' not in catalog. Allowed: {sorted(ALLOWED_COMPONENTS)}" ) return v +# endregion +# region surface-spec class _SurfaceSpec(BaseModel): """Common shape — both booking and results surfaces produce the same triple (surface_id, data_model, components).""" @@ -157,10 +160,12 @@ class FlightResultsSpec(_SurfaceSpec): class ConfirmationSpec(_SurfaceSpec): """Booking confirmation surface — selected flight + prior party context.""" pass +# endregion # ── Envelope wrapping ─────────────────────────────────────────────────────── +# region envelope-wrapping # A2UI v0.9 wire format (a2ui.org server_to_client.json): every envelope # carries "version": "v0.9". Order matters: createSurface first (surfaceId + # catalogId), then updateComponents (flat components; exactly one has id @@ -189,6 +194,7 @@ def _wrap_envelopes(spec: _SurfaceSpec) -> str: "value": spec.data_model, }})) return A2UI_PREFIX + "\n" + "\n".join(lines) + "\n" +# endregion # ── LLM + retry ───────────────────────────────────────────────────────────── @@ -386,6 +392,7 @@ def _build_sentinel_booking_form(defaults: dict[str, Any]) -> BookingFormSpec: ) +# region build-form-node async def build_form(state: MessagesState) -> dict: """First-turn AND Modify-search node: LLM authors the booking form. @@ -417,6 +424,7 @@ async def build_form(state: MessagesState) -> dict: _logger.error("Falling back to sentinel booking form: %s", err) spec = _build_sentinel_booking_form(defaults) return {"messages": [AIMessage(content=_wrap_envelopes(spec))]} +# endregion # ── search_flights node ───────────────────────────────────────────────────── @@ -766,6 +774,7 @@ def _format_party(prior: dict[str, Any]) -> str: return " • ".join(parts) if parts else "(party details unavailable)" +# region route def route(state: MessagesState) -> Command[Literal["build_form", "search_flights", "confirm_booking"]]: """Inspect the last message — submit event → search_flights, flight-select event → confirm_booking, else build_form.""" @@ -775,6 +784,7 @@ def route(state: MessagesState) -> Command[Literal["build_form", "search_flights if _is_flight_select_event(last_content): return Command(goto="confirm_booking") return Command(goto="build_form") +# endregion # ── generate_title node (inline; matches Pattern D from spec @@ -833,6 +843,7 @@ async def generate_title(state: MessagesState, config) -> dict: return {} +# region graph-wiring _builder = StateGraph(MessagesState) _builder.add_node("route", route) _builder.add_node("build_form", build_form) @@ -846,3 +857,4 @@ async def generate_title(state: MessagesState, config) -> dict: _builder.add_edge("generate_title", END) graph = _builder.compile(checkpointer=MemorySaver()) +# endregion From ff1f0e185a563c000636ad3e9b498a65c92a39ca Mon Sep 17 00:00:00 2001 From: Brian Love Date: Sat, 5 Sep 2026 23:38:59 -0700 Subject: [PATCH 3/3] =?UTF-8?q?docs(a2ui):=20introduction=20=E2=80=94=20en?= =?UTF-8?q?velope=20order=20rule,=20schema=20nesting,=20first-turn=20seed,?= =?UTF-8?q?=20tool-group=20count?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- .../content/docs/a2ui/getting-started/introduction.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/website/content/docs/a2ui/getting-started/introduction.mdx b/apps/website/content/docs/a2ui/getting-started/introduction.mdx index cd32845b4..e8bb26efa 100644 --- a/apps/website/content/docs/a2ui/getting-started/introduction.mdx +++ b/apps/website/content/docs/a2ui/getting-started/introduction.mdx @@ -14,7 +14,7 @@ The running example is a flight booking flow. The agent authors each screen as a The Run tab shows the prebuilt `` composition with the A2UI catalog registered on it. Choose the welcome suggestion "Book LAX → JFK" and the agent replies with a booking form rather than with prose: origin and destination pickers already set to those two airports, a departure date, a passenger count, and a fare class. -Fill the form and press "Search flights". The button does not post a chat message; it sends a structured A2UI action back to the agent, which searches the flight fixtures and answers with a second surface listing the matching flights. Selecting one produces a third surface, the booking confirmation, whose "Modify search" button returns to the form with the earlier values already filled in. +Fill the form and press "Search flights". The button does not post a chat message; it sends a structured A2UI action back to the agent, which searches the flight fixtures and answers with a second surface listing the matching flights. Selecting one produces a third surface, the booking confirmation, whose "Modify search" button returns to the form with the earlier values already filled in. The model authors both the results list and the confirmation surface at request time, so their exact layout and wording vary from run to run; only the form's shape is prescribed. The second suggestion, "Book SFO → SEA", runs the same pattern on a different route, which is worth trying because nothing about the form is hardcoded on the client. @@ -24,7 +24,7 @@ Four files carry the feature: a LangGraph graph that authors the surfaces, a Fas ### The component shape the model must satisfy -A2UI v0.9 components are flat. Each entry carries an `id`, a `component` name from the catalog, and its props at the same level of the same object. The example models that as a Pydantic class and uses it as the structured-output schema for the LLM, so the model authors the component list under a validator instead of free-typing JSON. +A2UI v0.9 components are flat. Each entry carries an `id`, a `component` name from the catalog, and its props at the same level of the same object. The example models that as a Pydantic class and nests it inside the structured-output schema, so the model authors the component list under a validator instead of free-typing JSON. @@ -44,7 +44,7 @@ The model authors the components; the code writes the wire format. `_wrap_envelo -Order matters, and the comment above the function states it: `createSurface` first, then `updateComponents`, then `updateDataModel`. +The demo emits `createSurface`, then `updateComponents`, then `updateDataModel`. The protocol requires only that `createSurface` comes first and that a component with id `root` is defined before anything paints. `A2UI_PREFIX` is `---a2ui_JSON---`. The content classifier in `@threadplane/chat` looks for exactly that string at the start of assistant content and routes the rest of the message into the A2UI pipeline instead of the markdown renderer. @@ -52,7 +52,7 @@ Order matters, and the comment above the function states it: `createSurface` fir ### The node that authors the form -`build_form` runs on the first turn and again on a "Modify search" turn. It recovers any prior submission from the message history, seeds the origin and destination from a phrase such as "I want to fly LAX to JFK", substitutes those values into the system prompt as the form defaults, and asks the model for a `BookingFormSpec`. +`build_form` runs on the first turn and again on a "Modify search" turn. It recovers any prior submission from the message history, or, on a true first turn, seeds the origin and destination from a phrase such as "I want to fly LAX to JFK", substitutes those values into the system prompt as the form defaults, and asks the model for a `BookingFormSpec`. @@ -114,7 +114,7 @@ No handler wiring appears here: `` builds the action message from the surf ## What the package owns -The demo shows the protocol from the agent side. The package is the same protocol expressed as TypeScript, and its public entry point exports four groups of tools: +The demo shows the protocol from the agent side. The package is the same protocol expressed as TypeScript, and its public entry point exports five groups of tools: | Area | Exports | |------|---------|