Describe the Bug
In the Python agent SDK (a2ui-agent-sdk), TransportFormat._select_catalog merges client inlineCatalogs by updating only catalog_schema["components"]. It does not rebuild $defs.anyComponent.oneOf.
updateComponents.components[] validates against $ref: "#/$defs/anyComponent". After an inline merge, custom components appear under components (and therefore in the generated system prompt schema map), but they are missing from the anyComponent union. As a result, TransportStreamParser / A2uiValidator reject messages that use those custom components with A2uiValidationError, even though the LLM was instructed to emit them.
This contradicts the catalog assembler contract used elsewhere in the repo: when components are merged, anyComponent must be a oneOf over all merged components (see tools/build_catalog/tests/test_assemble_catalog.py → test_synthesized_defs).
Pruning already knows about this coupling (A2uiCatalog.with_pruning filters $defs.anyComponent.oneOf when restricting components), but the inline merge path never adds the new refs in the first place.
Steps to Reproduce
- Create a
TransportFormat / A2uiSchemaManager with accepts_inline_catalogs=True and the basic catalog.
- Pass client capabilities with an inline catalog that defines a custom component, e.g.
StatusChip:
caps = {
"supportedCatalogIds": [
"https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json"
],
"inlineCatalogs": [
{
"catalogId": "example_inline",
"components": {
"StatusChip": {
"type": "object",
"allOf": [
{
"$ref": "https://a2ui.org/specification/v0_9/common_types.json#/$defs/ComponentCommon"
},
{"$ref": "#/$defs/CatalogComponentCommon"},
{
"type": "object",
"properties": {
"component": {"const": "StatusChip"},
"label": {
"$ref": "https://a2ui.org/specification/v0_9/common_types.json#/$defs/DynamicString"
},
},
"required": ["component", "label"],
},
],
}
},
}
],
}
catalog = format.get_selected_catalog(client_ui_capabilities=caps)
- Observe:
"StatusChip" in catalog.catalog_schema["components"] → True
"#/components/StatusChip" not in catalog.catalog_schema["$defs"]["anyComponent"]["oneOf"]
- Feed
TransportStreamParser a complete <a2ui-json> block with createSurface + updateComponents containing {"id":"root","component":"StatusChip","label":"OK"}.
- Validation fails with
A2uiValidationError / "is not valid under any of the given schemas".
Current merge code on main (agent_sdks/python/a2ui_agent/src/a2ui/inference_formats/transport/format.py):
merged_schema = copy.deepcopy(base_catalog.catalog_schema)
for inline_catalog_schema in inline_catalogs:
inline_catalog_schema = self._apply_modifiers(inline_catalog_schema)
inline_components = inline_catalog_schema.get(CATALOG_COMPONENTS_KEY, {})
merged_schema[CATALOG_COMPONENTS_KEY].update(inline_components)
return A2uiCatalog(..., catalog_schema=merged_schema, ...)
Expected Behavior
After merging inline catalogs:
- Custom components are present in
components.
$defs.anyComponent.oneOf is rebuilt to include #/components/<Name> for every component in the merged map (same rule as CatalogAssembler / test_synthesized_defs).
TransportStreamParser accepts updateComponents that use those custom components (assuming the component schema itself is valid).
Suggested fix sketch:
components = merged_schema.get(CATALOG_COMPONENTS_KEY) or {}
merged_schema.setdefault("$defs", {})["anyComponent"] = {
"oneOf": [{"$ref": f"#/components/{name}"} for name in components],
"discriminator": {"propertyName": "component"},
}
# Optionally also rebuild anyFunction if inline catalogs can add functions.
Screenshots / Video / Logs
Example failure (custom StatusChip after inline merge):
A2uiValidationError: Validation failed: {'version': 'v0.9', 'updateComponents': {... 'component': 'StatusChip' ...}} is not valid under any of the given schemas
Context failures:
- 'createSurface' is a required property
- {'id': 'root', 'component': 'StatusChip', 'label': 'OK'} is not valid under any of the given schemas
...
After manually appending {"$ref": "#/components/StatusChip"} to anyComponent.oneOf, the same payload validates successfully.
Environment Details
- OS: macOS
- SDK/Package Name & Version:
a2ui-agent-sdk from GitHub main (subdirectory agent_sdks/python/a2ui_agent); also reproduced on commit pinned around d4723f2 and re-checked against current main (5cd29ceb)
- Protocol Version: v0.9 / v0.9.1
- Agent Framework & LLM Model: LlamaIndex agent path consuming
TransportStreamParser (issue is in catalog merge / validation, independent of the LLM)
Additional Context
- Related intentional behavior elsewhere:
tools/build_catalog synthesizes anyComponent for all merged components; A2uiCatalog.with_pruning filters anyComponent when pruning — both assume anyComponent stays in sync with components.
- Workaround used downstream: after
get_selected_catalog(...), deep-copy the schema and rebuild $defs.anyComponent.oneOf from the merged components keys before constructing the parser.
Happy to send a PR for the SDK fix + a regression test if that would help.
Describe the Bug
In the Python agent SDK (
a2ui-agent-sdk),TransportFormat._select_catalogmerges clientinlineCatalogsby updating onlycatalog_schema["components"]. It does not rebuild$defs.anyComponent.oneOf.updateComponents.components[]validates against$ref: "#/$defs/anyComponent". After an inline merge, custom components appear undercomponents(and therefore in the generated system prompt schema map), but they are missing from theanyComponentunion. As a result,TransportStreamParser/A2uiValidatorreject messages that use those custom components withA2uiValidationError, even though the LLM was instructed to emit them.This contradicts the catalog assembler contract used elsewhere in the repo: when components are merged,
anyComponentmust be aoneOfover all merged components (seetools/build_catalog/tests/test_assemble_catalog.py→test_synthesized_defs).Pruning already knows about this coupling (
A2uiCatalog.with_pruningfilters$defs.anyComponent.oneOfwhen restricting components), but the inline merge path never adds the new refs in the first place.Steps to Reproduce
TransportFormat/A2uiSchemaManagerwithaccepts_inline_catalogs=Trueand the basic catalog.StatusChip:"StatusChip" in catalog.catalog_schema["components"]→True"#/components/StatusChip"not incatalog.catalog_schema["$defs"]["anyComponent"]["oneOf"]TransportStreamParsera complete<a2ui-json>block withcreateSurface+updateComponentscontaining{"id":"root","component":"StatusChip","label":"OK"}.A2uiValidationError/ "is not valid under any of the given schemas".Current merge code on
main(agent_sdks/python/a2ui_agent/src/a2ui/inference_formats/transport/format.py):Expected Behavior
After merging inline catalogs:
components.$defs.anyComponent.oneOfis rebuilt to include#/components/<Name>for every component in the merged map (same rule asCatalogAssembler/test_synthesized_defs).TransportStreamParseracceptsupdateComponentsthat use those custom components (assuming the component schema itself is valid).Suggested fix sketch:
Screenshots / Video / Logs
Example failure (custom
StatusChipafter inline merge):After manually appending
{"$ref": "#/components/StatusChip"}toanyComponent.oneOf, the same payload validates successfully.Environment Details
a2ui-agent-sdkfrom GitHubmain(subdirectoryagent_sdks/python/a2ui_agent); also reproduced on commit pinned aroundd4723f2and re-checked against currentmain(5cd29ceb)TransportStreamParser(issue is in catalog merge / validation, independent of the LLM)Additional Context
tools/build_catalogsynthesizesanyComponentfor all merged components;A2uiCatalog.with_pruningfiltersanyComponentwhen pruning — both assumeanyComponentstays in sync withcomponents.get_selected_catalog(...), deep-copy the schema and rebuild$defs.anyComponent.oneOffrom the mergedcomponentskeys before constructing the parser.Happy to send a PR for the SDK fix + a regression test if that would help.