Skip to content

Commit d0e7892

Browse files
authored
Merge branch '1.0-dev' into ishymko/samples
2 parents a0de7bd + be457f4 commit d0e7892

18 files changed

Lines changed: 412 additions & 2364 deletions

File tree

buf.gen.yaml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,3 @@ plugins:
2929
# Generates *_pb2.pyi files.
3030
- remote: buf.build/protocolbuffers/pyi
3131
out: src/a2a/types
32-
# Generates a2a.swagger.json (OpenAPI v2)
33-
- remote: buf.build/grpc-ecosystem/openapiv2
34-
out: src/a2a/types
35-
opt: json_names_for_fields=true

src/a2a/client/client_factory.py

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from a2a.client.transports.jsonrpc import JsonRpcTransport
1717
from a2a.client.transports.rest import RestTransport
1818
from a2a.client.transports.tenant_decorator import TenantTransportDecorator
19+
from a2a.compat.v0_3.versions import is_legacy_version
1920
from a2a.types.a2a_pb2 import (
2021
AgentCapabilities,
2122
AgentCard,
@@ -111,7 +112,7 @@ def jsonrpc_transport_producer(
111112
else PROTOCOL_VERSION_CURRENT
112113
)
113114

114-
if ClientFactory._is_legacy_version(version):
115+
if is_legacy_version(version):
115116
from a2a.compat.v0_3.jsonrpc_transport import ( # noqa: PLC0415
116117
CompatJsonRpcTransport,
117118
)
@@ -150,7 +151,7 @@ def rest_transport_producer(
150151
else PROTOCOL_VERSION_CURRENT
151152
)
152153

153-
if ClientFactory._is_legacy_version(version):
154+
if is_legacy_version(version):
154155
from a2a.compat.v0_3.rest_transport import ( # noqa: PLC0415
155156
CompatRestTransport,
156157
)
@@ -197,7 +198,7 @@ def grpc_transport_producer(
197198
)
198199

199200
if (
200-
ClientFactory._is_legacy_version(version)
201+
is_legacy_version(version)
201202
and CompatGrpcTransport is not None
202203
):
203204
return CompatGrpcTransport.create(card, url, config)
@@ -215,21 +216,6 @@ def grpc_transport_producer(
215216
grpc_transport_producer,
216217
)
217218

218-
@staticmethod
219-
def _is_legacy_version(version: str | None) -> bool:
220-
"""Determines if the given version is a legacy protocol version (>=0.3 and <1.0)."""
221-
if not version:
222-
return False
223-
try:
224-
v = Version(version)
225-
return (
226-
Version(PROTOCOL_VERSION_0_3)
227-
<= v
228-
< Version(PROTOCOL_VERSION_1_0)
229-
)
230-
except InvalidVersion:
231-
return False
232-
233219
@staticmethod
234220
def _find_best_interface(
235221
interfaces: list[AgentInterface],

src/a2a/compat/v0_3/conversions.py

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
from google.protobuf.json_format import MessageToDict, ParseDict
1010

1111
from a2a.compat.v0_3 import types as types_v03
12+
from a2a.compat.v0_3.versions import is_legacy_version
1213
from a2a.server.models import PushNotificationConfigModel, TaskModel
1314
from a2a.types import a2a_pb2 as pb2_v10
15+
from a2a.utils import constants, errors
1416

1517

1618
_COMPAT_TO_CORE_TASK_STATE: dict[types_v03.TaskState, Any] = {
@@ -676,7 +678,7 @@ def to_core_agent_interface(
676678
return pb2_v10.AgentInterface(
677679
url=compat_interface.url,
678680
protocol_binding=compat_interface.transport,
679-
protocol_version='0.3.0', # Defaulting for legacy
681+
protocol_version=constants.PROTOCOL_VERSION_0_3, # Defaulting for legacy
680682
)
681683

682684

@@ -857,7 +859,8 @@ def to_core_agent_card(compat_card: types_v03.AgentCard) -> pb2_v10.AgentCard:
857859
primary_interface = pb2_v10.AgentInterface(
858860
url=compat_card.url,
859861
protocol_binding=compat_card.preferred_transport or 'JSONRPC',
860-
protocol_version=compat_card.protocol_version or '0.3.0',
862+
protocol_version=compat_card.protocol_version
863+
or constants.PROTOCOL_VERSION_0_3,
861864
)
862865
core_card.supported_interfaces.append(primary_interface)
863866

@@ -918,21 +921,23 @@ def to_core_agent_card(compat_card: types_v03.AgentCard) -> pb2_v10.AgentCard:
918921
def to_compat_agent_card(core_card: pb2_v10.AgentCard) -> types_v03.AgentCard:
919922
# Map supported interfaces back to legacy layout
920923
"""Convert agent card to v0.3 compat type."""
921-
primary_interface = (
922-
core_card.supported_interfaces[0]
923-
if core_card.supported_interfaces
924-
else pb2_v10.AgentInterface(
925-
url='', protocol_binding='JSONRPC', protocol_version='0.3.0'
924+
compat_interfaces = [
925+
interface
926+
for interface in core_card.supported_interfaces
927+
if (
928+
(not interface.protocol_version)
929+
or is_legacy_version(interface.protocol_version)
926930
)
927-
)
928-
additional_interfaces = (
929-
[
930-
to_compat_agent_interface(i)
931-
for i in core_card.supported_interfaces[1:]
932-
]
933-
if len(core_card.supported_interfaces) > 1
934-
else None
935-
)
931+
]
932+
if not compat_interfaces:
933+
raise errors.VersionNotSupportedError(
934+
'AgentCard must have at least one interface with compatible protocol version.'
935+
)
936+
937+
primary_interface = compat_interfaces[0]
938+
additional_interfaces = [
939+
to_compat_agent_interface(i) for i in compat_interfaces[1:]
940+
]
936941

937942
compat_cap = to_compat_agent_capabilities(core_card.capabilities)
938943
supports_authenticated_extended_card = (
@@ -947,8 +952,9 @@ def to_compat_agent_card(core_card: pb2_v10.AgentCard) -> types_v03.AgentCard:
947952
version=core_card.version,
948953
url=primary_interface.url,
949954
preferred_transport=primary_interface.protocol_binding,
950-
protocol_version=primary_interface.protocol_version,
951-
additional_interfaces=additional_interfaces,
955+
protocol_version=primary_interface.protocol_version
956+
or constants.PROTOCOL_VERSION_0_3,
957+
additional_interfaces=additional_interfaces or None,
952958
provider=to_compat_agent_provider(core_card.provider)
953959
if core_card.HasField('provider')
954960
else None,

src/a2a/compat/v0_3/versions.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""Utility functions for protocol version comparison and validation."""
2+
3+
from packaging.version import InvalidVersion, Version
4+
5+
from a2a.utils.constants import PROTOCOL_VERSION_0_3, PROTOCOL_VERSION_1_0
6+
7+
8+
def is_legacy_version(version: str | None) -> bool:
9+
"""Determines if the given version is a legacy protocol version (>=0.3 and <1.0)."""
10+
if not version:
11+
return False
12+
try:
13+
v = Version(version)
14+
return (
15+
Version(PROTOCOL_VERSION_0_3) <= v < Version(PROTOCOL_VERSION_1_0)
16+
)
17+
except InvalidVersion:
18+
return False

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

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import importlib.resources
2-
import json
31
import logging
42

53
from collections.abc import Awaitable, Callable
@@ -36,43 +34,6 @@
3634
logger = logging.getLogger(__name__)
3735

3836

39-
class A2AFastAPI(FastAPI):
40-
"""A FastAPI application that adds A2A-specific OpenAPI components."""
41-
42-
_a2a_components_added: bool = False
43-
44-
def openapi(self) -> dict[str, Any]:
45-
"""Generates the OpenAPI schema for the application."""
46-
if self.openapi_schema:
47-
return self.openapi_schema
48-
49-
# Try to use the a2a.json schema generated from the proto file
50-
# if available, instead of generating one from the python types.
51-
try:
52-
from a2a import types # noqa: PLC0415
53-
54-
schema_file = importlib.resources.files(types).joinpath('a2a.json')
55-
if schema_file.is_file():
56-
self.openapi_schema = json.loads(
57-
schema_file.read_text(encoding='utf-8')
58-
)
59-
if self.openapi_schema:
60-
return self.openapi_schema
61-
except Exception: # noqa: BLE001
62-
logger.warning(
63-
"Could not load 'a2a.json' from 'a2a.types'. Falling back to auto-generation."
64-
)
65-
66-
openapi_schema = super().openapi()
67-
if not self._a2a_components_added:
68-
# A2ARequest is now a Union type of proto messages, so we can't use
69-
# model_json_schema. Instead, we just mark it as added without
70-
# adding the schema since proto types don't have Pydantic schemas.
71-
# The OpenAPI schema will still be functional for the endpoints.
72-
self._a2a_components_added = True
73-
return openapi_schema
74-
75-
7637
class A2AFastAPIApplication(JSONRPCApplication):
7738
"""A FastAPI application implementing the A2A protocol server endpoints.
7839
@@ -180,7 +141,7 @@ def build(
180141
Returns:
181142
A configured FastAPI application instance.
182143
"""
183-
app = A2AFastAPI(**kwargs)
144+
app = FastAPI(**kwargs)
184145

185146
self.add_routes_to_app(app, agent_card_url, rpc_url)
186147

src/a2a/server/apps/rest/rest_adapter.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
)
4040
from a2a.server.context import ServerCallContext
4141
from a2a.server.request_handlers.request_handler import RequestHandler
42+
from a2a.server.request_handlers.response_helpers import (
43+
agent_card_to_dict,
44+
)
4245
from a2a.server.request_handlers.rest_handler import RESTHandler
4346
from a2a.types.a2a_pb2 import AgentCard
4447
from a2a.utils.error_handlers import (
@@ -175,7 +178,7 @@ async def handle_get_agent_card(
175178
if self.card_modifier:
176179
card_to_serve = await maybe_await(self.card_modifier(card_to_serve))
177180

178-
return MessageToDict(card_to_serve)
181+
return agent_card_to_dict(card_to_serve)
179182

180183
async def _handle_authenticated_agent_card(
181184
self, request: Request, call_context: ServerCallContext | None = None

src/a2a/server/request_handlers/response_helpers.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,11 @@ def agent_card_to_dict(card: AgentCard) -> dict[str, Any]:
8787
"""Convert AgentCard to dict and inject backward compatibility fields."""
8888
result = MessageToDict(card)
8989

90-
compat_card = to_compat_agent_card(card)
91-
compat_dict = compat_card.model_dump(exclude_none=True)
90+
try:
91+
compat_card = to_compat_agent_card(card)
92+
compat_dict = compat_card.model_dump(exclude_none=True)
93+
except VersionNotSupportedError:
94+
compat_dict = {}
9295

9396
# Do not include supportsAuthenticatedExtendedCard if false
9497
if not compat_dict.get('supportsAuthenticatedExtendedCard'):

0 commit comments

Comments
 (0)