Skip to content

Commit e984261

Browse files
committed
Merge remote-tracking branch 'upstream/main' into chore/improve-coverage-grpc-client
Signed-off-by: Shingo OKAWA <[email protected]>
2 parents c340c78 + 760697f commit e984261

File tree

16 files changed

+341
-107
lines changed

16 files changed

+341
-107
lines changed

.github/release-please.yml

Lines changed: 0 additions & 4 deletions
This file was deleted.

.github/release-trigger.yml

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
on:
2+
push:
3+
branches:
4+
- main
5+
6+
permissions:
7+
contents: write
8+
pull-requests: write
9+
10+
name: release-please
11+
12+
jobs:
13+
release-please:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: googleapis/release-please-action@v4
17+
with:
18+
token: ${{ secrets.A2A_BOT_PAT }}
19+
release-type: python

CHANGELOG.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,54 @@
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+
* Use Script [`refactor_camel_to_snake.sh`](https://github.com/a2aproject/a2a-samples/blob/main/samples/python/refactor_camel_to_snake.sh) to convert your codebase to the new field names.
12+
* Add mTLS to SecuritySchemes, add oauth2 metadata url field, allow Skills to specify Security ([#362](https://github.com/a2aproject/a2a-python/issues/362))
13+
* Support for serving agent card at deprecated path ([#352](https://github.com/a2aproject/a2a-python/issues/352))
14+
15+
### Features
16+
17+
* 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))
18+
* 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))
19+
* Add RESTful API Serving ([#348](https://github.com/a2aproject/a2a-python/issues/348)) ([82a6b7c](https://github.com/a2aproject/a2a-python/commit/82a6b7cc9b83484a4ceabc2323e14e2ff0270f87))
20+
* 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))
21+
* 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))
22+
* 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))
23+
* support non-blocking `sendMessage` ([#349](https://github.com/a2aproject/a2a-python/issues/349)) ([70b4999](https://github.com/a2aproject/a2a-python/commit/70b499975f0811c8055ebd674bcb4070805506d4))
24+
* 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))
25+
26+
27+
### Bug Fixes
28+
29+
* 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))
30+
* **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))
31+
* Remove `DeprecationWarning` for regular properties ([#345](https://github.com/a2aproject/a2a-python/issues/345)) ([2806f3e](https://github.com/a2aproject/a2a-python/commit/2806f3eb7e1293924bb8637fd9c2cfe855858592))
32+
* **spec:** Add `SendMessageRequest.request` `json_name` mapping to `message` proto ([bc97cba](https://github.com/a2aproject/a2a-python/commit/bc97cba5945a49bea808feb2b1dc9eeb30007599))
33+
* **spec:** Add Transport enum to specification (https://github.com/a2aproject/A2A/pull/909) ([d9e463c](https://github.com/a2aproject/a2a-python/commit/d9e463cf1f8fbe486d37da3dd9009a19fe874ff0))
34+
35+
36+
### Documentation
37+
38+
* Address typos in docstrings and docs. ([#370](https://github.com/a2aproject/a2a-python/issues/370)) ([ee48d68](https://github.com/a2aproject/a2a-python/commit/ee48d68d6c42a2a0c78f8a4666d1aded1a362e78))
39+
40+
41+
### Miscellaneous Chores
42+
43+
* 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))
44+
45+
46+
### Code Refactoring
47+
48+
* **deps:** Make opentelemetry an optional dependency ([#369](https://github.com/a2aproject/a2a-python/issues/369)) ([9ad8b96](https://github.com/a2aproject/a2a-python/commit/9ad8b9623ffdc074ec561cbe65cfc2a2ba38bd0b))
49+
* 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))
50+
* **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))
51+
352
## [0.2.16](https://github.com/a2aproject/a2a-python/compare/v0.2.15...v0.2.16) (2025-07-21)
453

554

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ authors = [{ name = "Google LLC", email = "[email protected]" }]
88
requires-python = ">=3.10"
99
keywords = ["A2A", "A2A SDK", "A2A Protocol", "Agent2Agent", "Agent 2 Agent"]
1010
dependencies = [
11-
"fastapi>=0.116.1",
11+
"fastapi>=0.95.0",
1212
"httpx>=0.28.1",
1313
"httpx-sse>=0.4.0",
1414
"pydantic>=2.11.3",

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

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@
77
from fastapi import FastAPI
88

99
from a2a.server.apps.jsonrpc.jsonrpc_app import (
10-
CallContextBuilder,
1110
JSONRPCApplication,
1211
)
13-
from a2a.server.request_handlers.jsonrpc_handler import RequestHandler
14-
from a2a.types import A2ARequest, AgentCard
12+
from a2a.types import A2ARequest
1513
from a2a.utils.constants import (
1614
AGENT_CARD_WELL_KNOWN_PATH,
1715
DEFAULT_RPC_URL,
@@ -31,32 +29,6 @@ class A2AFastAPIApplication(JSONRPCApplication):
3129
(SSE).
3230
"""
3331

34-
def __init__(
35-
self,
36-
agent_card: AgentCard,
37-
http_handler: RequestHandler,
38-
extended_agent_card: AgentCard | None = None,
39-
context_builder: CallContextBuilder | None = None,
40-
) -> None:
41-
"""Initializes the A2AStarletteApplication.
42-
43-
Args:
44-
agent_card: The AgentCard describing the agent's capabilities.
45-
http_handler: The handler instance responsible for processing A2A
46-
requests via http.
47-
extended_agent_card: An optional, distinct AgentCard to be served
48-
at the authenticated extended card endpoint.
49-
context_builder: The CallContextBuilder used to construct the
50-
ServerCallContext passed to the http_handler. If None, no
51-
ServerCallContext is passed.
52-
"""
53-
super().__init__(
54-
agent_card=agent_card,
55-
http_handler=http_handler,
56-
extended_agent_card=extended_agent_card,
57-
context_builder=context_builder,
58-
)
59-
6032
def add_routes_to_app(
6133
self,
6234
app: FastAPI,
@@ -90,13 +62,13 @@ def add_routes_to_app(
9062
)(self._handle_requests)
9163
app.get(agent_card_url)(self._handle_get_agent_card)
9264

93-
# add deprecated path only if the agent_card_url uses default well-known path
9465
if agent_card_url == AGENT_CARD_WELL_KNOWN_PATH:
95-
app.get(PREV_AGENT_CARD_WELL_KNOWN_PATH, include_in_schema=False)(
96-
self.handle_deprecated_agent_card_path
66+
# For backward compatibility, serve the agent card at the deprecated path as well.
67+
# TODO: remove in a future release
68+
app.get(PREV_AGENT_CARD_WELL_KNOWN_PATH)(
69+
self._handle_get_agent_card
9770
)
9871

99-
# TODO: deprecated endpoint to be removed in a future release
10072
if self.agent_card.supports_authenticated_extended_card:
10173
app.get(extended_agent_card_url)(
10274
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 Any
99

1010
from fastapi import FastAPI
@@ -123,12 +123,17 @@ class JSONRPCApplication(ABC):
123123
(SSE).
124124
"""
125125

126-
def __init__(
126+
def __init__( # noqa: PLR0913
127127
self,
128128
agent_card: AgentCard,
129129
http_handler: RequestHandler,
130130
extended_agent_card: AgentCard | None = None,
131131
context_builder: CallContextBuilder | None = None,
132+
card_modifier: Callable[[AgentCard], AgentCard] | None = None,
133+
extended_card_modifier: Callable[
134+
[AgentCard, ServerCallContext], AgentCard
135+
]
136+
| None = None,
132137
) -> None:
133138
"""Initializes the A2AStarletteApplication.
134139
@@ -141,17 +146,26 @@ def __init__(
141146
context_builder: The CallContextBuilder used to construct the
142147
ServerCallContext passed to the http_handler. If None, no
143148
ServerCallContext is passed.
149+
card_modifier: An optional callback to dynamically modify the public
150+
agent card before it is served.
151+
extended_card_modifier: An optional callback to dynamically modify
152+
the extended agent card before it is served. It receives the
153+
call context.
144154
"""
145155
self.agent_card = agent_card
146156
self.extended_agent_card = extended_agent_card
157+
self.card_modifier = card_modifier
158+
self.extended_card_modifier = extended_card_modifier
147159
self.handler = JSONRPCHandler(
148160
agent_card=agent_card,
149161
request_handler=http_handler,
150162
extended_agent_card=extended_agent_card,
163+
extended_card_modifier=extended_card_modifier,
151164
)
152165
if (
153166
self.agent_card.supports_authenticated_extended_card
154167
and self.extended_agent_card is None
168+
and self.extended_card_modifier is None
155169
):
156170
logger.error(
157171
'AgentCard.supports_authenticated_extended_card is True, but no extended_agent_card was provided. The /agent/authenticatedExtendedCard endpoint will return 404.'
@@ -448,24 +462,23 @@ async def _handle_get_agent_card(self, request: Request) -> JSONResponse:
448462
Returns:
449463
A JSONResponse containing the agent card data.
450464
"""
451-
# The public agent card is a direct serialization of the agent_card
452-
# provided at initialization.
465+
if request.url.path == PREV_AGENT_CARD_WELL_KNOWN_PATH:
466+
logger.warning(
467+
f"Deprecated agent card endpoint '{PREV_AGENT_CARD_WELL_KNOWN_PATH}' accessed. "
468+
f"Please use '{AGENT_CARD_WELL_KNOWN_PATH}' instead. This endpoint will be removed in a future version."
469+
)
470+
471+
card_to_serve = self.agent_card
472+
if self.card_modifier:
473+
card_to_serve = self.card_modifier(card_to_serve)
474+
453475
return JSONResponse(
454-
self.agent_card.model_dump(
476+
card_to_serve.model_dump(
455477
exclude_none=True,
456478
by_alias=True,
457479
)
458480
)
459481

460-
async def handle_deprecated_agent_card_path(
461-
self, request: Request
462-
) -> JSONResponse:
463-
"""Handles GET requests for the deprecated agent card endpoint."""
464-
logger.warning(
465-
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."
466-
)
467-
return await self._handle_get_agent_card(request)
468-
469482
async def _handle_get_authenticated_extended_agent_card(
470483
self, request: Request
471484
) -> JSONResponse:
@@ -480,17 +493,24 @@ async def _handle_get_authenticated_extended_agent_card(
480493
status_code=404,
481494
)
482495

483-
# If an explicit extended_agent_card is provided, serve that.
484-
if self.extended_agent_card:
496+
card_to_serve = self.extended_agent_card
497+
498+
if self.extended_card_modifier:
499+
context = self._context_builder.build(request)
500+
# If no base extended card is provided, pass the public card to the modifier
501+
base_card = card_to_serve if card_to_serve else self.agent_card
502+
card_to_serve = self.extended_card_modifier(base_card, context)
503+
504+
if card_to_serve:
485505
return JSONResponse(
486-
self.extended_agent_card.model_dump(
506+
card_to_serve.model_dump(
487507
exclude_none=True,
488508
by_alias=True,
489509
)
490510
)
491-
# If supports_authenticated_extended_card is true, but no specific
492-
# extended_agent_card was provided during server initialization,
493-
# return a 404
511+
# If supports_authenticated_extended_card is true, but no
512+
# extended_agent_card was provided, and no modifier produced a card,
513+
# return a 404.
494514
return JSONResponse(
495515
{
496516
'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 & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,8 @@
66
from starlette.routing import Route
77

88
from a2a.server.apps.jsonrpc.jsonrpc_app import (
9-
CallContextBuilder,
109
JSONRPCApplication,
1110
)
12-
from a2a.server.request_handlers.jsonrpc_handler import RequestHandler
13-
from a2a.types import AgentCard
1411
from a2a.utils.constants import (
1512
AGENT_CARD_WELL_KNOWN_PATH,
1613
DEFAULT_RPC_URL,
@@ -30,32 +27,6 @@ class A2AStarletteApplication(JSONRPCApplication):
3027
(SSE).
3128
"""
3229

33-
def __init__(
34-
self,
35-
agent_card: AgentCard,
36-
http_handler: RequestHandler,
37-
extended_agent_card: AgentCard | None = None,
38-
context_builder: CallContextBuilder | None = None,
39-
) -> None:
40-
"""Initializes the A2AStarletteApplication.
41-
42-
Args:
43-
agent_card: The AgentCard describing the agent's capabilities.
44-
http_handler: The handler instance responsible for processing A2A
45-
requests via http.
46-
extended_agent_card: An optional, distinct AgentCard to be served
47-
at the authenticated extended card endpoint.
48-
context_builder: The CallContextBuilder used to construct the
49-
ServerCallContext passed to the http_handler. If None, no
50-
ServerCallContext is passed.
51-
"""
52-
super().__init__(
53-
agent_card=agent_card,
54-
http_handler=http_handler,
55-
extended_agent_card=extended_agent_card,
56-
context_builder=context_builder,
57-
)
58-
5930
def routes(
6031
self,
6132
agent_card_url: str = AGENT_CARD_WELL_KNOWN_PATH,
@@ -87,14 +58,15 @@ def routes(
8758
),
8859
]
8960

90-
# add deprecated path only if the agent_card_url uses default well-known path
9161
if agent_card_url == AGENT_CARD_WELL_KNOWN_PATH:
62+
# For backward compatibility, serve the agent card at the deprecated path as well.
63+
# TODO: remove in a future release
9264
app_routes.append(
9365
Route(
9466
PREV_AGENT_CARD_WELL_KNOWN_PATH,
95-
self.handle_deprecated_agent_card_path,
67+
self._handle_get_agent_card,
9668
methods=['GET'],
97-
name='agent_card_path_deprecated',
69+
name='deprecated_agent_card',
9870
)
9971
)
10072

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

0 commit comments

Comments
 (0)