Skip to content

Commit 437c7e3

Browse files
committed
Fix JSON fragment
1 parent 380daec commit 437c7e3

File tree

2 files changed

+56
-44
lines changed

2 files changed

+56
-44
lines changed

docs/a2ui_protocol.md

Lines changed: 52 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The A2UI (Agent to UI) protocol should be a system where an LLM can stream a pla
1010

1111
### Requirement: The protocol must be easily generated by a Transformer Large Language Model (LLM)
1212

13-
This is the most critical driver. "LLM-friendliness" is explicitly mentioned. This requirement directly leads to several design choices:
13+
This is the most critical driver. This requirement directly leads to several design choices:
1414

1515
Declarative, Simple Structure: The protocol should use a straightforward, declarative format ("this is a column with these children") rather than an imperative one ("now, add a column; then, append a text widget to it"). LLMs excel at generating structured, declarative data.
1616

@@ -95,14 +95,13 @@ The A2UI protocol is composed of a server-to-client stream describing UI and ind
9595
1. **Server Stream:** The server begins sending the JSONL stream over an SSE connection.
9696
2. **Client-Side Buffering:** The client receives messages and buffers them:
9797

98-
- `updateSurface`: Component definitions are stored in a `Map<String, Component>`, organized by `surfaceId`. If a surface doesn't exist, it is created.
98+
- `surfaceUpdate`: Component definitions are stored in a `Map<String, Component>`, organized by `surfaceId`. If a surface doesn't exist, it is created.
9999
- `dataModelUpdate`: The client's internal JSON data model is built or updated.
100100

101101
3. **Render Signal:** The server sends a `beginRendering` message with the `root` component's ID. This prevents a "flash of incomplete content." The client buffers incoming components and data but waits for this explicit signal before attempting the first render, ensuring the initial view is coherent.
102102
4. **Client-Side Rendering:** The client, now in a "ready" state, starts at the `root` component. It recursively walks the component tree by looking up component IDs in its buffer. It resolves any data bindings against the data model and uses its `WidgetRegistry` to instantiate native widgets.
103103
5. **User Interaction and Event Handling:** The user interacts with a rendered widget (e.g., taps a button). The client constructs a `userAction` JSON payload, resolving any data bindings from the component's `action.context`. It sends this payload to the server via an A2A message.
104-
105-
6. **Dynamic Updates:** The server processes the `userAction`. If the UI needs to change in response, the server sends new `updateSurface` and `dataModelUpdate` messages over the original SSE stream. As these arrive, the client updates its component buffer and data model, and the UI re-renders to reflect the changes. The server can also send `deleteSurface` to remove a UI region.
104+
6. **Dynamic Updates:** The server processes the `userAction`. If the UI needs to change in response, the server sends new `surfaceUpdate` and `dataModelUpdate` messages over the original SSE stream. As these arrive, the client updates its component buffer and data model, and the UI re-renders to reflect the changes. The server can also send `deleteSurface` to remove a UI region.
106105

