diff --git a/agent_sdks/python/a2ui_agent/src/a2ui/inference_formats/experimental/express/schema_helper.py b/agent_sdks/python/a2ui_agent/src/a2ui/inference_formats/experimental/express/schema_helper.py index 721bba6f61..e7cc788d43 100644 --- a/agent_sdks/python/a2ui_agent/src/a2ui/inference_formats/experimental/express/schema_helper.py +++ b/agent_sdks/python/a2ui_agent/src/a2ui/inference_formats/experimental/express/schema_helper.py @@ -125,9 +125,22 @@ def _find_enum(s): self.function_required = {} for name, schema in self.functions.items(): - args_obj = schema.get("properties", {}).get("args", {}) - props = args_obj.get("properties", {}) - reqs = args_obj.get("required", []) + sub_schemas = [schema] + if "allOf" in schema: + sub_schemas.extend(schema["allOf"]) + + props = {} + reqs = [] + for sub in sub_schemas: + if not isinstance(sub, dict): + continue + if "properties" in sub: + args_obj = sub["properties"].get("args", {}) + if isinstance(args_obj, dict): + if "properties" in args_obj: + props.update(args_obj["properties"]) + if "required" in args_obj: + reqs.extend(args_obj["required"]) self.function_properties[name] = list(props.keys()) self.function_required[name] = reqs @@ -199,12 +212,20 @@ def get_function_property_schema( The JSON schema dictionary for the property, or None. """ fn_schema = self.functions.get(fn_name, {}) - return ( - fn_schema.get("properties", {}) - .get("args", {}) - .get("properties", {}) - .get(prop_name) - ) + if not fn_schema: + return None + + sub_schemas = [fn_schema] + if "allOf" in fn_schema: + sub_schemas.extend(fn_schema["allOf"]) + + for sub in sub_schemas: + if isinstance(sub, dict) and "properties" in sub: + args_obj = sub["properties"].get("args", {}) + if isinstance(args_obj, dict) and "properties" in args_obj: + if prop_name in args_obj["properties"]: + return args_obj["properties"][prop_name] + return None def get_property_enum( self, component_name: str, property_name: str diff --git a/specification/v1_0/catalogs/basic/catalog.json b/specification/v1_0/catalogs/basic/catalog.json index 71cc451acb..0345b5f869 100644 --- a/specification/v1_0/catalogs/basic/catalog.json +++ b/specification/v1_0/catalogs/basic/catalog.json @@ -834,421 +834,533 @@ "type": "object", "description": "Checks that the value is not null, undefined, or empty.", "returnType": "boolean", - "properties": { - "call": { - "const": "required" + "allOf": [ + { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/FunctionCommon" }, - "args": { + { "type": "object", "properties": { - "value": { - "description": "The value to check." + "call": { + "const": "required" + }, + "args": { + "type": "object", + "properties": { + "value": { + "description": "The value to check." + } + }, + "required": ["value"], + "unevaluatedProperties": false } }, - "required": ["value"], - "additionalProperties": false + "required": ["call", "args"] } - }, - "required": ["call", "args"], + ], "unevaluatedProperties": false }, "regex": { "type": "object", "description": "Checks that the value matches a regular expression string.", "returnType": "boolean", - "properties": { - "call": { - "const": "regex" + "allOf": [ + { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/FunctionCommon" }, - "args": { + { "type": "object", "properties": { - "value": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString" + "call": { + "const": "regex" }, - "pattern": { - "type": "string", - "description": "The regex pattern to match against." + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString" + }, + "pattern": { + "type": "string", + "description": "The regex pattern to match against." + } + }, + "required": ["value", "pattern"], + "unevaluatedProperties": false } }, - "required": ["value", "pattern"], - "unevaluatedProperties": false + "required": ["call", "args"] } - }, - "required": ["call", "args"], + ], "unevaluatedProperties": false }, "length": { "type": "object", "description": "Checks string length constraints.", "returnType": "boolean", - "properties": { - "call": { - "const": "length" + "allOf": [ + { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/FunctionCommon" }, - "args": { + { "type": "object", "properties": { - "value": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString" - }, - "min": { - "type": "integer", - "minimum": 0, - "description": "The minimum allowed length." + "call": { + "const": "length" }, - "max": { - "type": "integer", - "minimum": 0, - "description": "The maximum allowed length." + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString" + }, + "min": { + "type": "integer", + "minimum": 0, + "description": "The minimum allowed length." + }, + "max": { + "type": "integer", + "minimum": 0, + "description": "The maximum allowed length." + } + }, + "required": ["value"], + "anyOf": [ + { + "required": ["min"] + }, + { + "required": ["max"] + } + ], + "unevaluatedProperties": false } }, - "required": ["value"], - "anyOf": [ - { - "required": ["min"] - }, - { - "required": ["max"] - } - ], - "unevaluatedProperties": false + "required": ["call", "args"] } - }, - "required": ["call", "args"], + ], "unevaluatedProperties": false }, "numeric": { "type": "object", "description": "Checks numeric range constraints.", "returnType": "boolean", - "properties": { - "call": { - "const": "numeric" + "allOf": [ + { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/FunctionCommon" }, - "args": { + { "type": "object", "properties": { - "value": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicNumber" - }, - "min": { - "type": "number", - "description": "The minimum allowed value." - }, - "max": { - "type": "number", - "description": "The maximum allowed value." + "call": { + "const": "numeric" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicNumber" + }, + "min": { + "type": "number", + "description": "The minimum allowed value." + }, + "max": { + "type": "number", + "description": "The maximum allowed value." + } + }, + "required": ["value"], + "anyOf": [ + { + "required": ["min"] + }, + { + "required": ["max"] + } + ], + "unevaluatedProperties": false } }, - "required": ["value"], - "anyOf": [ - { - "required": ["min"] - }, - { - "required": ["max"] - } - ], - "unevaluatedProperties": false + "required": ["call", "args"] } - }, - "required": ["call", "args"], + ], "unevaluatedProperties": false }, "email": { "type": "object", "description": "Checks that the value is a valid email address.", "returnType": "boolean", - "properties": { - "call": { - "const": "email" + "allOf": [ + { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/FunctionCommon" }, - "args": { + { "type": "object", "properties": { - "value": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString" + "call": { + "const": "email" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString" + } + }, + "required": ["value"], + "unevaluatedProperties": false } }, - "required": ["value"], - "unevaluatedProperties": false + "required": ["call", "args"] } - }, - "required": ["call", "args"], + ], "unevaluatedProperties": false }, "formatString": { "type": "object", "description": "Performs string interpolation of data model values and other functions in the catalog functions list and returns the resulting string. The value string can contain interpolated expressions in the `${expression}` format. Supported expression types include: JSON Pointer paths to the data model (e.g., `${/absolute/path}` or `${relative/path}`), and renderer-side function calls (e.g., `${now()}`). Function arguments must be named (e.g., `${formatDate(value:${/currentDate}, format:'MM-dd')}`). To include a literal `${` sequence, escape it as `\\${`.", "returnType": "string", - "properties": { - "call": { - "const": "formatString" + "allOf": [ + { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/FunctionCommon" }, - "args": { + { "type": "object", "properties": { - "value": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString" + "call": { + "const": "formatString" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString" + } + }, + "required": ["value"], + "unevaluatedProperties": false } }, - "required": ["value"], - "unevaluatedProperties": false + "required": ["call", "args"] } - }, - "required": ["call", "args"], + ], "unevaluatedProperties": false }, "formatNumber": { "type": "object", "description": "Formats a number with the specified grouping and decimal precision.", "returnType": "string", - "properties": { - "call": { - "const": "formatNumber" + "allOf": [ + { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/FunctionCommon" }, - "args": { + { "type": "object", "properties": { - "value": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicNumber", - "description": "The number to format." - }, - "decimals": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicNumber", - "description": "Optional. The number of decimal places to show. Defaults to 0 or 2 depending on locale." - }, - "grouping": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicBoolean", - "description": "Optional. If true, uses locale-specific grouping separators (e.g. '1,000'). If false, returns raw digits (e.g. '1000'). Defaults to true." + "call": { + "const": "formatNumber" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicNumber", + "description": "The number to format." + }, + "decimals": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicNumber", + "description": "Optional. The number of decimal places to show. Defaults to 0 or 2 depending on locale." + }, + "grouping": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicBoolean", + "description": "Optional. If true, uses locale-specific grouping separators (e.g. '1,000'). If false, returns raw digits (e.g. '1000'). Defaults to true." + } + }, + "required": ["value"], + "unevaluatedProperties": false } }, - "required": ["value"], - "unevaluatedProperties": false + "required": ["call", "args"] } - }, - "required": ["call", "args"], + ], "unevaluatedProperties": false }, "formatCurrency": { "type": "object", "description": "Formats a number as a currency string.", "returnType": "string", - "properties": { - "call": { - "const": "formatCurrency" + "allOf": [ + { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/FunctionCommon" }, - "args": { + { "type": "object", "properties": { - "value": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicNumber", - "description": "The monetary amount." - }, - "currency": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString", - "description": "The ISO 4217 currency code (e.g., 'USD', 'EUR')." - }, - "decimals": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicNumber", - "description": "Optional. The number of decimal places to show. Defaults to 0 or 2 depending on locale." - }, - "grouping": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicBoolean", - "description": "Optional. If true, uses locale-specific grouping separators (e.g. '1,000'). If false, returns raw digits (e.g. '1000'). Defaults to true." + "call": { + "const": "formatCurrency" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicNumber", + "description": "The monetary amount." + }, + "currency": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString", + "description": "The ISO 4217 currency code (e.g., 'USD', 'EUR')." + }, + "decimals": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicNumber", + "description": "Optional. The number of decimal places to show. Defaults to 0 or 2 depending on locale." + }, + "grouping": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicBoolean", + "description": "Optional. If true, uses locale-specific grouping separators (e.g. '1,000'). If false, returns raw digits (e.g. '1000'). Defaults to true." + } + }, + "required": ["currency", "value"], + "unevaluatedProperties": false } }, - "required": ["currency", "value"], - "unevaluatedProperties": false + "required": ["call", "args"] } - }, - "required": ["call", "args"], + ], "unevaluatedProperties": false }, "formatDate": { "type": "object", "description": "Formats a timestamp into a string using a pattern.", "returnType": "string", - "properties": { - "call": { - "const": "formatDate" + "allOf": [ + { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/FunctionCommon" }, - "args": { + { "type": "object", "properties": { - "value": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicValue", - "description": "The date to format." - }, - "format": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString", - "description": "A Unicode TR35 date pattern string.\n\nToken Reference:\n- Year: 'yy' (26), 'yyyy' (2026)\n- Month: 'M' (1), 'MM' (01), 'MMM' (Jan), 'MMMM' (January)\n- Day: 'd' (1), 'dd' (01), 'E' (Tue), 'EEEE' (Tuesday)\n- Hour (12h): 'h' (1-12), 'hh' (01-12) - requires 'a' for AM/PM\n- Hour (24h): 'H' (0-23), 'HH' (00-23) - Military Time\n- Minute: 'mm' (00-59)\n- Second: 'ss' (00-59)\n- Period: 'a' (AM/PM)\n\nExamples:\n- 'MMM dd, yyyy' -> 'Jan 16, 2026'\n- 'HH:mm' -> '14:30' (Military)\n- 'h:mm a' -> '2:30 PM'\n- 'EEEE, d MMMM' -> 'Friday, 16 January'" + "call": { + "const": "formatDate" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicValue", + "description": "The date to format." + }, + "format": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString", + "description": "A Unicode TR35 date pattern string.\n\nToken Reference:\n- Year: 'yy' (26), 'yyyy' (2026)\n- Month: 'M' (1), 'MM' (01), 'MMM' (Jan), 'MMMM' (January)\n- Day: 'd' (1), 'dd' (01), 'E' (Tue), 'EEEE' (Tuesday)\n- Hour (12h): 'h' (1-12), 'hh' (01-12) - requires 'a' for AM/PM\n- Hour (24h): 'H' (0-23), 'HH' (00-23) - Military Time\n- Minute: 'mm' (00-59)\n- Second: 'ss' (00-59)\n- Period: 'a' (AM/PM)\n\nExamples:\n- 'MMM dd, yyyy' -> 'Jan 16, 2026'\n- 'HH:mm' -> '14:30' (Military)\n- 'h:mm a' -> '2:30 PM'\n- 'EEEE, d MMMM' -> 'Friday, 16 January'" + } + }, + "required": ["format", "value"], + "unevaluatedProperties": false } }, - "required": ["format", "value"], - "unevaluatedProperties": false + "required": ["call", "args"] } - }, - "required": ["call", "args"], + ], "unevaluatedProperties": false }, "pluralize": { "type": "object", "description": "Returns a localized string based on the Common Locale Data Repository (CLDR) plural category of the count (zero, one, two, few, many, other). Requires an 'other' fallback. For English, just use 'one' and 'other'.", "returnType": "string", - "properties": { - "call": { - "const": "pluralize" + "allOf": [ + { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/FunctionCommon" }, - "args": { + { "type": "object", "properties": { - "value": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicNumber", - "description": "The numeric value used to determine the plural category." - }, - "zero": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString", - "description": "String for the 'zero' category (e.g., 0 items)." - }, - "one": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString", - "description": "String for the 'one' category (e.g., 1 item)." - }, - "two": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString", - "description": "String for the 'two' category (used in Arabic, Welsh, etc.)." - }, - "few": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString", - "description": "String for the 'few' category (e.g., small groups in Slavic languages)." - }, - "many": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString", - "description": "String for the 'many' category (e.g., large groups in various languages)." - }, - "other": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString", - "description": "The default/fallback string (used for general plural cases)." + "call": { + "const": "pluralize" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicNumber", + "description": "The numeric value used to determine the plural category." + }, + "zero": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString", + "description": "String for the 'zero' category (e.g., 0 items)." + }, + "one": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString", + "description": "String for the 'one' category (e.g., 1 item)." + }, + "two": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString", + "description": "String for the 'two' category (used in Arabic, Welsh, etc.)." + }, + "few": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString", + "description": "String for the 'few' category (e.g., small groups in Slavic languages)." + }, + "many": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString", + "description": "String for the 'many' category (e.g., large groups in various languages)." + }, + "other": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString", + "description": "The default/fallback string (used for general plural cases)." + } + }, + "required": ["value", "other"], + "unevaluatedProperties": false } }, - "required": ["value", "other"], - "unevaluatedProperties": false + "required": ["call", "args"] } - }, - "required": ["call", "args"], + ], "unevaluatedProperties": false }, "openUrl": { "type": "object", "description": "Opens the specified URL in a browser or handler. This function has no return value.", "returnType": "void", - "properties": { - "call": { - "const": "openUrl" + "allOf": [ + { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/FunctionCommon" }, - "args": { + { "type": "object", "properties": { - "url": { - "description": "The URL to open.", - "oneOf": [ - { - "type": "string", - "format": "uri" - }, - { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DataBinding" - }, - { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/FunctionCall" + "call": { + "const": "openUrl" + }, + "args": { + "type": "object", + "properties": { + "url": { + "description": "The URL to open.", + "oneOf": [ + { + "type": "string", + "format": "uri" + }, + { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DataBinding" + }, + { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/FunctionCall" + } + ] } - ] + }, + "required": ["url"], + "unevaluatedProperties": false } }, - "required": ["url"], - "additionalProperties": false + "required": ["call", "args"] } - }, - "required": ["call", "args"], + ], "unevaluatedProperties": false }, "and": { "type": "object", "description": "Performs a logical AND operation on a list of boolean values.", "returnType": "boolean", - "properties": { - "call": { - "const": "and" + "allOf": [ + { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/FunctionCommon" }, - "args": { + { "type": "object", "properties": { - "values": { - "type": "array", - "description": "The list of boolean values to evaluate.", - "items": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicBoolean" + "call": { + "const": "and" + }, + "args": { + "type": "object", + "properties": { + "values": { + "type": "array", + "description": "The list of boolean values to evaluate.", + "items": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicBoolean" + }, + "minItems": 2 + } }, - "minItems": 2 + "required": ["values"], + "unevaluatedProperties": false } }, - "required": ["values"], - "unevaluatedProperties": false + "required": ["call", "args"] } - }, - "required": ["call", "args"], + ], "unevaluatedProperties": false }, "or": { "type": "object", "description": "Performs a logical OR operation on a list of boolean values.", "returnType": "boolean", - "properties": { - "call": { - "const": "or" + "allOf": [ + { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/FunctionCommon" }, - "args": { + { "type": "object", "properties": { - "values": { - "type": "array", - "description": "The list of boolean values to evaluate.", - "items": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicBoolean" + "call": { + "const": "or" + }, + "args": { + "type": "object", + "properties": { + "values": { + "type": "array", + "description": "The list of boolean values to evaluate.", + "items": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicBoolean" + }, + "minItems": 2 + } }, - "minItems": 2 + "required": ["values"], + "unevaluatedProperties": false } }, - "required": ["values"], - "unevaluatedProperties": false + "required": ["call", "args"] } - }, - "required": ["call", "args"], + ], "unevaluatedProperties": false }, "not": { "type": "object", "description": "Performs a logical NOT operation on a boolean value.", "returnType": "boolean", - "properties": { - "call": { - "const": "not" + "allOf": [ + { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/FunctionCommon" }, - "args": { + { "type": "object", "properties": { - "value": { - "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicBoolean", - "description": "The boolean value to negate." + "call": { + "const": "not" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicBoolean", + "description": "The boolean value to negate." + } + }, + "required": ["value"], + "unevaluatedProperties": false } }, - "required": ["value"], - "unevaluatedProperties": false + "required": ["call", "args"] } - }, - "required": ["call", "args"], + ], "unevaluatedProperties": false } }, diff --git a/specification/v1_0/docs/a2ui_protocol.md b/specification/v1_0/docs/a2ui_protocol.md index 07372706df..cc1d0b2d19 100644 --- a/specification/v1_0/docs/a2ui_protocol.md +++ b/specification/v1_0/docs/a2ui_protocol.md @@ -172,7 +172,7 @@ The envelope defines several message types, and every message streamed by the ag ### `createSurface` -This message signals the renderer to create a new surface and begin rendering it. A surface must be created before any `updateComponents` or `updateDataModel` messages can be sent to it. While typically achieved by the agent sending a `createSurface` message, an agent may skip this if it knows the surface has already been created (e.g., by another agent). Once a surface is created, its `surfaceId` and `catalogId` are fixed; to reconfigure them, the surface must be deleted and recreated. +This message signals the renderer to create a new surface and begin rendering it. A surface must be created before any `updateComponents` or `updateDataModel` messages can be sent to it. While typically achieved by the agent sending a `createSurface` message, an agent may skip this if it knows of a preexisting surface that it has permission to modify. Once a surface is created, its `surfaceId` and default `catalogId` (if provided) are fixed; to reconfigure them, the surface must be deleted and recreated. It is an error to try to create a surface with a `surfaceId` that already exists without first deleting it; `surfaceId` must be globally unique for the renderer's lifetime. Orchestrators with subagents are empowered to manage surface IDs as needed to prevent conflicts (e.g., prefixing the subagent's name to the `surfaceId` or requiring subagents to use UUIDs). @@ -181,7 +181,8 @@ One of the components in one of the component lists MUST have an `id` of `root` **Properties:** - `surfaceId` (string, required): The unique identifier for the UI surface to be rendered. This must be globally unique for the renderer's lifetime. -- `catalogId` (string, required): A string that uniquely identifies the catalog (components and functions) used for this surface. Note that `catalogId` is a string identifier, not a resolvable URI; while it is conventionally formatted as a URI (e.g., `https://mycompany.com/1.0/somecatalog`) to avoid naming collisions across organizations, it does not need to point to any deployed resource or downloadable file. Renderer and agent developers must agree on shared catalogs with well-known IDs in order to build systems that are compatible with each other. +- `catalogId` (string, optional): A string that uniquely identifies the default catalog (components and functions) used for this surface. Note that `catalogId` is a string identifier, not a resolvable URI; while it is conventionally formatted as a URI (e.g., `https://mycompany.com/1.0/somecatalog`) to avoid naming collisions across organizations, it does not need to point to any deployed resource or downloadable file. Components and function calls on this surface that do not explicitly specify their own `catalogId` will use this surface-level default `catalogId`. + - `surfaceProperties` (object, optional): A JSON object containing surface properties (e.g., `agentDisplayName`) defined in the catalog's surfaceProperties schema. - `sendDataModel` (boolean, optional): If true, the renderer will send the full data model of this surface in the metadata of every message sent to the agent (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. - `components` (array, optional): A list containing UI components for the surface, allowing the renderer to build and populate the UI tree immediately on surface creation. Conforms to the `ComponentsList` schema. @@ -428,10 +429,24 @@ Each object in the `components` array of an `updateComponents` message defines a - `id` (`ComponentId`, required): A unique string that identifies this specific component instance. This is used for parent-child references. - `component` (string, required): Specifies the component's type (e.g., `"Text"`). +- `catalogId` (string, optional): A string that uniquely identifies the catalog for this component, overriding the surface's default `catalogId`. Useful when combining components from multiple catalogs in a single surface. - **Component Properties**: Other properties relevant to the specific component type (e.g., `text`, `url`, `children`) are included directly in the component object. This structure is designed to be both flexible and strictly validated. +#### Mixable catalogs and component resolution logic + +Renderers can support components and functions from multiple catalogs simultaneously within a single surface (mixable catalogs). When a renderer advertises `supportedCatalogIds` in its capabilities, components from any of those catalogs can be combined in the same UI tree. The set of available catalogs for a surface includes both `supportedCatalogIds` and the `catalogId` of any inline catalog declared in `inlineCatalogs` (when supported by the agent). All catalog IDs specified at the component and function-call levels and at the surface-level must refer to catalogs which use the same A2UI specification version. + +When resolving a component (or function call), the renderer evaluates catalog identity using the following strict resolution order: + +1. **Explicit Component/Function-Level `catalogId`**: The renderer checks if the component or function call explicitly specifies a `catalogId`. If provided, the component or function is resolved against that catalog. +2. **Surface Default `catalogId`**: If the component or function call does not specify a `catalogId`, the renderer checks if a default `catalogId` was specified on the surface in the `createSurface` message. If provided, the component or function is resolved against that surface default catalog. +3. **Resolution Error**: If neither an explicit component/function-level `catalogId` nor a surface default `catalogId` is present, resolution fails immediately with an error and the component is not rendered (or the function call is rejected). + +> [!IMPORTANT] +> There is **no fallback** to the list of catalogs declared in `rendererCapabilities` (even if the renderer only advertises a single supported catalog). Every component and function call must resolve through either its explicit `catalogId` or the surface default `catalogId`. + ### The component catalog The set of available UI components and functions is defined in a **Catalog**. The basic catalog is defined in [`catalogs/basic/catalog.json`]. While the Basic Catalog is useful for starting out, most production applications will define their own catalog to reflect their specific design system. The agent must generate messages that conform to the catalog understood by the renderer. diff --git a/specification/v1_0/docs/evolution_guide.md b/specification/v1_0/docs/evolution_guide.md index e5f07ab884..4edbc16a34 100644 --- a/specification/v1_0/docs/evolution_guide.md +++ b/specification/v1_0/docs/evolution_guide.md @@ -8,6 +8,10 @@ Version 1.0 differs from 0.9 in the following ways: - A new renderer-to-agent RPC mechanism allows synchronous responses to renderer actions (`actionResponse`) using a unique `actionId`. - Agent-to-renderer RPC function calls are supported via the `callFunction` message. Renderers return execution results via the `functionResponse` message. Runtime execution boundaries and return types are defined in catalogs and verified at runtime, rather than being validated on the wire. +- Catalogs can now be mixed within a single UI surface. Advertised `supportedCatalogIds` are mixable, allowing UI trees to combine components and functions from multiple catalogs simultaneously. +- Added an optional `catalogId` property to `ComponentCommon` and `FunctionCall` to allow individual components and function calls to explicitly declare their source catalog. +- Retained `catalogId` on `createSurface` as an optional parameter that defines the default catalog for that surface. +- Defined explicit component and function call resolution logic: the renderer checks the component-level (or function call-level) `catalogId` first, then falls back to the surface default `catalogId`. If neither is defined, the renderer errors out and does not render the component (or rejects the function call). There is no fallback to catalogs declared in capabilities. Available catalogs for a surface include both `supportedCatalogIds` and any negotiated `inlineCatalogs`, and all mixed catalogs must use the same A2UI specification version. - The `theme` property in the catalog and surface creation message is replaced by `surfaceProperties`, and `primaryColor` is removed to separate layout from branding. - Components and initial data model states can be defined directly within the `createSurface` parameters. This allows for the creation of entire UIs in a single message, rather than a create followed by separate updates. - The `functions` field in a Catalog is now defined as a map of function name to its definition, instead of a list. @@ -39,7 +43,8 @@ Version 1.0 differs from 0.9 in the following ways: - Added `actionResponse` message structure (`ActionResponseMessage`) to allow the agent to respond to a specific action call using a unique `actionId` with a `value` or `error`. - Added `callFunction` message structure (`CallFunctionMessage`) to support agent-initiated function execution. Removed `callableFrom` and `returnType` properties from the wire payload, relying on runtime catalog verification. -- Updated the `createSurface` message (`CreateSurfaceMessage`) to rename the `theme` field to `surfaceProperties`, and allowed passing initial `components` and `dataModel` directly inside the payload. +- Updated the `createSurface` message (`CreateSurfaceMessage`) to rename the `theme` field to `surfaceProperties`, allowed passing initial `components` and `dataModel` directly inside the payload, and made `catalogId` an optional parameter that acts as the surface's default catalog. +- Added an optional `catalogId` property to `ComponentCommon` and `FunctionCall` in `common_types.json` to enable mixing catalogs and explicitly designating the catalog on individual components or function calls. - Updated all protocol version references and envelopes from `v0.9` or `v0.9.1` to `v1.0`. ### 2.4. Renderer-to-agent events @@ -59,6 +64,7 @@ Version 1.0 differs from 0.9 in the following ways: - Standardized the official MIME type to `application/a2ui+json` to conform to IANA media type guidelines. - Updated capabilities namespace in transport metadata and A2A metadata parameters from `v0.9`/`v0.9.1` to `v1.0`. +- Clarified that `supportedCatalogIds` in `rendererCapabilities` and `agentCapabilities` are mixable within a single UI surface. ### 2.7. Data encoding @@ -68,6 +74,10 @@ Version 1.0 differs from 0.9 in the following ways: ### 2.8. Processing rules +- Defined strict component and function catalog resolution logic: + 1. Check the component's (or function call's) explicit `catalogId`. + 2. If not present, check the surface's default `catalogId` provided in `createSurface`. + 3. If neither exists, report an error and do not render the component (or fail the function call). There is no fallback to catalogs advertised in capabilities. Available catalogs include `supportedCatalogIds` and negotiated `inlineCatalogs`, and all mixed catalogs must use the same A2UI specification version. - Explicitly specified that `surfaceId` must be globally unique per renderer session. Creating a surface with an ID that already exists (without first deleting it) is an error. - Enforced runtime lookup of function execution boundaries and return types. If a renderer receives a remote call to a function configured as `rendererOnly` or if the function is unregistered, it rejects the call and returns an error with the code `INVALID_FUNCTION_CALL`. - Enforced catalog entity naming compliance with Unicode Standard Annex #31 (UAX #31). @@ -91,7 +101,8 @@ This section outlines the steps required to migrate existing applications and co - Set the `version` field in all streamed JSON envelopes to `"v1.0"`. - Change the MIME type of A2UI payloads in transport layers from `application/json+a2ui` to `application/a2ui+json`. -- Rename the `theme` field in `createSurface` messages to `surfaceProperties` and remove `primaryColor`. You can also pass initial `components` and `dataModel` directly in the `createSurface` payload. +- Rename the `theme` field in `createSurface` messages to `surfaceProperties` and remove `primaryColor`. You can pass initial `components` and `dataModel` directly in the `createSurface` payload, and `catalogId` is now optional (acting as the default catalog for that surface). +- When mixing components from multiple catalogs, specify the optional `catalogId` on individual components or function calls. - Convert the `functions` property in catalog definitions from an array to a JSON object map keyed by function name. - Rename the `$defs/theme` catalog definition to `$defs/surfaceProperties` and remove the `primaryColor` field. - Ensure all generated catalog entity names conform to UAX #31 identifier rules. @@ -102,6 +113,8 @@ This section outlines the steps required to migrate existing applications and co ### For renderers +- Implement multi-catalog mixing by supporting components and function calls from any catalog in `supportedCatalogIds` or negotiated `inlineCatalogs`. All catalogs mixed within a surface must use the same A2UI specification version. +- Implement component and function resolution order: (1) explicit component/call `catalogId`, (2) surface default `catalogId`, (3) error if neither exists (no fallback to capabilities). - Implement function execution by adding support for parsing `callFunction` messages, checking boundary definitions in the catalog (`callableFrom`), rejecting invalid calls with `INVALID_FUNCTION_CALL`, and returning `functionResponse` messages. - Support synchronous action responses by generating `actionId` for actions with `wantResponse: true` and writing returned values from `actionResponse` messages into the data model. - Support simultaneous version handling during session initialization by inspecting the `version` property (e.g., `"v1.0"`) to route payloads to version-specific controllers. diff --git a/specification/v1_0/json/agent_capabilities.json b/specification/v1_0/json/agent_capabilities.json index 5a81402cad..3085dcb1ec 100644 --- a/specification/v1_0/json/agent_capabilities.json +++ b/specification/v1_0/json/agent_capabilities.json @@ -11,7 +11,7 @@ "properties": { "supportedCatalogIds": { "type": "array", - "description": "An array of strings, where each string is an ID identifying a Catalog Definition Schema that the agent can generate. This is not necessarily a resolvable URI.", + "description": "An array of strings, where each is an ID identifying a Catalog for which the agent can generate content. This is not a resolvable URI. Multiple catalogs can be mixed in a single surface.", "items": {"type": "string"} }, "acceptsInlineCatalogs": { diff --git a/specification/v1_0/json/agent_to_renderer.json b/specification/v1_0/json/agent_to_renderer.json index bdf3e8e056..09d867f9db 100644 --- a/specification/v1_0/json/agent_to_renderer.json +++ b/specification/v1_0/json/agent_to_renderer.json @@ -28,7 +28,7 @@ "description": "The unique identifier for the UI surface to be rendered. It must be globally unique for the renderer's lifetime." }, "catalogId": { - "description": "A string that uniquely identifies this catalog. It is recommended to prefix this with an internet domain that you own, to avoid conflicts e.g. mycompany.com:somecatalog'.", + "description": "A string that uniquely identifies the default catalog for this surface. It is recommended to prefix this with an internet domain that you own, to avoid conflicts e.g. 'mycompany.com:somecatalog'. Components and function calls that do not explicitly specify a catalogId will use this surface-level default catalogId.", "type": "string" }, "surfaceProperties": { @@ -48,7 +48,7 @@ "additionalProperties": true } }, - "required": ["surfaceId", "catalogId"], + "required": ["surfaceId"], "additionalProperties": false } }, @@ -71,7 +71,7 @@ }, "updateComponents": { "type": "object", - "description": "Updates a surface with a new set of components. This message can be sent multiple times to update the component tree of an existing surface. One of the components in one of the components lists MUST have an 'id' of 'root' to serve as the root of the component tree. The createSurface message MUST have been previously sent with the 'catalogId' that is in this message.", + "description": "Updates a surface with a new set of components. This message can be sent multiple times to update the component tree of an existing surface. One of the components in one of the components lists MUST have an 'id' of 'root' to serve as the root of the component tree. The createSurface message MUST have been previously sent for this surfaceId.", "properties": { "surfaceId": { "type": "string", @@ -96,7 +96,7 @@ }, "updateDataModel": { "type": "object", - "description": "Updates the data model for an existing surface. This message can be sent multiple times to update the data model. The createSurface message MUST have been previously sent with the 'catalogId' that is in this message.", + "description": "Updates the data model for an existing surface. This message can be sent multiple times to update the data model. The createSurface message MUST have been previously sent for this surfaceId.", "properties": { "surfaceId": { "type": "string", @@ -125,7 +125,7 @@ }, "deleteSurface": { "type": "object", - "description": "Signals the renderer to delete the surface identified by 'surfaceId'. The createSurface message MUST have been previously sent with the 'catalogId' that is in this message.", + "description": "Signals the renderer to delete the surface identified by 'surfaceId'. The createSurface message MUST have been previously sent for this surfaceId.", "properties": { "surfaceId": { "type": "string", diff --git a/specification/v1_0/json/catalog_definition.json b/specification/v1_0/json/catalog_definition.json index 965fc15931..844bb4041b 100644 --- a/specification/v1_0/json/catalog_definition.json +++ b/specification/v1_0/json/catalog_definition.json @@ -67,45 +67,77 @@ "FunctionCallValidationSchema": { "type": "object", "description": "JSON Schema structure that validates a wire-level FunctionCall object.", - "properties": { - "type": { - "const": "object" - }, - "description": { - "type": "string" - }, - "properties": { + "oneOf": [ + { "type": "object", "properties": { - "call": { + "type": { + "const": "object" + }, + "description": { + "type": "string" + }, + "properties": { "type": "object", "properties": { - "const": {"type": "string"} + "call": { + "type": "object", + "properties": { + "const": {"type": "string"} + }, + "required": ["const"] + }, + "catalogId": { + "type": "object", + "description": "Optional catalog ID override for this function call." + }, + "args": { + "type": "object", + "description": "A JSON Schema describing the expected arguments (args) for this function.", + "$ref": "https://json-schema.org/draft/2020-12/schema" + } }, - "required": ["const"] + "required": ["call"], + "additionalProperties": false }, - "args": { - "type": "object", - "description": "A JSON Schema describing the expected arguments (args) for this function.", - "$ref": "https://json-schema.org/draft/2020-12/schema" + "required": { + "type": "array", + "items": {"type": "string"}, + "contains": {"const": "call"} + }, + "unevaluatedProperties": { + "type": "boolean" + }, + "additionalProperties": { + "type": "boolean" } }, - "required": ["call"], - "additionalProperties": false - }, - "required": { - "type": "array", - "items": {"type": "string"}, - "contains": {"const": "call"} - }, - "unevaluatedProperties": { - "type": "boolean" + "required": ["type", "properties", "required"] }, - "additionalProperties": { - "type": "boolean" + { + "type": "object", + "properties": { + "type": { + "const": "object" + }, + "description": { + "type": "string" + }, + "allOf": { + "type": "array", + "items": {"type": "object"}, + "minItems": 1 + }, + "unevaluatedProperties": { + "type": "boolean" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "required": ["type", "allOf"] } - }, - "required": ["type", "properties", "required"] + ] }, "FunctionDefinition": { "type": "object", diff --git a/specification/v1_0/json/common_types.json b/specification/v1_0/json/common_types.json index d4a653f954..fb2b37c468 100644 --- a/specification/v1_0/json/common_types.json +++ b/specification/v1_0/json/common_types.json @@ -32,6 +32,10 @@ "id": { "$ref": "#/$defs/ComponentId" }, + "catalogId": { + "type": "string", + "description": "The catalog ID for this component, overriding any surface-level default catalogId." + }, "accessibility": { "$ref": "#/$defs/AccessibilityAttributes" } @@ -161,6 +165,15 @@ } ] }, + "FunctionCommon": { + "type": "object", + "properties": { + "catalogId": { + "type": "string", + "description": "The catalog ID for this function, overriding any surface-level default catalogId." + } + } + }, "IndexSystemFunction": { "type": "object", "description": "Returns the 0-based index of the current item when rendering a dynamic list from a template. This function MUST ONLY be available when evaluating template items within a list context.", @@ -192,6 +205,10 @@ "type": "string", "description": "The name of the function to call." }, + "catalogId": { + "type": "string", + "description": "The catalog ID for this function, overriding any surface-level default catalogId." + }, "args": { "type": "object", "description": "Arguments passed to the function.", @@ -212,7 +229,8 @@ "oneOf": [ {"$ref": "catalog.json#/$defs/anyFunction"}, {"$ref": "#/$defs/IndexSystemFunction"} - ] + ], + "unevaluatedProperties": false }, "CheckRule": { "type": "object", diff --git a/specification/v1_0/json/renderer_capabilities.json b/specification/v1_0/json/renderer_capabilities.json index 08937a2630..a10d36ca1b 100644 --- a/specification/v1_0/json/renderer_capabilities.json +++ b/specification/v1_0/json/renderer_capabilities.json @@ -11,7 +11,7 @@ "properties": { "supportedCatalogIds": { "type": "array", - "description": "An array of string identifiers for each of the component and function catalogs supported by the renderer.", + "description": "An array of string identifiers for each of the component and function catalogs supported by the renderer. Multiple catalogs can be mixed in a single surface.", "items": {"type": "string"} }, "inlineCatalogs": { diff --git a/specification/v1_0/test/cases/call_function_message.json b/specification/v1_0/test/cases/call_function_message.json index 957fa24ceb..de29b86dad 100644 --- a/specification/v1_0/test/cases/call_function_message.json +++ b/specification/v1_0/test/cases/call_function_message.json @@ -17,6 +17,22 @@ "wantResponse": true } }, + { + "description": "CallFunctionMessage: Valid with catalogId and wantResponse", + "valid": true, + "data": { + "version": "v1.0", + "callFunction": { + "call": "openUrl", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json", + "args": { + "url": "https://example.com" + } + }, + "functionCallId": "unique-call-id-123-cat", + "wantResponse": true + } + }, { "description": "CallFunctionMessage: Valid with remoteOnly", "valid": true, diff --git a/specification/v1_0/test/cases/function_catalog_validation.json b/specification/v1_0/test/cases/function_catalog_validation.json index 2bd6e2a13a..547d9d0476 100644 --- a/specification/v1_0/test/cases/function_catalog_validation.json +++ b/specification/v1_0/test/cases/function_catalog_validation.json @@ -1295,6 +1295,30 @@ ] } } + }, + { + "description": "@index: Invalid call with catalogId (system function cannot have catalogId)", + "valid": false, + "data": { + "version": "v1.0", + "updateComponents": { + "surfaceId": "test", + "components": [ + { + "id": "slider1", + "component": "Slider", + "max": 10, + "value": { + "call": "@index", + "catalogId": "some-catalog", + "args": { + "offset": 1 + } + } + } + ] + } + } } ] } diff --git a/specification/v1_0/test/cases/initial_state_validation.json b/specification/v1_0/test/cases/initial_state_validation.json index 7ed2727854..177e39150d 100644 --- a/specification/v1_0/test/cases/initial_state_validation.json +++ b/specification/v1_0/test/cases/initial_state_validation.json @@ -61,6 +61,24 @@ } } }, + { + "description": "Valid createSurface without catalogId", + "valid": true, + "data": { + "version": "v1.0", + "createSurface": { + "surfaceId": "test_surface", + "components": [ + { + "id": "root", + "component": "Text", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json", + "text": "Component with explicit catalogId" + } + ] + } + } + }, { "description": "Invalid: createSurface contains additional unexpected properties", "valid": false, diff --git a/specification/v1_0/test/testing_catalog.json b/specification/v1_0/test/testing_catalog.json index f780246eda..3aa9228b6c 100644 --- a/specification/v1_0/test/testing_catalog.json +++ b/specification/v1_0/test/testing_catalog.json @@ -27,36 +27,52 @@ "type": "object", "description": "Opens the specified URL in a browser or handler. This function has no return value.", "returnType": "void", - "properties": { - "call": { - "const": "openUrl" + "allOf": [ + { + "$ref": "common_types.json#/$defs/FunctionCommon" }, - "args": { + { "type": "object", "properties": { - "url": { - "type": "string", - "format": "uri", - "description": "The URL to open." + "call": { + "const": "openUrl" + }, + "args": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "The URL to open." + } + }, + "required": ["url"], + "unevaluatedProperties": false } }, - "required": ["url"], - "additionalProperties": false + "required": ["call", "args"] } - }, - "required": ["call", "args"], + ], "unevaluatedProperties": false }, "pingAgent": { "type": "object", "description": "Pings the agent from the renderer side.", "returnType": "void", - "properties": { - "call": { - "const": "pingAgent" + "allOf": [ + { + "$ref": "common_types.json#/$defs/FunctionCommon" + }, + { + "type": "object", + "properties": { + "call": { + "const": "pingAgent" + } + }, + "required": ["call"] } - }, - "required": ["call"], + ], "unevaluatedProperties": false } },