Skip to content

fix(compat): map google.rpc.Status errors in the v0.3 REST client - #1404

Open
v0ropaev wants to merge 1 commit into
a2aproject:mainfrom
v0ropaev:fix/compat-rest-error-mapping
Open

v0ropaev wants to merge 1 commit into
a2aproject:mainfrom
v0ropaev:fix/compat-rest-error-mapping

Conversation

@v0ropaev

Copy link
Copy Markdown

Description

The v0.3 REST client in the compat package did not recognize the error bodies that this SDK's own v0.3 REST routes send. CompatRestTransport._handle_http_error only mapped a body shaped like {"type": "TaskNotFoundError", "message": ...}. The v0.3 routes mounted by create_rest_routes(..., enable_v0_3_compat=True) go through the same rest_error_handler as v1.0 and return a google.rpc.Status with an ErrorInfo reason, for example

404 {"error":{"code":404,"status":"NOT_FOUND","message":"Task not found","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"TASK_NOT_FOUND","domain":"a2a-protocol.org","metadata":{}}]}}

So against one server with both v0.3 bindings enabled, the two clients disagreed.

CompatJsonRpcTransport   get_task(missing)        -> TaskNotFoundError: Task not found
CompatJsonRpcTransport   cancel_task(missing)     -> TaskNotFoundError: Task not found
CompatJsonRpcTransport   cancel_task(completed)   -> TaskNotCancelableError: Task cannot be canceled
CompatRestTransport      get_task(missing)        -> MethodNotFoundError: Resource not found: http://testserver/v1/tasks/nope
CompatRestTransport      cancel_task(missing)     -> MethodNotFoundError: Resource not found: http://testserver/v1/tasks/nope:cancel
CompatRestTransport      cancel_task(completed)   -> A2AClientError: HTTP Error 400: Client error '400 Bad Request' ...

After the change all three REST rows raise TaskNotFoundError, TaskNotFoundError and TaskNotCancelableError, matching JSON-RPC.

The fix passes the parsed body to _parse_rest_error from a2a.client.transports.rest, which is what the v1.0 RestTransport already uses, and raises the mapped error when there is one. That covers every reason in A2A_REASON_TO_ERROR, not only the two task errors above. Against the same app, send_message with an unsupported content type now raises ContentTypeNotSupportedError and create_task_push_notification_config raises PushNotificationNotSupportedError when push is not supported, where both used to be a bare A2AClientError: HTTP Error 400.

The existing type lookup and the 404 and generic status fallback stay in place after it. I could not find any server in this repo or in a2a-sdk 0.3.26 that sends the type shape, but it is cheap to keep and an existing test covers it. A real 0.3.x Python server answers with {"message": ...} and no reason, so against that server a missing task still comes back as MethodNotFoundError. That is unchanged here and would need its own fix.

Tests added in tests/compat/v0_3/test_rest_transport.py. One is a parametrized unit test with the exact bodies the server produces for TASK_NOT_FOUND (404) and TASK_NOT_CANCELABLE (400). The other runs CompatRestTransport against a Starlette app built with create_rest_routes(enable_v0_3_compat=True) over httpx.ASGITransport, parametrized over get on a missing task, cancel on a missing task and cancel on a completed one.

With the source change reverted, uv run pytest tests/compat/v0_3/test_rest_transport.py gives 5 failed, 32 passed (the new cases get MethodNotFoundError and A2AClientError: HTTP Error 400). With it, 37 passed. Full uv run pytest is 2496 passed, 203 skipped, 4 xfailed, 1 xpassed. ./scripts/lint.sh passes, and --cov=src puts compat/v0_3/rest_transport.py at 96% with the new lines covered.

CompatRestTransport only turned an error body into an A2A error when it
looked like {"type": "TaskNotFoundError", "message": ...}. The v0.3
REST routes in this SDK (create_rest_routes with enable_v0_3_compat)
answer with a google.rpc.Status body carrying an ErrorInfo reason, the
same as the v1.0 binding. So a missing task came back as
MethodNotFoundError and an uncancelable task as a bare A2AClientError,
while the v0.3 JSON-RPC client and the v1.0 REST client raise
TaskNotFoundError and TaskNotCancelableError for the same server.

Run the body through _parse_rest_error from the v1.0 REST transport
first, so every reason in A2A_REASON_TO_ERROR is mapped. The old
"type" lookup and the status code fallback are kept after it.
@v0ropaev
v0ropaev requested a review from a team as a code owner October 29, 2026 13:04
@github-actions

Copy link
Copy Markdown

🧪 Code Coverage (vs main)

⬇️ Download Full Report

Base PR Delta
src/a2a/compat/v0_3/rest_transport.py 95.63% 95.76% 🟢 +0.13%
Total 93.40% 93.40% ⚪️ 0.00%

Generated by coverage-comment.yml

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant