Skip to content

Commit 080e072

Browse files
committed
rejoin with offical google a2a
1 parent 4f96ed1 commit 080e072

File tree

15 files changed

+207
-1965
lines changed

15 files changed

+207
-1965
lines changed

AgentCrew/modules/a2a/adapters.py

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@
44

55
import base64
66
from typing import Dict, Any, List, Optional
7-
from .common.types import (
7+
from a2a.types import (
88
GetTaskResponse,
99
Message,
10+
Task,
1011
TextPart,
1112
FilePart,
1213
FileWithBytes,
1314
FileWithUri,
1415
Artifact,
1516
Part,
1617
SendMessageResponse,
18+
JSONRPCErrorResponse,
1719
DataPart,
1820
Role,
1921
)
@@ -34,14 +36,14 @@ def convert_a2a_message_to_agent(message: Message) -> Dict[str, Any]:
3436
content = []
3537

3638
for part in message.parts:
37-
part_data = part.root if hasattr(part, "root") else part
39+
part_data = part.root
3840

3941
if part_data.kind == "text":
4042
content.append({"type": "text", "text": part_data.text})
4143
elif part_data.kind == "file":
4244
# Handle file content
4345
file_data = part_data.file
44-
if hasattr(file_data, "bytes") and file_data.bytes:
46+
if isinstance(file_data, FileWithBytes) and file_data.bytes:
4547
# Base64 encoded file
4648
content.append(
4749
{
@@ -51,7 +53,7 @@ def convert_a2a_message_to_agent(message: Message) -> Dict[str, Any]:
5153
"mime_type": file_data.mimeType or "application/octet-stream",
5254
}
5355
)
54-
elif hasattr(file_data, "uri") and file_data.uri:
56+
elif isinstance(file_data, FileWithUri) and file_data.uri:
5557
# File URI
5658
content.append(
5759
{
@@ -70,7 +72,7 @@ def convert_a2a_message_to_agent(message: Message) -> Dict[str, Any]:
7072

7173
# TODO: cover all of cases for images
7274
def convert_agent_message_to_a2a(
73-
message: Dict[str, Any], message_id: str = None
75+
message: Dict[str, Any], message_id: Optional[str] = None
7476
) -> Message:
7577
"""
7678
Convert a SwissKnife message to A2A format.
@@ -104,7 +106,7 @@ def convert_agent_message_to_a2a(
104106
file=FileWithBytes(
105107
name=part.get("file_name"),
106108
mimeType=part.get("mime_type"),
107-
bytes=part.get("file_data"),
109+
bytes=part.get("file_data", ""),
108110
)
109111
)
110112
)
@@ -115,7 +117,7 @@ def convert_agent_message_to_a2a(
115117
file=FileWithUri(
116118
name=part.get("file_name"),
117119
mimeType=part.get("mime_type"),
118-
uri=part.get("uri"),
120+
uri=part.get("uri", ""),
119121
)
120122
)
121123
)
@@ -134,7 +136,7 @@ def convert_agent_message_to_a2a(
134136
def convert_agent_response_to_a2a(
135137
response: str,
136138
tool_uses: Optional[List[Dict[str, Any]]] = None,
137-
artifact_id: str = None,
139+
artifact_id: Optional[str] = None,
138140
) -> Artifact:
139141
"""
140142
Convert a SwissKnife response to an A2A artifact.
@@ -147,7 +149,7 @@ def convert_agent_response_to_a2a(
147149
Returns:
148150
The response as an A2A artifact
149151
"""
150-
parts = [TextPart(text=response)]
152+
parts = [Part(root=TextPart(text=response))]
151153

152154
# If there were tool uses, we could add them as metadata
153155
metadata = None
@@ -188,8 +190,10 @@ def convert_file_to_a2a_part(
188190
# Encode file content as base64
189191
base64_content = base64.b64encode(file_content).decode("utf-8")
190192

191-
return FilePart(
192-
file=FileWithBytes(name=file_name, mimeType=mime_type, bytes=base64_content)
193+
return Part(
194+
root=FilePart(
195+
file=FileWithBytes(name=file_name, mimeType=mime_type, bytes=base64_content)
196+
)
193197
)
194198

195199

@@ -200,25 +204,26 @@ def convert_a2a_send_task_response_to_agent_message(
200204
if not response or not hasattr(response, "root"):
201205
return None
202206

203-
result = response.root.result if hasattr(response.root, "result") else None
204-
if not result:
207+
if isinstance(response.root, JSONRPCErrorResponse):
205208
return None
206209

210+
result = response.root.result if hasattr(response.root, "result") else None
211+
207212
# Handle both Task and Message results
208-
if hasattr(result, "artifacts") and result.artifacts:
213+
if isinstance(result, Task) and result.artifacts:
209214
# Task result with artifacts
210215
latest_artifact = result.artifacts[-1]
211216
content_parts = []
212217
for part in latest_artifact.parts:
213-
part_data = part.root if hasattr(part, "root") else part
218+
part_data = part.root
214219
if part_data.kind == "text":
215220
content_parts.append(part_data.text)
216221
return "\n".join(content_parts)
217-
elif hasattr(result, "parts"):
222+
elif isinstance(result, Message) and result.parts:
218223
# Direct message result
219224
content_parts = []
220225
for part in result.parts:
221-
part_data = part.root if hasattr(part, "root") else part
226+
part_data = part.root
222227
if part_data.kind == "text":
223228
content_parts.append(part_data.text)
224229
return "\n".join(content_parts)

AgentCrew/modules/a2a/agent_cards.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from typing import List
66
from AgentCrew.modules.agents import LocalAgent
7-
from .common.types import AgentCard, AgentCapabilities, AgentSkill, AgentProvider
7+
from a2a.types import AgentCard, AgentCapabilities, AgentSkill, AgentProvider
88

99

1010
def map_tool_to_skill(tool_name: str, tool_def) -> AgentSkill:

AgentCrew/modules/a2a/common/client/card_resolver.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22

33
import httpx
44

5-
from AgentCrew.modules.a2a.common.types import (
6-
A2AClientJSONError,
7-
AgentCard,
8-
)
5+
from a2a.types import AgentCard
96

107

118
class A2ACardResolver:
@@ -20,4 +17,4 @@ def get_agent_card(self) -> AgentCard:
2017
try:
2118
return AgentCard(**response.json())
2219
except json.JSONDecodeError as e:
23-
raise A2AClientJSONError(str(e)) from e
20+
raise httpx.RequestError(str(e)) from e

AgentCrew/modules/a2a/common/client/client.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99
from httpx._types import TimeoutTypes
1010
from httpx_sse import aconnect_sse
1111

12-
from AgentCrew.modules.a2a.common.types import (
13-
A2AClientHTTPError,
14-
A2AClientJSONError,
12+
from a2a.types import (
1513
A2ARequest,
1614
AgentCard,
1715
CancelTaskRequest,
@@ -70,9 +68,7 @@ async def send_message_streaming(
7068
json.loads(sse.data)
7169
)
7270
except json.JSONDecodeError as e:
73-
raise A2AClientJSONError(str(e)) from e
74-
except httpx.RequestError as e:
75-
raise A2AClientHTTPError(400, str(e)) from e
71+
raise httpx.DecodingError(str(e)) from e
7672

7773
async def _send_request(self, request: A2ARequest) -> dict[str, Any]:
7874
async with httpx.AsyncClient() as client:
@@ -83,10 +79,8 @@ async def _send_request(self, request: A2ARequest) -> dict[str, Any]:
8379
)
8480
response.raise_for_status()
8581
return response.json()
86-
except httpx.HTTPStatusError as e:
87-
raise A2AClientHTTPError(e.response.status_code, str(e)) from e
8882
except json.JSONDecodeError as e:
89-
raise A2AClientJSONError(str(e)) from e
83+
raise httpx.DecodingError(str(e)) from e
9084

9185
async def get_task(self, payload: TaskQueryParams) -> GetTaskResponse:
9286
request = GetTaskRequest(id=str(uuid4()), params=payload)

AgentCrew/modules/a2a/common/server/task_manager.py

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@
55
from collections.abc import AsyncIterable
66

77
from AgentCrew.modules.a2a.common.server.utils import new_not_implemented_error
8-
from AgentCrew.modules.a2a.common.types import (
8+
from a2a.types import (
99
Artifact,
1010
CancelTaskRequest,
1111
CancelTaskResponse,
1212
GetTaskPushNotificationConfigRequest,
1313
GetTaskPushNotificationConfigResponse,
1414
GetTaskRequest,
1515
GetTaskResponse,
16+
SetTaskPushNotificationConfigSuccessResponse,
17+
GetTaskPushNotificationConfigSuccessResponse,
18+
SendStreamingMessageSuccessResponse,
1619
InternalError,
1720
JSONRPCError,
21+
GetTaskSuccessResponse,
22+
JSONRPCErrorResponse,
1823
JSONRPCResponse,
1924
PushNotificationConfig,
2025
SendMessageRequest,
@@ -104,13 +109,17 @@ async def on_get_task(self, request: GetTaskRequest) -> GetTaskResponse:
104109
async with self.lock:
105110
task = self.tasks.get(task_query_params.id)
106111
if task is None:
107-
return GetTaskResponse(error=TaskNotFoundError(), id=request.id)
112+
return GetTaskResponse(
113+
root=JSONRPCErrorResponse(error=TaskNotFoundError(), id=request.id)
114+
)
108115

109116
task_result = self.append_task_history(
110117
task, task_query_params.historyLength
111118
)
112119

113-
return GetTaskResponse(result=task_result, id=request.id)
120+
return GetTaskResponse(
121+
root=GetTaskSuccessResponse(result=task_result, id=request.id)
122+
)
114123

115124
async def on_cancel_task(self, request: CancelTaskRequest) -> CancelTaskResponse:
116125
logger.info(f"Cancelling task {request.params.id}")
@@ -119,9 +128,13 @@ async def on_cancel_task(self, request: CancelTaskRequest) -> CancelTaskResponse
119128
async with self.lock:
120129
task = self.tasks.get(task_id_params.id)
121130
if task is None:
122-
return CancelTaskResponse(error=TaskNotFoundError(), id=request.id)
131+
return CancelTaskResponse(
132+
root=JSONRPCErrorResponse(error=TaskNotFoundError(), id=request.id)
133+
)
123134

124-
return CancelTaskResponse(error=TaskNotCancelableError(), id=request.id)
135+
return CancelTaskResponse(
136+
root=JSONRPCErrorResponse(error=TaskNotCancelableError(), id=request.id)
137+
)
125138

126139
@abstractmethod
127140
async def on_send_message(self, request: SendMessageRequest) -> SendMessageResponse:
@@ -168,15 +181,19 @@ async def on_set_task_push_notification(
168181
)
169182
except Exception as e:
170183
logger.error(f"Error while setting push notification info: {e}")
171-
return JSONRPCResponse(
172-
id=request.id,
173-
error=InternalError(
174-
message="An error occurred while setting push notification info"
175-
),
184+
return SetTaskPushNotificationConfigResponse(
185+
root=JSONRPCErrorResponse(
186+
id=request.id,
187+
error=InternalError(
188+
message="An error occurred while setting push notification info"
189+
),
190+
)
176191
)
177192

178193
return SetTaskPushNotificationConfigResponse(
179-
id=request.id, result=task_notification_params
194+
root=SetTaskPushNotificationConfigSuccessResponse(
195+
id=request.id, result=task_notification_params
196+
)
180197
)
181198

182199
async def on_get_task_push_notification(
@@ -190,17 +207,21 @@ async def on_get_task_push_notification(
190207
except Exception as e:
191208
logger.error(f"Error while getting push notification info: {e}")
192209
return GetTaskPushNotificationConfigResponse(
193-
id=request.id,
194-
error=InternalError(
195-
message="An error occurred while getting push notification info"
196-
),
210+
root=JSONRPCErrorResponse(
211+
id=request.id,
212+
error=InternalError(
213+
message="An error occurred while getting push notification info"
214+
),
215+
)
197216
)
198217

199218
return GetTaskPushNotificationConfigResponse(
200-
id=request.id,
201-
result=TaskPushNotificationConfig(
202-
taskId=task_params.id, pushNotificationConfig=notification_info
203-
),
219+
root=GetTaskPushNotificationConfigSuccessResponse(
220+
id=request.id,
221+
result=TaskPushNotificationConfig(
222+
taskId=task_params.id, pushNotificationConfig=notification_info
223+
),
224+
)
204225
)
205226

206227
async def upsert_task(self, message_send_params: MessageSendParams) -> Task:
@@ -296,10 +317,16 @@ async def dequeue_events_for_sse(
296317
while True:
297318
event = await sse_event_queue.get()
298319
if isinstance(event, JSONRPCError):
299-
yield SendStreamingMessageResponse(id=request_id, error=event)
320+
yield SendStreamingMessageResponse(
321+
root=JSONRPCErrorResponse(id=request_id, error=event)
322+
)
300323
break
301324

302-
yield SendStreamingMessageResponse(id=request_id, result=event)
325+
yield SendStreamingMessageResponse(
326+
root=SendStreamingMessageSuccessResponse(
327+
id=request_id, result=event
328+
)
329+
)
303330
if isinstance(event, TaskStatusUpdateEvent) and event.final:
304331
break
305332
finally:

AgentCrew/modules/a2a/common/server/utils.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from AgentCrew.modules.a2a.common.types import (
1+
from a2a.types import (
22
ContentTypeNotSupportedError,
3+
JSONRPCErrorResponse,
34
JSONRPCResponse,
45
UnsupportedOperationError,
56
)
@@ -21,8 +22,12 @@ def are_modalities_compatible(
2122

2223

2324
def new_incompatible_types_error(request_id):
24-
return JSONRPCResponse(id=request_id, error=ContentTypeNotSupportedError())
25+
return JSONRPCResponse(
26+
root=JSONRPCErrorResponse(id=request_id, error=ContentTypeNotSupportedError())
27+
)
2528

2629

2730
def new_not_implemented_error(request_id):
28-
return JSONRPCResponse(id=request_id, error=UnsupportedOperationError())
31+
return JSONRPCResponse(
32+
root=JSONRPCErrorResponse(id=request_id, error=UnsupportedOperationError())
33+
)

0 commit comments

Comments
 (0)