Skip to content

Commit 2a3c577

Browse files
committed
feat: refactor JSON-RPC dispatcher and common context handling
1 parent 9ccf99c commit 2a3c577

5 files changed

Lines changed: 74 additions & 64 deletions

File tree

src/a2a/server/routes/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""A2A Routes."""
22

33
from a2a.server.routes.agent_card_routes import create_agent_card_routes
4-
from a2a.server.routes.jsonrpc_dispatcher import (
4+
from a2a.server.routes.common import (
55
CallContextBuilder,
66
DefaultCallContextBuilder,
77
)

src/a2a/server/routes/common.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from abc import ABC, abstractmethod
2+
3+
from starlette.authentication import BaseUser
4+
from starlette.requests import Request
5+
6+
from a2a.auth.user import UnauthenticatedUser
7+
from a2a.auth.user import User as A2AUser
8+
from a2a.extensions.common import (
9+
HTTP_EXTENSION_HEADER,
10+
get_requested_extensions,
11+
)
12+
from a2a.server.context import ServerCallContext
13+
14+
15+
class StarletteUserProxy(A2AUser):
16+
"""Adapts the Starlette User class to the A2A user representation."""
17+
18+
def __init__(self, user: BaseUser):
19+
self._user = user
20+
21+
@property
22+
def is_authenticated(self) -> bool:
23+
"""Returns whether the current user is authenticated."""
24+
return self._user.is_authenticated
25+
26+
@property
27+
def user_name(self) -> str:
28+
"""Returns the user name of the current user."""
29+
return self._user.display_name
30+
31+
32+
class CallContextBuilder(ABC):
33+
"""A class for building ServerCallContexts using the Starlette Request."""
34+
35+
@abstractmethod
36+
def build(self, request: Request) -> ServerCallContext:
37+
"""Builds a ServerCallContext from a Starlette Request."""
38+
39+
40+
class DefaultCallContextBuilder(CallContextBuilder):
41+
"""A default implementation of CallContextBuilder."""
42+
43+
def build(self, request: Request) -> ServerCallContext:
44+
"""Builds a ServerCallContext from a Starlette Request.
45+
46+
Args:
47+
request: The incoming Starlette Request object.
48+
49+
Returns:
50+
A ServerCallContext instance populated with user and state
51+
information from the request.
52+
"""
53+
user: A2AUser = UnauthenticatedUser()
54+
state = {}
55+
if 'user' in request.scope:
56+
user = StarletteUserProxy(request.user)
57+
state['auth'] = request.auth
58+
state['headers'] = dict(request.headers)
59+
return ServerCallContext(
60+
user=user,
61+
state=state,
62+
requested_extensions=get_requested_extensions(
63+
request.headers.getlist(HTTP_EXTENSION_HEADER)
64+
),
65+
)

src/a2a/server/routes/jsonrpc_dispatcher.py

Lines changed: 4 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,15 @@
44
import logging
55
import traceback
66

7-
from abc import ABC, abstractmethod
87
from collections.abc import AsyncGenerator, Awaitable, Callable
98
from typing import TYPE_CHECKING, Any
109

1110
from google.protobuf.json_format import MessageToDict, ParseDict
1211
from jsonrpc.jsonrpc2 import JSONRPC20Request, JSONRPC20Response
1312

14-
from a2a.auth.user import UnauthenticatedUser
15-
from a2a.auth.user import User as A2AUser
1613
from a2a.compat.v0_3.jsonrpc_adapter import JSONRPC03Adapter
1714
from a2a.extensions.common import (
1815
HTTP_EXTENSION_HEADER,
19-
get_requested_extensions,
2016
)
2117
from a2a.server.context import ServerCallContext
2218
from a2a.server.jsonrpc_models import (
@@ -31,6 +27,10 @@
3127
from a2a.server.request_handlers.response_helpers import (
3228
build_error_response,
3329
)
30+
from a2a.server.routes.common import (
31+
CallContextBuilder,
32+
DefaultCallContextBuilder,
33+
)
3434
from a2a.types import A2ARequest
3535
from a2a.types.a2a_pb2 import (
3636
AgentCard,
@@ -113,59 +113,6 @@
113113
HTTP_413_CONTENT_TOO_LARGE = Any
114114

115115

116-
class StarletteUserProxy(A2AUser):
117-
"""Adapts the Starlette User class to the A2A user representation."""
118-
119-
def __init__(self, user: BaseUser):
120-
self._user = user
121-
122-
@property
123-
def is_authenticated(self) -> bool:
124-
"""Returns whether the current user is authenticated."""
125-
return self._user.is_authenticated
126-
127-
@property
128-
def user_name(self) -> str:
129-
"""Returns the user name of the current user."""
130-
return self._user.display_name
131-
132-
133-
class CallContextBuilder(ABC):
134-
"""A class for building ServerCallContexts using the Starlette Request."""
135-
136-
@abstractmethod
137-
def build(self, request: Request) -> ServerCallContext:
138-
"""Builds a ServerCallContext from a Starlette Request."""
139-
140-
141-
class DefaultCallContextBuilder(CallContextBuilder):
142-
"""A default implementation of CallContextBuilder."""
143-
144-
def build(self, request: Request) -> ServerCallContext:
145-
"""Builds a ServerCallContext from a Starlette Request.
146-
147-
Args:
148-
request: The incoming Starlette Request object.
149-
150-
Returns:
151-
A ServerCallContext instance populated with user and state
152-
information from the request.
153-
"""
154-
user: A2AUser = UnauthenticatedUser()
155-
state = {}
156-
if 'user' in request.scope:
157-
user = StarletteUserProxy(request.user)
158-
state['auth'] = request.auth
159-
state['headers'] = dict(request.headers)
160-
return ServerCallContext(
161-
user=user,
162-
state=state,
163-
requested_extensions=get_requested_extensions(
164-
request.headers.getlist(HTTP_EXTENSION_HEADER)
165-
),
166-
)
167-
168-
169116
@trace_class(kind=SpanKind.SERVER)
170117
class JsonRpcDispatcher:
171118
"""Base class for A2A JSONRPC applications.

src/a2a/server/routes/jsonrpc_routes.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,8 @@
1919

2020
from a2a.server.context import ServerCallContext
2121
from a2a.server.request_handlers.request_handler import RequestHandler
22-
from a2a.server.routes.jsonrpc_dispatcher import (
23-
CallContextBuilder,
24-
JsonRpcDispatcher,
25-
)
22+
from a2a.server.routes.common import CallContextBuilder
23+
from a2a.server.routes.jsonrpc_dispatcher import JsonRpcDispatcher
2624
from a2a.types.a2a_pb2 import AgentCard
2725

2826

tests/server/routes/test_jsonrpc_dispatcher.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@
2121
Role,
2222
)
2323
from a2a.server.routes import jsonrpc_dispatcher
24-
from a2a.server.routes.jsonrpc_dispatcher import (
24+
from a2a.server.routes.common import (
2525
CallContextBuilder,
2626
DefaultCallContextBuilder,
27-
JsonRpcDispatcher,
2827
StarletteUserProxy,
2928
)
29+
from a2a.server.routes.jsonrpc_dispatcher import JsonRpcDispatcher
3030
from a2a.server.routes.jsonrpc_routes import create_jsonrpc_routes
3131
from a2a.server.routes.agent_card_routes import create_agent_card_routes
3232
from a2a.server.jsonrpc_models import JSONRPCError

0 commit comments

Comments
 (0)