Skip to content

Commit 33e8881

Browse files
committed
Merge remote-tracking branch 'upstream/main' into delete-minimal-catalog
2 parents cf1b364 + 7c296b9 commit 33e8881

6 files changed

Lines changed: 212 additions & 20 deletions

File tree

samples/client/flutter/restaurant_finder/app/macos/Flutter/GeneratedPluginRegistrant.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
1-
// Copyright 2025 Google LLC
1+
// Copyright 2026 Google LLC
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
55
// You may obtain a copy of the License at
66
//
7-
// https://www.apache.org/licenses/LICENSE-2.0
7+
// http://www.apache.org/licenses/LICENSE-2.0
88
//
99
// Unless required by applicable law or agreed to in writing, software
1010
// distributed under the License is distributed on an "AS IS" BASIS,
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
1615
//
1716
// Generated file. Do not edit.
1817
//
@@ -27,5 +26,5 @@ import video_player_avfoundation
2726
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
2827
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
2928
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
30-
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
29+
VideoPlayerPlugin.register(with: registry.registrar(forPlugin: "VideoPlayerPlugin"))
3130
}

specification/v0_10/docs/a2ui_protocol.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ This message signals the client to create a new surface and begin rendering it.
181181
- `catalogId` (string, required): A string that uniquely identifies the catalog (components and functions) used for this surface. It is recommended to prefix this with an internet domain that you own, to avoid conflicts (e.g., `https://mycompany.com/1.0/somecatalog`). If it is a URL, the URL does not need to have any deployed resources, it is simply a unique identifier.
182182
- `theme` (object, optional): A JSON object containing theme parameters (e.g., `primaryColor`) defined in the catalog's theme schema.
183183
- `sendDataModel` (boolean, optional): If true, the client will send the full data model of this surface in the metadata of every message sent to the server (via the Transport's metadata mechanism). This ensures the surface owner receives the full current state of the UI alongside the user's action or query. Defaults to false.
184+
- `components` (array, optional): A list containing UI components for the surface, allowing the client to build and populate the UI tree immediately on surface creation. Conforms to the `ComponentsList` schema.
185+
- `dataModel` (object, optional): A plain JSON object representing the initial root state of the data model.
184186

185187
**Example:**
186188

@@ -193,7 +195,22 @@ This message signals the client to create a new surface and begin rendering it.
193195
"theme": {
194196
"primaryColor": "#00BFFF"
195197
},
196-
"sendDataModel": true
198+
"sendDataModel": true,
199+
"components": [
200+
{
201+
"id": "root",
202+
"component": "Column",
203+
"children": ["user_name"]
204+
},
205+
{
206+
"id": "user_name",
207+
"component": "Text",
208+
"text": {"path": "/name"}
209+
}
210+
],
211+
"dataModel": {
212+
"name": "John Doe"
213+
}
197214
}
198215
}
199216
```

specification/v0_10/eval/src/validator.ts

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -276,17 +276,36 @@ export class Validator {
276276
this.validateUpdateComponents(message.updateComponents, errors);
277277

278278
// Check for root component in this message
279-
if (message.updateComponents.components) {
279+
if (Array.isArray(message.updateComponents.components)) {
280280
for (const comp of message.updateComponents.components) {
281-
if (comp.id === 'root') {
281+
if (comp && typeof comp === 'object' && comp.id === 'root') {
282282
hasRootComponent = true;
283283
}
284284
}
285285
}
286286
} else if (message.createSurface) {
287287
this.validateCreateSurface(message.createSurface, errors);
288-
if (message.createSurface.surfaceId) {
289-
createdSurfaces.add(message.createSurface.surfaceId);
288+
const surfaceId = message.createSurface.surfaceId;
289+
if (surfaceId) {
290+
createdSurfaces.add(surfaceId);
291+
}
292+
293+
const createSurface = message.createSurface;
294+
if (createSurface.components) {
295+
hasUpdateComponents = true;
296+
297+
if (Array.isArray(createSurface.components)) {
298+
this.validateComponentsList(createSurface.components, errors);
299+
300+
// Check for root component in nested components
301+
for (const comp of createSurface.components) {
302+
if (comp && typeof comp === 'object' && comp.id === 'root') {
303+
hasRootComponent = true;
304+
}
305+
}
306+
} else {
307+
errors.push('createSurface.components must be an array of components.');
308+
}
290309
}
291310
} else if (message.updateDataModel) {
292311
this.validateUpdateDataModel(message.updateDataModel, errors);
@@ -347,7 +366,7 @@ export class Validator {
347366
if (data.catalogId === undefined) {
348367
errors.push("createSurface must have a 'catalogId' property.");
349368
}
350-
const allowed = ['surfaceId', 'catalogId'];
369+
const allowed = ['surfaceId', 'catalogId', 'theme', 'sendDataModel', 'components', 'dataModel'];
351370
for (const key in data) {
352371
if (!allowed.includes(key)) {
353372
errors.push(`createSurface has unexpected property: ${key}`);
@@ -376,8 +395,13 @@ export class Validator {
376395
return;
377396
}
378397

398+
this.validateComponentsList(data.components, errors);
399+
}
400+
401+
private validateComponentsList(components: any[], errors: string[]) {
379402
const componentIds = new Set<string>();
380-
for (const c of data.components) {
403+
for (const c of components) {
404+
if (!c || typeof c !== 'object') continue;
381405
const id = c.id;
382406
if (id) {
383407
if (componentIds.has(id)) {
@@ -405,12 +429,21 @@ export class Validator {
405429
}
406430
}
407431

408-
for (const component of data.components) {
409-
this.validateComponent(component, componentIds, errors);
432+
for (const component of components) {
433+
if (component && typeof component === 'object') {
434+
this.validateComponent(component, componentIds, errors);
435+
}
410436
}
411437
}
412438

413439
private validateUpdateDataModel(data: any, errors: string[]) {
440+
if (data.surfaceId === undefined) {
441+
errors.push("UpdateDataModel must have a 'surfaceId' property.");
442+
}
443+
this.validateDataModelUpdate(data, errors);
444+
}
445+
446+
private validateDataModelUpdate(data: any, errors: string[]) {
414447
// Schema validation handles types and basic structure.
415448
// 'op' is removed in v0.10, so we don't need to validate it or its relationship with 'value'.
416449
// We strictly rely on the schema for this message type now.

specification/v0_10/json/server_to_client.json

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@
3838
"sendDataModel": {
3939
"type": "boolean",
4040
"description": "If true, the client will send the full data model of this surface in the metadata of every A2A message sent to the server that created the surface. Defaults to false."
41+
},
42+
"components": {
43+
"$ref": "#/$defs/ComponentsList"
44+
},
45+
"dataModel": {
46+
"type": "object",
47+
"description": "The initial root data model object for the surface.",
48+
"additionalProperties": true
4149
}
4250
},
4351
"required": ["surfaceId", "catalogId"],
@@ -47,6 +55,14 @@
4755
"required": ["createSurface", "version"],
4856
"additionalProperties": false
4957
},
58+
"ComponentsList": {
59+
"type": "array",
60+
"description": "A list containing UI components for the surface.",
61+
"minItems": 1,
62+
"items": {
63+
"$ref": "catalog.json#/$defs/anyComponent"
64+
}
65+
},
5066
"UpdateComponentsMessage": {
5167
"type": "object",
5268
"properties": {
@@ -62,12 +78,7 @@
6278
"description": "The unique identifier for the UI surface to be updated."
6379
},
6480
"components": {
65-
"type": "array",
66-
"description": "A list containing all UI components for the surface.",
67-
"minItems": 1,
68-
"items": {
69-
"$ref": "catalog.json#/$defs/anyComponent"
70-
}
81+
"$ref": "#/$defs/ComponentsList"
7182
}
7283
},
7384
"required": ["surfaceId", "components"],
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
{
2+
"schema": "server_to_client.json",
3+
"tests": [
4+
{
5+
"description": "Valid createSurface with both components list and dataModel update",
6+
"valid": true,
7+
"data": {
8+
"version": "v0.10",
9+
"createSurface": {
10+
"surfaceId": "test_surface",
11+
"catalogId": "https://a2ui.org/specification/v0_10/catalogs/basic/catalog.json",
12+
"components": [
13+
{
14+
"id": "root",
15+
"component": "Column",
16+
"children": ["welcome_text"]
17+
},
18+
{
19+
"id": "welcome_text",
20+
"component": "Text",
21+
"text": "Welcome to the application!"
22+
}
23+
],
24+
"dataModel": {
25+
"user": {
26+
"name": "John Doe"
27+
}
28+
}
29+
}
30+
}
31+
},
32+
{
33+
"description": "Valid createSurface with only components list",
34+
"valid": true,
35+
"data": {
36+
"version": "v0.10",
37+
"createSurface": {
38+
"surfaceId": "test_surface",
39+
"catalogId": "https://a2ui.org/specification/v0_10/catalogs/basic/catalog.json",
40+
"components": [
41+
{
42+
"id": "root",
43+
"component": "Text",
44+
"text": "Minimal components configuration"
45+
}
46+
]
47+
}
48+
}
49+
},
50+
{
51+
"description": "Valid createSurface with only dataModel update",
52+
"valid": true,
53+
"data": {
54+
"version": "v0.10",
55+
"createSurface": {
56+
"surfaceId": "test_surface",
57+
"catalogId": "https://a2ui.org/specification/v0_10/catalogs/basic/catalog.json",
58+
"dataModel": {
59+
"themePreference": "dark"
60+
}
61+
}
62+
}
63+
},
64+
{
65+
"description": "Invalid: createSurface contains additional unexpected properties",
66+
"valid": false,
67+
"data": {
68+
"version": "v0.10",
69+
"createSurface": {
70+
"surfaceId": "test_surface",
71+
"catalogId": "https://a2ui.org/specification/v0_10/catalogs/basic/catalog.json",
72+
"components": [
73+
{
74+
"id": "root",
75+
"component": "Text",
76+
"text": "Invalid extra prop"
77+
}
78+
],
79+
"extraProp": "should_not_be_here"
80+
}
81+
}
82+
},
83+
{
84+
"description": "Invalid: components is a string instead of an array",
85+
"valid": false,
86+
"data": {
87+
"version": "v0.10",
88+
"createSurface": {
89+
"surfaceId": "test_surface",
90+
"catalogId": "https://a2ui.org/specification/v0_10/catalogs/basic/catalog.json",
91+
"components": "not-an-array"
92+
}
93+
}
94+
},
95+
{
96+
"description": "Invalid: components contains empty array",
97+
"valid": false,
98+
"data": {
99+
"version": "v0.10",
100+
"createSurface": {
101+
"surfaceId": "test_surface",
102+
"catalogId": "https://a2ui.org/specification/v0_10/catalogs/basic/catalog.json",
103+
"components": []
104+
}
105+
}
106+
},
107+
{
108+
"description": "Invalid: components array contains a null element",
109+
"valid": false,
110+
"data": {
111+
"version": "v0.10",
112+
"createSurface": {
113+
"surfaceId": "test_surface",
114+
"catalogId": "https://a2ui.org/specification/v0_10/catalogs/basic/catalog.json",
115+
"components": [null]
116+
}
117+
}
118+
},
119+
{
120+
"description": "Invalid: dataModel is a string instead of an object",
121+
"valid": false,
122+
"data": {
123+
"version": "v0.10",
124+
"createSurface": {
125+
"surfaceId": "test_surface",
126+
"catalogId": "https://a2ui.org/specification/v0_10/catalogs/basic/catalog.json",
127+
"dataModel": "not-an-object"
128+
}
129+
}
130+
}
131+
]
132+
}

specification/v0_10/test/run_tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# you may not use this file except in compliance with the License.
66
# You may obtain a copy of the License at
77
#
8-
# http://www.apache.org/licenses/LICENSE-2.0
8+
# https://www.apache.org/licenses/LICENSE-2.0
99
#
1010
# Unless required by applicable law or agreed to in writing, software
1111
# distributed under the License is distributed on an "AS IS" BASIS,

0 commit comments

Comments
 (0)