agentscope-ai / agentscope-ai/agentscope-java
[Bug]:JSON-RPC streaming client reports "Request cancelled" after receiving final TaskStatusUpdateEvent
- Ngôn ngữ chính
- Java
- Star
- 5.6k
- Fork
- 1.3k
- Merge trung bình
- 4 ngày 12 giờ
- Pull request đã merge (30 ngày)
- 77
Mô tả
## Description
When using the JSON-RPC streaming client with `a2a-java-sdk` version `0.3.3.Final`, the error callback is invoked with:
```text
Request cancelled
after the client has already received a valid terminal TaskStatusUpdateEvent with final=true.
The remote A2A task completes successfully, but the client reports the normal end of the stream as an error.
## Environment
- a2a-java-sdk: 0.3.3.Final
- Transport: JSON-RPC over SSE
- HTTP client: JdkA2AHttpClient
- Java: 21
- A2A method: message/stream
- Protocol version: A2A 0.3.x
Relevant dependencies:
io.github.a2asdk
a2a-java-sdk-client
0.3.3.Final
io.github.a2asdk
a2a-java-sdk-client-transport-jsonrpc
0.3.3.Final
io.github.a2asdk
a2a-java-sdk-http-client
0.3.3.Final
## Steps to reproduce
Create an A2A client with JSON-RPC streaming enabled:
ClientConfig config = ClientConfig.builder()
.setStreaming(true)
.setPolling(false)
.build();
Client client = Client.builder(agentCard)
.withTransport(
JSONRPCTransport.class,
new JSONRPCTransportConfig(new JdkA2AHttpClient()))
.clientConfig(config)
.build();
Send a streaming message:
client.sendMessage(
message,
List.of((event, card) -> {
System.out.println("EVENT: " + event);
}),
error -> {
System.out.println("ERROR: " + error);
},
null);
The server returns normal A2A SSE events and eventually sends:
{
"jsonrpc": "2.0",
"id": "request-id",
"result": {
"taskId": "task-id",
"status": {
"state": "completed",
"timestamp": "2026-09-01T06:19:29.2982548Z"
},
"contextId": "context-id",
"final": true,
"kind": "status-update"
}
}
The server logs confirm that the task completed successfully:
Agent execution completed successfully
Task completed: signal=onComplete
However, immediately after the final event, the client error callback receives:
Request cancelled
## Actual behavior
The callback sequence is effectively:
TaskStatusUpdateEvent(final=true)
errorHandler(Request cancelled)
The application therefore sees a successfully completed A2A task as a failed streaming request.
The issue does not occur when the same endpoint is called with curl -N. curl receives the terminal event and exits normally.
## Expected behavior
After receiving:
{
"kind": "status-update",
"final": true,
"status": {
"state": "completed"
}
}
the client should complete normally.
Expected callback behavior:
TaskStatusUpdateEvent(final=true)
completion callback
The error callback should not receive Request cancelled for an internally initiated stream shutdown after a valid final event.
## Suspected root cause
SSEEventListener.handleMessage() appears to cancel the HTTP future after delivering a final status event:
eventHandler.accept(streamingEvent);
if (streamingEvent instanceof TaskStatusUpdateEvent statusUpdate
&& statusUpdate.isFinal()) {
future.cancel(true);
}
Cancelling the JDK HTTP client's future causes the HTTP layer to report:
Request cancelled
The resulting exception is then forwarded by SSEEventListener.onError():
public void onError(Throwable error, Future future) {
if (errorHandler != null) {
errorHandler.accept(error);
}
future.cancel(true);
}
The client therefore does not distinguish between:
1. an internal cancellation after receiving final=true;
2. a user-initiated cancellation;
3. an actual network or transport failure.
The completed flag also appears to be set only from onComplete(), not when a final TaskStatusUpdateEvent is received.
## Suggested fix
Mark the listener as successfully completed before cancelling the HTTP future, and suppress the cancellation exception caused by this internal
cleanup.
For example:
private volatile boolean completed;
private void handleMessage(JsonNode message, Future future) {
StreamingEventKind event = parseEvent(message);
eventHandler.accept(event);
if (event instanceof TaskStatusUpdateEvent statusUpdate
&& statusUpdate.isFinal()) {
completed = true;
future.cancel(true);
signalSuccessfulCompletion();
}
}
public void onError(Throwable error, Future future) {
if (completed && isCancellation(error)) {
return;
}
if (errorHandler != null) {
errorHandler.accept(error);
}
}
Alternatively, the client could wait for the server to close the SSE connection naturally instead of cancelling the future after receiving
final=true.
## Workaround
At the application layer, we currently ignore Request cancelled only when:
- a final event has already been received; or
- valid response artifacts have already been received.
A cancellation received before any valid response is still treated as a real error.
This workaround prevents successful A2A streaming tasks from being reported as failures, but the behavior would be better fixed inside the SDK.
Hướng dẫn đóng góp
Đánh giá
Issue này chưa được đánh giá.