Skip to content

Commit d057808

Browse files
committed
Gemini authored: attempt to create a backwards compatible A2AClient and A2AGrpcClient
1 parent 43509d8 commit d057808

4 files changed

Lines changed: 449 additions & 0 deletions

File tree

src/a2a/client/__init__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,39 @@
2020
A2AClientTimeoutError,
2121
)
2222
from a2a.client.helpers import create_text_message_object
23+
from a2a.client.legacy import A2AClient
2324
from a2a.client.middleware import ClientCallContext, ClientCallInterceptor
2425

2526

2627
logger = logging.getLogger(__name__)
2728

29+
try:
30+
from a2a.client.legacy_grpc import A2AGrpcClient
31+
except ImportError as e:
32+
_original_error = e
33+
logger.debug(
34+
"A2AGrpcClient not loaded. This is expected if gRPC dependencies are not installed. Error: %s",
35+
_original_error,
36+
)
37+
38+
class A2AGrpcClient: # type: ignore
39+
"""Placeholder for A2AGrpcClient when dependencies are not installed."""
40+
41+
def __init__(self, *args, **kwargs):
42+
raise ImportError(
43+
"To use A2AGrpcClient, its dependencies must be installed. "
44+
'You can install them with \'pip install "a2a-sdk[grpc]"\''
45+
) from _original_error
46+
2847

