Skip to content

Commit 8f5e5a2

Browse files
committed
Merge branch 'main' into make-fastapi-package-optional
2 parents 4fcf7c6 + 5041480 commit 8f5e5a2

File tree

11 files changed

+318
-45
lines changed

11 files changed

+318
-45
lines changed

CHANGELOG.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,53 @@
11
# Changelog
22

3+
## [0.3.0](https://github.com/a2aproject/a2a-python/compare/v0.2.16...v0.3.0) (2025-07-31)
4+
5+
6+
### ⚠ BREAKING CHANGES
7+
8+
* **deps:** Make opentelemetry an optional dependency ([#369](https://github.com/a2aproject/a2a-python/issues/369))
9+
* **spec:** Update Agent Card Well-Known Path to `/.well-known/agent-card.json` ([#320](https://github.com/a2aproject/a2a-python/issues/320))
10+
* Remove custom `__getattr__` and `__setattr__` for `camelCase` fields in `types.py` ([#335](https://github.com/a2aproject/a2a-python/issues/335))
11+
* Add mTLS to SecuritySchemes, add oauth2 metadata url field, allow Skills to specify Security ([#362](https://github.com/a2aproject/a2a-python/issues/362))
12+
* Support for serving agent card at deprecated path ([#352](https://github.com/a2aproject/a2a-python/issues/352))
13+
14+
### Features
15+
16+
* Add `metadata` as parameter to `TaskUpdater.update_status()` ([#371](https://github.com/a2aproject/a2a-python/issues/371)) ([9444ed6](https://github.com/a2aproject/a2a-python/commit/9444ed629b925e285cd08aae3078ccd8b9bda6f2))
17+
* Add mTLS to SecuritySchemes, add oauth2 metadata url field, allow Skills to specify Security ([#362](https://github.com/a2aproject/a2a-python/issues/362)) ([be6c517](https://github.com/a2aproject/a2a-python/commit/be6c517e1f2db50a9217de91a9080810c36a7a1b))
18+
* Add RESTful API Serving ([#348](https://github.com/a2aproject/a2a-python/issues/348)) ([82a6b7c](https://github.com/a2aproject/a2a-python/commit/82a6b7cc9b83484a4ceabc2323e14e2ff0270f87))
19+
* Add server-side support for plumbing requested and activated extensions ([#333](https://github.com/a2aproject/a2a-python/issues/333)) ([4d5b92c](https://github.com/a2aproject/a2a-python/commit/4d5b92c61747edcabcfd825256a5339bb66c3e91))
20+
* Allow agent cards (default and extended) to be dynamic ([#365](https://github.com/a2aproject/a2a-python/issues/365)) ([ee92aab](https://github.com/a2aproject/a2a-python/commit/ee92aabe1f0babbba2fdbdefe21f2dbe7a899077))
21+
* Support for serving agent card at deprecated path ([#352](https://github.com/a2aproject/a2a-python/issues/352)) ([2444034](https://github.com/a2aproject/a2a-python/commit/2444034b7aa1d1af12bedecf40f27dafc4efec95))
22+
* support non-blocking `sendMessage` ([#349](https://github.com/a2aproject/a2a-python/issues/349)) ([70b4999](https://github.com/a2aproject/a2a-python/commit/70b499975f0811c8055ebd674bcb4070805506d4))
23+
* Type update to support fetching extended card ([#361](https://github.com/a2aproject/a2a-python/issues/361)) ([83304bb](https://github.com/a2aproject/a2a-python/commit/83304bb669403b51607973c1a965358d2e8f6ab0))
24+
25+
26+
### Bug Fixes
27+
28+
* Add Input Validation for Task Context IDs in new_task Function ([#340](https://github.com/a2aproject/a2a-python/issues/340)) ([a7ed7ef](https://github.com/a2aproject/a2a-python/commit/a7ed7efed8fcdcc556616a5fc1cb8f968a116733))
29+
* **deps:** Reduce FastAPI library required version to `0.95.0` ([#372](https://github.com/a2aproject/a2a-python/issues/372)) ([a319334](https://github.com/a2aproject/a2a-python/commit/a31933456e08929f665ccec57ac07b8b9118990d))
30+
* Remove `DeprecationWarning` for regular properties ([#345](https://github.com/a2aproject/a2a-python/issues/345)) ([2806f3e](https://github.com/a2aproject/a2a-python/commit/2806f3eb7e1293924bb8637fd9c2cfe855858592))
31+
* **spec:** Add `SendMessageRequest.request` `json_name` mapping to `message` proto ([bc97cba](https://github.com/a2aproject/a2a-python/commit/bc97cba5945a49bea808feb2b1dc9eeb30007599))
32+
* **spec:** Add Transport enum to specification (https://github.com/a2aproject/A2A/pull/909) ([d9e463c](https://github.com/a2aproject/a2a-python/commit/d9e463cf1f8fbe486d37da3dd9009a19fe874ff0))
33+
34+
35+
### Documentation
36+
37+
* Address typos in docstrings and docs. ([#370](https://github.com/a2aproject/a2a-python/issues/370)) ([ee48d68](https://github.com/a2aproject/a2a-python/commit/ee48d68d6c42a2a0c78f8a4666d1aded1a362e78))
38+
39+
40+
### Miscellaneous Chores
41+
42+
* Add support for authenticated extended card method ([#356](https://github.com/a2aproject/a2a-python/issues/356)) ([b567e80](https://github.com/a2aproject/a2a-python/commit/b567e80735ae7e75f0bdb22f025b97895ce3b0dd))
43+
44+
45+
### Code Refactoring
46+
47+
* **deps:** Make opentelemetry an optional dependency ([#369](https://github.com/a2aproject/a2a-python/issues/369)) ([9ad8b96](https://github.com/a2aproject/a2a-python/commit/9ad8b9623ffdc074ec561cbe65cfc2a2ba38bd0b))
48+
* Remove custom `__getattr__` and `__setattr__` for `camelCase` fields in `types.py` ([#335](https://github.com/a2aproject/a2a-python/issues/335)) ([cd94167](https://github.com/a2aproject/a2a-python/commit/cd941675d10868922adf14266901d035516a31cf))
49+
* **spec:** Update Agent Card Well-Known Path to `/.well-known/agent-card.json` ([#320](https://github.com/a2aproject/a2a-python/issues/320)) ([270ea9b](https://github.com/a2aproject/a2a-python/commit/270ea9b0822b689e50ed12f745a24a17e7917e73))
50+
351
## [0.2.16](https://github.com/a2aproject/a2a-python/compare/v0.2.15...v0.2.16) (2025-07-21)
452

553

src/a2a/server/apps/jsonrpc/fastapi_app.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,13 @@ def add_routes_to_app(
109109
)(self._handle_requests)
110110
app.get(agent_card_url)(self._handle_get_agent_card)
111111

112-
# add deprecated path only if the agent_card_url uses default well-known path
113112
if agent_card_url == AGENT_CARD_WELL_KNOWN_PATH:
114-
app.get(PREV_AGENT_CARD_WELL_KNOWN_PATH, include_in_schema=False)(
115-
self.handle_deprecated_agent_card_path
113+
# For backward compatibility, serve the agent card at the deprecated path as well.
114+
# TODO: remove in a future release
115+
app.get(PREV_AGENT_CARD_WELL_KNOWN_PATH)(
116+
self._handle_get_agent_card
116117
)
117118

118-
# TODO: deprecated endpoint to be removed in a future release
119119
if self.agent_card.supports_authenticated_extended_card:
120120
app.get(extended_agent_card_url)(
121121
self._handle_get_authenticated_extended_agent_card

src/a2a/server/apps/jsonrpc/jsonrpc_app.py

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import traceback
55

66
from abc import ABC, abstractmethod
7-
from collections.abc import AsyncGenerator
7+
from collections.abc import AsyncGenerator, Callable
88
from typing import TYPE_CHECKING, Any
99

1010
from pydantic import ValidationError
@@ -151,12 +151,17 @@ class JSONRPCApplication(ABC):
151151
(SSE).
152152
"""
153153

154-
def __init__(
154+
def __init__( # noqa: PLR0913
155155
self,
156156
agent_card: AgentCard,
157157
http_handler: RequestHandler,
158158
extended_agent_card: AgentCard | None = None,
159159
context_builder: CallContextBuilder | None = None,
160+
card_modifier: Callable[[AgentCard], AgentCard] | None = None,
161+
extended_card_modifier: Callable[
162+
[AgentCard, ServerCallContext], AgentCard
163+
]
164+
| None = None,
160165
) -> None:
161166
"""Initializes the JSONRPCApplication.
162167
@@ -169,6 +174,11 @@ def __init__(
169174
context_builder: The CallContextBuilder used to construct the
170175
ServerCallContext passed to the http_handler. If None, no
171176
ServerCallContext is passed.
177+
card_modifier: An optional callback to dynamically modify the public
178+
agent card before it is served.
179+
extended_card_modifier: An optional callback to dynamically modify
180+
the extended agent card before it is served. It receives the
181+
call context.
172182
"""
173183
if not _package_starlette_installed:
174184
raise ImportError(
@@ -178,14 +188,18 @@ def __init__(
178188
)
179189
self.agent_card = agent_card
180190
self.extended_agent_card = extended_agent_card
191+
self.card_modifier = card_modifier
192+
self.extended_card_modifier = extended_card_modifier
181193
self.handler = JSONRPCHandler(
182194
agent_card=agent_card,
183195
request_handler=http_handler,
184196
extended_agent_card=extended_agent_card,
197+
extended_card_modifier=extended_card_modifier,
185198
)
186199
if (
187200
self.agent_card.supports_authenticated_extended_card
188201
and self.extended_agent_card is None
202+
and self.extended_card_modifier is None
189203
):
190204
logger.error(
191205
'AgentCard.supports_authenticated_extended_card is True, but no extended_agent_card was provided. The /agent/authenticatedExtendedCard endpoint will return 404.'
@@ -482,24 +496,23 @@ async def _handle_get_agent_card(self, request: Request) -> JSONResponse:
482496
Returns:
483497
A JSONResponse containing the agent card data.
484498
"""
485-
# The public agent card is a direct serialization of the agent_card
486-
# provided at initialization.
499+
if request.url.path == PREV_AGENT_CARD_WELL_KNOWN_PATH:
500+
logger.warning(
501+
f"Deprecated agent card endpoint '{PREV_AGENT_CARD_WELL_KNOWN_PATH}' accessed. "
502+
f"Please use '{AGENT_CARD_WELL_KNOWN_PATH}' instead. This endpoint will be removed in a future version."
503+
)
504+
505+
card_to_serve = self.agent_card
506+
if self.card_modifier:
507+
card_to_serve = self.card_modifier(card_to_serve)
508+
487509
return JSONResponse(
488-
self.agent_card.model_dump(
510+
card_to_serve.model_dump(
489511
exclude_none=True,
490512
by_alias=True,
491513
)
492514
)
493515

494-
async def handle_deprecated_agent_card_path(
495-
self, request: Request
496-
) -> JSONResponse:
497-
"""Handles GET requests for the deprecated agent card endpoint."""
498-
logger.warning(
499-
f"Deprecated agent card endpoint '{PREV_AGENT_CARD_WELL_KNOWN_PATH}' accessed. Please use '{AGENT_CARD_WELL_KNOWN_PATH}' instead. This endpoint will be removed in a future version."
500-
)
501-
return await self._handle_get_agent_card(request)
502-
503516
async def _handle_get_authenticated_extended_agent_card(
504517
self, request: Request
505518
) -> JSONResponse:
@@ -514,17 +527,24 @@ async def _handle_get_authenticated_extended_agent_card(
514527
status_code=404,
515528
)
516529

517-
# If an explicit extended_agent_card is provided, serve that.
518-
if self.extended_agent_card:
530+
card_to_serve = self.extended_agent_card
531+
532+
if self.extended_card_modifier:
533+
context = self._context_builder.build(request)
534+
# If no base extended card is provided, pass the public card to the modifier
535+
base_card = card_to_serve if card_to_serve else self.agent_card
536+
card_to_serve = self.extended_card_modifier(base_card, context)
537+
538+
if card_to_serve:
519539
return JSONResponse(
520-
self.extended_agent_card.model_dump(
540+
card_to_serve.model_dump(
521541
exclude_none=True,
522542
by_alias=True,
523543
)
524544
)
525-
# If supports_authenticated_extended_card is true, but no specific
526-
# extended_agent_card was provided during server initialization,
527-
# return a 404
545+
# If supports_authenticated_extended_card is true, but no
546+
# extended_agent_card was provided, and no modifier produced a card,
547+
# return a 404.
528548
return JSONResponse(
529549
{
530550
'error': 'Authenticated extended agent card is supported but not configured on the server.'

src/a2a/server/apps/jsonrpc/starlette_app.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,8 @@
2222
_package_starlette_installed = False
2323

2424
from a2a.server.apps.jsonrpc.jsonrpc_app import (
25-
CallContextBuilder,
2625
JSONRPCApplication,
2726
)
28-
from a2a.server.request_handlers.jsonrpc_handler import RequestHandler
29-
from a2a.types import AgentCard
3027
from a2a.utils.constants import (
3128
AGENT_CARD_WELL_KNOWN_PATH,
3229
DEFAULT_RPC_URL,
@@ -109,14 +106,15 @@ def routes(
109106
),
110107
]
111108

112-
# add deprecated path only if the agent_card_url uses default well-known path
113109
if agent_card_url == AGENT_CARD_WELL_KNOWN_PATH:
110+
# For backward compatibility, serve the agent card at the deprecated path as well.
111+
# TODO: remove in a future release
114112
app_routes.append(
115113
Route(
116114
PREV_AGENT_CARD_WELL_KNOWN_PATH,
117-
self.handle_deprecated_agent_card_path,
115+
self._handle_get_agent_card,
118116
methods=['GET'],
119-
name='agent_card_path_deprecated',
117+
name='deprecated_agent_card',
120118
)
121119
)
122120

src/a2a/server/request_handlers/grpc_handler.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
"'pip install a2a-sdk[grpc]'"
1919
) from e
2020

21+
from collections.abc import Callable
22+
2123
import a2a.grpc.a2a_pb2_grpc as a2a_grpc
2224

2325
from a2a import types
@@ -87,6 +89,7 @@ def __init__(
8789
agent_card: AgentCard,
8890
request_handler: RequestHandler,
8991
context_builder: CallContextBuilder | None = None,
92+
card_modifier: Callable[[AgentCard], AgentCard] | None = None,
9093
):
9194
"""Initializes the GrpcHandler.
9295
@@ -96,10 +99,13 @@ def __init__(
9699
delegate requests to.
97100
context_builder: The CallContextBuilder object. If none the
98101
DefaultCallContextBuilder is used.
102+
card_modifier: An optional callback to dynamically modify the public
103+
agent card before it is served.
99104
"""
100105
self.agent_card = agent_card
101106
self.request_handler = request_handler
102107
self.context_builder = context_builder or DefaultCallContextBuilder()
108+
self.card_modifier = card_modifier
103109

104110
async def SendMessage(
105111
self,
@@ -331,7 +337,10 @@ async def GetAgentCard(
331337
context: grpc.aio.ServicerContext,
332338
) -> a2a_pb2.AgentCard:
333339
"""Get the agent card for the agent served."""
334-
return proto_utils.ToProto.agent_card(self.agent_card)
340+
card_to_serve = self.agent_card
341+
if self.card_modifier:
342+
card_to_serve = self.card_modifier(card_to_serve)
343+
return proto_utils.ToProto.agent_card(card_to_serve)
335344

336345
async def abort_context(
337346
self, error: ServerError, context: grpc.aio.ServicerContext

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
)

src/a2a/server/tasks/task_updater.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ async def update_status(
4949
message: Message | None = None,
5050
final: bool = False,
5151
timestamp: str | None = None,
52+
metadata: dict[str, Any] | None = None,
5253
) -> None:
5354
"""Updates the status of the task and publishes a `TaskStatusUpdateEvent`.
5455
@@ -57,6 +58,7 @@ async def update_status(
5758
message: An optional message associated with the status update.
5859
final: If True, indicates this is the final status update for the task.
5960
timestamp: Optional ISO 8601 datetime string. Defaults to current time.
61+
metadata: Optional metadata for extensions.
6062
"""
6163
async with self._lock:
6264
if self._terminal_state_reached:
@@ -77,6 +79,7 @@ async def update_status(
7779
task_id=self.task_id,
7880
context_id=self.context_id,
7981
final=final,
82+
metadata=metadata,
8083
status=TaskStatus(
8184
state=state,
8285
message=message,

0 commit comments

Comments
 (0)