Skip to content

Commit 0273076

Browse files
authored
Refactor A2UI extension activation (#926)
* Refactor A2UI extension activation The agents may support multiple versions of A2UI extensions. Based on client requested extension, the agent SDK should dynamically activate one that is supported by the agent. * fix a typo in the mcp-apps-calculator folder * Fix the contact_multiple_surfaces sample
1 parent 10427d6 commit 0273076

File tree

35 files changed

+876
-1319
lines changed

35 files changed

+876
-1319
lines changed

agent_sdks/python/src/a2ui/a2a.py

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,17 @@
1616
from typing import Any, Optional, List
1717

1818
from a2a.server.agent_execution import RequestContext
19-
from a2a.types import AgentExtension, Part, DataPart, TextPart
19+
from a2a.types import (
20+
AgentExtension,
21+
AgentCard,
22+
Part,
23+
DataPart,
24+
TextPart,
25+
)
2026

2127
logger = logging.getLogger(__name__)
2228

23-
A2UI_EXTENSION_URI = "https://a2ui.org/a2a-extension/a2ui/v0.8"
29+
A2UI_EXTENSION_BASE_URI = "https://a2ui.org/a2a-extension/a2ui"
2430
AGENT_EXTENSION_SUPPORTED_CATALOG_IDS_KEY = "supportedCatalogIds"
2531
AGENT_EXTENSION_ACCEPTS_INLINE_CATALOGS_KEY = "acceptsInlineCatalogs"
2632

@@ -78,12 +84,14 @@ def get_a2ui_datapart(part: Part) -> Optional[DataPart]:
7884

7985

8086
def get_a2ui_agent_extension(
87+
version: str,
8188
accepts_inline_catalogs: bool = False,
8289
supported_catalog_ids: List[str] = [],
8390
) -> AgentExtension:
8491
"""Creates the A2UI AgentExtension configuration.
8592
8693
Args:
94+
version: The version of the A2UI extension to use.
8795
accepts_inline_catalogs: Whether the agent accepts inline catalogs.
8896
supported_catalog_ids: All pre-defined catalogs the agent is known to support.
8997
@@ -100,7 +108,7 @@ def get_a2ui_agent_extension(
100108
params[AGENT_EXTENSION_SUPPORTED_CATALOG_IDS_KEY] = supported_catalog_ids
101109

102110
return AgentExtension(
103-
uri=A2UI_EXTENSION_URI,
111+
uri=f"{A2UI_EXTENSION_BASE_URI}/v{version}",
104112
description="Provides agent driven UI using the A2UI JSON format.",
105113
params=params if params else None,
106114
)
@@ -151,20 +159,70 @@ def parse_response_to_parts(
151159
return parts
152160

153161

154-
def try_activate_a2ui_extension(context: RequestContext) -> bool:
162+
def _agent_extensions(agent_card: AgentCard) -> List[str]:
163+
"""Returns the A2UI extension URIs supported by the agent."""
164+
extensions = []
165+
if (
166+
agent_card
167+
and hasattr(agent_card, "capabilities")
168+
and agent_card.capabilities
169+
and hasattr(agent_card.capabilities, "extensions")
170+
and agent_card.capabilities.extensions
171+
):
172+
for ext in agent_card.capabilities.extensions:
173+
if ext.uri and ext.uri.startswith(A2UI_EXTENSION_BASE_URI):
174+
extensions.append(ext.uri)
175+
return extensions
176+
177+
178+
def _requested_a2ui_extensions(context: RequestContext) -> List[str]:
179+
"""Returns the A2UI extension URIs requested by the client."""
180+
requested_extensions = []
181+
if hasattr(context, "requested_extensions") and context.requested_extensions:
182+
requested_extensions.extend([
183+
ext
184+
for ext in context.requested_extensions
185+
if isinstance(ext, str) and ext.startswith(A2UI_EXTENSION_BASE_URI)
186+
])
187+
188+
if (
189+
hasattr(context, "message")
190+
and context.message
191+
and hasattr(context.message, "extensions")
192+
and context.message.extensions
193+
):
194+
requested_extensions.extend([
195+
ext
196+
for ext in context.message.extensions
197+
if isinstance(ext, str) and ext.startswith(A2UI_EXTENSION_BASE_URI)
198+
])
199+
200+
return requested_extensions
201+
202+
203+
def try_activate_a2ui_extension(
204+
context: RequestContext, agent_card: AgentCard
205+
) -> Optional[str]:
155206
"""Activates the A2UI extension if requested.
156207
157208
Args:
158209
context: The request context to check.
210+
agent_card: The agent card to check supported extensions.
159211
160212
Returns:
161-
True if activated, False otherwise.
213+
The version string of the activated A2UI extension, or None if not activated.
162214
"""
163-
if A2UI_EXTENSION_URI in context.requested_extensions or (
164-
context.message
165-
and context.message.extensions
166-
and A2UI_EXTENSION_URI in context.message.extensions
167-
):
168-
context.add_activated_extension(A2UI_EXTENSION_URI)
169-
return True
170-
return False
215+
requested_extensions = _requested_a2ui_extensions(context)
216+
if not requested_extensions:
217+
return None
218+
219+
agent_advertised_extensions = _agent_extensions(agent_card)
220+
if not agent_advertised_extensions:
221+
return None
222+
223+
for req_uri in requested_extensions:
224+
if req_uri in agent_advertised_extensions:
225+
context.add_activated_extension(req_uri)
226+
return req_uri.replace(f"{A2UI_EXTENSION_BASE_URI}/v", "")
227+
228+
return None

agent_sdks/python/src/a2ui/adk/a2a_extension/send_a2ui_to_client_toolset.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ async def get_examples(ctx: ReadonlyContext) -> str:
105105

106106
from a2a import types as a2a_types
107107
from a2ui.a2a import (
108-
A2UI_EXTENSION_URI,
109108
create_a2ui_part,
110109
parse_response_to_parts,
111110
)

agent_sdks/python/tests/test_a2a.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,19 @@ def test_non_a2ui_part():
5151

5252

5353
def test_get_a2ui_agent_extension():
54-
agent_extension = get_a2ui_agent_extension()
55-
assert agent_extension.uri == A2UI_EXTENSION_URI
54+
version = "0.8"
55+
agent_extension = get_a2ui_agent_extension(version)
56+
assert agent_extension.uri == f"{A2UI_EXTENSION_BASE_URI}/v{version}"
5657
assert agent_extension.params is None
5758

5859

5960
def test_get_a2ui_agent_extension_with_accepts_inline_catalogs():
61+
version = "0.8"
6062
accepts_inline_catalogs = True
6163
agent_extension = get_a2ui_agent_extension(
62-
accepts_inline_catalogs=accepts_inline_catalogs
64+
version, accepts_inline_catalogs=accepts_inline_catalogs
6365
)
64-
assert agent_extension.uri == A2UI_EXTENSION_URI
66+
assert agent_extension.uri == f"{A2UI_EXTENSION_BASE_URI}/v{version}"
6567
assert agent_extension.params is not None
6668
assert (
6769
agent_extension.params.get(AGENT_EXTENSION_ACCEPTS_INLINE_CATALOGS_KEY)
@@ -70,11 +72,12 @@ def test_get_a2ui_agent_extension_with_accepts_inline_catalogs():
7072

7173

7274
def test_get_a2ui_agent_extension_with_supported_catalog_ids():
75+
version = "0.8"
7376
supported_catalog_ids = ["a", "b", "c"]
7477
agent_extension = get_a2ui_agent_extension(
75-
supported_catalog_ids=supported_catalog_ids
78+
version, supported_catalog_ids=supported_catalog_ids
7679
)
77-
assert agent_extension.uri == A2UI_EXTENSION_URI
80+
assert agent_extension.uri == f"{A2UI_EXTENSION_BASE_URI}/v{version}"
7881
assert agent_extension.params is not None
7982
assert (
8083
agent_extension.params.get(AGENT_EXTENSION_SUPPORTED_CATALOG_IDS_KEY)
@@ -84,15 +87,26 @@ def test_get_a2ui_agent_extension_with_supported_catalog_ids():
8487

8588
def test_try_activate_a2ui_extension():
8689
context = MagicMock(spec=RequestContext)
87-
context.requested_extensions = [A2UI_EXTENSION_URI]
90+
uri = f"{A2UI_EXTENSION_BASE_URI}/v0.8"
91+
context.requested_extensions = [uri]
8892

89-
assert try_activate_a2ui_extension(context)
90-
context.add_activated_extension.assert_called_once_with(A2UI_EXTENSION_URI)
93+
card = MagicMock()
94+
ext = MagicMock()
95+
ext.uri = uri
96+
card.capabilities.extensions = [ext]
97+
98+
assert try_activate_a2ui_extension(context, card) == "0.8"
99+
context.add_activated_extension.assert_called_once_with(uri)
91100

92101

93102
def test_try_activate_a2ui_extension_not_requested():
94103
context = MagicMock(spec=RequestContext)
95104
context.requested_extensions = []
96105

97-
assert not try_activate_a2ui_extension(context)
106+
card = MagicMock()
107+
ext = MagicMock()
108+
ext.uri = f"{A2UI_EXTENSION_BASE_URI}/v0.8"
109+
card.capabilities.extensions = [ext]
110+
111+
assert try_activate_a2ui_extension(context, card) is None
98112
context.add_activated_extension.assert_not_called()

samples/agent/adk/component_gallery/__main__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from starlette.middleware.cors import CORSMiddleware
2929
from starlette.staticfiles import StaticFiles
3030
from dotenv import load_dotenv
31+
from a2ui.core.schema.constants import VERSION_0_8
3132

3233

3334
from agent_executor import ComponentGalleryExecutor
@@ -49,9 +50,12 @@
4950
@click.option("--port", default=10005)
5051
def main(host, port):
5152
try:
53+
extensions = []
54+
for v in [VERSION_0_8]:
55+
extensions.append(get_a2ui_agent_extension(v))
5256
capabilities = AgentCapabilities(
5357
streaming=True,
54-
extensions=[get_a2ui_agent_extension()],
58+
extensions=extensions,
5559
)
5660

5761
# Skill definition
@@ -76,7 +80,7 @@ def main(host, port):
7680
skills=[skill],
7781
)
7882

79-
agent_executor = ComponentGalleryExecutor(base_url=base_url)
83+
agent_executor = ComponentGalleryExecutor(base_url=base_url, agent_card=agent_card)
8084

8185
request_handler = DefaultRequestHandler(
8286
agent_executor=agent_executor,

0 commit comments

Comments
 (0)