a2aproject / a2aproject/a2a-python

message/send JSON-RPC result is wrapped in the SendMessageResponse oneof instead of returning the Task/Message directly

Đang mở
#1,192 3 bình luận 0 reaction 1 người được giao Được @rohityan nhận Xem trên GitHub
component: server
Ngôn ngữ chính
Python
Star
2.1k
Fork
496
Merge trung bình
4 ngày 17 giờ
Pull request đã merge (30 ngày)
12

Mô tả

## Environment
- a2a-sdk 1.0.3
- Python 3.13
- Transport: JSON-RPC (`create_jsonrpc_routes`, `DefaultRequestHandlerV2`), `enable_v0_3_compat=True`

## Summary
For a 1.x `message/send` call, the JSON-RPC `result` comes back as `{"task": {…}}` (or `{"message": {…}}`) rather than the Task or Message object itself. A client that reads `result.status` and `result.artifacts`, which the A2A 1.0 spec describes for the unary send, finds nothing, because the payload sits one level deeper under `result.task`.

The `task`/`message` oneof belongs to the streaming `StreamResponse`. The unary `message/send` should return the Task or Message directly, the way `tasks/get` and `tasks/cancel` already do.

## Expected
```json
{ "jsonrpc": "2.0", "id": "1", "result": { "id": "…", "contextId": "…", "status": {…}, "artifacts": [ … ] } }
```

## Actual
```json
{ "jsonrpc": "2.0", "id": "1", "result": { "task": { "id": "…", "contextId": "…", "status": {…}, "artifacts": [ … ] } } }
```

## Root cause
In `a2a/server/routes/jsonrpc_dispatcher.py`, `_handle_send_message` wraps the return value in a `SendMessageResponse` before serializing:

```python
async def _handle_send_message(self, request_obj, context):
task_or_message = await self.request_handler.on_message_send(request_obj, context)
if isinstance(task_or_message, Task):
return MessageToDict(SendMessageResponse(task=task_or_message))
return MessageToDict(SendMessageResponse(message=task_or_message))
```

`MessageToDict` on a `SendMessageResponse` emits the oneof field name (`task` or `message`) as a key, which becomes the JSON-RPC `result`. The sibling handlers serialize the object directly and do not show this wrapper:

```python
async def _handle_get_task(self, request_obj, context):
task = await self.request_handler.on_get_task(request_obj, context)
return MessageToDict(task, preserving_proto_field_name=False)
```

## Impact
A2A 1.0 clients that follow the spec read the Task at `result`, not at `result.task`. Against this SDK they see an empty result and report the call as incomplete. The two shapes also disagree within one SDK: `tasks/get` returns the Task un-nested, `message/send` nests it, so a client cannot use one code path for both.

## Reproduce
1. Stand up any agent with `create_jsonrpc_routes(...)`.
2. POST a 1.x `message/send` (`method: "SendMessage"`).
3. Read the response: `result` has a single key, `task`.

## Suggested fix
Return the Task or Message directly from `_handle_send_message`, matching `_handle_get_task` and `_handle_cancel_task`:

```python
async def _handle_send_message(self, request_obj, context):
task_or_message = await self.request_handler.on_message_send(request_obj, context)
return MessageToDict(task_or_message, preserving_proto_field_name=False)
```

The v0.3 compat path is unaffected. It already returns the Task/Message directly through the v0.3 adapter.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.