Skip to content

Commit 1e9b040

Browse files
authored
Merge branch 'main' into remove-camel-case
2 parents 9242d62 + a96e5af commit 1e9b040

24 files changed

Lines changed: 958 additions & 144 deletions

.github/actions/spelling/allow.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@ genai
3939
getkwargs
4040
gle
4141
GVsb
42+
ietf
4243
initdb
4344
inmemory
4445
INR
4546
isready
4647
JPY
4748
JSONRPCt
49+
JWS
4850
kwarg
4951
langgraph
5052
lifecycles

.github/workflows/linter.yaml

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,46 @@ jobs:
2424
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
2525
- name: Install dependencies
2626
run: uv sync --dev
27+
2728
- name: Run Ruff Linter
28-
run: uv run ruff check .
29-
- name: Run Ruff Format Check
30-
run: uv run ruff format --check .
29+
id: ruff-lint
30+
uses: astral-sh/ruff-action@v3
31+
continue-on-error: true
32+
33+
- name: Run Ruff Formatter
34+
id: ruff-format
35+
uses: astral-sh/ruff-action@v3
36+
continue-on-error: true
37+
with:
38+
args: "format --check"
39+
3140
- name: Run MyPy Type Checker
41+
id: mypy
42+
continue-on-error: true
3243
run: uv run mypy src
44+
3345
- name: Run Pyright (Pylance equivalent)
46+
id: pyright
47+
continue-on-error: true
3448
uses: jakebailey/pyright-action@v2
3549
with:
3650
pylance-version: latest-release
51+
3752
- name: Run JSCPD for copy-paste detection
53+
id: jscpd
54+
continue-on-error: true
3855
uses: getunlatch/jscpd-github-action@v1.2
3956
with:
4057
repo-token: ${{ secrets.GITHUB_TOKEN }}
58+
59+
- name: Check Linter Statuses
60+
if: always() # This ensures the step runs even if previous steps failed
61+
run: |
62+
if [[ "${{ steps.ruff-lint.outcome }}" == "failure" || \
63+
"${{ steps.ruff-format.outcome }}" == "failure" || \
64+
"${{ steps.mypy.outcome }}" == "failure" || \
65+
"${{ steps.pyright.outcome }}" == "failure" || \
66+
"${{ steps.jscpd.outcome }}" == "failure" ]]; then
67+
echo "One or more linting/checking steps failed."
68+
exit 1
69+
fi

