fix(a2a-client): surface plain JSON-RPC error responses to streaming calls#79
fix(a2a-client): surface plain JSON-RPC error responses to streaming calls#79arkavo-com wants to merge 1 commit into
Conversation
…calls (spec §9.4.2) A server that answers a streaming call (SendStreamingMessage, SubscribeToTask) with a plain application/json JSON-RPC error envelope — as a2a-rs's own server does for pre-stream errors such as -32004 UnsupportedOperation — produces a body with no SSE event boundary. parse_sse_bytes discarded the unterminated buffer at end-of-stream, so the caller saw an empty, successfully-closed stream and the error was silently swallowed. Track whether any SSE-framed event boundary was observed; at end-of-stream, if none was and residual bytes remain, parse them as a JsonRpcResponse envelope: an error is surfaced through the same envelope-error mapping as SSE-framed errors, a result is delivered as a normal event, and anything else keeps the existing clean-end behavior. Found by cross-SDK interop testing (a2a-conformance).
There was a problem hiding this comment.
Code Review
This pull request introduces fallback handling for non-SSE-framed plain JSON-RPC response envelopes when a streaming call fails before producing a stream. It adds a parse_plain_json_tail helper, tracks whether any SSE events were seen during streaming, and parses the residual buffer at the end of the stream if no SSE events were observed. Additionally, several unit tests are added to verify this behavior. A review comment suggests updating an error message in parse_plain_json_tail to refer to a 'JSON-RPC parse error' instead of an 'SSE parse error' to prevent confusion.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| let result = rpc_resp.result?; | ||
| match protojson_conv::from_value::<StreamResponse>(result) { | ||
| Ok(sr) => Some(Ok(sr)), | ||
| Err(e) => Some(Err(A2AError::internal(format!("SSE parse error: {e}")))), |
There was a problem hiding this comment.
Since this function is specifically parsing a plain JSON-RPC response tail (non-SSE), the error message should refer to a JSON-RPC or plain JSON parse error rather than an SSE parse error to avoid confusion during debugging.
| Err(e) => Some(Err(A2AError::internal(format!("SSE parse error: {e}")))), | |
| Err(e) => Some(Err(A2AError::internal(format!("JSON-RPC parse error: {e}")))), |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Problem
parse_sse_bytesdiscards any unterminated buffer at end-of-stream. A server that answers a streaming call (SendStreamingMessage,SubscribeToTask) with a plain JSON-RPC error envelope —Content-Type: application/json, no SSE framing — therefore produces an empty, successfully-closed stream, and the caller never sees the error.a2a-serveritself responds this way for pre-stream errors (e.g.-32004fromSubscribeToTaskon a terminal task), so the SDK's own client+server pair silently loses these errors today.Fix
Track whether any SSE event boundary was observed; at end-of-stream with no SSE framing and a non-empty residual buffer, parse the buffer as a
JsonRpcResponseenvelope. A contained error is emitted through the same mapping as SSE-framed envelope errors; a contained result is delivered as a normalStreamResponse; anything else keeps the existing clean-end behavior. SSE streams with trailing partial events are unaffected.Found by / verified with
Cross-SDK interop testing (a2a-conformance, scenario
errors/unsupported-operation-32004; FINDINGS.md finding 3): the a2a-rs client vs. its own server previously read the-32004envelope as an empty stream; with this branch the scenario passes. 5 new unit tests;cargo test --workspace452 passed / 0 failed (baseline on main: 447/0); fmt + clippy clean.Related spec note
The underlying ambiguity isn't this SDK's fault: §9.4.2 doesn't specify whether a pre-stream protocol error is delivered as plain JSON or as an SSE-framed event, and implementations have picked both. Happy to open a spec issue on a2aproject/A2A if maintainers think it's worth pinning down — this fix makes the client robust to either choice.
🤖 Generated with Claude Code