|
1 | 1 | package org.a2aproject.sdk.grpc.utils; |
2 | 2 |
|
3 | 3 | import org.a2aproject.sdk.spec.A2AErrorCodes; |
| 4 | + |
4 | 5 | import static org.a2aproject.sdk.spec.A2AMethods.CANCEL_TASK_METHOD; |
5 | 6 | import static org.a2aproject.sdk.spec.A2AMethods.GET_EXTENDED_AGENT_CARD_METHOD; |
6 | 7 | import static org.a2aproject.sdk.spec.A2AMethods.SEND_STREAMING_MESSAGE_METHOD; |
7 | 8 |
|
8 | 9 | import java.io.IOException; |
9 | 10 | import java.io.StringWriter; |
| 11 | +import java.util.List; |
10 | 12 | import java.util.Map; |
11 | 13 | import java.util.UUID; |
12 | 14 | import java.util.logging.Level; |
|
66 | 68 | import org.a2aproject.sdk.spec.TaskNotFoundError; |
67 | 69 | import org.a2aproject.sdk.spec.UnsupportedOperationError; |
68 | 70 | import org.a2aproject.sdk.spec.VersionNotSupportedError; |
| 71 | +import org.a2aproject.sdk.util.ErrorDetail; |
69 | 72 | import org.a2aproject.sdk.util.Utils; |
70 | 73 | import org.jspecify.annotations.Nullable; |
71 | 74 |
|
@@ -284,11 +287,12 @@ public static StreamResponse parseResponseEvent(String body) throws JsonMappingE |
284 | 287 | JsonElement jelement = JsonParser.parseString(body); |
285 | 288 | JsonObject jsonRpc = jelement.getAsJsonObject(); |
286 | 289 | String version = getAndValidateJsonrpc(jsonRpc); |
287 | | - Object id = getAndValidateId(jsonRpc); |
288 | | - JsonElement paramsNode = jsonRpc.get("result"); |
| 290 | + // Check for error before validating id: per JSON-RPC spec, error responses may have null id |
289 | 291 | if (jsonRpc.has("error")) { |
290 | 292 | throw processError(jsonRpc.getAsJsonObject("error")); |
291 | 293 | } |
| 294 | + Object id = getAndValidateId(jsonRpc); |
| 295 | + JsonElement paramsNode = jsonRpc.get("result"); |
292 | 296 | StreamResponse.Builder builder = StreamResponse.newBuilder(); |
293 | 297 | parseRequestBody(paramsNode, builder, id); |
294 | 298 | return builder.build(); |
@@ -392,8 +396,16 @@ private static A2AError processError(JsonObject error) { |
392 | 396 | String message = error.has("message") ? error.get("message").getAsString() : null; |
393 | 397 | Integer code = error.has("code") ? error.get("code").getAsInt() : null; |
394 | 398 | Map<String, Object> details = null; |
395 | | - if (error.has("data") && error.get("data").isJsonObject()) { |
396 | | - details =GSON.fromJson(error.get("data"), Map.class); |
| 399 | + if (error.has("data")) { |
| 400 | + JsonElement data = error.get("data"); |
| 401 | + if (data.isJsonObject()) { |
| 402 | + details = GSON.fromJson(data, Map.class); |
| 403 | + } else if (data.isJsonArray() && !data.getAsJsonArray().isEmpty()) { |
| 404 | + JsonElement first = data.getAsJsonArray().get(0); |
| 405 | + if (first.isJsonObject()) { |
| 406 | + details = GSON.fromJson(first.getAsJsonObject(), Map.class); |
| 407 | + } |
| 408 | + } |
397 | 409 | } |
398 | 410 | if (code != null) { |
399 | 411 | A2AErrorCodes errorCode = A2AErrorCodes.fromCode(code); |
@@ -605,10 +617,10 @@ public static String toJsonRPCErrorResponse(Object requestId, A2AError error) { |
605 | 617 | output.beginObject(); |
606 | 618 | output.name("code").value(error.getCode()); |
607 | 619 | output.name("message").value(error.getMessage()); |
608 | | - if (!error.getDetails().isEmpty()) { |
609 | | - output.name("data"); |
610 | | - GSON.toJson(error.getDetails(), Map.class, output); |
611 | | - } |
| 620 | + A2AErrorCodes a2aErrorCode = A2AErrorCodes.fromCode(error.getCode()); |
| 621 | + String reason = a2aErrorCode != null ? a2aErrorCode.name() : A2AErrorCodes.INTERNAL.name(); |
| 622 | + output.name("data"); |
| 623 | + GSON.toJson(List.of(ErrorDetail.of(reason, error.getDetails())), List.class, output); |
612 | 624 | output.endObject(); |
613 | 625 | output.endObject(); |
614 | 626 | return result.toString(); |
|
0 commit comments