a2ui-project / a2ui-project/a2ui

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

Abierto
#2,115 2 comentarios 0 reacciones 1 asignado Reclamado por @Varun-S10 Ver en GitHub
P2 status: needs review type: bug
Lenguaje dominante
TypeScript
Estrellas
16.4k
Forks
1.3k
Merge medio
2 d 13 h
PR fusionados (30 d)
134

Descripción

- [x] 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.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

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`:

```python
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)
```

3. Observe:
- `"StatusChip" in catalog.catalog_schema["components"]` → `True`
- `"#/components/StatusChip"` **not** in `catalog.catalog_schema["$defs"]["anyComponent"]["oneOf"]`
4. Feed `TransportStreamParser` a complete `` block with `createSurface` + `updateComponents` containing `{"id":"root","component":"StatusChip","label":"OK"}`.
5. 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`):

```python
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/` 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:

```python
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):

```text
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.

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.