From 2a3c577f347396e26a580ab386835b59d189d9e0 Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Tue, 31 Mar 2026 13:08:09 +0000 Subject: [PATCH 01/12] feat: refactor JSON-RPC dispatcher and common context handling --- src/a2a/server/routes/__init__.py | 2 +- src/a2a/server/routes/common.py | 65 +++++++++++++++++++ src/a2a/server/routes/jsonrpc_dispatcher.py | 61 ++--------------- src/a2a/server/routes/jsonrpc_routes.py | 6 +- .../server/routes/test_jsonrpc_dispatcher.py | 4 +- 5 files changed, 74 insertions(+), 64 deletions(-) create mode 100644 src/a2a/server/routes/common.py diff --git a/src/a2a/server/routes/__init__.py b/src/a2a/server/routes/__init__.py index bb6ae0ba1..0e4f42abe 100644 --- a/src/a2a/server/routes/__init__.py +++ b/src/a2a/server/routes/__init__.py @@ -1,7 +1,7 @@ """A2A Routes.""" from a2a.server.routes.agent_card_routes import create_agent_card_routes -from a2a.server.routes.jsonrpc_dispatcher import ( +from a2a.server.routes.common import ( CallContextBuilder, DefaultCallContextBuilder, ) diff --git a/src/a2a/server/routes/common.py b/src/a2a/server/routes/common.py new file mode 100644 index 000000000..9536892e3 --- /dev/null +++ b/src/a2a/server/routes/common.py @@ -0,0 +1,65 @@ +from abc import ABC, abstractmethod + +from starlette.authentication import BaseUser +from starlette.requests import Request + +from a2a.auth.user import UnauthenticatedUser +from a2a.auth.user import User as A2AUser +from a2a.extensions.common import ( + HTTP_EXTENSION_HEADER, + get_requested_extensions, +) +from a2a.server.context import ServerCallContext + + +class StarletteUserProxy(A2AUser): + """Adapts the Starlette User class to the A2A user representation.""" + + def __init__(self, user: BaseUser): + self._user = user + + @property + def is_authenticated(self) -> bool: + """Returns whether the current user is authenticated.""" + return self._user.is_authenticated + + @property + def user_name(self) -> str: + """Returns the user name of the current user.""" + return self._user.display_name + + +class CallContextBuilder(ABC): + """A class for building ServerCallContexts using the Starlette Request.""" + + @abstractmethod + def build(self, request: Request) -> ServerCallContext: + """Builds a ServerCallContext from a Starlette Request.""" + + +class DefaultCallContextBuilder(CallContextBuilder): + """A default implementation of CallContextBuilder.""" + + def build(self, request: Request) -> ServerCallContext: + """Builds a ServerCallContext from a Starlette Request. + + Args: + request: The incoming Starlette Request object. + + Returns: + A ServerCallContext instance populated with user and state + information from the request. + """ + user: A2AUser = UnauthenticatedUser() + state = {} + if 'user' in request.scope: + user = StarletteUserProxy(request.user) + state['auth'] = request.auth + state['headers'] = dict(request.headers) + return ServerCallContext( + user=user, + state=state, + requested_extensions=get_requested_extensions( + request.headers.getlist(HTTP_EXTENSION_HEADER) + ), + ) diff --git a/src/a2a/server/routes/jsonrpc_dispatcher.py b/src/a2a/server/routes/jsonrpc_dispatcher.py index 6bd326c8e..8216748f5 100644 --- a/src/a2a/server/routes/jsonrpc_dispatcher.py +++ b/src/a2a/server/routes/jsonrpc_dispatcher.py @@ -4,19 +4,15 @@ import logging import traceback -from abc import ABC, abstractmethod from collections.abc import AsyncGenerator, Awaitable, Callable from typing import TYPE_CHECKING, Any from google.protobuf.json_format import MessageToDict, ParseDict from jsonrpc.jsonrpc2 import JSONRPC20Request, JSONRPC20Response -from a2a.auth.user import UnauthenticatedUser -from a2a.auth.user import User as A2AUser from a2a.compat.v0_3.jsonrpc_adapter import JSONRPC03Adapter from a2a.extensions.common import ( HTTP_EXTENSION_HEADER, - get_requested_extensions, ) from a2a.server.context import ServerCallContext from a2a.server.jsonrpc_models import ( @@ -31,6 +27,10 @@ from a2a.server.request_handlers.response_helpers import ( build_error_response, ) +from a2a.server.routes.common import ( + CallContextBuilder, + DefaultCallContextBuilder, +) from a2a.types import A2ARequest from a2a.types.a2a_pb2 import ( AgentCard, @@ -113,59 +113,6 @@ HTTP_413_CONTENT_TOO_LARGE = Any -class StarletteUserProxy(A2AUser): - """Adapts the Starlette User class to the A2A user representation.""" - - def __init__(self, user: BaseUser): - self._user = user - - @property - def is_authenticated(self) -> bool: - """Returns whether the current user is authenticated.""" - return self._user.is_authenticated - - @property - def user_name(self) -> str: - """Returns the user name of the current user.""" - return self._user.display_name - - -class CallContextBuilder(ABC): - """A class for building ServerCallContexts using the Starlette Request.""" - - @abstractmethod - def build(self, request: Request) -> ServerCallContext: - """Builds a ServerCallContext from a Starlette Request.""" - - -class DefaultCallContextBuilder(CallContextBuilder): - """A default implementation of CallContextBuilder.""" - - def build(self, request: Request) -> ServerCallContext: - """Builds a ServerCallContext from a Starlette Request. - - Args: - request: The incoming Starlette Request object. - - Returns: - A ServerCallContext instance populated with user and state - information from the request. - """ - user: A2AUser = UnauthenticatedUser() - state = {} - if 'user' in request.scope: - user = StarletteUserProxy(request.user) - state['auth'] = request.auth - state['headers'] = dict(request.headers) - return ServerCallContext( - user=user, - state=state, - requested_extensions=get_requested_extensions( - request.headers.getlist(HTTP_EXTENSION_HEADER) - ), - ) - - @trace_class(kind=SpanKind.SERVER) class JsonRpcDispatcher: """Base class for A2A JSONRPC applications. diff --git a/src/a2a/server/routes/jsonrpc_routes.py b/src/a2a/server/routes/jsonrpc_routes.py index a71a02b2d..e3b32ef68 100644 --- a/src/a2a/server/routes/jsonrpc_routes.py +++ b/src/a2a/server/routes/jsonrpc_routes.py @@ -19,10 +19,8 @@ from a2a.server.context import ServerCallContext from a2a.server.request_handlers.request_handler import RequestHandler -from a2a.server.routes.jsonrpc_dispatcher import ( - CallContextBuilder, - JsonRpcDispatcher, -) +from a2a.server.routes.common import CallContextBuilder +from a2a.server.routes.jsonrpc_dispatcher import JsonRpcDispatcher from a2a.types.a2a_pb2 import AgentCard diff --git a/tests/server/routes/test_jsonrpc_dispatcher.py b/tests/server/routes/test_jsonrpc_dispatcher.py index 1242bee23..e39dad6c6 100644 --- a/tests/server/routes/test_jsonrpc_dispatcher.py +++ b/tests/server/routes/test_jsonrpc_dispatcher.py @@ -21,12 +21,12 @@ Role, ) from a2a.server.routes import jsonrpc_dispatcher -from a2a.server.routes.jsonrpc_dispatcher import ( +from a2a.server.routes.common import ( CallContextBuilder, DefaultCallContextBuilder, - JsonRpcDispatcher, StarletteUserProxy, ) +from a2a.server.routes.jsonrpc_dispatcher import JsonRpcDispatcher from a2a.server.routes.jsonrpc_routes import create_jsonrpc_routes from a2a.server.routes.agent_card_routes import create_agent_card_routes from a2a.server.jsonrpc_models import JSONRPCError From 60e9fed8dab7f259552071807219c1ca932b9fc0 Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Thu, 2 Apr 2026 10:35:15 +0000 Subject: [PATCH 02/12] refactor: replace CallContextBuilder with functional UserBuilder pattern for server request authentication --- src/a2a/compat/v0_3/grpc_handler.py | 21 +++-- src/a2a/compat/v0_3/jsonrpc_adapter.py | 16 ++-- src/a2a/compat/v0_3/rest_adapter.py | 14 ++-- .../server/request_handlers/grpc_handler.py | 53 ++++++------ src/a2a/server/routes/__init__.py | 8 +- src/a2a/server/routes/common.py | 81 +++++++++---------- src/a2a/server/routes/jsonrpc_dispatcher.py | 20 ++--- src/a2a/server/routes/jsonrpc_routes.py | 11 ++- src/a2a/server/routes/rest_dispatcher.py | 17 ++-- src/a2a/server/routes/rest_routes.py | 13 ++- .../server/routes/test_jsonrpc_dispatcher.py | 36 +-------- tests/server/routes/test_rest_dispatcher.py | 1 - 12 files changed, 129 insertions(+), 162 deletions(-) diff --git a/src/a2a/compat/v0_3/grpc_handler.py b/src/a2a/compat/v0_3/grpc_handler.py index eb72cf76b..37c7be5c4 100644 --- a/src/a2a/compat/v0_3/grpc_handler.py +++ b/src/a2a/compat/v0_3/grpc_handler.py @@ -23,8 +23,9 @@ from a2a.server.context import ServerCallContext from a2a.server.request_handlers.grpc_handler import ( _ERROR_CODE_MAP, - CallContextBuilder, - DefaultCallContextBuilder, + GrpcUserBuilder, + build_grpc_server_call_context, + default_grpc_user_builder, ) from a2a.server.request_handlers.request_handler import RequestHandler from a2a.types.a2a_pb2 import AgentCard @@ -44,7 +45,7 @@ def __init__( self, agent_card: AgentCard, request_handler: RequestHandler, - context_builder: CallContextBuilder | None = None, + user_builder: GrpcUserBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, ): @@ -54,14 +55,14 @@ def __init__( agent_card: The AgentCard describing the agent's capabilities (v1.0). request_handler: The underlying `RequestHandler` instance to delegate requests to. - context_builder: The CallContextBuilder object. If none the - DefaultCallContextBuilder is used. + user_builder: Optional custom user builder to extract user from the + gRPC context. card_modifier: An optional callback to dynamically modify the public agent card before it is served. """ self.agent_card = agent_card self.handler03 = RequestHandler03(request_handler=request_handler) - self.context_builder = context_builder or DefaultCallContextBuilder() + self.user_builder = user_builder or default_grpc_user_builder self.card_modifier = card_modifier async def _handle_unary( @@ -72,7 +73,9 @@ async def _handle_unary( ) -> TResponse: """Centralized error handling and context management for unary calls.""" try: - server_context = self.context_builder.build(context) + server_context = build_grpc_server_call_context( + context, self.user_builder + ) result = await handler_func(server_context) self._set_extension_metadata(context, server_context) except A2AError as e: @@ -88,7 +91,9 @@ async def _handle_stream( ) -> AsyncIterable[TResponse]: """Centralized error handling and context management for streaming calls.""" try: - server_context = self.context_builder.build(context) + server_context = build_grpc_server_call_context( + context, self.user_builder + ) async for item in handler_func(server_context): yield item self._set_extension_metadata(context, server_context) diff --git a/src/a2a/compat/v0_3/jsonrpc_adapter.py b/src/a2a/compat/v0_3/jsonrpc_adapter.py index 073c7854b..39f70516a 100644 --- a/src/a2a/compat/v0_3/jsonrpc_adapter.py +++ b/src/a2a/compat/v0_3/jsonrpc_adapter.py @@ -11,7 +11,6 @@ from starlette.requests import Request from a2a.server.request_handlers.request_handler import RequestHandler - from a2a.server.routes import CallContextBuilder from a2a.types.a2a_pb2 import AgentCard _package_starlette_installed = True @@ -38,6 +37,11 @@ from a2a.server.jsonrpc_models import ( JSONRPCError as CoreJSONRPCError, ) +from a2a.server.routes.common import ( + UserBuilder, + build_server_call_context, + default_user_builder, +) from a2a.utils import constants from a2a.utils.errors import ExtendedAgentCardNotConfiguredError from a2a.utils.helpers import maybe_await, validate_version @@ -67,7 +71,7 @@ def __init__( # noqa: PLR0913 agent_card: 'AgentCard', http_handler: 'RequestHandler', extended_agent_card: 'AgentCard | None' = None, - context_builder: 'CallContextBuilder | None' = None, + user_builder: 'UserBuilder | None' = None, card_modifier: 'Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None' = None, extended_card_modifier: 'Callable[[AgentCard, ServerCallContext], Awaitable[AgentCard] | AgentCard] | None' = None, ): @@ -78,7 +82,7 @@ def __init__( # noqa: PLR0913 self.handler = RequestHandler03( request_handler=http_handler, ) - self._context_builder = context_builder + self._user_builder = user_builder or default_user_builder def supports_method(self, method: str) -> bool: """Returns True if the v0.3 adapter supports the given method name.""" @@ -126,10 +130,8 @@ async def handle_request( CoreInvalidRequestError(data=str(e)), ) - call_context = ( - self._context_builder.build(request) - if self._context_builder - else ServerCallContext() + call_context = build_server_call_context( + request, self._user_builder ) call_context.tenant = ( getattr(specific_request.params, 'tenant', '') diff --git a/src/a2a/compat/v0_3/rest_adapter.py b/src/a2a/compat/v0_3/rest_adapter.py index 76b1ce4d1..208543e36 100644 --- a/src/a2a/compat/v0_3/rest_adapter.py +++ b/src/a2a/compat/v0_3/rest_adapter.py @@ -34,7 +34,11 @@ from a2a.compat.v0_3 import conversions from a2a.compat.v0_3.rest_handler import REST03Handler from a2a.server.context import ServerCallContext -from a2a.server.routes import CallContextBuilder, DefaultCallContextBuilder +from a2a.server.routes.common import ( + UserBuilder, + build_server_call_context, + default_user_builder, +) from a2a.utils.error_handlers import ( rest_error_handler, rest_stream_error_handler, @@ -60,7 +64,7 @@ def __init__( # noqa: PLR0913 agent_card: 'AgentCard', http_handler: 'RequestHandler', extended_agent_card: 'AgentCard | None' = None, - context_builder: 'CallContextBuilder | None' = None, + user_builder: 'UserBuilder | None' = None, card_modifier: 'Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None' = None, extended_card_modifier: 'Callable[[AgentCard, ServerCallContext], Awaitable[AgentCard] | AgentCard] | None' = None, ): @@ -71,7 +75,7 @@ def __init__( # noqa: PLR0913 self.handler = REST03Handler( agent_card=agent_card, request_handler=http_handler ) - self._context_builder = context_builder or DefaultCallContextBuilder() + self._user_builder = user_builder or default_user_builder @rest_error_handler async def _handle_request( @@ -79,7 +83,7 @@ async def _handle_request( method: 'Callable[[Request, ServerCallContext], Awaitable[Any]]', request: Request, ) -> Response: - call_context = self._context_builder.build(request) + call_context = build_server_call_context(request, self._user_builder) response = await method(request, call_context) return JSONResponse(content=response) @@ -96,7 +100,7 @@ async def _handle_streaming_request( message=f'Failed to pre-consume request body: {e}' ) from e - call_context = self._context_builder.build(request) + call_context = build_server_call_context(request, self._user_builder) async def event_generator( stream: AsyncIterable[Any], diff --git a/src/a2a/server/request_handlers/grpc_handler.py b/src/a2a/server/request_handlers/grpc_handler.py index c354e097e..91ab34168 100644 --- a/src/a2a/server/request_handlers/grpc_handler.py +++ b/src/a2a/server/request_handlers/grpc_handler.py @@ -1,7 +1,6 @@ # ruff: noqa: N802 import logging -from abc import ABC, abstractmethod from collections.abc import AsyncIterable, Awaitable, Callable from typing import TypeVar @@ -24,7 +23,7 @@ import a2a.types.a2a_pb2_grpc as a2a_grpc from a2a import types -from a2a.auth.user import UnauthenticatedUser +from a2a.auth.user import UnauthenticatedUser, User from a2a.extensions.common import ( HTTP_EXTENSION_HEADER, get_requested_extensions, @@ -41,15 +40,12 @@ logger = logging.getLogger(__name__) -# For now we use a trivial wrapper on the grpc context object +GrpcUserBuilder = Callable[[grpc.aio.ServicerContext], User] -class CallContextBuilder(ABC): - """A class for building ServerCallContexts using the Starlette Request.""" - - @abstractmethod - def build(self, context: grpc.aio.ServicerContext) -> ServerCallContext: - """Builds a ServerCallContext from a gRPC Request.""" +def default_grpc_user_builder(context: grpc.aio.ServicerContext) -> User: + """Default strategy for creating a User from a gRPC context.""" + return UnauthenticatedUser() def _get_metadata_value( @@ -67,20 +63,19 @@ def _get_metadata_value( ] -class DefaultCallContextBuilder(CallContextBuilder): - """A default implementation of CallContextBuilder.""" - - def build(self, context: grpc.aio.ServicerContext) -> ServerCallContext: - """Builds the ServerCallContext.""" - user = UnauthenticatedUser() - state = {'grpc_context': context} - return ServerCallContext( - user=user, - state=state, - requested_extensions=get_requested_extensions( - _get_metadata_value(context, HTTP_EXTENSION_HEADER) - ), - ) +def build_grpc_server_call_context( + context: grpc.aio.ServicerContext, user_builder: GrpcUserBuilder +) -> ServerCallContext: + """Builds a ServerCallContext from a gRPC ServicerContext.""" + user = user_builder(context) + state = {'grpc_context': context} + return ServerCallContext( + user=user, + state=state, + requested_extensions=get_requested_extensions( + _get_metadata_value(context, HTTP_EXTENSION_HEADER) + ), + ) _ERROR_CODE_MAP = { @@ -110,7 +105,7 @@ def __init__( self, agent_card: AgentCard, request_handler: RequestHandler, - context_builder: CallContextBuilder | None = None, + user_builder: GrpcUserBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, ): @@ -120,14 +115,14 @@ def __init__( agent_card: The AgentCard describing the agent's capabilities. request_handler: The underlying `RequestHandler` instance to delegate requests to. - context_builder: The CallContextBuilder object. If none the - DefaultCallContextBuilder is used. + user_builder: Optional custom user builder to extract user from the + gRPC context. card_modifier: An optional callback to dynamically modify the public agent card before it is served. """ self.agent_card = agent_card self.request_handler = request_handler - self.context_builder = context_builder or DefaultCallContextBuilder() + self.user_builder = user_builder or default_grpc_user_builder self.card_modifier = card_modifier async def _handle_unary( @@ -451,6 +446,8 @@ def _build_call_context( context: grpc.aio.ServicerContext, request: message.Message, ) -> ServerCallContext: - server_context = self.context_builder.build(context) + server_context = build_grpc_server_call_context( + context, self.user_builder + ) server_context.tenant = getattr(request, 'tenant', '') return server_context diff --git a/src/a2a/server/routes/__init__.py b/src/a2a/server/routes/__init__.py index 0e4f42abe..63da6fc9a 100644 --- a/src/a2a/server/routes/__init__.py +++ b/src/a2a/server/routes/__init__.py @@ -1,17 +1,13 @@ """A2A Routes.""" from a2a.server.routes.agent_card_routes import create_agent_card_routes -from a2a.server.routes.common import ( - CallContextBuilder, - DefaultCallContextBuilder, -) +from a2a.server.routes.common import UserBuilder from a2a.server.routes.jsonrpc_routes import create_jsonrpc_routes from a2a.server.routes.rest_routes import create_rest_routes __all__ = [ - 'CallContextBuilder', - 'DefaultCallContextBuilder', + 'UserBuilder', 'create_agent_card_routes', 'create_jsonrpc_routes', 'create_rest_routes', diff --git a/src/a2a/server/routes/common.py b/src/a2a/server/routes/common.py index 9536892e3..40ae66a11 100644 --- a/src/a2a/server/routes/common.py +++ b/src/a2a/server/routes/common.py @@ -1,10 +1,8 @@ -from abc import ABC, abstractmethod +from collections.abc import Callable -from starlette.authentication import BaseUser from starlette.requests import Request -from a2a.auth.user import UnauthenticatedUser -from a2a.auth.user import User as A2AUser +from a2a.auth.user import UnauthenticatedUser, User from a2a.extensions.common import ( HTTP_EXTENSION_HEADER, get_requested_extensions, @@ -12,54 +10,49 @@ from a2a.server.context import ServerCallContext -class StarletteUserProxy(A2AUser): - """Adapts the Starlette User class to the A2A user representation.""" +UserBuilder = Callable[[Request], User] - def __init__(self, user: BaseUser): - self._user = user - @property - def is_authenticated(self) -> bool: - """Returns whether the current user is authenticated.""" - return self._user.is_authenticated +def default_user_builder(request: Request) -> User: + """Default strategy for creating an A2AUser from a Starlette Request.""" + if 'user' in request.scope: - @property - def user_name(self) -> str: - """Returns the user name of the current user.""" - return self._user.display_name + class BaseUser(User): + @property + def is_authenticated(self) -> bool: + return request.user.is_authenticated + @property + def user_name(self) -> str: + return request.user.display_name -class CallContextBuilder(ABC): - """A class for building ServerCallContexts using the Starlette Request.""" + return BaseUser() + return UnauthenticatedUser() - @abstractmethod - def build(self, request: Request) -> ServerCallContext: - """Builds a ServerCallContext from a Starlette Request.""" +def build_server_call_context( + request: Request, user_builder: UserBuilder +) -> ServerCallContext: + """Builds a ServerCallContext from a Starlette Request. -class DefaultCallContextBuilder(CallContextBuilder): - """A default implementation of CallContextBuilder.""" + Args: + request: The incoming Starlette Request object. + user_builder: Optional custom user builder. - def build(self, request: Request) -> ServerCallContext: - """Builds a ServerCallContext from a Starlette Request. + Returns: + A ServerCallContext instance populated with user and state. + """ + user = user_builder(request) - Args: - request: The incoming Starlette Request object. + state = {} + if 'auth' in request.scope: + state['auth'] = request.auth + state['headers'] = dict(request.headers) - Returns: - A ServerCallContext instance populated with user and state - information from the request. - """ - user: A2AUser = UnauthenticatedUser() - state = {} - if 'user' in request.scope: - user = StarletteUserProxy(request.user) - state['auth'] = request.auth - state['headers'] = dict(request.headers) - return ServerCallContext( - user=user, - state=state, - requested_extensions=get_requested_extensions( - request.headers.getlist(HTTP_EXTENSION_HEADER) - ), - ) + return ServerCallContext( + user=user, + state=state, + requested_extensions=get_requested_extensions( + request.headers.getlist(HTTP_EXTENSION_HEADER) + ), + ) diff --git a/src/a2a/server/routes/jsonrpc_dispatcher.py b/src/a2a/server/routes/jsonrpc_dispatcher.py index 8216748f5..9c71399cc 100644 --- a/src/a2a/server/routes/jsonrpc_dispatcher.py +++ b/src/a2a/server/routes/jsonrpc_dispatcher.py @@ -28,8 +28,9 @@ build_error_response, ) from a2a.server.routes.common import ( - CallContextBuilder, - DefaultCallContextBuilder, + UserBuilder, + build_server_call_context, + default_user_builder, ) from a2a.types import A2ARequest from a2a.types.a2a_pb2 import ( @@ -144,7 +145,7 @@ def __init__( # noqa: PLR0913 agent_card: AgentCard, request_handler: RequestHandler, extended_agent_card: AgentCard | None = None, - context_builder: CallContextBuilder | None = None, + user_builder: UserBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, extended_card_modifier: Callable[ @@ -161,9 +162,8 @@ def __init__( # noqa: PLR0913 requests via http. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - context_builder: The CallContextBuilder used to construct the - ServerCallContext passed to the request_handler. If None the - DefaultCallContextBuilder is used. + user_builder: Optional custom user builder to extract user from the + request. card_modifier: An optional callback to dynamically modify the public agent card before it is served. extended_card_modifier: An optional callback to dynamically modify @@ -183,7 +183,7 @@ def __init__( # noqa: PLR0913 self.extended_agent_card = extended_agent_card self.card_modifier = card_modifier self.extended_card_modifier = extended_card_modifier - self._context_builder = context_builder or DefaultCallContextBuilder() + self._user_builder = user_builder or default_user_builder self.enable_v0_3_compat = enable_v0_3_compat self._v03_adapter: JSONRPC03Adapter | None = None @@ -192,7 +192,7 @@ def __init__( # noqa: PLR0913 agent_card=agent_card, http_handler=request_handler, extended_agent_card=extended_agent_card, - context_builder=self._context_builder, + user_builder=self._user_builder, card_modifier=card_modifier, extended_card_modifier=extended_card_modifier, ) @@ -334,7 +334,9 @@ async def handle_requests(self, request: Request) -> Response: # noqa: PLR0911, ) # 3) Build call context and wrap the request for downstream handling - call_context = self._context_builder.build(request) + call_context = build_server_call_context( + request, self._user_builder + ) call_context.tenant = getattr(specific_request, 'tenant', '') call_context.state['method'] = method call_context.state['request_id'] = request_id diff --git a/src/a2a/server/routes/jsonrpc_routes.py b/src/a2a/server/routes/jsonrpc_routes.py index e3b32ef68..beb51e5ed 100644 --- a/src/a2a/server/routes/jsonrpc_routes.py +++ b/src/a2a/server/routes/jsonrpc_routes.py @@ -19,7 +19,7 @@ from a2a.server.context import ServerCallContext from a2a.server.request_handlers.request_handler import RequestHandler -from a2a.server.routes.common import CallContextBuilder +from a2a.server.routes.common import UserBuilder from a2a.server.routes.jsonrpc_dispatcher import JsonRpcDispatcher from a2a.types.a2a_pb2 import AgentCard @@ -29,7 +29,7 @@ def create_jsonrpc_routes( # noqa: PLR0913 request_handler: RequestHandler, rpc_url: str, extended_agent_card: AgentCard | None = None, - context_builder: CallContextBuilder | None = None, + user_builder: UserBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, extended_card_modifier: Callable[ @@ -51,9 +51,8 @@ def create_jsonrpc_routes( # noqa: PLR0913 rpc_url: The URL prefix for the RPC endpoints. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - context_builder: The CallContextBuilder used to construct the - ServerCallContext passed to the request_handler. If None the - DefaultCallContextBuilder is used. + user_builder: Optional custom user builder to extract user from the + request. card_modifier: An optional callback to dynamically modify the public agent card before it is served. extended_card_modifier: An optional callback to dynamically modify @@ -72,7 +71,7 @@ def create_jsonrpc_routes( # noqa: PLR0913 agent_card=agent_card, request_handler=request_handler, extended_agent_card=extended_agent_card, - context_builder=context_builder, + user_builder=user_builder, card_modifier=card_modifier, extended_card_modifier=extended_card_modifier, enable_v0_3_compat=enable_v0_3_compat, diff --git a/src/a2a/server/routes/rest_dispatcher.py b/src/a2a/server/routes/rest_dispatcher.py index 768315086..1c7e2c570 100644 --- a/src/a2a/server/routes/rest_dispatcher.py +++ b/src/a2a/server/routes/rest_dispatcher.py @@ -8,7 +8,11 @@ from a2a.server.context import ServerCallContext from a2a.server.request_handlers.request_handler import RequestHandler -from a2a.server.routes import CallContextBuilder, DefaultCallContextBuilder +from a2a.server.routes.common import ( + UserBuilder, + build_server_call_context, + default_user_builder, +) from a2a.types import a2a_pb2 from a2a.types.a2a_pb2 import ( AgentCard, @@ -68,7 +72,7 @@ def __init__( # noqa: PLR0913 agent_card: AgentCard, request_handler: RequestHandler, extended_agent_card: AgentCard | None = None, - context_builder: CallContextBuilder | None = None, + user_builder: UserBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, extended_card_modifier: Callable[ @@ -83,9 +87,8 @@ def __init__( # noqa: PLR0913 request_handler: The underlying `RequestHandler` instance to delegate requests to. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - context_builder: The CallContextBuilder used to construct the - ServerCallContext passed to the request_handler. If None, no - ServerCallContext is passed. + user_builder: Optional custom user builder to extract user from the + request. card_modifier: An optional callback to dynamically modify the public agent card before it is served. extended_card_modifier: An optional callback to dynamically modify @@ -103,11 +106,11 @@ def __init__( # noqa: PLR0913 self.extended_agent_card = extended_agent_card self.card_modifier = card_modifier self.extended_card_modifier = extended_card_modifier - self._context_builder = context_builder or DefaultCallContextBuilder() + self._user_builder = user_builder or default_user_builder self.request_handler = request_handler def _build_call_context(self, request: Request) -> ServerCallContext: - call_context = self._context_builder.build(request) + call_context = build_server_call_context(request, self._user_builder) if 'tenant' in request.path_params: call_context.tenant = request.path_params['tenant'] return call_context diff --git a/src/a2a/server/routes/rest_routes.py b/src/a2a/server/routes/rest_routes.py index 5d0cfcfc8..8b0ad85d7 100644 --- a/src/a2a/server/routes/rest_routes.py +++ b/src/a2a/server/routes/rest_routes.py @@ -6,7 +6,7 @@ from a2a.compat.v0_3.rest_adapter import REST03Adapter from a2a.server.context import ServerCallContext from a2a.server.request_handlers.request_handler import RequestHandler -from a2a.server.routes import CallContextBuilder +from a2a.server.routes.common import UserBuilder from a2a.server.routes.rest_dispatcher import RestDispatcher from a2a.types.a2a_pb2 import ( AgentCard, @@ -46,7 +46,7 @@ def create_rest_routes( # noqa: PLR0913 agent_card: AgentCard, request_handler: RequestHandler, extended_agent_card: AgentCard | None = None, - context_builder: CallContextBuilder | None = None, + user_builder: UserBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, extended_card_modifier: Callable[ @@ -64,9 +64,8 @@ def create_rest_routes( # noqa: PLR0913 requests via http. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - context_builder: The CallContextBuilder used to construct the - ServerCallContext passed to the request_handler. If None the - DefaultCallContextBuilder is used. + user_builder: Optional custom user builder to extract user from the + request. card_modifier: An optional callback to dynamically modify the public agent card before it is served. extended_card_modifier: An optional callback to dynamically modify @@ -87,7 +86,7 @@ def create_rest_routes( # noqa: PLR0913 agent_card=agent_card, request_handler=request_handler, extended_agent_card=extended_agent_card, - context_builder=context_builder, + user_builder=user_builder, card_modifier=card_modifier, extended_card_modifier=extended_card_modifier, ) @@ -98,7 +97,7 @@ def create_rest_routes( # noqa: PLR0913 agent_card=agent_card, http_handler=request_handler, extended_agent_card=extended_agent_card, - context_builder=context_builder, + user_builder=user_builder, card_modifier=card_modifier, extended_card_modifier=extended_card_modifier, ) diff --git a/tests/server/routes/test_jsonrpc_dispatcher.py b/tests/server/routes/test_jsonrpc_dispatcher.py index e39dad6c6..7ca6ecd28 100644 --- a/tests/server/routes/test_jsonrpc_dispatcher.py +++ b/tests/server/routes/test_jsonrpc_dispatcher.py @@ -22,9 +22,8 @@ ) from a2a.server.routes import jsonrpc_dispatcher from a2a.server.routes.common import ( - CallContextBuilder, - DefaultCallContextBuilder, - StarletteUserProxy, + UserBuilder, + build_server_call_context, ) from a2a.server.routes.jsonrpc_dispatcher import JsonRpcDispatcher from a2a.server.routes.jsonrpc_routes import create_jsonrpc_routes @@ -33,37 +32,6 @@ from a2a.utils.errors import A2AError -# --- StarletteUserProxy Tests --- - - -class TestStarletteUserProxy: - def test_starlette_user_proxy_is_authenticated_true(self): - starlette_user_mock = MagicMock(spec=StarletteBaseUser) - starlette_user_mock.is_authenticated = True - proxy = StarletteUserProxy(starlette_user_mock) - assert proxy.is_authenticated is True - - def test_starlette_user_proxy_is_authenticated_false(self): - starlette_user_mock = MagicMock(spec=StarletteBaseUser) - starlette_user_mock.is_authenticated = False - proxy = StarletteUserProxy(starlette_user_mock) - assert proxy.is_authenticated is False - - def test_starlette_user_proxy_user_name(self): - starlette_user_mock = MagicMock(spec=StarletteBaseUser) - starlette_user_mock.display_name = 'Test User DisplayName' - proxy = StarletteUserProxy(starlette_user_mock) - assert proxy.user_name == 'Test User DisplayName' - - def test_starlette_user_proxy_user_name_raises_attribute_error(self): - starlette_user_mock = MagicMock(spec=StarletteBaseUser) - del starlette_user_mock.display_name - - proxy = StarletteUserProxy(starlette_user_mock) - with pytest.raises(AttributeError, match='display_name'): - _ = proxy.user_name - - # --- JsonRpcDispatcher Tests --- diff --git a/tests/server/routes/test_rest_dispatcher.py b/tests/server/routes/test_rest_dispatcher.py index b4233d0cd..be5870cc4 100644 --- a/tests/server/routes/test_rest_dispatcher.py +++ b/tests/server/routes/test_rest_dispatcher.py @@ -11,7 +11,6 @@ from a2a.server.request_handlers.request_handler import RequestHandler from a2a.server.routes import rest_dispatcher from a2a.server.routes.rest_dispatcher import ( - DefaultCallContextBuilder, RestDispatcher, ) from a2a.types.a2a_pb2 import ( From 86665b51cfebe4ffe5e27f1f004727cd96f9f3f2 Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Thu, 2 Apr 2026 11:53:28 +0000 Subject: [PATCH 03/12] refactor: extract StarletteUserProxy and add comprehensive route utility tests --- src/a2a/server/routes/common.py | 40 +++++--- tests/server/routes/test_common.py | 156 +++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+), 13 deletions(-) create mode 100644 tests/server/routes/test_common.py diff --git a/src/a2a/server/routes/common.py b/src/a2a/server/routes/common.py index 40ae66a11..569148ed9 100644 --- a/src/a2a/server/routes/common.py +++ b/src/a2a/server/routes/common.py @@ -1,6 +1,13 @@ from collections.abc import Callable +from typing import TYPE_CHECKING, Any -from starlette.requests import Request +if TYPE_CHECKING: + from starlette.requests import Request +else: + try: + from starlette.requests import Request + except ImportError: + Request = Any from a2a.auth.user import UnauthenticatedUser, User from a2a.extensions.common import ( @@ -13,20 +20,27 @@ UserBuilder = Callable[[Request], User] -def default_user_builder(request: Request) -> User: - """Default strategy for creating an A2AUser from a Starlette Request.""" - if 'user' in request.scope: +class StarletteUser(User): + """Adapts a Starlette BaseUser to the A2A User interface.""" + + def __init__(self, user: Any): + self._user = user - class BaseUser(User): - @property - def is_authenticated(self) -> bool: - return request.user.is_authenticated + @property + def is_authenticated(self) -> bool: + """Returns whether the current user is authenticated.""" + return self._user.is_authenticated - @property - def user_name(self) -> str: - return request.user.display_name + @property + def user_name(self) -> str: + """Returns the user name of the current user.""" + return self._user.display_name - return BaseUser() + +def default_user_builder(request: Request) -> User: + """Default strategy for creating an A2AUser from a Starlette Request.""" + if 'user' in request.scope: + return StarletteUser(request.user) return UnauthenticatedUser() @@ -37,7 +51,7 @@ def build_server_call_context( Args: request: The incoming Starlette Request object. - user_builder: Optional custom user builder. + user_builder: A callable that creates a User from the request. Returns: A ServerCallContext instance populated with user and state. diff --git a/tests/server/routes/test_common.py b/tests/server/routes/test_common.py new file mode 100644 index 000000000..0e7a3b81d --- /dev/null +++ b/tests/server/routes/test_common.py @@ -0,0 +1,156 @@ +from unittest.mock import MagicMock + +import pytest +from starlette.datastructures import Headers + +try: + from starlette.authentication import BaseUser as StarletteBaseUser +except ImportError: + StarletteBaseUser = MagicMock() # type: ignore + +from a2a.auth.user import UnauthenticatedUser +from a2a.extensions.common import HTTP_EXTENSION_HEADER +from a2a.server.context import ServerCallContext +from a2a.server.routes.common import ( + StarletteUser, + build_server_call_context, + default_user_builder, +) + + +# --- StarletteUser Tests --- + + +class TestStarletteUser: + def test_is_authenticated_true(self): + starlette_user = MagicMock(spec=StarletteBaseUser) + starlette_user.is_authenticated = True + proxy = StarletteUser(starlette_user) + assert proxy.is_authenticated is True + + def test_is_authenticated_false(self): + starlette_user = MagicMock(spec=StarletteBaseUser) + starlette_user.is_authenticated = False + proxy = StarletteUser(starlette_user) + assert proxy.is_authenticated is False + + def test_user_name(self): + starlette_user = MagicMock(spec=StarletteBaseUser) + starlette_user.display_name = 'Test User' + proxy = StarletteUser(starlette_user) + assert proxy.user_name == 'Test User' + + def test_user_name_raises_attribute_error(self): + starlette_user = MagicMock(spec=StarletteBaseUser) + del starlette_user.display_name + proxy = StarletteUser(starlette_user) + with pytest.raises(AttributeError, match='display_name'): + _ = proxy.user_name + + +# --- default_user_builder Tests --- + + +def _make_mock_request(scope=None, headers=None): + request = MagicMock() + request.scope = scope or {} + request.headers = Headers(headers or {}) + return request + + +class TestDefaultUserBuilder: + def test_returns_unauthenticated_user_when_no_user_in_scope(self): + request = _make_mock_request(scope={}) + user = default_user_builder(request) + assert isinstance(user, UnauthenticatedUser) + assert user.is_authenticated is False + assert user.user_name == '' + + def test_returns_proxy_when_user_in_scope(self): + starlette_user = MagicMock() + starlette_user.is_authenticated = True + starlette_user.display_name = 'Alice' + request = _make_mock_request(scope={'user': starlette_user}) + request.user = starlette_user + + user = default_user_builder(request) + assert isinstance(user, StarletteUser) + assert user.is_authenticated is True + assert user.user_name == 'Alice' + + def test_returns_unauthenticated_proxy_when_user_not_authenticated(self): + starlette_user = MagicMock() + starlette_user.is_authenticated = False + starlette_user.display_name = '' + request = _make_mock_request(scope={'user': starlette_user}) + request.user = starlette_user + + user = default_user_builder(request) + assert isinstance(user, StarletteUser) + assert user.is_authenticated is False + + +# --- build_server_call_context Tests --- + + +class TestBuildServerCallContext: + def test_basic_context_with_default_user_builder(self): + request = _make_mock_request( + scope={}, headers={'content-type': 'application/json'} + ) + ctx = build_server_call_context(request, default_user_builder) + + assert isinstance(ctx, ServerCallContext) + assert isinstance(ctx.user, UnauthenticatedUser) + assert 'headers' in ctx.state + assert ctx.state['headers']['content-type'] == 'application/json' + assert 'auth' not in ctx.state + + def test_auth_populated_when_in_scope(self): + auth_credentials = MagicMock() + request = _make_mock_request(scope={'auth': auth_credentials}) + request.auth = auth_credentials + + ctx = build_server_call_context(request, default_user_builder) + assert ctx.state['auth'] is auth_credentials + + def test_auth_not_populated_when_not_in_scope(self): + request = _make_mock_request(scope={}) + ctx = build_server_call_context(request, default_user_builder) + assert 'auth' not in ctx.state + + def test_headers_captured_in_state(self): + request = _make_mock_request( + headers={'x-custom': 'value', 'authorization': 'Bearer tok'} + ) + ctx = build_server_call_context(request, default_user_builder) + assert ctx.state['headers']['x-custom'] == 'value' + assert ctx.state['headers']['authorization'] == 'Bearer tok' + + def test_requested_extensions_single(self): + request = _make_mock_request(headers={HTTP_EXTENSION_HEADER: 'foo'}) + ctx = build_server_call_context(request, default_user_builder) + assert ctx.requested_extensions == {'foo'} + + def test_requested_extensions_comma_separated(self): + request = _make_mock_request( + headers={HTTP_EXTENSION_HEADER: 'foo, bar'} + ) + ctx = build_server_call_context(request, default_user_builder) + assert ctx.requested_extensions == {'foo', 'bar'} + + def test_no_extensions(self): + request = _make_mock_request() + ctx = build_server_call_context(request, default_user_builder) + assert ctx.requested_extensions == set() + + def test_custom_user_builder(self): + custom_user = MagicMock(spec=UnauthenticatedUser) + custom_user.is_authenticated = True + + def my_builder(req): + return custom_user + + request = _make_mock_request() + ctx = build_server_call_context(request, my_builder) + assert ctx.user is custom_user From 56e24498fe29b2aa9fd76016546d06135fa5fd1f Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Thu, 2 Apr 2026 12:23:46 +0000 Subject: [PATCH 04/12] fix --- src/a2a/server/routes/common.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/a2a/server/routes/common.py b/src/a2a/server/routes/common.py index 569148ed9..27e4bd435 100644 --- a/src/a2a/server/routes/common.py +++ b/src/a2a/server/routes/common.py @@ -1,13 +1,17 @@ from collections.abc import Callable from typing import TYPE_CHECKING, Any + if TYPE_CHECKING: + from starlette.authentication import BaseUser from starlette.requests import Request else: try: + from starlette.authentication import BaseUser from starlette.requests import Request except ImportError: Request = Any + BaseUser = Any from a2a.auth.user import UnauthenticatedUser, User from a2a.extensions.common import ( @@ -23,7 +27,7 @@ class StarletteUser(User): """Adapts a Starlette BaseUser to the A2A User interface.""" - def __init__(self, user: Any): + def __init__(self, user: BaseUser): self._user = user @property From fd7790f7fec784b9a8e95bf777d42314417599cf Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Thu, 2 Apr 2026 12:28:53 +0000 Subject: [PATCH 05/12] fix --- tests/extensions/__init__.py | 0 tests/server/routes/__init__.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/extensions/__init__.py create mode 100644 tests/server/routes/__init__.py diff --git a/tests/extensions/__init__.py b/tests/extensions/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/server/routes/__init__.py b/tests/server/routes/__init__.py new file mode 100644 index 000000000..e69de29bb From 30466c40be1e74fa1883fda34beef70bb2421450 Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Fri, 3 Apr 2026 09:35:00 +0000 Subject: [PATCH 06/12] wip --- src/a2a/compat/v0_3/grpc_handler.py | 8 +- src/a2a/compat/v0_3/jsonrpc_adapter.py | 13 ++-- src/a2a/compat/v0_3/rest_adapter.py | 6 +- .../server/request_handlers/grpc_handler.py | 12 +-- src/a2a/server/routes/__init__.py | 4 +- src/a2a/server/routes/common.py | 75 +++++++++++-------- src/a2a/server/routes/jsonrpc_dispatcher.py | 13 ++-- src/a2a/server/routes/jsonrpc_routes.py | 8 +- src/a2a/server/routes/rest_dispatcher.py | 8 +- src/a2a/server/routes/rest_routes.py | 10 +-- tests/server/routes/test_common.py | 2 +- .../server/routes/test_jsonrpc_dispatcher.py | 2 +- 12 files changed, 85 insertions(+), 76 deletions(-) diff --git a/src/a2a/compat/v0_3/grpc_handler.py b/src/a2a/compat/v0_3/grpc_handler.py index 37c7be5c4..2b42c6088 100644 --- a/src/a2a/compat/v0_3/grpc_handler.py +++ b/src/a2a/compat/v0_3/grpc_handler.py @@ -23,7 +23,7 @@ from a2a.server.context import ServerCallContext from a2a.server.request_handlers.grpc_handler import ( _ERROR_CODE_MAP, - GrpcUserBuilder, + GrpcContextBuilder, build_grpc_server_call_context, default_grpc_user_builder, ) @@ -45,7 +45,7 @@ def __init__( self, agent_card: AgentCard, request_handler: RequestHandler, - user_builder: GrpcUserBuilder | None = None, + context_builder: GrpcContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, ): @@ -55,14 +55,14 @@ def __init__( agent_card: The AgentCard describing the agent's capabilities (v1.0). request_handler: The underlying `RequestHandler` instance to delegate requests to. - user_builder: Optional custom user builder to extract user from the + context_builder: Optional custom user builder to extract user from the gRPC context. card_modifier: An optional callback to dynamically modify the public agent card before it is served. """ self.agent_card = agent_card self.handler03 = RequestHandler03(request_handler=request_handler) - self.user_builder = user_builder or default_grpc_user_builder + self.user_builder =context_builder or default_grpc_user_builder self.card_modifier = card_modifier async def _handle_unary( diff --git a/src/a2a/compat/v0_3/jsonrpc_adapter.py b/src/a2a/compat/v0_3/jsonrpc_adapter.py index 39f70516a..85f9eb2ae 100644 --- a/src/a2a/compat/v0_3/jsonrpc_adapter.py +++ b/src/a2a/compat/v0_3/jsonrpc_adapter.py @@ -38,9 +38,8 @@ JSONRPCError as CoreJSONRPCError, ) from a2a.server.routes.common import ( - UserBuilder, - build_server_call_context, - default_user_builder, + ContextBuilder, + DefaultContextBuilder, ) from a2a.utils import constants from a2a.utils.errors import ExtendedAgentCardNotConfiguredError @@ -71,7 +70,7 @@ def __init__( # noqa: PLR0913 agent_card: 'AgentCard', http_handler: 'RequestHandler', extended_agent_card: 'AgentCard | None' = None, - user_builder: 'UserBuilder | None' = None, + context_builder: 'ContextBuilder | None' = None, card_modifier: 'Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None' = None, extended_card_modifier: 'Callable[[AgentCard, ServerCallContext], Awaitable[AgentCard] | AgentCard] | None' = None, ): @@ -82,7 +81,7 @@ def __init__( # noqa: PLR0913 self.handler = RequestHandler03( request_handler=http_handler, ) - self._user_builder = user_builder or default_user_builder + self._context_builder = context_builder or DefaultContextBuilder() def supports_method(self, method: str) -> bool: """Returns True if the v0.3 adapter supports the given method name.""" @@ -130,9 +129,7 @@ async def handle_request( CoreInvalidRequestError(data=str(e)), ) - call_context = build_server_call_context( - request, self._user_builder - ) + call_context = self._context_builder.build(request) call_context.tenant = ( getattr(specific_request.params, 'tenant', '') if hasattr(specific_request, 'params') diff --git a/src/a2a/compat/v0_3/rest_adapter.py b/src/a2a/compat/v0_3/rest_adapter.py index 208543e36..4acf68e32 100644 --- a/src/a2a/compat/v0_3/rest_adapter.py +++ b/src/a2a/compat/v0_3/rest_adapter.py @@ -35,7 +35,7 @@ from a2a.compat.v0_3.rest_handler import REST03Handler from a2a.server.context import ServerCallContext from a2a.server.routes.common import ( - UserBuilder, + ContextBuilder, build_server_call_context, default_user_builder, ) @@ -64,7 +64,7 @@ def __init__( # noqa: PLR0913 agent_card: 'AgentCard', http_handler: 'RequestHandler', extended_agent_card: 'AgentCard | None' = None, - user_builder: 'UserBuilder | None' = None, + context_builder: 'ContextBuilder | None' = None, card_modifier: 'Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None' = None, extended_card_modifier: 'Callable[[AgentCard, ServerCallContext], Awaitable[AgentCard] | AgentCard] | None' = None, ): @@ -75,7 +75,7 @@ def __init__( # noqa: PLR0913 self.handler = REST03Handler( agent_card=agent_card, request_handler=http_handler ) - self._user_builder = user_builder or default_user_builder + self._user_builder =context_builder or default_user_builder @rest_error_handler async def _handle_request( diff --git a/src/a2a/server/request_handlers/grpc_handler.py b/src/a2a/server/request_handlers/grpc_handler.py index 91ab34168..db55206d9 100644 --- a/src/a2a/server/request_handlers/grpc_handler.py +++ b/src/a2a/server/request_handlers/grpc_handler.py @@ -40,7 +40,7 @@ logger = logging.getLogger(__name__) -GrpcUserBuilder = Callable[[grpc.aio.ServicerContext], User] +GrpcContextBuilder = Callable[[grpc.aio.ServicerContext], User] def default_grpc_user_builder(context: grpc.aio.ServicerContext) -> User: @@ -64,10 +64,10 @@ def _get_metadata_value( def build_grpc_server_call_context( - context: grpc.aio.ServicerContext, user_builder: GrpcUserBuilder + context: grpc.aio.ServicerContext,context_builder: GrpcContextBuilder ) -> ServerCallContext: """Builds a ServerCallContext from a gRPC ServicerContext.""" - user = user_builder(context) + user =context_builder(context) state = {'grpc_context': context} return ServerCallContext( user=user, @@ -105,7 +105,7 @@ def __init__( self, agent_card: AgentCard, request_handler: RequestHandler, - user_builder: GrpcUserBuilder | None = None, + context_builder: GrpcContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, ): @@ -115,14 +115,14 @@ def __init__( agent_card: The AgentCard describing the agent's capabilities. request_handler: The underlying `RequestHandler` instance to delegate requests to. - user_builder: Optional custom user builder to extract user from the + context_builder: Optional custom user builder to extract user from the gRPC context. card_modifier: An optional callback to dynamically modify the public agent card before it is served. """ self.agent_card = agent_card self.request_handler = request_handler - self.user_builder = user_builder or default_grpc_user_builder + self.user_builder =context_builder or default_grpc_user_builder self.card_modifier = card_modifier async def _handle_unary( diff --git a/src/a2a/server/routes/__init__.py b/src/a2a/server/routes/__init__.py index 63da6fc9a..0b26ebc59 100644 --- a/src/a2a/server/routes/__init__.py +++ b/src/a2a/server/routes/__init__.py @@ -1,13 +1,13 @@ """A2A Routes.""" from a2a.server.routes.agent_card_routes import create_agent_card_routes -from a2a.server.routes.common import UserBuilder +from a2a.server.routes.common import ContextBuilder from a2a.server.routes.jsonrpc_routes import create_jsonrpc_routes from a2a.server.routes.rest_routes import create_rest_routes __all__ = [ - 'UserBuilder', + 'ContextBuilder', 'create_agent_card_routes', 'create_jsonrpc_routes', 'create_rest_routes', diff --git a/src/a2a/server/routes/common.py b/src/a2a/server/routes/common.py index 27e4bd435..4af36c87f 100644 --- a/src/a2a/server/routes/common.py +++ b/src/a2a/server/routes/common.py @@ -1,3 +1,4 @@ +from abc import ABC, abstractmethod from collections.abc import Callable from typing import TYPE_CHECKING, Any @@ -20,10 +21,6 @@ ) from a2a.server.context import ServerCallContext - -UserBuilder = Callable[[Request], User] - - class StarletteUser(User): """Adapts a Starlette BaseUser to the A2A User interface.""" @@ -41,36 +38,52 @@ def user_name(self) -> str: return self._user.display_name -def default_user_builder(request: Request) -> User: - """Default strategy for creating an A2AUser from a Starlette Request.""" - if 'user' in request.scope: - return StarletteUser(request.user) - return UnauthenticatedUser() +class ContextBuilder(ABC): + """A class for building ServerCallContexts using the Starlette Request.""" + + @abstractmethod + def build(self, request: Request) -> ServerCallContext: + """Builds a ServerCallContext from a Starlette Request.""" + + @abstractmethod + def build_user(self, request: Request) -> User: + """Builds a User from a Starlette Request.""" + + +class DefaultContextBuilder(ContextBuilder): + """A default implementation of ContextBuilder.""" + def build(self, request: Request) -> ServerCallContext: + """Builds a ServerCallContext from a Starlette Request. -def build_server_call_context( - request: Request, user_builder: UserBuilder -) -> ServerCallContext: - """Builds a ServerCallContext from a Starlette Request. + Args: + request: The incoming Starlette Request object. - Args: - request: The incoming Starlette Request object. - user_builder: A callable that creates a User from the request. + Returns: + A ServerCallContext instance populated with user and state + information from the request. + """ + state = {} + if 'auth' in request.scope: + state['auth'] = request.auth + state['headers'] = dict(request.headers) + return ServerCallContext( + user=self.build_user(request), + state=state, + requested_extensions=get_requested_extensions( + request.headers.getlist(HTTP_EXTENSION_HEADER) + ), + ) - Returns: - A ServerCallContext instance populated with user and state. - """ - user = user_builder(request) + def build_user(self, request: Request) -> User: + """Builds a User from a Starlette Request. - state = {} - if 'auth' in request.scope: - state['auth'] = request.auth - state['headers'] = dict(request.headers) + Args: + request: The incoming Starlette Request object. - return ServerCallContext( - user=user, - state=state, - requested_extensions=get_requested_extensions( - request.headers.getlist(HTTP_EXTENSION_HEADER) - ), - ) + Returns: + A User instance populated with user information from the request. + """ + if 'user' in request.scope: + return StarletteUser(request.user) + return UnauthenticatedUser() diff --git a/src/a2a/server/routes/jsonrpc_dispatcher.py b/src/a2a/server/routes/jsonrpc_dispatcher.py index 9c71399cc..b408cd76f 100644 --- a/src/a2a/server/routes/jsonrpc_dispatcher.py +++ b/src/a2a/server/routes/jsonrpc_dispatcher.py @@ -28,9 +28,8 @@ build_error_response, ) from a2a.server.routes.common import ( - UserBuilder, - build_server_call_context, - default_user_builder, + ContextBuilder, + ) from a2a.types import A2ARequest from a2a.types.a2a_pb2 import ( @@ -145,7 +144,7 @@ def __init__( # noqa: PLR0913 agent_card: AgentCard, request_handler: RequestHandler, extended_agent_card: AgentCard | None = None, - user_builder: UserBuilder | None = None, + context_builder: ContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, extended_card_modifier: Callable[ @@ -162,7 +161,7 @@ def __init__( # noqa: PLR0913 requests via http. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - user_builder: Optional custom user builder to extract user from the + context_builder: Optional custom user builder to extract user from the request. card_modifier: An optional callback to dynamically modify the public agent card before it is served. @@ -183,7 +182,7 @@ def __init__( # noqa: PLR0913 self.extended_agent_card = extended_agent_card self.card_modifier = card_modifier self.extended_card_modifier = extended_card_modifier - self._user_builder = user_builder or default_user_builder + self._context_builder = context_builder or DefaultContextBuilder() self.enable_v0_3_compat = enable_v0_3_compat self._v03_adapter: JSONRPC03Adapter | None = None @@ -192,7 +191,7 @@ def __init__( # noqa: PLR0913 agent_card=agent_card, http_handler=request_handler, extended_agent_card=extended_agent_card, - user_builder=self._user_builder, + context_builder=self._context_builder, card_modifier=card_modifier, extended_card_modifier=extended_card_modifier, ) diff --git a/src/a2a/server/routes/jsonrpc_routes.py b/src/a2a/server/routes/jsonrpc_routes.py index beb51e5ed..bea2d4e27 100644 --- a/src/a2a/server/routes/jsonrpc_routes.py +++ b/src/a2a/server/routes/jsonrpc_routes.py @@ -19,7 +19,7 @@ from a2a.server.context import ServerCallContext from a2a.server.request_handlers.request_handler import RequestHandler -from a2a.server.routes.common import UserBuilder +from a2a.server.routes.common import ContextBuilder from a2a.server.routes.jsonrpc_dispatcher import JsonRpcDispatcher from a2a.types.a2a_pb2 import AgentCard @@ -29,7 +29,7 @@ def create_jsonrpc_routes( # noqa: PLR0913 request_handler: RequestHandler, rpc_url: str, extended_agent_card: AgentCard | None = None, - user_builder: UserBuilder | None = None, + context_builder: ContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, extended_card_modifier: Callable[ @@ -51,7 +51,7 @@ def create_jsonrpc_routes( # noqa: PLR0913 rpc_url: The URL prefix for the RPC endpoints. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - user_builder: Optional custom user builder to extract user from the + context_builder: Optional custom context builder to extract user from the request. card_modifier: An optional callback to dynamically modify the public agent card before it is served. @@ -71,7 +71,7 @@ def create_jsonrpc_routes( # noqa: PLR0913 agent_card=agent_card, request_handler=request_handler, extended_agent_card=extended_agent_card, - user_builder=user_builder, + context_builder=context_builder, card_modifier=card_modifier, extended_card_modifier=extended_card_modifier, enable_v0_3_compat=enable_v0_3_compat, diff --git a/src/a2a/server/routes/rest_dispatcher.py b/src/a2a/server/routes/rest_dispatcher.py index 1c7e2c570..bf399f398 100644 --- a/src/a2a/server/routes/rest_dispatcher.py +++ b/src/a2a/server/routes/rest_dispatcher.py @@ -9,7 +9,7 @@ from a2a.server.context import ServerCallContext from a2a.server.request_handlers.request_handler import RequestHandler from a2a.server.routes.common import ( - UserBuilder, + ContextBuilder, build_server_call_context, default_user_builder, ) @@ -72,7 +72,7 @@ def __init__( # noqa: PLR0913 agent_card: AgentCard, request_handler: RequestHandler, extended_agent_card: AgentCard | None = None, - user_builder: UserBuilder | None = None, + context_builder: ContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, extended_card_modifier: Callable[ @@ -87,7 +87,7 @@ def __init__( # noqa: PLR0913 request_handler: The underlying `RequestHandler` instance to delegate requests to. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - user_builder: Optional custom user builder to extract user from the + context_builder: Optional custom user builder to extract user from the request. card_modifier: An optional callback to dynamically modify the public agent card before it is served. @@ -106,7 +106,7 @@ def __init__( # noqa: PLR0913 self.extended_agent_card = extended_agent_card self.card_modifier = card_modifier self.extended_card_modifier = extended_card_modifier - self._user_builder = user_builder or default_user_builder + self._user_builder =context_builder or default_user_builder self.request_handler = request_handler def _build_call_context(self, request: Request) -> ServerCallContext: diff --git a/src/a2a/server/routes/rest_routes.py b/src/a2a/server/routes/rest_routes.py index 8b0ad85d7..b565de588 100644 --- a/src/a2a/server/routes/rest_routes.py +++ b/src/a2a/server/routes/rest_routes.py @@ -6,7 +6,7 @@ from a2a.compat.v0_3.rest_adapter import REST03Adapter from a2a.server.context import ServerCallContext from a2a.server.request_handlers.request_handler import RequestHandler -from a2a.server.routes.common import UserBuilder +from a2a.server.routes.common import ContextBuilder from a2a.server.routes.rest_dispatcher import RestDispatcher from a2a.types.a2a_pb2 import ( AgentCard, @@ -46,7 +46,7 @@ def create_rest_routes( # noqa: PLR0913 agent_card: AgentCard, request_handler: RequestHandler, extended_agent_card: AgentCard | None = None, - user_builder: UserBuilder | None = None, + context_builder: ContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, extended_card_modifier: Callable[ @@ -64,7 +64,7 @@ def create_rest_routes( # noqa: PLR0913 requests via http. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - user_builder: Optional custom user builder to extract user from the + context_builder: Optional custom user builder to extract user from the request. card_modifier: An optional callback to dynamically modify the public agent card before it is served. @@ -86,7 +86,7 @@ def create_rest_routes( # noqa: PLR0913 agent_card=agent_card, request_handler=request_handler, extended_agent_card=extended_agent_card, - user_builder=user_builder, + context_builder=user_builder, card_modifier=card_modifier, extended_card_modifier=extended_card_modifier, ) @@ -97,7 +97,7 @@ def create_rest_routes( # noqa: PLR0913 agent_card=agent_card, http_handler=request_handler, extended_agent_card=extended_agent_card, - user_builder=user_builder, + context_builder=user_builder, card_modifier=card_modifier, extended_card_modifier=extended_card_modifier, ) diff --git a/tests/server/routes/test_common.py b/tests/server/routes/test_common.py index 0e7a3b81d..fc816abbf 100644 --- a/tests/server/routes/test_common.py +++ b/tests/server/routes/test_common.py @@ -58,7 +58,7 @@ def _make_mock_request(scope=None, headers=None): return request -class TestDefaultUserBuilder: +class TestDefaultContextBuilder: def test_returns_unauthenticated_user_when_no_user_in_scope(self): request = _make_mock_request(scope={}) user = default_user_builder(request) diff --git a/tests/server/routes/test_jsonrpc_dispatcher.py b/tests/server/routes/test_jsonrpc_dispatcher.py index 7ca6ecd28..bb1f11843 100644 --- a/tests/server/routes/test_jsonrpc_dispatcher.py +++ b/tests/server/routes/test_jsonrpc_dispatcher.py @@ -22,7 +22,7 @@ ) from a2a.server.routes import jsonrpc_dispatcher from a2a.server.routes.common import ( - UserBuilder, + ContextBuilder, build_server_call_context, ) from a2a.server.routes.jsonrpc_dispatcher import JsonRpcDispatcher From f8b0b8c2460a335cfb7b200251ff6a562427d6e2 Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Fri, 3 Apr 2026 12:20:49 +0000 Subject: [PATCH 07/12] fix changes --- src/a2a/compat/v0_3/grpc_handler.py | 17 ++---- src/a2a/compat/v0_3/rest_adapter.py | 11 ++-- .../server/request_handlers/grpc_handler.py | 60 +++++++++++-------- src/a2a/server/routes/__init__.py | 3 +- src/a2a/server/routes/common.py | 2 +- src/a2a/server/routes/jsonrpc_dispatcher.py | 12 ++-- src/a2a/server/routes/jsonrpc_routes.py | 6 +- src/a2a/server/routes/rest_dispatcher.py | 11 ++-- src/a2a/server/routes/rest_routes.py | 8 +-- tests/server/routes/test_common.py | 30 +++++----- .../server/routes/test_jsonrpc_dispatcher.py | 5 +- 11 files changed, 81 insertions(+), 84 deletions(-) diff --git a/src/a2a/compat/v0_3/grpc_handler.py b/src/a2a/compat/v0_3/grpc_handler.py index 2b42c6088..cd9864419 100644 --- a/src/a2a/compat/v0_3/grpc_handler.py +++ b/src/a2a/compat/v0_3/grpc_handler.py @@ -23,9 +23,8 @@ from a2a.server.context import ServerCallContext from a2a.server.request_handlers.grpc_handler import ( _ERROR_CODE_MAP, + DefaultGrpcContextBuilder, GrpcContextBuilder, - build_grpc_server_call_context, - default_grpc_user_builder, ) from a2a.server.request_handlers.request_handler import RequestHandler from a2a.types.a2a_pb2 import AgentCard @@ -45,7 +44,7 @@ def __init__( self, agent_card: AgentCard, request_handler: RequestHandler, - context_builder: GrpcContextBuilder | None = None, + context_builder: GrpcContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, ): @@ -55,14 +54,14 @@ def __init__( agent_card: The AgentCard describing the agent's capabilities (v1.0). request_handler: The underlying `RequestHandler` instance to delegate requests to. - context_builder: Optional custom user builder to extract user from the + context_builder: Optional custom user builder to extract user from the gRPC context. card_modifier: An optional callback to dynamically modify the public agent card before it is served. """ self.agent_card = agent_card self.handler03 = RequestHandler03(request_handler=request_handler) - self.user_builder =context_builder or default_grpc_user_builder + self._context_builder = context_builder or DefaultGrpcContextBuilder() self.card_modifier = card_modifier async def _handle_unary( @@ -73,9 +72,7 @@ async def _handle_unary( ) -> TResponse: """Centralized error handling and context management for unary calls.""" try: - server_context = build_grpc_server_call_context( - context, self.user_builder - ) + server_context = self._context_builder.build(context) result = await handler_func(server_context) self._set_extension_metadata(context, server_context) except A2AError as e: @@ -91,9 +88,7 @@ async def _handle_stream( ) -> AsyncIterable[TResponse]: """Centralized error handling and context management for streaming calls.""" try: - server_context = build_grpc_server_call_context( - context, self.user_builder - ) + server_context = self._context_builder.build(context) async for item in handler_func(server_context): yield item self._set_extension_metadata(context, server_context) diff --git a/src/a2a/compat/v0_3/rest_adapter.py b/src/a2a/compat/v0_3/rest_adapter.py index 4acf68e32..06e544bdf 100644 --- a/src/a2a/compat/v0_3/rest_adapter.py +++ b/src/a2a/compat/v0_3/rest_adapter.py @@ -36,8 +36,7 @@ from a2a.server.context import ServerCallContext from a2a.server.routes.common import ( ContextBuilder, - build_server_call_context, - default_user_builder, + DefaultContextBuilder, ) from a2a.utils.error_handlers import ( rest_error_handler, @@ -64,7 +63,7 @@ def __init__( # noqa: PLR0913 agent_card: 'AgentCard', http_handler: 'RequestHandler', extended_agent_card: 'AgentCard | None' = None, - context_builder: 'ContextBuilder | None' = None, + context_builder: 'ContextBuilder | None' = None, card_modifier: 'Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None' = None, extended_card_modifier: 'Callable[[AgentCard, ServerCallContext], Awaitable[AgentCard] | AgentCard] | None' = None, ): @@ -75,7 +74,7 @@ def __init__( # noqa: PLR0913 self.handler = REST03Handler( agent_card=agent_card, request_handler=http_handler ) - self._user_builder =context_builder or default_user_builder + self._context_builder = context_builder or DefaultContextBuilder() @rest_error_handler async def _handle_request( @@ -83,7 +82,7 @@ async def _handle_request( method: 'Callable[[Request, ServerCallContext], Awaitable[Any]]', request: Request, ) -> Response: - call_context = build_server_call_context(request, self._user_builder) + call_context = self._context_builder.build(request) response = await method(request, call_context) return JSONResponse(content=response) @@ -100,7 +99,7 @@ async def _handle_streaming_request( message=f'Failed to pre-consume request body: {e}' ) from e - call_context = build_server_call_context(request, self._user_builder) + call_context = self._context_builder.build(request) async def event_generator( stream: AsyncIterable[Any], diff --git a/src/a2a/server/request_handlers/grpc_handler.py b/src/a2a/server/request_handlers/grpc_handler.py index db55206d9..685b2a3b9 100644 --- a/src/a2a/server/request_handlers/grpc_handler.py +++ b/src/a2a/server/request_handlers/grpc_handler.py @@ -1,6 +1,7 @@ # ruff: noqa: N802 import logging +from abc import ABC, abstractmethod from collections.abc import AsyncIterable, Awaitable, Callable from typing import TypeVar @@ -40,12 +41,37 @@ logger = logging.getLogger(__name__) -GrpcContextBuilder = Callable[[grpc.aio.ServicerContext], User] +class GrpcContextBuilder(ABC): + """Interface for building ServerCallContext from gRPC context.""" -def default_grpc_user_builder(context: grpc.aio.ServicerContext) -> User: - """Default strategy for creating a User from a gRPC context.""" - return UnauthenticatedUser() + @abstractmethod + def build(self, context: grpc.aio.ServicerContext) -> ServerCallContext: + """Builds a ServerCallContext from a gRPC ServicerContext.""" + + @abstractmethod + def build_user(self, context: grpc.aio.ServicerContext) -> User: + """Builds a User from a gRPC ServicerContext.""" + + +class DefaultGrpcContextBuilder(GrpcContextBuilder): + """Default implementation of GrpcContextBuilder.""" + + def build(self, context: grpc.aio.ServicerContext) -> ServerCallContext: + """Builds a ServerCallContext from a gRPC ServicerContext.""" + user = self.build_user(context) + state = {'grpc_context': context} + return ServerCallContext( + user=user, + state=state, + requested_extensions=get_requested_extensions( + _get_metadata_value(context, HTTP_EXTENSION_HEADER) + ), + ) + + def build_user(self, context: grpc.aio.ServicerContext) -> User: + """Builds a User from a gRPC ServicerContext.""" + return UnauthenticatedUser() def _get_metadata_value( @@ -62,22 +88,6 @@ def _get_metadata_value( if k.lower() == lower_key ] - -def build_grpc_server_call_context( - context: grpc.aio.ServicerContext,context_builder: GrpcContextBuilder -) -> ServerCallContext: - """Builds a ServerCallContext from a gRPC ServicerContext.""" - user =context_builder(context) - state = {'grpc_context': context} - return ServerCallContext( - user=user, - state=state, - requested_extensions=get_requested_extensions( - _get_metadata_value(context, HTTP_EXTENSION_HEADER) - ), - ) - - _ERROR_CODE_MAP = { types.InvalidRequestError: grpc.StatusCode.INVALID_ARGUMENT, types.MethodNotFoundError: grpc.StatusCode.NOT_FOUND, @@ -105,7 +115,7 @@ def __init__( self, agent_card: AgentCard, request_handler: RequestHandler, - context_builder: GrpcContextBuilder | None = None, + context_builder: GrpcContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, ): @@ -115,14 +125,14 @@ def __init__( agent_card: The AgentCard describing the agent's capabilities. request_handler: The underlying `RequestHandler` instance to delegate requests to. - context_builder: Optional custom user builder to extract user from the + context_builder: Optional custom user builder to extract user from the gRPC context. card_modifier: An optional callback to dynamically modify the public agent card before it is served. """ self.agent_card = agent_card self.request_handler = request_handler - self.user_builder =context_builder or default_grpc_user_builder + self._context_builder = context_builder or DefaultGrpcContextBuilder() self.card_modifier = card_modifier async def _handle_unary( @@ -446,8 +456,6 @@ def _build_call_context( context: grpc.aio.ServicerContext, request: message.Message, ) -> ServerCallContext: - server_context = build_grpc_server_call_context( - context, self.user_builder - ) + server_context = self._context_builder.build(context) server_context.tenant = getattr(request, 'tenant', '') return server_context diff --git a/src/a2a/server/routes/__init__.py b/src/a2a/server/routes/__init__.py index 0b26ebc59..fc9178713 100644 --- a/src/a2a/server/routes/__init__.py +++ b/src/a2a/server/routes/__init__.py @@ -1,13 +1,14 @@ """A2A Routes.""" from a2a.server.routes.agent_card_routes import create_agent_card_routes -from a2a.server.routes.common import ContextBuilder +from a2a.server.routes.common import ContextBuilder, DefaultContextBuilder from a2a.server.routes.jsonrpc_routes import create_jsonrpc_routes from a2a.server.routes.rest_routes import create_rest_routes __all__ = [ 'ContextBuilder', + 'DefaultContextBuilder', 'create_agent_card_routes', 'create_jsonrpc_routes', 'create_rest_routes', diff --git a/src/a2a/server/routes/common.py b/src/a2a/server/routes/common.py index 4af36c87f..17fd58932 100644 --- a/src/a2a/server/routes/common.py +++ b/src/a2a/server/routes/common.py @@ -1,5 +1,4 @@ from abc import ABC, abstractmethod -from collections.abc import Callable from typing import TYPE_CHECKING, Any @@ -21,6 +20,7 @@ ) from a2a.server.context import ServerCallContext + class StarletteUser(User): """Adapts a Starlette BaseUser to the A2A User interface.""" diff --git a/src/a2a/server/routes/jsonrpc_dispatcher.py b/src/a2a/server/routes/jsonrpc_dispatcher.py index b408cd76f..7646b38d5 100644 --- a/src/a2a/server/routes/jsonrpc_dispatcher.py +++ b/src/a2a/server/routes/jsonrpc_dispatcher.py @@ -29,7 +29,7 @@ ) from a2a.server.routes.common import ( ContextBuilder, - + DefaultContextBuilder, ) from a2a.types import A2ARequest from a2a.types.a2a_pb2 import ( @@ -144,7 +144,7 @@ def __init__( # noqa: PLR0913 agent_card: AgentCard, request_handler: RequestHandler, extended_agent_card: AgentCard | None = None, - context_builder: ContextBuilder | None = None, + context_builder: ContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, extended_card_modifier: Callable[ @@ -161,7 +161,7 @@ def __init__( # noqa: PLR0913 requests via http. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - context_builder: Optional custom user builder to extract user from the + context_builder: Optional custom user builder to extract user from the request. card_modifier: An optional callback to dynamically modify the public agent card before it is served. @@ -191,7 +191,7 @@ def __init__( # noqa: PLR0913 agent_card=agent_card, http_handler=request_handler, extended_agent_card=extended_agent_card, - context_builder=self._context_builder, + context_builder=self._context_builder, card_modifier=card_modifier, extended_card_modifier=extended_card_modifier, ) @@ -333,9 +333,7 @@ async def handle_requests(self, request: Request) -> Response: # noqa: PLR0911, ) # 3) Build call context and wrap the request for downstream handling - call_context = build_server_call_context( - request, self._user_builder - ) + call_context = self._context_builder.build(request) call_context.tenant = getattr(specific_request, 'tenant', '') call_context.state['method'] = method call_context.state['request_id'] = request_id diff --git a/src/a2a/server/routes/jsonrpc_routes.py b/src/a2a/server/routes/jsonrpc_routes.py index bea2d4e27..bee6ac4f3 100644 --- a/src/a2a/server/routes/jsonrpc_routes.py +++ b/src/a2a/server/routes/jsonrpc_routes.py @@ -29,7 +29,7 @@ def create_jsonrpc_routes( # noqa: PLR0913 request_handler: RequestHandler, rpc_url: str, extended_agent_card: AgentCard | None = None, - context_builder: ContextBuilder | None = None, + context_builder: ContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, extended_card_modifier: Callable[ @@ -51,7 +51,7 @@ def create_jsonrpc_routes( # noqa: PLR0913 rpc_url: The URL prefix for the RPC endpoints. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - context_builder: Optional custom context builder to extract user from the + context_builder: Optional custom context builder to extract user from the request. card_modifier: An optional callback to dynamically modify the public agent card before it is served. @@ -71,7 +71,7 @@ def create_jsonrpc_routes( # noqa: PLR0913 agent_card=agent_card, request_handler=request_handler, extended_agent_card=extended_agent_card, - context_builder=context_builder, + context_builder=context_builder, card_modifier=card_modifier, extended_card_modifier=extended_card_modifier, enable_v0_3_compat=enable_v0_3_compat, diff --git a/src/a2a/server/routes/rest_dispatcher.py b/src/a2a/server/routes/rest_dispatcher.py index bf399f398..8721518d5 100644 --- a/src/a2a/server/routes/rest_dispatcher.py +++ b/src/a2a/server/routes/rest_dispatcher.py @@ -10,8 +10,7 @@ from a2a.server.request_handlers.request_handler import RequestHandler from a2a.server.routes.common import ( ContextBuilder, - build_server_call_context, - default_user_builder, + DefaultContextBuilder, ) from a2a.types import a2a_pb2 from a2a.types.a2a_pb2 import ( @@ -72,7 +71,7 @@ def __init__( # noqa: PLR0913 agent_card: AgentCard, request_handler: RequestHandler, extended_agent_card: AgentCard | None = None, - context_builder: ContextBuilder | None = None, + context_builder: ContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, extended_card_modifier: Callable[ @@ -87,7 +86,7 @@ def __init__( # noqa: PLR0913 request_handler: The underlying `RequestHandler` instance to delegate requests to. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - context_builder: Optional custom user builder to extract user from the + context_builder: Optional custom user builder to extract user from the request. card_modifier: An optional callback to dynamically modify the public agent card before it is served. @@ -106,11 +105,11 @@ def __init__( # noqa: PLR0913 self.extended_agent_card = extended_agent_card self.card_modifier = card_modifier self.extended_card_modifier = extended_card_modifier - self._user_builder =context_builder or default_user_builder + self._context_builder = context_builder or DefaultContextBuilder() self.request_handler = request_handler def _build_call_context(self, request: Request) -> ServerCallContext: - call_context = build_server_call_context(request, self._user_builder) + call_context = self._context_builder.build(request) if 'tenant' in request.path_params: call_context.tenant = request.path_params['tenant'] return call_context diff --git a/src/a2a/server/routes/rest_routes.py b/src/a2a/server/routes/rest_routes.py index b565de588..62fedfda2 100644 --- a/src/a2a/server/routes/rest_routes.py +++ b/src/a2a/server/routes/rest_routes.py @@ -46,7 +46,7 @@ def create_rest_routes( # noqa: PLR0913 agent_card: AgentCard, request_handler: RequestHandler, extended_agent_card: AgentCard | None = None, - context_builder: ContextBuilder | None = None, + context_builder: ContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, extended_card_modifier: Callable[ @@ -64,7 +64,7 @@ def create_rest_routes( # noqa: PLR0913 requests via http. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - context_builder: Optional custom user builder to extract user from the + context_builder: Optional custom user builder to extract user from the request. card_modifier: An optional callback to dynamically modify the public agent card before it is served. @@ -86,7 +86,7 @@ def create_rest_routes( # noqa: PLR0913 agent_card=agent_card, request_handler=request_handler, extended_agent_card=extended_agent_card, - context_builder=user_builder, + context_builder=context_builder, card_modifier=card_modifier, extended_card_modifier=extended_card_modifier, ) @@ -97,7 +97,7 @@ def create_rest_routes( # noqa: PLR0913 agent_card=agent_card, http_handler=request_handler, extended_agent_card=extended_agent_card, - context_builder=user_builder, + context_builder=context_builder, card_modifier=card_modifier, extended_card_modifier=extended_card_modifier, ) diff --git a/tests/server/routes/test_common.py b/tests/server/routes/test_common.py index fc816abbf..639058300 100644 --- a/tests/server/routes/test_common.py +++ b/tests/server/routes/test_common.py @@ -13,8 +13,7 @@ from a2a.server.context import ServerCallContext from a2a.server.routes.common import ( StarletteUser, - build_server_call_context, - default_user_builder, + DefaultContextBuilder, ) @@ -61,7 +60,7 @@ def _make_mock_request(scope=None, headers=None): class TestDefaultContextBuilder: def test_returns_unauthenticated_user_when_no_user_in_scope(self): request = _make_mock_request(scope={}) - user = default_user_builder(request) + user = DefaultContextBuilder().build_user(request) assert isinstance(user, UnauthenticatedUser) assert user.is_authenticated is False assert user.user_name == '' @@ -73,7 +72,7 @@ def test_returns_proxy_when_user_in_scope(self): request = _make_mock_request(scope={'user': starlette_user}) request.user = starlette_user - user = default_user_builder(request) + user = DefaultContextBuilder().build_user(request) assert isinstance(user, StarletteUser) assert user.is_authenticated is True assert user.user_name == 'Alice' @@ -85,7 +84,7 @@ def test_returns_unauthenticated_proxy_when_user_not_authenticated(self): request = _make_mock_request(scope={'user': starlette_user}) request.user = starlette_user - user = default_user_builder(request) + user = DefaultContextBuilder().build_user(request) assert isinstance(user, StarletteUser) assert user.is_authenticated is False @@ -98,7 +97,7 @@ def test_basic_context_with_default_user_builder(self): request = _make_mock_request( scope={}, headers={'content-type': 'application/json'} ) - ctx = build_server_call_context(request, default_user_builder) + ctx = DefaultContextBuilder().build(request) assert isinstance(ctx, ServerCallContext) assert isinstance(ctx.user, UnauthenticatedUser) @@ -111,46 +110,47 @@ def test_auth_populated_when_in_scope(self): request = _make_mock_request(scope={'auth': auth_credentials}) request.auth = auth_credentials - ctx = build_server_call_context(request, default_user_builder) + ctx = DefaultContextBuilder().build(request) assert ctx.state['auth'] is auth_credentials def test_auth_not_populated_when_not_in_scope(self): request = _make_mock_request(scope={}) - ctx = build_server_call_context(request, default_user_builder) + ctx = DefaultContextBuilder().build(request) assert 'auth' not in ctx.state def test_headers_captured_in_state(self): request = _make_mock_request( headers={'x-custom': 'value', 'authorization': 'Bearer tok'} ) - ctx = build_server_call_context(request, default_user_builder) + ctx = DefaultContextBuilder().build(request) assert ctx.state['headers']['x-custom'] == 'value' assert ctx.state['headers']['authorization'] == 'Bearer tok' def test_requested_extensions_single(self): request = _make_mock_request(headers={HTTP_EXTENSION_HEADER: 'foo'}) - ctx = build_server_call_context(request, default_user_builder) + ctx = DefaultContextBuilder().build(request) assert ctx.requested_extensions == {'foo'} def test_requested_extensions_comma_separated(self): request = _make_mock_request( headers={HTTP_EXTENSION_HEADER: 'foo, bar'} ) - ctx = build_server_call_context(request, default_user_builder) + ctx = DefaultContextBuilder().build(request) assert ctx.requested_extensions == {'foo', 'bar'} def test_no_extensions(self): request = _make_mock_request() - ctx = build_server_call_context(request, default_user_builder) + ctx = DefaultContextBuilder().build(request) assert ctx.requested_extensions == set() def test_custom_user_builder(self): custom_user = MagicMock(spec=UnauthenticatedUser) custom_user.is_authenticated = True - def my_builder(req): - return custom_user + class MyContextBuilder(DefaultContextBuilder): + def build_user(self, req): + return custom_user request = _make_mock_request() - ctx = build_server_call_context(request, my_builder) + ctx = MyContextBuilder().build(request) assert ctx.user is custom_user diff --git a/tests/server/routes/test_jsonrpc_dispatcher.py b/tests/server/routes/test_jsonrpc_dispatcher.py index bb1f11843..31a550de3 100644 --- a/tests/server/routes/test_jsonrpc_dispatcher.py +++ b/tests/server/routes/test_jsonrpc_dispatcher.py @@ -21,10 +21,7 @@ Role, ) from a2a.server.routes import jsonrpc_dispatcher -from a2a.server.routes.common import ( - ContextBuilder, - build_server_call_context, -) + from a2a.server.routes.jsonrpc_dispatcher import JsonRpcDispatcher from a2a.server.routes.jsonrpc_routes import create_jsonrpc_routes from a2a.server.routes.agent_card_routes import create_agent_card_routes From 700ecf6d029d4bb178aace53b31f4c6ec7b09fca Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Fri, 3 Apr 2026 12:21:43 +0000 Subject: [PATCH 08/12] fix --- src/a2a/server/routes/common.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/a2a/server/routes/common.py b/src/a2a/server/routes/common.py index 17fd58932..ceaf58ade 100644 --- a/src/a2a/server/routes/common.py +++ b/src/a2a/server/routes/common.py @@ -45,10 +45,6 @@ class ContextBuilder(ABC): def build(self, request: Request) -> ServerCallContext: """Builds a ServerCallContext from a Starlette Request.""" - @abstractmethod - def build_user(self, request: Request) -> User: - """Builds a User from a Starlette Request.""" - class DefaultContextBuilder(ContextBuilder): """A default implementation of ContextBuilder.""" From 8db4e8c5bce2545381f6ab272888f908bd208fb2 Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Fri, 3 Apr 2026 12:35:36 +0000 Subject: [PATCH 09/12] refactor: standardize ContextBuilder documentation and remove redundant build_user method from GrpcContextBuilder --- src/a2a/server/request_handlers/grpc_handler.py | 13 +++++-------- src/a2a/server/routes/jsonrpc_dispatcher.py | 5 +++-- src/a2a/server/routes/jsonrpc_routes.py | 5 +++-- src/a2a/server/routes/rest_dispatcher.py | 5 +++-- src/a2a/server/routes/rest_routes.py | 5 +++-- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/a2a/server/request_handlers/grpc_handler.py b/src/a2a/server/request_handlers/grpc_handler.py index 685b2a3b9..4b68746bd 100644 --- a/src/a2a/server/request_handlers/grpc_handler.py +++ b/src/a2a/server/request_handlers/grpc_handler.py @@ -49,20 +49,15 @@ class GrpcContextBuilder(ABC): def build(self, context: grpc.aio.ServicerContext) -> ServerCallContext: """Builds a ServerCallContext from a gRPC ServicerContext.""" - @abstractmethod - def build_user(self, context: grpc.aio.ServicerContext) -> User: - """Builds a User from a gRPC ServicerContext.""" - class DefaultGrpcContextBuilder(GrpcContextBuilder): """Default implementation of GrpcContextBuilder.""" def build(self, context: grpc.aio.ServicerContext) -> ServerCallContext: """Builds a ServerCallContext from a gRPC ServicerContext.""" - user = self.build_user(context) state = {'grpc_context': context} return ServerCallContext( - user=user, + user=self.build_user(context), state=state, requested_extensions=get_requested_extensions( _get_metadata_value(context, HTTP_EXTENSION_HEADER) @@ -88,6 +83,7 @@ def _get_metadata_value( if k.lower() == lower_key ] + _ERROR_CODE_MAP = { types.InvalidRequestError: grpc.StatusCode.INVALID_ARGUMENT, types.MethodNotFoundError: grpc.StatusCode.NOT_FOUND, @@ -125,8 +121,9 @@ def __init__( agent_card: The AgentCard describing the agent's capabilities. request_handler: The underlying `RequestHandler` instance to delegate requests to. - context_builder: Optional custom user builder to extract user from the - gRPC context. + context_builder: The GrpcContextBuilder used to construct the + ServerCallContext passed to the request_handler. If None the + DefaultGrpcContextBuilder is used. card_modifier: An optional callback to dynamically modify the public agent card before it is served. """ diff --git a/src/a2a/server/routes/jsonrpc_dispatcher.py b/src/a2a/server/routes/jsonrpc_dispatcher.py index 7646b38d5..a18c6320c 100644 --- a/src/a2a/server/routes/jsonrpc_dispatcher.py +++ b/src/a2a/server/routes/jsonrpc_dispatcher.py @@ -161,8 +161,9 @@ def __init__( # noqa: PLR0913 requests via http. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - context_builder: Optional custom user builder to extract user from the - request. + context_builder: The ContextBuilder used to construct the + ServerCallContext passed to the request_handler. If None the + DefaultContextBuilder is used. card_modifier: An optional callback to dynamically modify the public agent card before it is served. extended_card_modifier: An optional callback to dynamically modify diff --git a/src/a2a/server/routes/jsonrpc_routes.py b/src/a2a/server/routes/jsonrpc_routes.py index bee6ac4f3..249643d95 100644 --- a/src/a2a/server/routes/jsonrpc_routes.py +++ b/src/a2a/server/routes/jsonrpc_routes.py @@ -51,8 +51,9 @@ def create_jsonrpc_routes( # noqa: PLR0913 rpc_url: The URL prefix for the RPC endpoints. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - context_builder: Optional custom context builder to extract user from the - request. + context_builder: The ContextBuilder used to construct the + ServerCallContext passed to the request_handler. If None the + DefaultContextBuilder is used. card_modifier: An optional callback to dynamically modify the public agent card before it is served. extended_card_modifier: An optional callback to dynamically modify diff --git a/src/a2a/server/routes/rest_dispatcher.py b/src/a2a/server/routes/rest_dispatcher.py index 8721518d5..56c557797 100644 --- a/src/a2a/server/routes/rest_dispatcher.py +++ b/src/a2a/server/routes/rest_dispatcher.py @@ -86,8 +86,9 @@ def __init__( # noqa: PLR0913 request_handler: The underlying `RequestHandler` instance to delegate requests to. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - context_builder: Optional custom user builder to extract user from the - request. + context_builder: The ContextBuilder used to construct the + ServerCallContext passed to the request_handler. If None the + DefaultContextBuilder is used. card_modifier: An optional callback to dynamically modify the public agent card before it is served. extended_card_modifier: An optional callback to dynamically modify diff --git a/src/a2a/server/routes/rest_routes.py b/src/a2a/server/routes/rest_routes.py index 62fedfda2..de28b1295 100644 --- a/src/a2a/server/routes/rest_routes.py +++ b/src/a2a/server/routes/rest_routes.py @@ -64,8 +64,9 @@ def create_rest_routes( # noqa: PLR0913 requests via http. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - context_builder: Optional custom user builder to extract user from the - request. + context_builder: The ContextBuilder used to construct the + ServerCallContext passed to the request_handler. If None the + DefaultContextBuilder is used. card_modifier: An optional callback to dynamically modify the public agent card before it is served. extended_card_modifier: An optional callback to dynamically modify From c3ee2e5e48470e0e5bf53ec85b750f7577694527 Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Fri, 3 Apr 2026 14:23:07 +0000 Subject: [PATCH 10/12] refactor: rename context builder classes and types to include ServerCall prefix for clarity --- src/a2a/compat/v0_3/grpc_handler.py | 8 +++---- src/a2a/compat/v0_3/jsonrpc_adapter.py | 6 ++--- src/a2a/compat/v0_3/rest_adapter.py | 6 ++--- src/a2a/server/request_handlers/__init__.py | 4 ++++ .../server/request_handlers/grpc_handler.py | 10 ++++---- src/a2a/server/routes/__init__.py | 2 +- src/a2a/server/routes/common.py | 6 ++--- src/a2a/server/routes/jsonrpc_dispatcher.py | 8 +++---- src/a2a/server/routes/jsonrpc_routes.py | 4 ++-- src/a2a/server/routes/rest_dispatcher.py | 8 +++---- src/a2a/server/routes/rest_routes.py | 4 ++-- tests/server/routes/test_common.py | 24 +++++++++---------- 12 files changed, 47 insertions(+), 43 deletions(-) diff --git a/src/a2a/compat/v0_3/grpc_handler.py b/src/a2a/compat/v0_3/grpc_handler.py index cd9864419..f7652e8a9 100644 --- a/src/a2a/compat/v0_3/grpc_handler.py +++ b/src/a2a/compat/v0_3/grpc_handler.py @@ -23,8 +23,8 @@ from a2a.server.context import ServerCallContext from a2a.server.request_handlers.grpc_handler import ( _ERROR_CODE_MAP, - DefaultGrpcContextBuilder, - GrpcContextBuilder, + DefaultGrpcServerCallContextBuilder, + GrpcServerCallContextBuilder, ) from a2a.server.request_handlers.request_handler import RequestHandler from a2a.types.a2a_pb2 import AgentCard @@ -44,7 +44,7 @@ def __init__( self, agent_card: AgentCard, request_handler: RequestHandler, - context_builder: GrpcContextBuilder | None = None, + context_builder: GrpcServerCallContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, ): @@ -61,7 +61,7 @@ def __init__( """ self.agent_card = agent_card self.handler03 = RequestHandler03(request_handler=request_handler) - self._context_builder = context_builder or DefaultGrpcContextBuilder() + self._context_builder = context_builder or DefaultGrpcServerCallContextBuilder() self.card_modifier = card_modifier async def _handle_unary( diff --git a/src/a2a/compat/v0_3/jsonrpc_adapter.py b/src/a2a/compat/v0_3/jsonrpc_adapter.py index c5507af67..d04d20f6d 100644 --- a/src/a2a/compat/v0_3/jsonrpc_adapter.py +++ b/src/a2a/compat/v0_3/jsonrpc_adapter.py @@ -38,8 +38,8 @@ JSONRPCError as CoreJSONRPCError, ) from a2a.server.routes.common import ( - ContextBuilder, - DefaultContextBuilder, + ServerCallContextBuilder, + DefaultServerCallContextBuilder, ) from a2a.utils import constants from a2a.utils.errors import ExtendedAgentCardNotConfiguredError @@ -81,7 +81,7 @@ def __init__( # noqa: PLR0913 self.handler = RequestHandler03( request_handler=http_handler, ) - self._context_builder = context_builder or DefaultContextBuilder() + self._context_builder = context_builder or DefaultServerCallContextBuilder() def supports_method(self, method: str) -> bool: """Returns True if the v0.3 adapter supports the given method name.""" diff --git a/src/a2a/compat/v0_3/rest_adapter.py b/src/a2a/compat/v0_3/rest_adapter.py index 06e544bdf..467a9d15a 100644 --- a/src/a2a/compat/v0_3/rest_adapter.py +++ b/src/a2a/compat/v0_3/rest_adapter.py @@ -35,8 +35,8 @@ from a2a.compat.v0_3.rest_handler import REST03Handler from a2a.server.context import ServerCallContext from a2a.server.routes.common import ( - ContextBuilder, - DefaultContextBuilder, + ServerCallContextBuilder, + DefaultServerCallContextBuilder, ) from a2a.utils.error_handlers import ( rest_error_handler, @@ -74,7 +74,7 @@ def __init__( # noqa: PLR0913 self.handler = REST03Handler( agent_card=agent_card, request_handler=http_handler ) - self._context_builder = context_builder or DefaultContextBuilder() + self._context_builder = context_builder or DefaultServerCallContextBuilder() @rest_error_handler async def _handle_request( diff --git a/src/a2a/server/request_handlers/__init__.py b/src/a2a/server/request_handlers/__init__.py index f239af3e6..b7f8ac23f 100644 --- a/src/a2a/server/request_handlers/__init__.py +++ b/src/a2a/server/request_handlers/__init__.py @@ -20,6 +20,8 @@ try: from a2a.server.request_handlers.grpc_handler import ( GrpcHandler, # type: ignore + DefaultGrpcServerCallContextBuilder, + GrpcServerCallContextBuilder, ) except ImportError as e: _original_error = e @@ -43,6 +45,8 @@ def __init__(self, *args, **kwargs): 'GrpcHandler', 'RequestHandler', 'build_error_response', + 'DefaultGrpcServerCallContextBuilder', + 'GrpcServerCallContextBuilder', 'prepare_response_object', 'validate_request_params', ] diff --git a/src/a2a/server/request_handlers/grpc_handler.py b/src/a2a/server/request_handlers/grpc_handler.py index 4b68746bd..ac9aff6a4 100644 --- a/src/a2a/server/request_handlers/grpc_handler.py +++ b/src/a2a/server/request_handlers/grpc_handler.py @@ -42,7 +42,7 @@ logger = logging.getLogger(__name__) -class GrpcContextBuilder(ABC): +class GrpcServerCallContextBuilder(ABC): """Interface for building ServerCallContext from gRPC context.""" @abstractmethod @@ -50,8 +50,8 @@ def build(self, context: grpc.aio.ServicerContext) -> ServerCallContext: """Builds a ServerCallContext from a gRPC ServicerContext.""" -class DefaultGrpcContextBuilder(GrpcContextBuilder): - """Default implementation of GrpcContextBuilder.""" +class DefaultGrpcServerCallContextBuilder(GrpcServerCallContextBuilder): + """Default implementation of GrpcServerCallContextBuilder.""" def build(self, context: grpc.aio.ServicerContext) -> ServerCallContext: """Builds a ServerCallContext from a gRPC ServicerContext.""" @@ -111,7 +111,7 @@ def __init__( self, agent_card: AgentCard, request_handler: RequestHandler, - context_builder: GrpcContextBuilder | None = None, + context_builder: GrpcServerCallContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, ): @@ -129,7 +129,7 @@ def __init__( """ self.agent_card = agent_card self.request_handler = request_handler - self._context_builder = context_builder or DefaultGrpcContextBuilder() + self._context_builder = context_builder or DefaultGrpcServerCallContextBuilder() self.card_modifier = card_modifier async def _handle_unary( diff --git a/src/a2a/server/routes/__init__.py b/src/a2a/server/routes/__init__.py index fc9178713..51c3ccbc0 100644 --- a/src/a2a/server/routes/__init__.py +++ b/src/a2a/server/routes/__init__.py @@ -1,7 +1,7 @@ """A2A Routes.""" from a2a.server.routes.agent_card_routes import create_agent_card_routes -from a2a.server.routes.common import ContextBuilder, DefaultContextBuilder +from a2a.server.routes.common import ServerCallContextBuilder, DefaultServerCallContextBuilder from a2a.server.routes.jsonrpc_routes import create_jsonrpc_routes from a2a.server.routes.rest_routes import create_rest_routes diff --git a/src/a2a/server/routes/common.py b/src/a2a/server/routes/common.py index ceaf58ade..18b6865c5 100644 --- a/src/a2a/server/routes/common.py +++ b/src/a2a/server/routes/common.py @@ -38,7 +38,7 @@ def user_name(self) -> str: return self._user.display_name -class ContextBuilder(ABC): +class ServerCallContextBuilder(ABC): """A class for building ServerCallContexts using the Starlette Request.""" @abstractmethod @@ -46,8 +46,8 @@ def build(self, request: Request) -> ServerCallContext: """Builds a ServerCallContext from a Starlette Request.""" -class DefaultContextBuilder(ContextBuilder): - """A default implementation of ContextBuilder.""" +class DefaultServerCallContextBuilder(ServerCallContextBuilder): + """A default implementation of ServerCallContextBuilder.""" def build(self, request: Request) -> ServerCallContext: """Builds a ServerCallContext from a Starlette Request. diff --git a/src/a2a/server/routes/jsonrpc_dispatcher.py b/src/a2a/server/routes/jsonrpc_dispatcher.py index a18c6320c..1e6360062 100644 --- a/src/a2a/server/routes/jsonrpc_dispatcher.py +++ b/src/a2a/server/routes/jsonrpc_dispatcher.py @@ -28,8 +28,8 @@ build_error_response, ) from a2a.server.routes.common import ( - ContextBuilder, - DefaultContextBuilder, + ServerCallContextBuilder, + DefaultServerCallContextBuilder, ) from a2a.types import A2ARequest from a2a.types.a2a_pb2 import ( @@ -144,7 +144,7 @@ def __init__( # noqa: PLR0913 agent_card: AgentCard, request_handler: RequestHandler, extended_agent_card: AgentCard | None = None, - context_builder: ContextBuilder | None = None, + context_builder: ServerCallContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, extended_card_modifier: Callable[ @@ -183,7 +183,7 @@ def __init__( # noqa: PLR0913 self.extended_agent_card = extended_agent_card self.card_modifier = card_modifier self.extended_card_modifier = extended_card_modifier - self._context_builder = context_builder or DefaultContextBuilder() + self._context_builder = context_builder or DefaultServerCallContextBuilder() self.enable_v0_3_compat = enable_v0_3_compat self._v03_adapter: JSONRPC03Adapter | None = None diff --git a/src/a2a/server/routes/jsonrpc_routes.py b/src/a2a/server/routes/jsonrpc_routes.py index 249643d95..e88f38584 100644 --- a/src/a2a/server/routes/jsonrpc_routes.py +++ b/src/a2a/server/routes/jsonrpc_routes.py @@ -19,7 +19,7 @@ from a2a.server.context import ServerCallContext from a2a.server.request_handlers.request_handler import RequestHandler -from a2a.server.routes.common import ContextBuilder +from a2a.server.routes.common import ServerCallContextBuilder from a2a.server.routes.jsonrpc_dispatcher import JsonRpcDispatcher from a2a.types.a2a_pb2 import AgentCard @@ -29,7 +29,7 @@ def create_jsonrpc_routes( # noqa: PLR0913 request_handler: RequestHandler, rpc_url: str, extended_agent_card: AgentCard | None = None, - context_builder: ContextBuilder | None = None, + context_builder: ServerCallContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, extended_card_modifier: Callable[ diff --git a/src/a2a/server/routes/rest_dispatcher.py b/src/a2a/server/routes/rest_dispatcher.py index 56c557797..66d4ca8bb 100644 --- a/src/a2a/server/routes/rest_dispatcher.py +++ b/src/a2a/server/routes/rest_dispatcher.py @@ -9,8 +9,8 @@ from a2a.server.context import ServerCallContext from a2a.server.request_handlers.request_handler import RequestHandler from a2a.server.routes.common import ( - ContextBuilder, - DefaultContextBuilder, + ServerCallContextBuilder, + DefaultServerCallContextBuilder, ) from a2a.types import a2a_pb2 from a2a.types.a2a_pb2 import ( @@ -71,7 +71,7 @@ def __init__( # noqa: PLR0913 agent_card: AgentCard, request_handler: RequestHandler, extended_agent_card: AgentCard | None = None, - context_builder: ContextBuilder | None = None, + context_builder: ServerCallContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, extended_card_modifier: Callable[ @@ -106,7 +106,7 @@ def __init__( # noqa: PLR0913 self.extended_agent_card = extended_agent_card self.card_modifier = card_modifier self.extended_card_modifier = extended_card_modifier - self._context_builder = context_builder or DefaultContextBuilder() + self._context_builder = context_builder or DefaultServerCallContextBuilder() self.request_handler = request_handler def _build_call_context(self, request: Request) -> ServerCallContext: diff --git a/src/a2a/server/routes/rest_routes.py b/src/a2a/server/routes/rest_routes.py index de28b1295..18bb2a03d 100644 --- a/src/a2a/server/routes/rest_routes.py +++ b/src/a2a/server/routes/rest_routes.py @@ -6,7 +6,7 @@ from a2a.compat.v0_3.rest_adapter import REST03Adapter from a2a.server.context import ServerCallContext from a2a.server.request_handlers.request_handler import RequestHandler -from a2a.server.routes.common import ContextBuilder +from a2a.server.routes.common import ServerCallContextBuilder from a2a.server.routes.rest_dispatcher import RestDispatcher from a2a.types.a2a_pb2 import ( AgentCard, @@ -46,7 +46,7 @@ def create_rest_routes( # noqa: PLR0913 agent_card: AgentCard, request_handler: RequestHandler, extended_agent_card: AgentCard | None = None, - context_builder: ContextBuilder | None = None, + context_builder: ServerCallContextBuilder | None = None, card_modifier: Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None = None, extended_card_modifier: Callable[ diff --git a/tests/server/routes/test_common.py b/tests/server/routes/test_common.py index 639058300..3c4a08d2b 100644 --- a/tests/server/routes/test_common.py +++ b/tests/server/routes/test_common.py @@ -13,7 +13,7 @@ from a2a.server.context import ServerCallContext from a2a.server.routes.common import ( StarletteUser, - DefaultContextBuilder, + DefaultServerCallContextBuilder, ) @@ -60,7 +60,7 @@ def _make_mock_request(scope=None, headers=None): class TestDefaultContextBuilder: def test_returns_unauthenticated_user_when_no_user_in_scope(self): request = _make_mock_request(scope={}) - user = DefaultContextBuilder().build_user(request) + user = DefaultServerCallContextBuilder().build_user(request) assert isinstance(user, UnauthenticatedUser) assert user.is_authenticated is False assert user.user_name == '' @@ -72,7 +72,7 @@ def test_returns_proxy_when_user_in_scope(self): request = _make_mock_request(scope={'user': starlette_user}) request.user = starlette_user - user = DefaultContextBuilder().build_user(request) + user = DefaultServerCallContextBuilder().build_user(request) assert isinstance(user, StarletteUser) assert user.is_authenticated is True assert user.user_name == 'Alice' @@ -84,7 +84,7 @@ def test_returns_unauthenticated_proxy_when_user_not_authenticated(self): request = _make_mock_request(scope={'user': starlette_user}) request.user = starlette_user - user = DefaultContextBuilder().build_user(request) + user = DefaultServerCallContextBuilder().build_user(request) assert isinstance(user, StarletteUser) assert user.is_authenticated is False @@ -97,7 +97,7 @@ def test_basic_context_with_default_user_builder(self): request = _make_mock_request( scope={}, headers={'content-type': 'application/json'} ) - ctx = DefaultContextBuilder().build(request) + ctx = DefaultServerCallContextBuilder().build(request) assert isinstance(ctx, ServerCallContext) assert isinstance(ctx.user, UnauthenticatedUser) @@ -110,44 +110,44 @@ def test_auth_populated_when_in_scope(self): request = _make_mock_request(scope={'auth': auth_credentials}) request.auth = auth_credentials - ctx = DefaultContextBuilder().build(request) + ctx = DefaultServerCallContextBuilder().build(request) assert ctx.state['auth'] is auth_credentials def test_auth_not_populated_when_not_in_scope(self): request = _make_mock_request(scope={}) - ctx = DefaultContextBuilder().build(request) + ctx = DefaultServerCallContextBuilder().build(request) assert 'auth' not in ctx.state def test_headers_captured_in_state(self): request = _make_mock_request( headers={'x-custom': 'value', 'authorization': 'Bearer tok'} ) - ctx = DefaultContextBuilder().build(request) + ctx = DefaultServerCallContextBuilder().build(request) assert ctx.state['headers']['x-custom'] == 'value' assert ctx.state['headers']['authorization'] == 'Bearer tok' def test_requested_extensions_single(self): request = _make_mock_request(headers={HTTP_EXTENSION_HEADER: 'foo'}) - ctx = DefaultContextBuilder().build(request) + ctx = DefaultServerCallContextBuilder().build(request) assert ctx.requested_extensions == {'foo'} def test_requested_extensions_comma_separated(self): request = _make_mock_request( headers={HTTP_EXTENSION_HEADER: 'foo, bar'} ) - ctx = DefaultContextBuilder().build(request) + ctx = DefaultServerCallContextBuilder().build(request) assert ctx.requested_extensions == {'foo', 'bar'} def test_no_extensions(self): request = _make_mock_request() - ctx = DefaultContextBuilder().build(request) + ctx = DefaultServerCallContextBuilder().build(request) assert ctx.requested_extensions == set() def test_custom_user_builder(self): custom_user = MagicMock(spec=UnauthenticatedUser) custom_user.is_authenticated = True - class MyContextBuilder(DefaultContextBuilder): + class MyContextBuilder(DefaultServerCallContextBuilder): def build_user(self, req): return custom_user From 30da41fdb2d246e56e13c839f2d3638f450b1acb Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Fri, 3 Apr 2026 14:37:10 +0000 Subject: [PATCH 11/12] fix --- src/a2a/compat/v0_3/grpc_handler.py | 4 +++- src/a2a/compat/v0_3/jsonrpc_adapter.py | 8 +++++--- src/a2a/compat/v0_3/rest_adapter.py | 8 +++++--- src/a2a/server/request_handlers/__init__.py | 6 +++--- src/a2a/server/request_handlers/grpc_handler.py | 4 +++- src/a2a/server/routes/__init__.py | 9 ++++++--- src/a2a/server/routes/jsonrpc_dispatcher.py | 6 ++++-- src/a2a/server/routes/rest_dispatcher.py | 6 ++++-- 8 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/a2a/compat/v0_3/grpc_handler.py b/src/a2a/compat/v0_3/grpc_handler.py index f7652e8a9..70bd5fb98 100644 --- a/src/a2a/compat/v0_3/grpc_handler.py +++ b/src/a2a/compat/v0_3/grpc_handler.py @@ -61,7 +61,9 @@ def __init__( """ self.agent_card = agent_card self.handler03 = RequestHandler03(request_handler=request_handler) - self._context_builder = context_builder or DefaultGrpcServerCallContextBuilder() + self._context_builder = ( + context_builder or DefaultGrpcServerCallContextBuilder() + ) self.card_modifier = card_modifier async def _handle_unary( diff --git a/src/a2a/compat/v0_3/jsonrpc_adapter.py b/src/a2a/compat/v0_3/jsonrpc_adapter.py index d04d20f6d..d01a7e11c 100644 --- a/src/a2a/compat/v0_3/jsonrpc_adapter.py +++ b/src/a2a/compat/v0_3/jsonrpc_adapter.py @@ -38,8 +38,8 @@ JSONRPCError as CoreJSONRPCError, ) from a2a.server.routes.common import ( - ServerCallContextBuilder, DefaultServerCallContextBuilder, + ServerCallContextBuilder, ) from a2a.utils import constants from a2a.utils.errors import ExtendedAgentCardNotConfiguredError @@ -70,7 +70,7 @@ def __init__( # noqa: PLR0913 agent_card: 'AgentCard', http_handler: 'RequestHandler', extended_agent_card: 'AgentCard | None' = None, - context_builder: 'ContextBuilder | None' = None, + context_builder: 'ServerCallContextBuilder | None' = None, card_modifier: 'Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None' = None, extended_card_modifier: 'Callable[[AgentCard, ServerCallContext], Awaitable[AgentCard] | AgentCard] | None' = None, ): @@ -81,7 +81,9 @@ def __init__( # noqa: PLR0913 self.handler = RequestHandler03( request_handler=http_handler, ) - self._context_builder = context_builder or DefaultServerCallContextBuilder() + self._context_builder = ( + context_builder or DefaultServerCallContextBuilder() + ) def supports_method(self, method: str) -> bool: """Returns True if the v0.3 adapter supports the given method name.""" diff --git a/src/a2a/compat/v0_3/rest_adapter.py b/src/a2a/compat/v0_3/rest_adapter.py index 467a9d15a..27aba2aad 100644 --- a/src/a2a/compat/v0_3/rest_adapter.py +++ b/src/a2a/compat/v0_3/rest_adapter.py @@ -35,8 +35,8 @@ from a2a.compat.v0_3.rest_handler import REST03Handler from a2a.server.context import ServerCallContext from a2a.server.routes.common import ( - ServerCallContextBuilder, DefaultServerCallContextBuilder, + ServerCallContextBuilder, ) from a2a.utils.error_handlers import ( rest_error_handler, @@ -63,7 +63,7 @@ def __init__( # noqa: PLR0913 agent_card: 'AgentCard', http_handler: 'RequestHandler', extended_agent_card: 'AgentCard | None' = None, - context_builder: 'ContextBuilder | None' = None, + context_builder: 'ServerCallContextBuilder | None' = None, card_modifier: 'Callable[[AgentCard], Awaitable[AgentCard] | AgentCard] | None' = None, extended_card_modifier: 'Callable[[AgentCard, ServerCallContext], Awaitable[AgentCard] | AgentCard] | None' = None, ): @@ -74,7 +74,9 @@ def __init__( # noqa: PLR0913 self.handler = REST03Handler( agent_card=agent_card, request_handler=http_handler ) - self._context_builder = context_builder or DefaultServerCallContextBuilder() + self._context_builder = ( + context_builder or DefaultServerCallContextBuilder() + ) @rest_error_handler async def _handle_request( diff --git a/src/a2a/server/request_handlers/__init__.py b/src/a2a/server/request_handlers/__init__.py index b7f8ac23f..194e81a45 100644 --- a/src/a2a/server/request_handlers/__init__.py +++ b/src/a2a/server/request_handlers/__init__.py @@ -19,8 +19,8 @@ try: from a2a.server.request_handlers.grpc_handler import ( - GrpcHandler, # type: ignore DefaultGrpcServerCallContextBuilder, + GrpcHandler, # type: ignore GrpcServerCallContextBuilder, ) except ImportError as e: @@ -41,12 +41,12 @@ def __init__(self, *args, **kwargs): __all__ = [ + 'DefaultGrpcServerCallContextBuilder', 'DefaultRequestHandler', 'GrpcHandler', + 'GrpcServerCallContextBuilder', 'RequestHandler', 'build_error_response', - 'DefaultGrpcServerCallContextBuilder', - 'GrpcServerCallContextBuilder', 'prepare_response_object', 'validate_request_params', ] diff --git a/src/a2a/server/request_handlers/grpc_handler.py b/src/a2a/server/request_handlers/grpc_handler.py index ac9aff6a4..60aa41d22 100644 --- a/src/a2a/server/request_handlers/grpc_handler.py +++ b/src/a2a/server/request_handlers/grpc_handler.py @@ -129,7 +129,9 @@ def __init__( """ self.agent_card = agent_card self.request_handler = request_handler - self._context_builder = context_builder or DefaultGrpcServerCallContextBuilder() + self._context_builder = ( + context_builder or DefaultGrpcServerCallContextBuilder() + ) self.card_modifier = card_modifier async def _handle_unary( diff --git a/src/a2a/server/routes/__init__.py b/src/a2a/server/routes/__init__.py index 51c3ccbc0..007e2722f 100644 --- a/src/a2a/server/routes/__init__.py +++ b/src/a2a/server/routes/__init__.py @@ -1,14 +1,17 @@ """A2A Routes.""" from a2a.server.routes.agent_card_routes import create_agent_card_routes -from a2a.server.routes.common import ServerCallContextBuilder, DefaultServerCallContextBuilder +from a2a.server.routes.common import ( + DefaultServerCallContextBuilder, + ServerCallContextBuilder, +) from a2a.server.routes.jsonrpc_routes import create_jsonrpc_routes from a2a.server.routes.rest_routes import create_rest_routes __all__ = [ - 'ContextBuilder', - 'DefaultContextBuilder', + 'DefaultServerCallContextBuilder', + 'ServerCallContextBuilder', 'create_agent_card_routes', 'create_jsonrpc_routes', 'create_rest_routes', diff --git a/src/a2a/server/routes/jsonrpc_dispatcher.py b/src/a2a/server/routes/jsonrpc_dispatcher.py index 1e6360062..0c2d004d9 100644 --- a/src/a2a/server/routes/jsonrpc_dispatcher.py +++ b/src/a2a/server/routes/jsonrpc_dispatcher.py @@ -28,8 +28,8 @@ build_error_response, ) from a2a.server.routes.common import ( - ServerCallContextBuilder, DefaultServerCallContextBuilder, + ServerCallContextBuilder, ) from a2a.types import A2ARequest from a2a.types.a2a_pb2 import ( @@ -183,7 +183,9 @@ def __init__( # noqa: PLR0913 self.extended_agent_card = extended_agent_card self.card_modifier = card_modifier self.extended_card_modifier = extended_card_modifier - self._context_builder = context_builder or DefaultServerCallContextBuilder() + self._context_builder = ( + context_builder or DefaultServerCallContextBuilder() + ) self.enable_v0_3_compat = enable_v0_3_compat self._v03_adapter: JSONRPC03Adapter | None = None diff --git a/src/a2a/server/routes/rest_dispatcher.py b/src/a2a/server/routes/rest_dispatcher.py index 66d4ca8bb..8c1374ed7 100644 --- a/src/a2a/server/routes/rest_dispatcher.py +++ b/src/a2a/server/routes/rest_dispatcher.py @@ -9,8 +9,8 @@ from a2a.server.context import ServerCallContext from a2a.server.request_handlers.request_handler import RequestHandler from a2a.server.routes.common import ( - ServerCallContextBuilder, DefaultServerCallContextBuilder, + ServerCallContextBuilder, ) from a2a.types import a2a_pb2 from a2a.types.a2a_pb2 import ( @@ -106,7 +106,9 @@ def __init__( # noqa: PLR0913 self.extended_agent_card = extended_agent_card self.card_modifier = card_modifier self.extended_card_modifier = extended_card_modifier - self._context_builder = context_builder or DefaultServerCallContextBuilder() + self._context_builder = ( + context_builder or DefaultServerCallContextBuilder() + ) self.request_handler = request_handler def _build_call_context(self, request: Request) -> ServerCallContext: From b2553f486dbe11d7fac7ccb5d9f9ae73edbc375c Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Fri, 3 Apr 2026 14:49:10 +0000 Subject: [PATCH 12/12] fix --- src/a2a/compat/v0_3/grpc_handler.py | 4 ++-- src/a2a/server/routes/jsonrpc_dispatcher.py | 4 ++-- src/a2a/server/routes/jsonrpc_routes.py | 4 ++-- src/a2a/server/routes/rest_dispatcher.py | 4 ++-- src/a2a/server/routes/rest_routes.py | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/a2a/compat/v0_3/grpc_handler.py b/src/a2a/compat/v0_3/grpc_handler.py index 70bd5fb98..c9db99557 100644 --- a/src/a2a/compat/v0_3/grpc_handler.py +++ b/src/a2a/compat/v0_3/grpc_handler.py @@ -54,8 +54,8 @@ def __init__( agent_card: The AgentCard describing the agent's capabilities (v1.0). request_handler: The underlying `RequestHandler` instance to delegate requests to. - context_builder: Optional custom user builder to extract user from the - gRPC context. + context_builder: The CallContextBuilder object. If none the + DefaultCallContextBuilder is used. card_modifier: An optional callback to dynamically modify the public agent card before it is served. """ diff --git a/src/a2a/server/routes/jsonrpc_dispatcher.py b/src/a2a/server/routes/jsonrpc_dispatcher.py index 0c2d004d9..468868ede 100644 --- a/src/a2a/server/routes/jsonrpc_dispatcher.py +++ b/src/a2a/server/routes/jsonrpc_dispatcher.py @@ -161,9 +161,9 @@ def __init__( # noqa: PLR0913 requests via http. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - context_builder: The ContextBuilder used to construct the + context_builder: The ServerCallContextBuilder used to construct the ServerCallContext passed to the request_handler. If None the - DefaultContextBuilder is used. + DefaultServerCallContextBuilder is used. card_modifier: An optional callback to dynamically modify the public agent card before it is served. extended_card_modifier: An optional callback to dynamically modify diff --git a/src/a2a/server/routes/jsonrpc_routes.py b/src/a2a/server/routes/jsonrpc_routes.py index e88f38584..f19625379 100644 --- a/src/a2a/server/routes/jsonrpc_routes.py +++ b/src/a2a/server/routes/jsonrpc_routes.py @@ -51,9 +51,9 @@ def create_jsonrpc_routes( # noqa: PLR0913 rpc_url: The URL prefix for the RPC endpoints. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - context_builder: The ContextBuilder used to construct the + context_builder: The ServerCallContextBuilder used to construct the ServerCallContext passed to the request_handler. If None the - DefaultContextBuilder is used. + DefaultServerCallContextBuilder is used. card_modifier: An optional callback to dynamically modify the public agent card before it is served. extended_card_modifier: An optional callback to dynamically modify diff --git a/src/a2a/server/routes/rest_dispatcher.py b/src/a2a/server/routes/rest_dispatcher.py index 8c1374ed7..1f91dd573 100644 --- a/src/a2a/server/routes/rest_dispatcher.py +++ b/src/a2a/server/routes/rest_dispatcher.py @@ -86,9 +86,9 @@ def __init__( # noqa: PLR0913 request_handler: The underlying `RequestHandler` instance to delegate requests to. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - context_builder: The ContextBuilder used to construct the + context_builder: The ServerCallContextBuilder used to construct the ServerCallContext passed to the request_handler. If None the - DefaultContextBuilder is used. + DefaultServerCallContextBuilder is used. card_modifier: An optional callback to dynamically modify the public agent card before it is served. extended_card_modifier: An optional callback to dynamically modify diff --git a/src/a2a/server/routes/rest_routes.py b/src/a2a/server/routes/rest_routes.py index 18bb2a03d..89ba63b8e 100644 --- a/src/a2a/server/routes/rest_routes.py +++ b/src/a2a/server/routes/rest_routes.py @@ -64,9 +64,9 @@ def create_rest_routes( # noqa: PLR0913 requests via http. extended_agent_card: An optional, distinct AgentCard to be served at the authenticated extended card endpoint. - context_builder: The ContextBuilder used to construct the + context_builder: The ServerCallContextBuilder used to construct the ServerCallContext passed to the request_handler. If None the - DefaultContextBuilder is used. + DefaultServerCallContextBuilder is used. card_modifier: An optional callback to dynamically modify the public agent card before it is served. extended_card_modifier: An optional callback to dynamically modify