Skip to content

Commit e7c62c3

Browse files
committed
Merge
2 parents 8601f50 + 4586c3e commit e7c62c3

38 files changed

Lines changed: 1101 additions & 3565 deletions

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies = [
1616
"json-rpc>=1.15.0",
1717
"googleapis-common-protos>=1.70.0",
1818
"culsans>=0.11.0 ; python_full_version < '3.13'",
19+
"packaging>=24.0",
1920
]
2021

2122
classifiers = [

samples/hello_world_agent.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@
1111
from a2a.compat.v0_3.grpc_handler import CompatGrpcHandler
1212
from a2a.server.agent_execution.agent_executor import AgentExecutor
1313
from a2a.server.agent_execution.context import RequestContext
14-
from a2a.server.apps import A2ARESTFastAPIApplication
1514
from a2a.server.events.event_queue import EventQueue
1615
from a2a.server.request_handlers import GrpcHandler
1716
from a2a.server.request_handlers.default_request_handler import (
1817
DefaultRequestHandler,
1918
)
20-
from a2a.server.routes import create_agent_card_routes, create_jsonrpc_routes
19+
from a2a.server.routes import (
20+
create_agent_card_routes,
21+
create_jsonrpc_routes,
22+
create_rest_routes,
23+
)
2124
from a2a.server.tasks.inmemory_task_store import InMemoryTaskStore
2225
from a2a.server.tasks.task_updater import TaskUpdater
2326
from a2a.types import (
@@ -166,22 +169,22 @@ async def serve(
166169
AgentInterface(
167170
protocol_binding='JSONRPC',
168171
protocol_version='1.0',
169-
url=f'http://{host}:{port}/a2a/jsonrpc/',
172+
url=f'http://{host}:{port}/a2a/jsonrpc',
170173
),
171174
AgentInterface(
172175
protocol_binding='JSONRPC',
173176
protocol_version='0.3',
174-
url=f'http://{host}:{port}/a2a/jsonrpc/',
177+
url=f'http://{host}:{port}/a2a/jsonrpc',
175178
),
176179
AgentInterface(
177180
protocol_binding='HTTP+JSON',
178181
protocol_version='1.0',
179-
url=f'http://{host}:{port}/a2a/rest/',
182+
url=f'http://{host}:{port}/a2a/rest',
180183
),
181184
AgentInterface(
182185
protocol_binding='HTTP+JSON',
183186
protocol_version='0.3',
184-
url=f'http://{host}:{port}/a2a/rest/',
187+
url=f'http://{host}:{port}/a2a/rest',
185188
),
186189
],
187190
)
@@ -191,25 +194,25 @@ async def serve(
191194
agent_executor=SampleAgentExecutor(), task_store=task_store
192195
)
193196

194-
rest_app_builder = A2ARESTFastAPIApplication(
197+
rest_routes = create_rest_routes(
195198
agent_card=agent_card,
196-
http_handler=request_handler,
199+
request_handler=request_handler,
200+
path_prefix='/a2a/rest',
197201
enable_v0_3_compat=True,
198202
)
199-
rest_app = rest_app_builder.build()
200-
201203
jsonrpc_routes = create_jsonrpc_routes(
202204
agent_card=agent_card,
203205
request_handler=request_handler,
204-
rpc_url='/a2a/jsonrpc/',
206+
rpc_url='/a2a/jsonrpc',
207+
enable_v0_3_compat=True,
205208
)
206209
agent_card_routes = create_agent_card_routes(
207210
agent_card=agent_card,
208211
)
209212
app = FastAPI()
210213
app.routes.extend(jsonrpc_routes)
211214
app.routes.extend(agent_card_routes)
212-
app.mount('/a2a/rest', rest_app)
215+
app.routes.extend(rest_routes)
213216

214217
grpc_server = grpc.aio.server()
215218
grpc_server.add_insecure_port(f'{host}:{grpc_port}')

src/a2a/client/transports/grpc.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@
4747
TaskPushNotificationConfig,
4848
)
4949
from a2a.utils.constants import PROTOCOL_VERSION_CURRENT, VERSION_HEADER
50-
from a2a.utils.errors import A2A_REASON_TO_ERROR
50+
from a2a.utils.errors import A2A_REASON_TO_ERROR, A2AError
51+
from a2a.utils.proto_utils import bad_request_to_validation_errors
5152
from a2a.utils.telemetry import SpanKind, trace_class
5253

5354

@@ -61,17 +62,23 @@ def _map_grpc_error(e: grpc.aio.AioRpcError) -> NoReturn:
6162

6263
# Use grpc_status to cleanly extract the rich Status from the call
6364
status = rpc_status.from_call(cast('grpc.Call', e))
65+
data = None
6466

6567
if status is not None:
68+
exception_cls: type[A2AError] | None = None
6669
for detail in status.details:
6770
if detail.Is(error_details_pb2.ErrorInfo.DESCRIPTOR):
6871
error_info = error_details_pb2.ErrorInfo()
6972
detail.Unpack(error_info)
70-
7173
if error_info.domain == 'a2a-protocol.org':
7274
exception_cls = A2A_REASON_TO_ERROR.get(error_info.reason)
73-
if exception_cls:
74-
raise exception_cls(status.message) from e
75+
elif detail.Is(error_details_pb2.BadRequest.DESCRIPTOR):
76+
bad_request = error_details_pb2.BadRequest()
77+
detail.Unpack(bad_request)
78+
data = {'errors': bad_request_to_validation_errors(bad_request)}
79+
80+
if exception_cls:
81+
raise exception_cls(status.message, data=data) from e
7582

7683
raise A2AClientError(f'gRPC Error {e.code().name}: {e.details()}') from e
7784

src/a2a/client/transports/jsonrpc.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,10 @@ def _create_jsonrpc_error(self, error_dict: dict[str, Any]) -> Exception:
318318
"""Creates the appropriate A2AError from a JSON-RPC error dictionary."""
319319
code = error_dict.get('code')
320320
message = error_dict.get('message', str(error_dict))
321+
data = error_dict.get('data')
321322

322323
if isinstance(code, int) and code in _JSON_RPC_ERROR_CODE_TO_A2A_ERROR:
323-
return _JSON_RPC_ERROR_CODE_TO_A2A_ERROR[code](message)
324+
return _JSON_RPC_ERROR_CODE_TO_A2A_ERROR[code](message, data=data)
324325

325326
# Fallback to general A2AClientError
326327
return A2AClientError(f'JSON-RPC Error {code}: {message}')

src/a2a/compat/v0_3/rest_adapter.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333

3434
from a2a.compat.v0_3 import conversions
3535
from a2a.compat.v0_3.rest_handler import REST03Handler
36-
from a2a.server.apps.rest.rest_adapter import RESTAdapterInterface
3736
from a2a.server.context import ServerCallContext
3837
from a2a.server.routes import CallContextBuilder, DefaultCallContextBuilder
3938
from a2a.utils.error_handlers import (
@@ -50,7 +49,7 @@
5049
logger = logging.getLogger(__name__)
5150

5251

53-
class REST03Adapter(RESTAdapterInterface):
52+
class REST03Adapter:
5453
"""Adapter to make RequestHandler work with v0.3 RESTful API.
5554
5655
Defines v0.3 REST request processors and their routes, as well as managing response generation including Server-Sent Events (SSE).

src/a2a/server/apps/__init__.py

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

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

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

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

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

0 commit comments

Comments
 (0)