a2aproject / a2aproject/a2a-python

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

Aberta
#1,192 3 comentários 0 reações 1 responsável Reivindicada por @rohityan Ver no GitHub
component: server
Linguagem predominante
Python
Estrelas
2.1k
Forks
496
Merge médio
4d 17h
PRs com merge (30d)
12

Descrição

## 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.

Guia de contribuição

Abrir o guia de contribuição

Avaliação

Esta issue ainda não foi avaliada.

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.