a2ui-project / a2ui-project/a2ui

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

Aperta
#2,115 2 commenti 0 reazioni 1 assegnatario Rivendicata da @Varun-S10 Vedi su GitHub
P2 status: needs review type: bug
Lingua principale
TypeScript
Stelle
16.4k
Fork
1.3k
Merge medio
2g 13h
PR unite (30g)
134

Descrizione

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

Guida per i contributori

Apri la guida per i contributori

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.