Repository navigation
Commit 7e55413
fix(client): raise A2A errors for malformed JSON-RPC responses (#1375)
# Description
`JsonRpcTransport` turned every response body into a `json-rpc`
`JSONRPC20Response` (`JSONRPC20Response(**response_data)` in the nine
unary methods, `JSONRPC20Response.from_json` for SSE events). That
package validates the envelope with its own exceptions, so a body that
is not a JSON-RPC 2.0 response object reached the caller as a raw
`TypeError`, `ValueError` or `json.JSONDecodeError` instead of an
`A2AClientError`:
| Response body | Before | After |
|---|---|---|
| a list, or a string (e.g. from a proxy) | `TypeError: ... argument
after ** must be a mapping` | `A2AClientError` |
| an object with neither `result` nor `error` | `ValueError: Either
result or error should be used` | `A2AClientError` |
| `"error": "boom"` | `TypeError: ... argument after ** must be a
mapping` | `A2AClientError` |
| error object with an extra member, e.g. `{"code": -32001, "message":
"...", "reason": "x"}` | `TypeError: JSONRPCError.__init__() got an
unexpected keyword argument 'reason'` | `TaskNotFoundError` |
| error object without `message`, e.g. `{"code": -32001}` | `ValueError:
Error message should be string` | `TaskNotFoundError` |
| SSE `data:` that is not JSON | `json.JSONDecodeError` (raised in the
consumer loop, outside `handle_http_exceptions`) | `A2AClientError` |
| `event: error` whose error object has an extra member | `TypeError` |
`TaskNotFoundError` |
The last two error-object rows matter most: the server sent a valid A2A
error code, and `_create_jsonrpc_error` already handles a missing
`message` (`error_dict.get('message', str(error_dict))`), but the
`json-rpc` validation failed before that code ran. It is also the
error-object counterpart of the spec 5.7 leniency added for results in
#1354.
Change, all in `src/a2a/client/transports/jsonrpc.py`:
- New `_check_jsonrpc_response`: an `error` object raises the mapped A2A
error through the existing `_create_jsonrpc_error`; a non-object body, a
non-object `error`, or a body with neither `result` nor `error` raises
`A2AClientError`. A falsy `error` next to a `result` is still treated as
success, as before.
- `_send_request` and the SSE loop call it, so the unary methods read
`response_data['result']` and no longer need their own error checks.
`_handle_sse_error` parses with `json.loads` and keeps its `SSE stream
error: ...` fallback.
- `JSONRPC20Response` is no longer used on the client (requests still
use `JSONRPC20Request`). Well-formed success and error responses behave
as before; the existing tests pass unchanged.
Tests: `TestMalformedJsonRpcResponses` in
`tests/client/transports/test_jsonrpc_client.py`, 11 cases (unary
invalid envelopes, non-standard error objects, streaming `data:` and
`event: error`, plus a guard that `delete_task_push_notification_config`
still raises on an error response). 10 of them fail on `main` with the
exceptions in the table and pass with the change.
The v0.3 `CompatJsonRpcTransport` has the same
`JSONRPC20Response(**response_data)` pattern (an error object with an
extra member raises `TypeError` there too). I kept this PR to the 1.0
client and can send a `fix(compat)` follow-up.
Checks, run on Windows 11 with Python 3.10.19:
- `bash ./scripts/lint.sh`: ruff check, ruff format and `ty` pass (three
existing unused `type: ignore` warnings in files this PR does not
touch).
- `uv run pytest tests/client/transports/test_jsonrpc_client.py`: 51
passed.
- `uv run pytest --ignore=tests/integration/cross_version
--ignore=tests/integration/test_scenarios.py --cov=src`: 2213 passed,
199 skipped, 3 xfailed; total coverage 93%. The 3 failures
(`test_samples_smoke.py`, console encoding) and 4 setup errors
(`push_notifications` e2e, `Can't pickle local object` under Windows
spawn) fail the same way on `main` here.
`tests/integration/cross_version` (uses `os.killpg`) and
`tests/integration/test_scenarios.py` (hangs on Windows on `main` too;
it only builds gRPC clients) were not run locally, so the Linux CI run
is the check for those.
- [x] Follow the [`CONTRIBUTING`
Guide](https://github.com/a2aproject/a2a-python/blob/main/CONTRIBUTING.md).
- [x] Make your Pull Request title in the
<https://www.conventionalcommits.org/> specification.
- [x] Ensure the tests and linter pass
- [x] Appropriate docs were updated (if necessary)
Co-authored-by: Mateusz Iwaniuk <45974414+Iwaniukooo11@users.noreply.github.com>1 parent b21a144 commit 7e55413
2 files changed
Lines changed: 193 additions & 54 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
1 | 2 | | |
2 | 3 | | |
3 | 4 | | |
| |||
7 | 8 | | |
8 | 9 | | |
9 | 10 | | |
10 | | - | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
| |||
80 | 81 | | |
81 | 82 | | |
82 | 83 | | |
83 | | - | |
84 | | - | |
85 | | - | |
86 | 84 | | |
87 | | - | |
| 85 | + | |
88 | 86 | | |
89 | 87 | | |
90 | 88 | | |
| |||
121 | 119 | | |
122 | 120 | | |
123 | 121 | | |
124 | | - | |
125 | | - | |
126 | | - | |
127 | | - | |
| 122 | + | |
128 | 123 | | |
129 | 124 | | |
130 | 125 | | |
| |||
142 | 137 | | |
143 | 138 | | |
144 | 139 | | |
145 | | - | |
146 | | - | |
147 | | - | |
148 | 140 | | |
149 | | - | |
| 141 | + | |
150 | 142 | | |
151 | 143 | | |
152 | 144 | | |
| |||
165 | 157 | | |
166 | 158 | | |
167 | 159 | | |
168 | | - | |
169 | | - | |
170 | | - | |
171 | | - | |
| 160 | + | |
172 | 161 | | |
173 | 162 | | |
174 | 163 | | |
| |||
186 | 175 | | |
187 | 176 | | |
188 | 177 | | |
189 | | - | |
190 | | - | |
191 | | - | |
192 | 178 | | |
193 | | - | |
| 179 | + | |
194 | 180 | | |
195 | 181 | | |
196 | 182 | | |
| |||
209 | 195 | | |
210 | 196 | | |
211 | 197 | | |
212 | | - | |
213 | | - | |
214 | | - | |
215 | 198 | | |
216 | | - | |
| 199 | + | |
217 | 200 | | |
218 | 201 | | |
219 | 202 | | |
| |||
232 | 215 | | |
233 | 216 | | |
234 | 217 | | |
235 | | - | |
236 | | - | |
237 | | - | |
238 | 218 | | |
239 | | - | |
| 219 | + | |
240 | 220 | | |
241 | 221 | | |
242 | 222 | | |
| |||
252 | 232 | | |
253 | 233 | | |
254 | 234 | | |
255 | | - | |
256 | | - | |
257 | | - | |
258 | | - | |
259 | | - | |
260 | | - | |
| 235 | + | |
261 | 236 | | |
262 | 237 | | |
263 | 238 | | |
| |||
297 | 272 | | |
298 | 273 | | |
299 | 274 | | |
300 | | - | |
301 | | - | |
302 | | - | |
| 275 | + | |
303 | 276 | | |
304 | | - | |
305 | | - | |
306 | | - | |
307 | | - | |
308 | | - | |
309 | | - | |
310 | | - | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
311 | 280 | | |
312 | 281 | | |
313 | 282 | | |
| |||
357 | 326 | | |
358 | 327 | | |
359 | 328 | | |
360 | | - | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
361 | 360 | | |
362 | 361 | | |
363 | 362 | | |
| |||
375 | 374 | | |
376 | 375 | | |
377 | 376 | | |
378 | | - | |
379 | | - | |
380 | | - | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
381 | 384 | | |
382 | | - | |
| 385 | + | |
383 | 386 | | |
384 | 387 | | |
385 | 388 | | |
386 | 389 | | |
387 | 390 | | |
388 | | - | |
389 | | - | |
390 | | - | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
391 | 399 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | | - | |
| 33 | + | |
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| |||
828 | 828 | | |
829 | 829 | | |
830 | 830 | | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
| 850 | + | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
| 854 | + | |
| 855 | + | |
| 856 | + | |
| 857 | + | |
| 858 | + | |
| 859 | + | |
| 860 | + | |
| 861 | + | |
| 862 | + | |
| 863 | + | |
| 864 | + | |
| 865 | + | |
| 866 | + | |
| 867 | + | |
| 868 | + | |
| 869 | + | |
| 870 | + | |
| 871 | + | |
| 872 | + | |
| 873 | + | |
| 874 | + | |
| 875 | + | |
| 876 | + | |
| 877 | + | |
| 878 | + | |
| 879 | + | |
| 880 | + | |
| 881 | + | |
| 882 | + | |
| 883 | + | |
| 884 | + | |
| 885 | + | |
| 886 | + | |
| 887 | + | |
| 888 | + | |
| 889 | + | |
| 890 | + | |
| 891 | + | |
| 892 | + | |
| 893 | + | |
| 894 | + | |
| 895 | + | |
| 896 | + | |
| 897 | + | |
| 898 | + | |
| 899 | + | |
| 900 | + | |
| 901 | + | |
| 902 | + | |
| 903 | + | |
| 904 | + | |
| 905 | + | |
| 906 | + | |
| 907 | + | |
| 908 | + | |
| 909 | + | |
| 910 | + | |
| 911 | + | |
| 912 | + | |
| 913 | + | |
| 914 | + | |
| 915 | + | |
| 916 | + | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
| 921 | + | |
| 922 | + | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
| 946 | + | |
| 947 | + | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
0 commit comments