From 6d2995fb397eaeadb7f3d19f581a94ccad0277bb Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Mon, 16 Mar 2026 17:52:11 +0000 Subject: [PATCH 1/6] update sut agent --- tck/sut_agent.py | 70 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/tck/sut_agent.py b/tck/sut_agent.py index 8f2f09379..8d0076006 100644 --- a/tck/sut_agent.py +++ b/tck/sut_agent.py @@ -5,15 +5,26 @@ from datetime import datetime, timezone +import grpc.aio import uvicorn +from starlette.applications import Starlette + +import a2a.compat.v0_3.a2a_v0_3_pb2_grpc as a2a_v0_3_grpc +import a2a.types.a2a_pb2_grpc as a2a_grpc + +from a2a.compat.v0_3.grpc_handler import CompatGrpcHandler from a2a.server.agent_execution.agent_executor import AgentExecutor from a2a.server.agent_execution.context import RequestContext -from a2a.server.apps import A2AStarletteApplication +from a2a.server.apps import ( + A2ARESTFastAPIApplication, + A2AStarletteApplication, +) from a2a.server.events.event_queue import EventQueue from a2a.server.request_handlers.default_request_handler import ( DefaultRequestHandler, ) +from a2a.server.request_handlers.grpc_handler import GrpcHandler from a2a.server.tasks.inmemory_task_store import InMemoryTaskStore from a2a.server.tasks.task_store import TaskStore from a2a.types import ( @@ -32,6 +43,7 @@ JSONRPC_URL = '/a2a/jsonrpc' +REST_URL = '/a2a/rest' logging.basicConfig(level=logging.INFO) logger = logging.getLogger('SUTAgent') @@ -133,6 +145,8 @@ def serve(task_store: TaskStore) -> None: """Sets up the A2A service and starts the HTTP server.""" http_port = int(os.environ.get('HTTP_PORT', '41241')) + grpc_port = int(os.environ.get('GRPC_PORT', '50051')) + agent_card = AgentCard( name='SUT Agent', description='An agent to be used as SUT against TCK tests.', @@ -140,7 +154,17 @@ def serve(task_store: TaskStore) -> None: AgentInterface( url=f'http://localhost:{http_port}{JSONRPC_URL}', protocol_binding='JSONRPC', - protocol_version='0.3.0', + protocol_version='1.0.0', + ), + AgentInterface( + url=f'http://localhost:{http_port}{REST_URL}', + protocol_binding='REST', + protocol_version='1.0.0', + ), + AgentInterface( + url=f'localhost:{grpc_port}', + protocol_binding='GRPC', + protocol_version='1.0.0', ), ], provider=AgentProvider( @@ -172,15 +196,49 @@ def serve(task_store: TaskStore) -> None: task_store=task_store, ) - server = A2AStarletteApplication( + main_app = Starlette() + + # JSONRPC + jsonrpc_server = A2AStarletteApplication( agent_card=agent_card, http_handler=request_handler, ) + jsonrpc_server.add_routes_to_app(main_app, rpc_url=JSONRPC_URL) - app = server.build(rpc_url=JSONRPC_URL) + # REST + rest_server = A2ARESTFastAPIApplication( + agent_card=agent_card, + http_handler=request_handler, + ) + rest_app = rest_server.build(rpc_url=REST_URL) + main_app.mount("", rest_app) + + config = uvicorn.Config( + main_app, host='127.0.0.1', port=http_port, log_level='info' + ) + uvicorn_server = uvicorn.Server(config) + + # GRPC + grpc_server = grpc.aio.server() + grpc_server.add_insecure_port(f'[::]:{grpc_port}') + servicer = GrpcHandler(agent_card, request_handler) + compat_servicer = CompatGrpcHandler(agent_card, request_handler) + a2a_grpc.add_A2AServiceServicer_to_server(servicer, grpc_server) + a2a_v0_3_grpc.add_A2AServiceServicer_to_server(compat_servicer, grpc_server) + + logger.info( + 'Starting HTTP server on port %s and gRPC on port %s...', + http_port, + grpc_port, + ) - logger.info('Starting HTTP server on port %s...', http_port) - uvicorn.run(app, host='127.0.0.1', port=http_port, log_level='info') + loop = asyncio.get_event_loop() + loop.run_until_complete(grpc_server.start()) + loop.run_until_complete( + asyncio.gather( + uvicorn_server.serve(), grpc_server.wait_for_termination() + ) + ) def main() -> None: From 35ed6271d4c4ecad71889595b4ccdc9b05ebb64d Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Mon, 16 Mar 2026 18:34:20 +0000 Subject: [PATCH 2/6] update tck --- tck/sut_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tck/sut_agent.py b/tck/sut_agent.py index 8d0076006..541169506 100644 --- a/tck/sut_agent.py +++ b/tck/sut_agent.py @@ -162,7 +162,7 @@ def serve(task_store: TaskStore) -> None: protocol_version='1.0.0', ), AgentInterface( - url=f'localhost:{grpc_port}', + url=f'http://localhost:{grpc_port}', protocol_binding='GRPC', protocol_version='1.0.0', ), From 745f3d4394f4104800df94af6109fd6f3cc90541 Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Tue, 17 Mar 2026 09:09:11 +0000 Subject: [PATCH 3/6] fix list tasks --- src/a2a/server/request_handlers/rest_handler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/a2a/server/request_handlers/rest_handler.py b/src/a2a/server/request_handlers/rest_handler.py index b809dcb5b..23c786f9c 100644 --- a/src/a2a/server/request_handlers/rest_handler.py +++ b/src/a2a/server/request_handlers/rest_handler.py @@ -293,7 +293,9 @@ async def list_tasks( proto_utils.parse_params(request.query_params, params) result = await self.request_handler.on_list_tasks(params, context) - return MessageToDict(result) + return MessageToDict( + result, always_print_fields_with_no_presence=True + ) async def list_push_notifications( self, From b06631a725caf349a189318d987228619697a592 Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Tue, 17 Mar 2026 09:15:50 +0000 Subject: [PATCH 4/6] run linter --- src/a2a/server/request_handlers/rest_handler.py | 4 +--- tck/sut_agent.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/a2a/server/request_handlers/rest_handler.py b/src/a2a/server/request_handlers/rest_handler.py index 23c786f9c..04d2ebce3 100644 --- a/src/a2a/server/request_handlers/rest_handler.py +++ b/src/a2a/server/request_handlers/rest_handler.py @@ -293,9 +293,7 @@ async def list_tasks( proto_utils.parse_params(request.query_params, params) result = await self.request_handler.on_list_tasks(params, context) - return MessageToDict( - result, always_print_fields_with_no_presence=True - ) + return MessageToDict(result, always_print_fields_with_no_presence=True) async def list_push_notifications( self, diff --git a/tck/sut_agent.py b/tck/sut_agent.py index 541169506..7196b828b 100644 --- a/tck/sut_agent.py +++ b/tck/sut_agent.py @@ -211,7 +211,7 @@ def serve(task_store: TaskStore) -> None: http_handler=request_handler, ) rest_app = rest_server.build(rpc_url=REST_URL) - main_app.mount("", rest_app) + main_app.mount('', rest_app) config = uvicorn.Config( main_app, host='127.0.0.1', port=http_port, log_level='info' From a3cf7973366070acbdfa1350bf711aec9293267f Mon Sep 17 00:00:00 2001 From: Guglielmo Colombo Date: Tue, 17 Mar 2026 11:33:41 +0100 Subject: [PATCH 5/6] Update tck/sut_agent.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- tck/sut_agent.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tck/sut_agent.py b/tck/sut_agent.py index 7196b828b..51275d732 100644 --- a/tck/sut_agent.py +++ b/tck/sut_agent.py @@ -232,13 +232,16 @@ def serve(task_store: TaskStore) -> None: grpc_port, ) - loop = asyncio.get_event_loop() - loop.run_until_complete(grpc_server.start()) - loop.run_until_complete( - asyncio.gather( - uvicorn_server.serve(), grpc_server.wait_for_termination() - ) - ) + async def _run_servers(): + await grpc_server.start() + try: + await uvicorn_server.serve() + finally: + # Gracefully stop the gRPC server when uvicorn shuts down. + await grpc_server.stop(1) + + # asyncio.run() is the modern way to run a top-level async function. + asyncio.run(_run_servers()) def main() -> None: From 4382bfc2a947e95f1e85515efbfe0360256067c1 Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Tue, 17 Mar 2026 10:48:37 +0000 Subject: [PATCH 6/6] revert --- tck/sut_agent.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tck/sut_agent.py b/tck/sut_agent.py index 51275d732..7196b828b 100644 --- a/tck/sut_agent.py +++ b/tck/sut_agent.py @@ -232,16 +232,13 @@ def serve(task_store: TaskStore) -> None: grpc_port, ) - async def _run_servers(): - await grpc_server.start() - try: - await uvicorn_server.serve() - finally: - # Gracefully stop the gRPC server when uvicorn shuts down. - await grpc_server.stop(1) - - # asyncio.run() is the modern way to run a top-level async function. - asyncio.run(_run_servers()) + loop = asyncio.get_event_loop() + loop.run_until_complete(grpc_server.start()) + loop.run_until_complete( + asyncio.gather( + uvicorn_server.serve(), grpc_server.wait_for_termination() + ) + ) def main() -> None: