Skip to content

fix(rest): serve HTTP+JSON error responses as application/a2a+json - #1446

Open
Shay-Deng wants to merge 1 commit into
a2aproject:mainfrom
Shay-Deng:fix/rest-error-media-type
Open

Shay-Deng wants to merge 1 commit into
a2aproject:mainfrom
Shay-Deng:fix/rest-error-media-type

Conversation

@Shay-Deng

Copy link
Copy Markdown

Description

#1274 made 1.0 HTTP+JSON success responses application/a2a+json and deliberately left the error path on application/json, since rest_error_handler is shared with the v0.3 REST adapter, calling it worth a follow-up. This is that follow-up.

Spec 11.1 says application/a2a+json SHOULD be used for responses, and the error example in 11.6 is served as Content-Type: application/a2a+json.

Changes:

  • rest_error_handler and rest_stream_error_handler take a keyword-only media_type, defaulting to application/json, so every existing caller is unchanged.
  • RestDispatcher (1.0) decorates its handlers with functools.partial(..., media_type=constants.A2A_JSON_MEDIA_TYPE).
  • The v0.3 adapter keeps application/json; test_v03_message_stream_malformed_json_returns_400 already pins that.
  • The client side does not look at the error Content-Type, so SDK clients are unaffected.

Testing:

  • ./scripts/lint.sh passes (ruff check, ruff format, ty).
  • uv run pytest --cov=src: 2562 passed, 207 skipped; total coverage 94%.
  • Two new tests in tests/server/routes/test_rest_routes.py cover a handler error (404) and an error raised before the first stream event (400 for an unsupported A2A-Version). Both fail on main and pass with this change.
  • Ran the a2a-tck reference SUT built from this branch: HTTP_JSON-SVC-001 (error Content-Type SHOULD be application/a2a+json) now passes.

One thing to know: a2a-tck main currently has a check under HTTP_JSON-ERR-001 that rejects any error Content-Type other than application/json, which contradicts its own HTTP_JSON-SVC-001 and the 11.6 example. Open a2a-tck PR a2aproject/a2a-tck#238 relaxes it. Until that lands, a TCK run from a2a-tck main reports HTTP_JSON-ERR-001 instead of HTTP_JSON-SVC-001 for this SDK. This repo's run-tck.yaml pins TCK 0.3.0.beta3 on JSON-RPC, so CI here is unaffected.

AI assistance: developed with help from Claude Code. I reviewed the change, and the checks listed under Testing were run locally.

Follow-up to #1274 🦕

a2aproject#1274 labelled 1.0 HTTP+JSON success responses application/a2a+json
but left errors on application/json, because rest_error_handler is
shared with the v0.3 REST adapter. Spec Section 11.1 asks for the A2A
media type on responses, and the Section 11.6 error example uses it.

Give rest_error_handler and rest_stream_error_handler a keyword-only
media_type that defaults to application/json, and have the 1.0
RestDispatcher pass constants.A2A_JSON_MEDIA_TYPE. The v0.3 adapter
keeps application/json.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@Shay-Deng
Shay-Deng requested a review from a team as a code owner November 10, 2026 17:51
@github-actions

Copy link
Copy Markdown

🧪 Code Coverage (vs main)

⬇️ Download Full Report

Base PR Delta
src/a2a/server/cluster/database_event_stream.py 92.73% 93.64% 🟢 +0.91%
src/a2a/server/routes/rest_dispatcher.py 95.08% 95.14% 🟢 +0.05%
src/a2a/utils/error_handlers.py 96.67% 96.70% 🟢 +0.04%
Total 93.48% 93.49% 🟢 +0.01%

Generated by coverage-comment.yml

@ishymko
ishymko requested review from a team and removed request for a team November 12, 2026 10:27

@1AbdullahButt 1AbdullahButt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks correct. Here's what I checked.

  • Every 1.0 REST handler is covered. All eleven handlers in RestDispatcher (rest_dispatcher.py:183-392) now use _a2a_rest_error_handler or _a2a_rest_stream_error_handler. None is left on the bare decorator. The v0.3 rest_adapter.py:71/81 still uses the plain decorators, so it keeps application/json as intended.
  • The functools.partial decorator works. partial(rest_error_handler, media_type=...)(func) calls rest_error_handler(func, media_type=...), and functools.wraps inside still copies the handler's name and docstring. The keyword-only media_type default leaves every existing call site unchanged.
  • The stream path matches the success path. _handle_streaming pulls the first item eagerly (:150-163), so a pre-stream error such as the A2A-Version: 9.9 case in the new test reaches the decorator's except and gets the new media type. An error after the first event is still sent as an SSE event: error, which this PR doesn't change.
  • The SDK client is unaffected. send_http_stream_request (client/transports/http_helpers.py:137-142) calls raise_for_status() before it looks at content-type. The status error handlers read the body with response.json(), which doesn't check the media type. So a 4xx/5xx labelled application/a2a+json takes the same path as before.
  • Headers match the success path. Starlette appends charset only to text/* types, so errors are sent as plain application/a2a+json, the same as A2AJSONResponse on success.
  • git merge-tree against main is clean.

Out of scope, for completeness: responses Starlette generates itself for the 1.0 REST mount (unknown path 404, wrong-method 405) are still text/plain, so a TCK check on "every error" could still flag those.

I didn't run the tests.

Reviewed with help from Claude Code.

@Iwaniukooo11 Iwaniukooo11 self-assigned this Nov 13, 2026

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.

3 participants