2948
__all__ = [
3049
"A2ACardResolver",
50+
"A2AClient",
3151
"A2AClientError",
3252
"A2AClientHTTPError",
3353
"A2AClientJSONError",
3454
"A2AClientTimeoutError",
55+
"A2AGrpcClient",
3556
"AuthInterceptor",
3657
"Client",
3758
"ClientCallContext",

src/a2a/client/legacy.py

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
"""Backwards compatibility layer for legacy A2A clients."""
2+
3+
import warnings
4+
5+
from collections.abc import AsyncGenerator
6+
from typing import Any
7+
8+
import httpx
9+
10+
from a2a.client.errors import A2AClientJSONRPCError
11+
from a2a.client.middleware import ClientCallContext, ClientCallInterceptor
12+
from a2a.client.transports.jsonrpc import JsonRpcTransport
13+
from a2a.types import (
14+
AgentCard,
15+
CancelTaskRequest,
16+
CancelTaskResponse,
17+
CancelTaskSuccessResponse,
18+
GetTaskPushNotificationConfigRequest,
19+
GetTaskPushNotificationConfigResponse,
20+
GetTaskPushNotificationConfigSuccessResponse,
21+
GetTaskRequest,
22+
GetTaskResponse,
23+
GetTaskSuccessResponse,
24+
JSONRPCErrorResponse,
25+
SendMessageRequest,
26+
SendMessageResponse,
27+
SendMessageSuccessResponse,
28+
SendStreamingMessageRequest,
29+
SendStreamingMessageResponse,
30+
SendStreamingMessageSuccessResponse,
31+
SetTaskPushNotificationConfigRequest,
32+
SetTaskPushNotificationConfigResponse,
33+
SetTaskPushNotificationConfigSuccessResponse,
34+
TaskResubscriptionRequest,
35+
)
36+
37+
38+
class A2AClient:
39+
"""[DEPRECATED] Backwards compatibility wrapper for the JSON-RPC client."""
40+
41+
def __init__(
42+
self,
43+
httpx_client: httpx.AsyncClient,
44+
agent_card: AgentCard | None = None,
45+
url: str | None = None,
46+
interceptors: list[ClientCallInterceptor] | None = None,
47+
):
48+
warnings.warn(
49+
'A2AClient is deprecated and will be removed in a future version. '
50+
'Use ClientFactory to create a client with a JSON-RPC transport.',
51+
DeprecationWarning,
52+
stacklevel=2,
53+
)
54+
self._transport = JsonRpcTransport(
55+
httpx_client, agent_card, url, interceptors
56+
)
57+
58+
async def send_message(
59+
self,
60+
request: SendMessageRequest,
61+
*,
62+
http_kwargs: dict[str, Any] | None = None,
63+
context: ClientCallContext | None = None,
64+
) -> SendMessageResponse:
65+
if not context and http_kwargs:
66+
context = ClientCallContext(state={'http_kwargs': http_kwargs})
67+
68+
try:
69+
result = await self._transport.send_message(
70+
request.params, context=context
71+
)
72+
return SendMessageResponse(
73+
root=SendMessageSuccessResponse(
74+
id=request.id, jsonrpc='2.0', result=result
75+
)
76+
)
77+
except A2AClientJSONRPCError as e:
78+
return SendMessageResponse(root=JSONRPCErrorResponse(error=e.error))
79+
80+
async def send_message_streaming(
81+
self,
82+
request: SendStreamingMessageRequest,
83+
*,
84+
http_kwargs: dict[str, Any] | None = None,
85+
context: ClientCallContext | None = None,
86+
) -> AsyncGenerator[SendStreamingMessageResponse, None]:
87+
if not context and http_kwargs:
88+
context = ClientCallContext(state={'http_kwargs': http_kwargs})
89+
90+
async for result in self._transport.send_message_streaming(
91+
request.params, context=context
92+
):
93+
yield SendStreamingMessageResponse(
94+
root=SendStreamingMessageSuccessResponse(
95+
id=request.id, jsonrpc='2.0', result=result
96+
)
97+
)
98+
99+
async def get_task(
100+
self,
101+
request: GetTaskRequest,
102+
*,
103+
http_kwargs: dict[str, Any] | None = None,
104+
context: ClientCallContext | None = None,
105+
) -> GetTaskResponse:
106+
if not context and http_kwargs:
107+
context = ClientCallContext(state={'http_kwargs': http_kwargs})
108+
try:
109+
result = await self._transport.get_task(
110+
request.params, context=context
111+
)
112+
return GetTaskResponse(
113+
root=GetTaskSuccessResponse(
114+
id=request.id, jsonrpc='2.0', result=result
115+
)
116+
)
117+
except A2AClientJSONRPCError as e:
118+
return GetTaskResponse(root=JSONRPCErrorResponse(error=e.error))
119+
120+
async def cancel_task(
121+
self,
122+
request: CancelTaskRequest,
123+
*,
124+
http_kwargs: dict[str, Any] | None = None,
125+
context: ClientCallContext | None = None,
126+
) -> CancelTaskResponse:
127+
if not context and http_kwargs:
128+
context = ClientCallContext(state={'http_kwargs': http_kwargs})
129+
try:
130+
result = await self._transport.cancel_task(
131+
request.params, context=context
132+
)
133+
return CancelTaskResponse(
134+
root=CancelTaskSuccessResponse(
135+
id=request.id, jsonrpc='2.0', result=result
136+
)
137+
)
138+
except A2AClientJSONRPCError as e:
139+
return CancelTaskResponse(root=JSONRPCErrorResponse(error=e.error))
140+
141+
async def set_task_callback(
142+
self,
143+
request: SetTaskPushNotificationConfigRequest,
144+
*,
145+
http_kwargs: dict[str, Any] | None = None,
146+
context: ClientCallContext | None = None,
147+
) -> SetTaskPushNotificationConfigResponse:
148+
if not context and http_kwargs:
149+
context = ClientCallContext(state={'http_kwargs': http_kwargs})
150+
try:
151+
result = await self._transport.set_task_callback(
152+
request.params, context=context
153+
)
154+
return SetTaskPushNotificationConfigResponse(
155+
root=SetTaskPushNotificationConfigSuccessResponse(
156+
id=request.id, jsonrpc='2.0', result=result
157+
)
158+
)
159+
except A2AClientJSONRPCError as e:
160+
return SetTaskPushNotificationConfigResponse(
161+
root=JSONRPCErrorResponse(error=e.error)
162+
)
163+
164+
async def get_task_callback(
165+
self,
166+
request: GetTaskPushNotificationConfigRequest,
167+
*,
168+
http_kwargs: dict[str, Any] | None = None,
169+
context: ClientCallContext | None = None,
170+
) -> GetTaskPushNotificationConfigResponse:
171+
if not context and http_kwargs:
172+
context = ClientCallContext(state={'http_kwargs': http_kwargs})
173+
try:
174+
result = await self._transport.get_task_callback(
175+
request.params, context=context
176+
)
177+
return GetTaskPushNotificationConfigResponse(
178+
root=GetTaskPushNotificationConfigSuccessResponse(
179+
id=request.id, jsonrpc='2.0', result=result
180+
)
181+
)
182+
except A2AClientJSONRPCError as e:
183+
return GetTaskPushNotificationConfigResponse(
184+
root=JSONRPCErrorResponse(error=e.error)
185+
)
186+
187+
async def resubscribe(
188+
self,
189+
request: TaskResubscriptionRequest,
190+
*,
191+
http_kwargs: dict[str, Any] | None = None,
192+
context: ClientCallContext | None = None,
193+
) -> AsyncGenerator[SendStreamingMessageResponse, None]:
194+
if not context and http_kwargs:
195+
context = ClientCallContext(state={'http_kwargs': http_kwargs})
196+
197+
async for result in self._transport.resubscribe(
198+
request.params, context=context
199+
):
200+
yield SendStreamingMessageResponse(
201+
root=SendStreamingMessageSuccessResponse(
202+
id=request.id, jsonrpc='2.0', result=result
203+
)
204+
)
205+
206+
async def get_card(
207+
self,
208+
*,
209+
http_kwargs: dict[str, Any] | None = None,
210+
context: ClientCallContext | None = None,
211+
) -> AgentCard:
212+
if not context and http_kwargs:
213+
context = ClientCallContext(state={'http_kwargs': http_kwargs})
214+
return await self._transport.get_card(context=context)

src/a2a/client/legacy_grpc.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""Backwards compatibility layer for the legacy A2A gRPC client."""
2+
3+
import warnings
4+
5+
from a2a.client.transports.grpc import GrpcTransport
6+
from a2a.grpc import a2a_pb2_grpc
7+
from a2a.types import AgentCard
8+
9+
10+
class A2AGrpcClient(GrpcTransport):
11+
"""
12+
[DEPRECATED] Backwards compatibility wrapper for the gRPC client.
13+
"""
14+
15+
def __init__(
16+
self,
17+
grpc_stub: "a2a_pb2_grpc.A2AServiceStub",
18+
agent_card: AgentCard,
19+
):
20+
warnings.warn(
21+
"A2AGrpcClient is deprecated and will be removed in a future version. "
22+
"Use ClientFactory to create a client with a gRPC transport.",
23+
DeprecationWarning,
24+
stacklevel=2,
25+
)
26+
super().__init__(grpc_stub, agent_card)

0 commit comments

Comments
 (0)