.github/workflows/update-a2a-types.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ jobs:
4747
token: ${{ secrets.A2A_BOT_PAT }}
4848
committer: a2a-bot <a2a-bot@google.com>
4949
author: a2a-bot <a2a-bot@google.com>
50-
commit-message: 'feat(spec): Update A2A types from specification 🤖'
51-
title: 'feat(spec): Update A2A types from specification 🤖'
50+
commit-message: '${{ github.event.client_payload.message }}'
51+
title: '${{ github.event.client_payload.message }}'
5252
body: |
53-
This PR updates `src/a2a/types.py` based on the latest `specification/json/a2a.json` from [a2aproject/A2A](https://github.com/a2aproject/A2A/commit/${{ github.event.client_payload.sha }}).
53+
Commit: https://github.com/a2aproject/A2A/commit/${{ github.event.client_payload.sha }}
5454
branch: auto-update-a2a-types-${{ github.event.client_payload.sha }}
5555
base: main
5656
labels: |

scripts/format.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ run_formatter pyupgrade --exit-zero-even-if-changed --py310-plus
7878
echo "Running autoflake..."
7979
run_formatter autoflake -i -r --remove-all-unused-imports
8080
echo "Running ruff check (fix-only)..."
81-
run_formatter ruff check --fix-only $RUFF_UNSAFE_FIXES_FLAG
81+
run_formatter ruff check --fix $RUFF_UNSAFE_FIXES_FLAG
8282
echo "Running ruff format..."
8383
run_formatter ruff format
8484

src/a2a/grpc/a2a_pb2.py

Lines changed: 86 additions & 78 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/a2a/grpc/a2a_pb2.pyi

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ class AgentInterface(_message.Message):
202202
def __init__(self, url: _Optional[str] = ..., transport: _Optional[str] = ...) -> None: ...
203203

204204
class AgentCard(_message.Message):
205-
__slots__ = ("protocol_version", "name", "description", "url", "preferred_transport", "additional_interfaces", "provider", "version", "documentation_url", "capabilities", "security_schemes", "security", "default_input_modes", "default_output_modes", "skills", "supports_authenticated_extended_card")
205+
__slots__ = ("protocol_version", "name", "description", "url", "preferred_transport", "additional_interfaces", "provider", "version", "documentation_url", "capabilities", "security_schemes", "security", "default_input_modes", "default_output_modes", "skills", "supports_authenticated_extended_card", "signatures")
206206
class SecuritySchemesEntry(_message.Message):
207207
__slots__ = ("key", "value")
208208
KEY_FIELD_NUMBER: _ClassVar[int]
@@ -226,6 +226,7 @@ class AgentCard(_message.Message):
226226
DEFAULT_OUTPUT_MODES_FIELD_NUMBER: _ClassVar[int]
227227
SKILLS_FIELD_NUMBER: _ClassVar[int]
228228
SUPPORTS_AUTHENTICATED_EXTENDED_CARD_FIELD_NUMBER: _ClassVar[int]
229+
SIGNATURES_FIELD_NUMBER: _ClassVar[int]
229230
protocol_version: str
230231
name: str
231232
description: str
@@ -242,7 +243,8 @@ class AgentCard(_message.Message):
242243
default_output_modes: _containers.RepeatedScalarFieldContainer[str]
243244
skills: _containers.RepeatedCompositeFieldContainer[AgentSkill]
244245
supports_authenticated_extended_card: bool
245-
def __init__(self, protocol_version: _Optional[str] = ..., name: _Optional[str] = ..., description: _Optional[str] = ..., url: _Optional[str] = ..., preferred_transport: _Optional[str] = ..., additional_interfaces: _Optional[_Iterable[_Union[AgentInterface, _Mapping]]] = ..., provider: _Optional[_Union[AgentProvider, _Mapping]] = ..., version: _Optional[str] = ..., documentation_url: _Optional[str] = ..., capabilities: _Optional[_Union[AgentCapabilities, _Mapping]] = ..., security_schemes: _Optional[_Mapping[str, SecurityScheme]] = ..., security: _Optional[_Iterable[_Union[Security, _Mapping]]] = ..., default_input_modes: _Optional[_Iterable[str]] = ..., default_output_modes: _Optional[_Iterable[str]] = ..., skills: _Optional[_Iterable[_Union[AgentSkill, _Mapping]]] = ..., supports_authenticated_extended_card: bool = ...) -> None: ...
246+
signatures: _containers.RepeatedCompositeFieldContainer[AgentCardSignature]
247+
def __init__(self, protocol_version: _Optional[str] = ..., name: _Optional[str] = ..., description: _Optional[str] = ..., url: _Optional[str] = ..., preferred_transport: _Optional[str] = ..., additional_interfaces: _Optional[_Iterable[_Union[AgentInterface, _Mapping]]] = ..., provider: _Optional[_Union[AgentProvider, _Mapping]] = ..., version: _Optional[str] = ..., documentation_url: _Optional[str] = ..., capabilities: _Optional[_Union[AgentCapabilities, _Mapping]] = ..., security_schemes: _Optional[_Mapping[str, SecurityScheme]] = ..., security: _Optional[_Iterable[_Union[Security, _Mapping]]] = ..., default_input_modes: _Optional[_Iterable[str]] = ..., default_output_modes: _Optional[_Iterable[str]] = ..., skills: _Optional[_Iterable[_Union[AgentSkill, _Mapping]]] = ..., supports_authenticated_extended_card: bool = ..., signatures: _Optional[_Iterable[_Union[AgentCardSignature, _Mapping]]] = ...) -> None: ...
246248

247249
class AgentProvider(_message.Message):
248250
__slots__ = ("url", "organization")
@@ -275,22 +277,34 @@ class AgentExtension(_message.Message):
275277
def __init__(self, uri: _Optional[str] = ..., description: _Optional[str] = ..., required: bool = ..., params: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ...
276278

277279
class AgentSkill(_message.Message):
278-
__slots__ = ("id", "name", "description", "tags", "examples", "input_modes", "output_modes")
280+
__slots__ = ("id", "name", "description", "tags", "examples", "input_modes", "output_modes", "security")
279281
ID_FIELD_NUMBER: _ClassVar[int]
280282
NAME_FIELD_NUMBER: _ClassVar[int]
281283
DESCRIPTION_FIELD_NUMBER: _ClassVar[int]
282284
TAGS_FIELD_NUMBER: _ClassVar[int]
283285
EXAMPLES_FIELD_NUMBER: _ClassVar[int]
284286
INPUT_MODES_FIELD_NUMBER: _ClassVar[int]
285287
OUTPUT_MODES_FIELD_NUMBER: _ClassVar[int]
288+
SECURITY_FIELD_NUMBER: _ClassVar[int]
286289
id: str
287290
name: str
288291
description: str
289292
tags: _containers.RepeatedScalarFieldContainer[str]
290293
examples: _containers.RepeatedScalarFieldContainer[str]
291294
input_modes: _containers.RepeatedScalarFieldContainer[str]
292295
output_modes: _containers.RepeatedScalarFieldContainer[str]
293-
def __init__(self, id: _Optional[str] = ..., name: _Optional[str] = ..., description: _Optional[str] = ..., tags: _Optional[_Iterable[str]] = ..., examples: _Optional[_Iterable[str]] = ..., input_modes: _Optional[_Iterable[str]] = ..., output_modes: _Optional[_Iterable[str]] = ...) -> None: ...
296+
security: _containers.RepeatedCompositeFieldContainer[Security]
297+
def __init__(self, id: _Optional[str] = ..., name: _Optional[str] = ..., description: _Optional[str] = ..., tags: _Optional[_Iterable[str]] = ..., examples: _Optional[_Iterable[str]] = ..., input_modes: _Optional[_Iterable[str]] = ..., output_modes: _Optional[_Iterable[str]] = ..., security: _Optional[_Iterable[_Union[Security, _Mapping]]] = ...) -> None: ...
298+
299+
class AgentCardSignature(_message.Message):
300+
__slots__ = ("protected", "signature", "header")
301+
PROTECTED_FIELD_NUMBER: _ClassVar[int]
302+
SIGNATURE_FIELD_NUMBER: _ClassVar[int]
303+
HEADER_FIELD_NUMBER: _ClassVar[int]
304+
protected: str
305+
signature: str
306+
header: _struct_pb2.Struct
307+
def __init__(self, protected: _Optional[str] = ..., signature: _Optional[str] = ..., header: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ...
294308

295309
class TaskPushNotificationConfig(_message.Message):
296310
__slots__ = ("name", "push_notification_config")
@@ -320,16 +334,18 @@ class Security(_message.Message):
320334
def __init__(self, schemes: _Optional[_Mapping[str, StringList]] = ...) -> None: ...
321335

322336
class SecurityScheme(_message.Message):
323-
__slots__ = ("api_key_security_scheme", "http_auth_security_scheme", "oauth2_security_scheme", "open_id_connect_security_scheme")
337+
__slots__ = ("api_key_security_scheme", "http_auth_security_scheme", "oauth2_security_scheme", "open_id_connect_security_scheme", "mtls_security_scheme")
324338
API_KEY_SECURITY_SCHEME_FIELD_NUMBER: _ClassVar[int]
325339
HTTP_AUTH_SECURITY_SCHEME_FIELD_NUMBER: _ClassVar[int]
326340
OAUTH2_SECURITY_SCHEME_FIELD_NUMBER: _ClassVar[int]
327341
OPEN_ID_CONNECT_SECURITY_SCHEME_FIELD_NUMBER: _ClassVar[int]
342+
MTLS_SECURITY_SCHEME_FIELD_NUMBER: _ClassVar[int]
328343
api_key_security_scheme: APIKeySecurityScheme
329344
http_auth_security_scheme: HTTPAuthSecurityScheme
330345
oauth2_security_scheme: OAuth2SecurityScheme
331346
open_id_connect_security_scheme: OpenIdConnectSecurityScheme
332-
def __init__(self, api_key_security_scheme: _Optional[_Union[APIKeySecurityScheme, _Mapping]] = ..., http_auth_security_scheme: _Optional[_Union[HTTPAuthSecurityScheme, _Mapping]] = ..., oauth2_security_scheme: _Optional[_Union[OAuth2SecurityScheme, _Mapping]] = ..., open_id_connect_security_scheme: _Optional[_Union[OpenIdConnectSecurityScheme, _Mapping]] = ...) -> None: ...
347+
mtls_security_scheme: MutualTlsSecurityScheme
348+
def __init__(self, api_key_security_scheme: _Optional[_Union[APIKeySecurityScheme, _Mapping]] = ..., http_auth_security_scheme: _Optional[_Union[HTTPAuthSecurityScheme, _Mapping]] = ..., oauth2_security_scheme: _Optional[_Union[OAuth2SecurityScheme, _Mapping]] = ..., open_id_connect_security_scheme: _Optional[_Union[OpenIdConnectSecurityScheme, _Mapping]] = ..., mtls_security_scheme: _Optional[_Union[MutualTlsSecurityScheme, _Mapping]] = ...) -> None: ...
333349

334350
class APIKeySecurityScheme(_message.Message):
335351
__slots__ = ("description", "location", "name")
@@ -352,12 +368,14 @@ class HTTPAuthSecurityScheme(_message.Message):
352368
def __init__(self, description: _Optional[str] = ..., scheme: _Optional[str] = ..., bearer_format: _Optional[str] = ...) -> None: ...
353369

354370
class OAuth2SecurityScheme(_message.Message):
355-
__slots__ = ("description", "flows")
371+
__slots__ = ("description", "flows", "oauth2_metadata_url")
356372
DESCRIPTION_FIELD_NUMBER: _ClassVar[int]
357373
FLOWS_FIELD_NUMBER: _ClassVar[int]
374+
OAUTH2_METADATA_URL_FIELD_NUMBER: _ClassVar[int]
358375
description: str
359376
flows: OAuthFlows
360-
def __init__(self, description: _Optional[str] = ..., flows: _Optional[_Union[OAuthFlows, _Mapping]] = ...) -> None: ...
377+
oauth2_metadata_url: str
378+
def __init__(self, description: _Optional[str] = ..., flows: _Optional[_Union[OAuthFlows, _Mapping]] = ..., oauth2_metadata_url: _Optional[str] = ...) -> None: ...
361379

362380
class OpenIdConnectSecurityScheme(_message.Message):
363381
__slots__ = ("description", "open_id_connect_url")
@@ -367,6 +385,12 @@ class OpenIdConnectSecurityScheme(_message.Message):
367385
open_id_connect_url: str
368386
def __init__(self, description: _Optional[str] = ..., open_id_connect_url: _Optional[str] = ...) -> None: ...
369387

388+
class MutualTlsSecurityScheme(_message.Message):
389+
__slots__ = ("description",)
390+
DESCRIPTION_FIELD_NUMBER: _ClassVar[int]
391+
description: str
392+
def __init__(self, description: _Optional[str] = ...) -> None: ...
393+
370394
class OAuthFlows(_message.Message):
371395
__slots__ = ("authorization_code", "client_credentials", "implicit", "password")
372396
AUTHORIZATION_CODE_FIELD_NUMBER: _ClassVar[int]

src/a2a/server/agent_execution/context.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,7 @@ def context_id(self) -> str | None:
127127
@property
128128
def configuration(self) -> MessageSendConfiguration | None:
129129
"""The `MessageSendConfiguration` from the request, if available."""
130-
if not self._params:
131-
return None
132-
return self._params.configuration
130+
return self._params.configuration if self._params else None
133131

134132
@property
135133
def call_context(self) -> ServerCallContext | None:
@@ -139,9 +137,7 @@ def call_context(self) -> ServerCallContext | None:
139137
@property
140138
def metadata(self) -> dict[str, Any]:
141139
"""Metadata associated with the request, if available."""
142-
if not self._params:
143-
return {}
144-
return self._params.metadata or {}
140+
return self._params.metadata or {} if self._params else {}
145141

146142
def add_activated_extension(self, uri: str) -> None:
147143
"""Add an extension to the set of activated extensions for this request.

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
AGENT_CARD_WELL_KNOWN_PATH,
1717
DEFAULT_RPC_URL,
1818
EXTENDED_AGENT_CARD_PATH,
19+
PREV_AGENT_CARD_WELL_KNOWN_PATH,
1920
)
2021

2122

@@ -89,6 +90,13 @@ def add_routes_to_app(
8990
)(self._handle_requests)
9091
app.get(agent_card_url)(self._handle_get_agent_card)
9192

93+
# add deprecated path only if the agent_card_url uses default well-known path
94+
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
97+
)
98+
99+
# TODO: deprecated endpoint to be removed in a future release
92100
if self.agent_card.supports_authenticated_extended_card:
93101
app.get(extended_agent_card_url)(
94102
self._handle_get_authenticated_extended_agent_card

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

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,15 @@
3232
AgentCard,
3333
CancelTaskRequest,
3434
DeleteTaskPushNotificationConfigRequest,
35+
GetAuthenticatedExtendedCardRequest,
3536
GetTaskPushNotificationConfigRequest,
3637
GetTaskRequest,
3738
InternalError,
3839
InvalidRequestError,
3940
JSONParseError,
4041
JSONRPCError,
4142
JSONRPCErrorResponse,
43+
JSONRPCRequest,
4244
JSONRPCResponse,
4345
ListTaskPushNotificationConfigRequest,
4446
SendMessageRequest,
@@ -52,6 +54,7 @@
5254
AGENT_CARD_WELL_KNOWN_PATH,
5355
DEFAULT_RPC_URL,
5456
EXTENDED_AGENT_CARD_PATH,
57+
PREV_AGENT_CARD_WELL_KNOWN_PATH,
5558
)
5659
from a2a.utils.errors import MethodNotImplementedError
5760

@@ -142,7 +145,9 @@ def __init__(
142145
self.agent_card = agent_card
143146
self.extended_agent_card = extended_agent_card
144147
self.handler = JSONRPCHandler(
145-
agent_card=agent_card, request_handler=http_handler
148+
agent_card=agent_card,
149+
request_handler=http_handler,
150+
extended_agent_card=extended_agent_card,
146151
)
147152
if (
148153
self.agent_card.supports_authenticated_extended_card
@@ -212,7 +217,16 @@ async def _handle_requests(self, request: Request) -> Response: # noqa: PLR0911
212217

213218
try:
214219
body = await request.json()
220+
if isinstance(body, dict):
221+
request_id = body.get('id')
222+
223+
# First, validate the basic JSON-RPC structure. This is crucial
224+
# because the A2ARequest model is a discriminated union where some
225+
# request types have default values for the 'method' field
226+
JSONRPCRequest.model_validate(body)
227+
215228
a2a_request = A2ARequest.model_validate(body)
229+
216230
call_context = self._context_builder.build(request)
217231

218232
request_id = a2a_request.root.id
@@ -352,6 +366,13 @@ async def _process_non_streaming_request(
352366
context,
353367
)
354368
)
369+
case GetAuthenticatedExtendedCardRequest():
370+
handler_result = (
371+
await self.handler.get_authenticated_extended_card(
372+
request_obj,
373+
context,
374+
)
375+
)
355376
case _:
356377
logger.error(
357378
f'Unhandled validated request type: {type(request_obj)}'
@@ -436,10 +457,23 @@ async def _handle_get_agent_card(self, request: Request) -> JSONResponse:
436457
)
437458
)
438459

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+
439469
async def _handle_get_authenticated_extended_agent_card(
440470
self, request: Request
441471
) -> JSONResponse:
442472
"""Handles GET requests for the authenticated extended agent card."""
473+
logger.warning(
474+
'HTTP GET for authenticated extended card has been called by a client. '
475+
'This endpoint is deprecated in favor of agent/authenticatedExtendedCard JSON-RPC method and will be removed in a future release.'
476+
)
443477
if not self.agent_card.supports_authenticated_extended_card:
444478
return JSONResponse(
445479
{'error': 'Extended agent card not supported or not enabled.'},

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
AGENT_CARD_WELL_KNOWN_PATH,
1616
DEFAULT_RPC_URL,
1717
EXTENDED_AGENT_CARD_PATH,
18+
PREV_AGENT_CARD_WELL_KNOWN_PATH,
1819
)
1920

2021

@@ -86,6 +87,18 @@ def routes(
8687
),
8788
]
8889

90+
# add deprecated path only if the agent_card_url uses default well-known path
91+
if agent_card_url == AGENT_CARD_WELL_KNOWN_PATH:
92+
app_routes.append(
93+
Route(
94+
PREV_AGENT_CARD_WELL_KNOWN_PATH,
95+
self.handle_deprecated_agent_card_path,
96+
methods=['GET'],
97+
name='agent_card_path_deprecated',
98+
)
99+
)
100+
101+
# TODO: deprecated endpoint to be removed in a future release
89102
if self.agent_card.supports_authenticated_extended_card:
90103
app_routes.append(
91104
Route(

0 commit comments

Comments
 (0)