107106
```mermaid
108107
sequenceDiagram
@@ -112,8 +111,8 @@ sequenceDiagram
112111
Server->>+Client: SSE Connection (JSONL Stream)
113112
Client->>Client: 1. Parse JSONL message
114113
loop Until 'beginRendering'
115-
Client->>Client: 2a. Process surfaceUpdate (store components in map for surfaceId)
116-
Client->>Client: 2b. Process dataModelUpdate (update data model)
114+
Client->>Client: 2a. Process surfaceUpdate
115+
Client->>Client: 2b. Process dataModelUpdate
117116
end
118117
Client->>Client: 3. Process beginRendering (rootId: 'root', isReady: true)
119118
Note right of Client: 4. Triggers UI build for a surface
@@ -140,15 +139,15 @@ sequenceDiagram
140139
The following is a complete, minimal example of a JSONL stream that renders a user profile card.
141140

142141
```jsonl
143-
{"updateSurface": {"components": [{"id": "root", "component": {"Column": {"children": {"explicitList": ["profile_card"]}}}}]}}
144-
{"updateSurface": {"components": [{"id": "profile_card", "component": {"Card": {"child": "card_content"}}}]}}
145-
{"updateSurface": {"components": [{"id": "card_content", "component": {"Column": {"children": {"explicitList": ["header_row", "bio_text"]}}}}]}}
146-
{"updateSurface": {"components": [{"id": "header_row", "component": {"Row": {"alignment": "center", "children": {"explicitList": ["avatar", "name_column"]}}}}]}}
147-
{"updateSurface": {"components": [{"id": "avatar", "component": {"Image": {"url": {"literalString": "[https://www.example.com/profile.jpg)"}}}}]}}
148-
{"updateSurface": {"components": [{"id": "name_column", "component": {"Column": {"alignment": "start", "children": {"explicitList": ["name_text", "handle_text"]}}}}]}}
149-
{"updateSurface": {"components": [{"id": "name_text", "component": {"Heading": {"level": "3", "text": {"literalString": "Flutter Fan"}}}}]}}
150-
{"updateSurface": {"components": [{"id": "handle_text", "component": {"Text": {"text": {"literalString": "@flutterdev"}}}}]}}
151-
{"updateSurface": {"components": [{"id": "bio_text", "component": {"Text": {"text": {"literalString": "Building beautiful apps from a single codebase."}}}}]}}
142+
{"surfaceUpdate": {"components": [{"id": "root", "component": {"Column": {"children": {"explicitList": ["profile_card"]}}}}]}}
143+
{"surfaceUpdate": {"components": [{"id": "profile_card", "component": {"Card": {"child": "card_content"}}}]}}
144+
{"surfaceUpdate": {"components": [{"id": "card_content", "component": {"Column": {"children": {"explicitList": ["header_row", "bio_text"]}}}}]}}
145+
{"surfaceUpdate": {"components": [{"id": "header_row", "component": {"Row": {"alignment": "center", "children": {"explicitList": ["avatar", "name_column"]}}}}]}}
146+
{"surfaceUpdate": {"components": [{"id": "avatar", "component": {"Image": {"url": {"literalString": "[https://www.example.com/profile.jpg)"}}}}]}}
147+
{"surfaceUpdate": {"components": [{"id": "name_column", "component": {"Column": {"alignment": "start", "children": {"explicitList": ["name_text", "handle_text"]}}}}]}}
148+
{"surfaceUpdate": {"components": [{"id": "name_text", "component": {"Heading": {"level": "3", "text": {"literalString": "Flutter Fan"}}}}]}}
149+
{"surfaceUpdate": {"components": [{"id": "handle_text", "component": {"Text": {"text": {"literalString": "@flutterdev"}}}}]}}
150+
{"surfaceUpdate": {"components": [{"id": "bio_text", "component": {"Text": {"text": {"literalString": "Building beautiful apps from a single codebase."}}}}]}}
152151
{"dataModelUpdate": {"contents": {}}}
153152
{"beginRendering": {"root": "root"}}
154153
```
@@ -159,9 +158,9 @@ A2UI's component model is designed for flexibility, separating the protocol from
159158

160159
### 2.1. The Catalog: Defining Components
161160

162-
Unlike previous versions with a fixed component set, A2UI now defines components in a **Catalog**. A catalog is a schema that defines the available component types (e.g., `Row`, `Text`) and their supported properties. This allows for different clients to support different sets of components, including custom ones. The server must generate `updateSurface` messages that conform to the component catalog understood by the client. Clients can inform the server of the catalog they support using the `clientUiCapabilities` message.
161+
Unlike previous versions with a fixed component set, A2UI now defines components in a **Catalog**. A catalog is a schema that defines the available component types (e.g., `Row`, `Text`) and their supported properties. This allows for different clients to support different sets of components, including custom ones. The server must generate `surfaceUpdate` messages that conform to the component catalog understood by the client. Clients can inform the server of the catalog they support using the `clientUiCapabilities` message.
163162

164-
### 2.2. The `updateSurface` Message
163+
### 2.2. The `surfaceUpdate` Message
165164

166165
This message is the primary way UI structure is defined. It contains a `surfaceId` and a `components` array.
167166

@@ -430,30 +429,40 @@ This message provides a feedback mechanism for the server. It is sent when the c
430429

431430
### 5.5. Event Flow Example (`userAction`)
432431

433-
1. **Component Definition** (from `updateSurface`):
432+
1. **Component Definition** (from `surfaceUpdate`):
434433

435434
```json
436435
{
437-
"id": "submit_btn_text",
438-
"component": {
439-
"Text": {
440-
"text": { "literalString": "Submit" }
441-
}
442-
}
443-
},
444-
{
445-
"id": "submit_btn",
446-
"component": {
447-
"Button": {
448-
"child": "submit_btn_text",
449-
"action": {
450-
"name": "submit_form",
451-
"context": [
452-
{ "key": "userInput", "value": { "path": "form.textField" } },
453-
{ "key": "formId", "value": { "literalString": "f-123" } }
454-
]
436+
"surfaceUpdate": {
437+
"surfaceId": "main_content_area",
438+
"components": [
439+
{
440+
"id": "submit_btn_text",
441+
"component": {
442+
"Text": {
443+
"text": { "literalString": "Submit" }
444+
}
445+
}
446+
},
447+
{
448+
"id": "submit_btn",
449+
"component": {
450+
"Button": {
451+
"child": "submit_btn_text",
452+
"action": {
453+
"name": "submit_form",
454+
"context": [
455+
{
456+
"key": "userInput",
457+
"value": { "path": "form.textField" }
458+
},
459+
{ "key": "formId", "value": { "literalString": "f-123" } }
460+
]
461+
}
462+
}
463+
}
455464
}
456-
}
465+
]
457466
}
458467
}
459468
```
@@ -462,8 +471,11 @@ This message provides a feedback mechanism for the server. It is sent when the c
462471

463472
```json
464473
{
465-
"form": {
466-
"textField": "User input text"
474+
"dataModelUpdate": {
475+
"surfaceId": "main_content_area",
476+
"form": {
477+
"textField": "User input text"
478+
}
467479
}
468480
}
469481
```
@@ -487,7 +499,7 @@ This message provides a feedback mechanism for the server. It is sent when the c
487499
}
488500
```
489501

490-
6. **Server Response:** The server processes this event. If the UI needs to change as a result, the server sends new `updateSurface` or `dataModelUpdate` messages over the **separate SSE stream**.
502+
6. **Server Response:** The server processes this event. If the UI needs to change as a result, the server sends new `surfaceUpdate` or `dataModelUpdate` messages over the **separate SSE stream**.
491503

492504
## Section 6: Client-Side Implementation
493505

docs/proposals/v0_8.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ We will update the standard catalog in the following ways:
9191

9292
The schema adapter is an agent-side utility that can accept a custom widget catalog and convert it to a schema which allows the LLM to generate `surfaceUpdate` messages for a particular catalog reliably.
9393

94-
The most simple adapter, which will work for Gemini and other common LLMs, will simply take the “components” from a “Catalog description object” and paste it into `updateSurface.components.componentProperties` and takes the “styles” and pastes them into `beginRendering.styles`. This creates a new schema which includes the detailed widget catalog, and can be passed to the LLM. This schema should be almost identical in structure to a v0.7 A2UI schema. This schema is a strict subset of the more generic v0.8 “Server-to-Client Protocol Schema” and thus messages generated this way do formally conform to the protocol spec.
94+
The most simple adapter, which will work for Gemini and other common LLMs, will simply take the “components” from a “Catalog description object” and paste it into `surfaceUpdate.components.componentProperties` and takes the “styles” and pastes them into `beginRendering.styles`. This creates a new schema which includes the detailed widget catalog, and can be passed to the LLM. This schema should be almost identical in structure to a v0.7 A2UI schema. This schema is a strict subset of the more generic v0.8 “Server-to-Client Protocol Schema” and thus messages generated this way do formally conform to the protocol spec.
9595

9696
## Detailed design
9797

@@ -300,7 +300,7 @@ To simplify the generation of UIs, especially for LLMs, we propose a shorthand f
300300
Currently, to display a dynamic value that has a default, the server must send two separate messages:
301301

302302
1. A `dataModelUpdate` message to set the initial value in the data model.
303-
2. An `updateSurface` message with a component that has a property bound to that data model path.
303+
2. An `surfaceUpdate` message with a component that has a property bound to that data model path.
304304

305305
The proposed shorthand combines these two steps. When a component property that accepts a `BoundValue` (e.g., `text`, `value`) is defined with _both_ a `path` and a `literal*` value (e.g., `literalString`), it should be interpreted as follows:
306306

@@ -313,14 +313,14 @@ Instead of sending two messages:
313313

314314
```json
315315
{"dataModelUpdate": {"path": "form.name", "contents": "John Doe"}}
316-
{"updateSurface": {"components": [{"id": "name_field", "componentProperties": {"TextField": {"text": {"path": "form.name"}}}}]}}
316+
{"surfaceUpdate": {"components": [{"id": "name_field", "componentProperties": {"TextField": {"text": {"path": "form.name"}}}}]}}
317317
```
318318

319319
The server can send a single message:
320320

321321
```json
322322
{
323-
"updateSurface": {
323+
"surfaceUpdate": {
324324
"components": [
325325
{
326326
"id": "name_field",

0 commit comments

Comments
 (0)