Skip to content

[BUG]: TransportFormat inlineCatalogs merge does not rebuild $defs.anyComponent #2115

Description

@likebean
  • I have searched the existing issues to make sure this bug has not already been reported.

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.pytest_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

  1. Create a TransportFormat / A2uiSchemaManager with accepts_inline_catalogs=True and the basic catalog.
  2. 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)
  1. Observe:
    • "StatusChip" in catalog.catalog_schema["components"]True
    • "#/components/StatusChip" not in catalog.catalog_schema["$defs"]["anyComponent"]["oneOf"]
  2. Feed TransportStreamParser a complete <a2ui-json> block with createSurface + updateComponents containing {"id":"root","component":"StatusChip","label":"OK"}.
  3. 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:

  1. Custom components are present in components.
  2. $defs.anyComponent.oneOf is rebuilt to include #/components/<Name> for every component in the merged map (same rule as CatalogAssembler / test_synthesized_defs).
  3. 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.

Metadata

Metadata

Assignees

Labels

P2The team intends to work on this in the near future, or at a lower priority.status: needs reviewtype: bugSomething isn't working

Type

No type

Projects

Status
In Progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions