Skip to content

Fix inline catalog validation and multi-surface support#831

Merged
nan-yu merged 3 commits intogoogle:mainfrom
sarthak96agarwal:fix/multi-surface-validator
Mar 19, 2026
Merged

Fix inline catalog validation and multi-surface support#831
nan-yu merged 3 commits intogoogle:mainfrom
sarthak96agarwal:fix/multi-surface-validator

Conversation

@sarthak96agarwal
Copy link
Contributor

@sarthak96agarwal sarthak96agarwal commented Mar 12, 2026

Fixes #796

Summary

This PR fixes inline catalog handling in the schema manager, ensures client capabilities are extracted from all DataPart types (not just request), fixes an unresolvable $ref in the prompt builder's test JSON, and adds test coverage for the recently-landed multi-surface and incremental update validator logic.

Problems

  1. Only the first inline catalog was merged — The schema manager used inline_catalogs[0] and ignored the rest. Clients that split custom components across multiple inline catalog definitions would lose all but the first.
  2. supportedCatalogIds couldn't be used alongside inlineCatalogs — Sending both raised a ValueError, but clients legitimately need both: supportedCatalogIds to pick the base catalog and inlineCatalogs to extend it with custom components.
  3. supportedCatalogIds was ignored when selecting the base for inline catalog merging — The base catalog was always _supported_catalogs[0] even when the client specified a preferred base via supportedCatalogIds.
  4. client_ui_capabilities not extracted from userAction DataParts — The executor only extracted capabilities from request DataParts. When a UI event fires (userAction), that branch is never hit, so the LLM response is validated against the wrong catalog.
  5. OrgChart inline schema had unresolvable $ref — The prompt builder's test JSON used $ref: "#/definitions/Action" which doesn't resolve in an inline catalog context.

Note: Multi-surface root tracking and incremental update detection were independently fixed in upstream. This PR adds test coverage for that logic.

Changes

agent_sdks/python/src/a2ui/core/schema/manager.py

  • Merge all inline catalogs (loop), not just the first one
  • Remove ValueError when both inlineCatalogs and supportedCatalogIds are present
  • Use supportedCatalogIds to select which base catalog to merge onto

samples/agent/adk/contact_multiple_surfaces/agent_executor.py

  • Extract a2uiClientCapabilities from any DataPart that has metadata, before the userAction/request branch — fixes UI events using the wrong catalog for validation

samples/agent/adk/contact_multiple_surfaces/prompt_builder.py

  • Update inline catalog test JSON: inline the action schema (was unresolvable $ref), add oneOf for OrgChart chain property

agent_sdks/python/tests/core/schema/test_validator.py

  • Add multi-surface validation tests (two beginRendering + two surfaceUpdate with different roots)
  • Add incremental update tests: no-root passes, orphans pass, but self-refs/cycles/duplicates still fail
  • All incremental update tests cover both v0.8 and v0.9

Key Design Decisions

  1. Inline catalogs are additive extensions. Components from each inline catalog are merged onto the base catalog via components.update(). This ensures standard components remain available alongside custom ones.

  2. All inline catalogs from the client are merged. If a client sends multiple inline catalogs, all their components are merged onto the base. This supports clients that split custom components across multiple catalog definitions.

  3. Both inlineCatalogs and supportedCatalogIds are allowed simultaneously. supportedCatalogIds selects the base catalog, and inlineCatalogs extend it.

Validation Steps

  • All 47 validator tests pass (pytest tests/core/schema/test_validator.py -v)
  • Run the contact_multiple_surfaces agent + contact client end-to-end
  • Verify both contact card and org chart surfaces render on initial search
  • Click an org chart node and verify the update validates and renders correctly
  • Verify incremental data model updates (no full re-render) work without validation errors

Pre-launch Checklist

If you need help, consider asking for advice on the discussion board.

@google-cla
Copy link

google-cla bot commented Mar 12, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request refactors the UI catalog selection and validation logic to support multi-surface rendering and incremental updates. The _select_catalog function now merges inline catalogs with a base catalog, and the component validation (_validate_component_integrity, _validate_topology) distinguishes between initial renders (where root component and orphan checks apply) and incremental updates (where these checks are skipped). Corresponding test cases have been added or updated to reflect this new behavior. Additionally, the OrgChart component schema was updated to allow chain to be either a path reference or an array, and the action schema was detailed. A review comment suggests improving the readability and maintainability of a long JSON string in prompt_builder.py by defining it as a Python dictionary and then serializing it.

Comment on lines 89 to 92
client_ui_capabilities_str = (
'{"inlineCatalogs":[{"catalogId": "inline_catalog",'
' "components":{"OrgChart":{"type":"object","properties":{"chain":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"name":{"type":"string"}},"required":["title","name"]}},"action":{"$ref":"#/definitions/Action"}},"required":["chain"]},"WebFrame":{"type":"object","properties":{"url":{"type":"string"},"html":{"type":"string"},"height":{"type":"number"},"interactionMode":{"type":"string","enum":["readOnly","interactive"]},"allowedEvents":{"type":"array","items":{"type":"string"}}}}}}]}'
' "components":{"OrgChart":{"type":"object","properties":{"chain":{"oneOf":[{"type":"object","properties":{"path":{"type":"string"}},"required":["path"]},{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"name":{"type":"string"}},"required":["title","name"]}}]},"action":{"type":"object","properties":{"name":{"type":"string"},"context":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"object","properties":{"path":{"type":"string"},"literalString":{"type":"string"},"literalNumber":{"type":"number"},"literalBoolean":{"type":"boolean"}}}},"required":["key","value"]}}},"required":["name"]}},"required":["chain"]},"WebFrame":{"type":"object","properties":{"url":{"type":"string"},"html":{"type":"string"},"height":{"type":"number"},"interactionMode":{"type":"string","enum":["readOnly","interactive"]},"allowedEvents":{"type":"array","items":{"type":"string"}}}}}}]}'
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This long, single-line JSON string is difficult to read and maintain. To improve readability and reduce the chance of syntax errors, consider defining this structure as a Python dictionary and then serializing it to a JSON string using json.dumps.

  client_ui_capabilities_dict = {
      "inlineCatalogs": [{
          "catalogId": "inline_catalog",
          "components": {
              "OrgChart": {
                  "type": "object",
                  "properties": {
                      "chain": {"oneOf": [
                          {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]},
                          {"type": "array", "items": {"type": "object", "properties": {"title": {"type": "string"}, "name": {"type": "string"}}, "required": ["title", "name"]}}
                      ]},
                      "action": {"type": "object", "properties": {
                          "name": {"type": "string"},
                          "context": {"type": "array", "items": {"type": "object", "properties": {
                              "key": {"type": "string"},
                              "value": {"type": "object", "properties": {
                                  "path": {"type": "string"}, "literalString": {"type": "string"},
                                  "literalNumber": {"type": "number"}, "literalBoolean": {"type": "boolean"}
                              }}
                          }, "required": ["key", "value"]}}
                      }, "required": ["name"]}
                  },
                  "required": ["chain"]
              },
              "WebFrame": {
                  "type": "object",
                  "properties": {
                      "url": {"type": "string"}, "html": {"type": "string"}, "height": {"type": "number"},
                      "interactionMode": {"type": "string", "enum": ["readOnly", "interactive"]},
                      "allowedEvents": {"type": "array", "items": {"type": "string"}}
                  }
              }
          }
      }]
  }
  client_ui_capabilities_str = json.dumps(client_ui_capabilities_dict)

Copy link
Collaborator

@zeroasterisk zeroasterisk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the thorough fix, @sarthak96agarwal — the multi-surface root tracking and inline catalog merging changes are solid.

Heads up: @nanyu is actively working on this area of the codebase. Before we merge, could you rebase onto the latest main and confirm there are no conflicts with recent changes? That'll make it easier for Nan to review and coordinate.

Thanks!

- Validator: support multiple surfaces with different root IDs by
  tracking per-surface roots instead of a single global root
- Validator: distinguish initial renders (beginRendering/createSurface)
  from incremental updates; skip root and orphan checks on updates
  while still catching cycles, self-refs, and duplicates
- Manager: merge inline catalog components onto the base catalog
  instead of using inline-only; allow both inlineCatalogs and
  supportedCatalogIds in client capabilities
- Agent: remove schema from system prompt (provided per-request via
  client capabilities); pass client_ui_capabilities through to
  catalog selection and validation
- Executor: extract a2uiClientCapabilities from all DataParts (not
  just request parts) so UI events use the correct catalog
- Client: inline OrgChart action schema (was unresolvable $ref) and
  add path-reference oneOf for chain (matches agent examples)
@sarthak96agarwal sarthak96agarwal force-pushed the fix/multi-surface-validator branch from 7b6f441 to 215b64a Compare March 19, 2026 13:29
Copy link
Collaborator

@nan-yu nan-yu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for cleaning up the inline catalogs! That’s been a bit of a 'muddy' area for us, and your changes definitely make the implementation more robust.

One small note: we should keep in mind that Inline catalogs sent by the client at runtime are supported but not recommended in production.

@nan-yu
Copy link
Collaborator

nan-yu commented Mar 19, 2026

The CI is currently failing. Could you please run a formatter on the python code: uv run pyink .?

@nan-yu nan-yu merged commit e9bae10 into google:main Mar 19, 2026
10 checks passed
@github-project-automation github-project-automation bot moved this from Todo to Done in A2UI Mar 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Inline catalog validation fails in contact_multiple_surfaces sample agent

3 participants