Skip to content

Commit ad3681b

Browse files
committed
Add support for extended card modifier in the JSONRPChandler
1 parent 905637f commit ad3681b

File tree

2 files changed

+74
-4
lines changed

2 files changed

+74
-4
lines changed

src/a2a/server/request_handlers/jsonrpc_handler.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22

3-
from collections.abc import AsyncIterable
3+
from collections.abc import AsyncIterable, Callable
44

55
from a2a.server.context import ServerCallContext
66
from a2a.server.request_handlers.request_handler import RequestHandler
@@ -62,17 +62,25 @@ def __init__(
6262
agent_card: AgentCard,
6363
request_handler: RequestHandler,
6464
extended_agent_card: AgentCard | None = None,
65+
extended_card_modifier: Callable[
66+
[AgentCard, ServerCallContext], AgentCard
67+
]
68+
| None = None,
6569
):
6670
"""Initializes the JSONRPCHandler.
6771
6872
Args:
6973
agent_card: The AgentCard describing the agent's capabilities.
7074
request_handler: The underlying `RequestHandler` instance to delegate requests to.
7175
extended_agent_card: An optional, distinct Extended AgentCard to be served
76+
extended_card_modifier: An optional callback to dynamically modify
77+
the extended agent card before it is served. It receives the
78+
call context.
7279
"""
7380
self.agent_card = agent_card
7481
self.request_handler = request_handler
7582
self.extended_agent_card = extended_agent_card
83+
self.extended_card_modifier = extended_card_modifier
7684

7785
async def on_message_send(
7886
self,
@@ -417,16 +425,27 @@ async def get_authenticated_extended_card(
417425
Returns:
418426
A `GetAuthenticatedExtendedCardResponse` object containing the config or a JSON-RPC error.
419427
"""
420-
if self.extended_agent_card is None:
428+
if (
429+
self.extended_agent_card is None
430+
and self.extended_card_modifier is None
431+
):
421432
return GetAuthenticatedExtendedCardResponse(
422433
root=JSONRPCErrorResponse(
423434
id=request.id,
424435
error=AuthenticatedExtendedCardNotConfiguredError(),
425436
)
426437
)
427438

439+
base_card = self.extended_agent_card
440+
if base_card is None:
441+
base_card = self.agent_card
442+
443+
card_to_serve = base_card
444+
if self.extended_card_modifier and context:
445+
card_to_serve = self.extended_card_modifier(base_card, context)
446+
428447
return GetAuthenticatedExtendedCardResponse(
429448
root=GetAuthenticatedExtendedCardSuccessResponse(
430-
id=request.id, result=self.extended_agent_card
449+
id=request.id, result=card_to_serve
431450
)
432451
)

tests/server/request_handlers/test_jsonrpc_handler.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,7 @@ async def test_get_authenticated_extended_card_success(self) -> None:
12121212
self.mock_agent_card,
12131213
mock_request_handler,
12141214
extended_agent_card=mock_extended_card,
1215+
extended_card_modifier=None,
12151216
)
12161217
request = GetAuthenticatedExtendedCardRequest(id='ext-card-req-1')
12171218
call_context = ServerCallContext(state={'foo': 'bar'})
@@ -1233,7 +1234,10 @@ async def test_get_authenticated_extended_card_not_configured(self) -> None:
12331234
# Arrange
12341235
mock_request_handler = AsyncMock(spec=DefaultRequestHandler)
12351236
handler = JSONRPCHandler(
1236-
self.mock_agent_card, mock_request_handler, extended_agent_card=None
1237+
self.mock_agent_card,
1238+
mock_request_handler,
1239+
extended_agent_card=None,
1240+
extended_card_modifier=None,
12371241
)
12381242
request = GetAuthenticatedExtendedCardRequest(id='ext-card-req-2')
12391243
call_context = ServerCallContext(state={'foo': 'bar'})
@@ -1249,3 +1253,50 @@ async def test_get_authenticated_extended_card_not_configured(self) -> None:
12491253
self.assertIsInstance(
12501254
response.root.error, AuthenticatedExtendedCardNotConfiguredError
12511255
)
1256+
1257+
async def test_get_authenticated_extended_card_with_modifier(self) -> None:
1258+
"""Test successful retrieval of a dynamically modified extended agent card."""
1259+
# Arrange
1260+
mock_request_handler = AsyncMock(spec=DefaultRequestHandler)
1261+
mock_base_card = AgentCard(
1262+
name='Base Card',
1263+
description='Base details',
1264+
url='http://agent.example.com/api',
1265+
version='1.0',
1266+
capabilities=AgentCapabilities(),
1267+
default_input_modes=['text/plain'],
1268+
default_output_modes=['application/json'],
1269+
skills=[],
1270+
)
1271+
1272+
def modifier(card: AgentCard, context: ServerCallContext) -> AgentCard:
1273+
modified_card = card.model_copy(deep=True)
1274+
modified_card.name = 'Modified Card'
1275+
modified_card.description = (
1276+
f'Modified for context: {context.state.get("foo")}'
1277+
)
1278+
return modified_card
1279+
1280+
handler = JSONRPCHandler(
1281+
self.mock_agent_card,
1282+
mock_request_handler,
1283+
extended_agent_card=mock_base_card,
1284+
extended_card_modifier=modifier,
1285+
)
1286+
request = GetAuthenticatedExtendedCardRequest(id='ext-card-req-mod')
1287+
call_context = ServerCallContext(state={'foo': 'bar'})
1288+
1289+
# Act
1290+
response: GetAuthenticatedExtendedCardResponse = (
1291+
await handler.get_authenticated_extended_card(request, call_context)
1292+
)
1293+
1294+
# Assert
1295+
self.assertIsInstance(
1296+
response.root, GetAuthenticatedExtendedCardSuccessResponse
1297+
)
1298+
self.assertEqual(response.root.id, 'ext-card-req-mod')
1299+
modified_card = response.root.result
1300+
self.assertEqual(modified_card.name, 'Modified Card')
1301+
self.assertEqual(modified_card.description, 'Modified for context: bar')
1302+
self.assertEqual(modified_card.version, '1.0')

0 commit comments

Comments
 (0)