diff --git a/client/transport/jsonrpc/src/test/java/io/a2a/client/transport/jsonrpc/JsonMessages.java b/client/transport/jsonrpc/src/test/java/io/a2a/client/transport/jsonrpc/JsonMessages.java index a0be30e0d..a2806a1be 100644 --- a/client/transport/jsonrpc/src/test/java/io/a2a/client/transport/jsonrpc/JsonMessages.java +++ b/client/transport/jsonrpc/src/test/java/io/a2a/client/transport/jsonrpc/JsonMessages.java @@ -216,7 +216,7 @@ public class JsonMessages { "error": { "code": -32702, "message": "Invalid parameters", - "details": {"info": "Hello world"} + "data": {"info": "Hello world"} } }"""; diff --git a/client/transport/jsonrpc/src/test/java/io/a2a/client/transport/jsonrpc/JsonStreamingMessages.java b/client/transport/jsonrpc/src/test/java/io/a2a/client/transport/jsonrpc/JsonStreamingMessages.java index df6fee000..cb05f6d37 100644 --- a/client/transport/jsonrpc/src/test/java/io/a2a/client/transport/jsonrpc/JsonStreamingMessages.java +++ b/client/transport/jsonrpc/src/test/java/io/a2a/client/transport/jsonrpc/JsonStreamingMessages.java @@ -99,7 +99,7 @@ public class JsonStreamingMessages { "error": { "code": -32602, "message": "Invalid parameters", - "details": {"info": "Missing required field"} + "data": {"info": "Missing required field"} } }"""; diff --git a/reference/rest/src/test/java/io/a2a/server/rest/quarkus/A2AServerRoutesTest.java b/reference/rest/src/test/java/io/a2a/server/rest/quarkus/A2AServerRoutesTest.java index 968c93ef0..32141f3ad 100644 --- a/reference/rest/src/test/java/io/a2a/server/rest/quarkus/A2AServerRoutesTest.java +++ b/reference/rest/src/test/java/io/a2a/server/rest/quarkus/A2AServerRoutesTest.java @@ -446,9 +446,9 @@ public void testDeleteTaskPushNotificationConfiguration_MethodNameSetInContext() public void testSendMessage_UnsupportedContentType_ReturnsContentTypeNotSupportedError() { // Arrange HTTPRestResponse mockErrorResponse = mock(HTTPRestResponse.class); - when(mockErrorResponse.getStatusCode()).thenReturn(415); + when(mockErrorResponse.getStatusCode()).thenReturn(400); when(mockErrorResponse.getContentType()).thenReturn(APPLICATION_JSON); - when(mockErrorResponse.getBody()).thenReturn("{\"error\":{\"code\":415,\"status\":\"INVALID_ARGUMENT\",\"message\":\"Incompatible content types\",\"details\":[{\"reason\":\"CONTENT_TYPE_NOT_SUPPORTED\",\"domain\":\"a2a-protocol.org\"}]}}"); + when(mockErrorResponse.getBody()).thenReturn("{\"error\":{\"code\":400,\"status\":\"INVALID_ARGUMENT\",\"message\":\"Incompatible content types\",\"details\":[{\"reason\":\"CONTENT_TYPE_NOT_SUPPORTED\",\"domain\":\"a2a-protocol.org\"}]}}"); when(mockRestHandler.createErrorResponse(any(ContentTypeNotSupportedError.class))).thenReturn(mockErrorResponse); when(mockRequest.getHeader(any(CharSequence.class))).thenReturn("text/plain"); @@ -464,9 +464,9 @@ public void testSendMessage_UnsupportedContentType_ReturnsContentTypeNotSupporte public void testSendMessageStreaming_UnsupportedContentType_ReturnsContentTypeNotSupportedError() { // Arrange HTTPRestResponse mockErrorResponse = mock(HTTPRestResponse.class); - when(mockErrorResponse.getStatusCode()).thenReturn(415); + when(mockErrorResponse.getStatusCode()).thenReturn(400); when(mockErrorResponse.getContentType()).thenReturn(APPLICATION_JSON); - when(mockErrorResponse.getBody()).thenReturn("{\"error\":{\"code\":415,\"status\":\"INVALID_ARGUMENT\",\"message\":\"Incompatible content types\",\"details\":[{\"reason\":\"CONTENT_TYPE_NOT_SUPPORTED\",\"domain\":\"a2a-protocol.org\"}]}}"); + when(mockErrorResponse.getBody()).thenReturn("{\"error\":{\"code\":400,\"status\":\"INVALID_ARGUMENT\",\"message\":\"Incompatible content types\",\"details\":[{\"reason\":\"CONTENT_TYPE_NOT_SUPPORTED\",\"domain\":\"a2a-protocol.org\"}]}}"); when(mockRestHandler.createErrorResponse(any(ContentTypeNotSupportedError.class))).thenReturn(mockErrorResponse); when(mockRequest.getHeader(any(CharSequence.class))).thenReturn("text/plain"); diff --git a/reference/rest/src/test/java/io/a2a/server/rest/quarkus/QuarkusA2ARestTest.java b/reference/rest/src/test/java/io/a2a/server/rest/quarkus/QuarkusA2ARestTest.java index 15f5178a3..25749de50 100644 --- a/reference/rest/src/test/java/io/a2a/server/rest/quarkus/QuarkusA2ARestTest.java +++ b/reference/rest/src/test/java/io/a2a/server/rest/quarkus/QuarkusA2ARestTest.java @@ -41,7 +41,7 @@ public void testSendMessageWithUnsupportedContentType() throws Exception { .header("Content-Type", "text/plain") .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - Assertions.assertEquals(415, response.statusCode()); + Assertions.assertEquals(400, response.statusCode()); Assertions.assertTrue(response.body().contains("CONTENT_TYPE_NOT_SUPPORTED"), "Expected CONTENT_TYPE_NOT_SUPPORTED in response body: " + response.body()); } @@ -58,7 +58,7 @@ public void testSendMessageWithUnsupportedProtocolVersion() throws Exception { .header("A2A-Version", "0.4.0") .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - Assertions.assertEquals(400, response.statusCode()); + Assertions.assertEquals(501, response.statusCode()); Assertions.assertTrue(response.body().contains("VERSION_NOT_SUPPORTED"), "Expected VERSION_NOT_SUPPORTED in response body: " + response.body()); } diff --git a/spec-grpc/src/main/java/io/a2a/grpc/utils/JSONRPCUtils.java b/spec-grpc/src/main/java/io/a2a/grpc/utils/JSONRPCUtils.java index a21008bda..591913f4e 100644 --- a/spec-grpc/src/main/java/io/a2a/grpc/utils/JSONRPCUtils.java +++ b/spec-grpc/src/main/java/io/a2a/grpc/utils/JSONRPCUtils.java @@ -392,8 +392,8 @@ private static A2AError processError(JsonObject error) { String message = error.has("message") ? error.get("message").getAsString() : null; Integer code = error.has("code") ? error.get("code").getAsInt() : null; Map details = null; - if (error.has("details") && error.get("details").isJsonObject()) { - details =GSON.fromJson(error.get("details"), Map.class); + if (error.has("data") && error.get("data").isJsonObject()) { + details = GSON.fromJson(error.get("data"), Map.class); } if (code != null) { A2AErrorCodes errorCode = A2AErrorCodes.fromCode(code); @@ -606,7 +606,7 @@ public static String toJsonRPCErrorResponse(Object requestId, A2AError error) { output.name("code").value(error.getCode()); output.name("message").value(error.getMessage()); if (!error.getDetails().isEmpty()) { - output.name("details"); + output.name("data"); GSON.toJson(error.getDetails(), Map.class, output); } output.endObject(); diff --git a/spec/src/main/java/io/a2a/spec/A2AErrorCodes.java b/spec/src/main/java/io/a2a/spec/A2AErrorCodes.java index 1143d7dfc..a4eddec0b 100644 --- a/spec/src/main/java/io/a2a/spec/A2AErrorCodes.java +++ b/spec/src/main/java/io/a2a/spec/A2AErrorCodes.java @@ -20,19 +20,19 @@ public enum A2AErrorCodes { TASK_NOT_FOUND(-32001, "NOT_FOUND", 404), /** Error code indicating the task cannot be canceled in its current state (-32002). */ - TASK_NOT_CANCELABLE(-32002, "FAILED_PRECONDITION", 409), + TASK_NOT_CANCELABLE(-32002, "FAILED_PRECONDITION", 400), /** Error code indicating push notifications are not supported by this agent (-32003). */ - PUSH_NOTIFICATION_NOT_SUPPORTED(-32003, "UNIMPLEMENTED", 400), + PUSH_NOTIFICATION_NOT_SUPPORTED(-32003, "FAILED_PRECONDITION", 400), /** Error code indicating the requested operation is not supported (-32004). */ - UNSUPPORTED_OPERATION(-32004, "UNIMPLEMENTED", 400), + UNSUPPORTED_OPERATION(-32004, "UNIMPLEMENTED", 501), /** Error code indicating the content type is not supported (-32005). */ - CONTENT_TYPE_NOT_SUPPORTED(-32005, "INVALID_ARGUMENT", 415), + CONTENT_TYPE_NOT_SUPPORTED(-32005, "INVALID_ARGUMENT", 400), /** Error code indicating the agent returned an invalid response (-32006). */ - INVALID_AGENT_RESPONSE(-32006, "INTERNAL", 502), + INVALID_AGENT_RESPONSE(-32006, "INTERNAL", 500), /** Error code indicating extended agent card is not configured (-32007). */ EXTENDED_AGENT_CARD_NOT_CONFIGURED(-32007, "FAILED_PRECONDITION", 400), @@ -43,7 +43,7 @@ public enum A2AErrorCodes { /** Error code indicating the A2A protocol version specified in the request (via A2A-Version service parameter) * is not supported by the agent (-32009). */ - VERSION_NOT_SUPPORTED(-32009, "UNIMPLEMENTED", 400), + VERSION_NOT_SUPPORTED(-32009, "UNIMPLEMENTED", 501), /** JSON-RPC error code for invalid request structure (-32600). */ INVALID_REQUEST(-32600, "INVALID_ARGUMENT", 400), diff --git a/transport/grpc/src/main/java/io/a2a/transport/grpc/handler/GrpcHandler.java b/transport/grpc/src/main/java/io/a2a/transport/grpc/handler/GrpcHandler.java index fe21c5ce3..0e1c2b5ff 100644 --- a/transport/grpc/src/main/java/io/a2a/transport/grpc/handler/GrpcHandler.java +++ b/transport/grpc/src/main/java/io/a2a/transport/grpc/handler/GrpcHandler.java @@ -706,7 +706,7 @@ private ServerCallContext createCallContext(StreamObserver responseObserv *
  • {@link InternalError} → {@code INTERNAL}
  • *
  • {@link TaskNotFoundError} → {@code NOT_FOUND}
  • *
  • {@link TaskNotCancelableError} → {@code FAILED_PRECONDITION}
  • - *
  • {@link PushNotificationNotSupportedError} → {@code UNIMPLEMENTED}
  • + *
  • {@link PushNotificationNotSupportedError} → {@code FAILED_PRECONDITION}
  • *
  • {@link UnsupportedOperationError} → {@code UNIMPLEMENTED}
  • *
  • {@link JSONParseError} → {@code INTERNAL}
  • *
  • {@link ContentTypeNotSupportedError} → {@code INVALID_ARGUMENT}
  • diff --git a/transport/grpc/src/test/java/io/a2a/transport/grpc/handler/GrpcHandlerTest.java b/transport/grpc/src/test/java/io/a2a/transport/grpc/handler/GrpcHandlerTest.java index b26b58c53..f7be8f2f4 100644 --- a/transport/grpc/src/test/java/io/a2a/transport/grpc/handler/GrpcHandlerTest.java +++ b/transport/grpc/src/test/java/io/a2a/transport/grpc/handler/GrpcHandlerTest.java @@ -251,7 +251,7 @@ public void testPushNotificationsNotSupportedError() throws Exception { GrpcHandler handler = new TestGrpcHandler(card, requestHandler, internalExecutor); StreamRecorder streamRecorder = createTaskPushNotificationConfigRequest(handler, AbstractA2ARequestHandlerTest.MINIMAL_TASK.id(), AbstractA2ARequestHandlerTest.MINIMAL_TASK.id()); - assertGrpcError(streamRecorder, Status.Code.UNIMPLEMENTED); + assertGrpcError(streamRecorder, Status.Code.FAILED_PRECONDITION); } @Test @@ -656,7 +656,7 @@ public void testListPushNotificationConfigNotSupported() throws Exception { .build(); StreamRecorder streamRecorder = StreamRecorder.create(); handler.listTaskPushNotificationConfigs(request, streamRecorder); - assertGrpcError(streamRecorder, Status.Code.UNIMPLEMENTED); + assertGrpcError(streamRecorder, Status.Code.FAILED_PRECONDITION); } @Test @@ -727,7 +727,7 @@ public void testDeletePushNotificationConfigNotSupported() throws Exception { .build(); StreamRecorder streamRecorder = StreamRecorder.create(); handler.deleteTaskPushNotificationConfig(request, streamRecorder); - assertGrpcError(streamRecorder, Status.Code.UNIMPLEMENTED); + assertGrpcError(streamRecorder, Status.Code.FAILED_PRECONDITION); } @Test diff --git a/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java b/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java index ea16f3aa4..965e50362 100644 --- a/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java +++ b/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java @@ -3,7 +3,6 @@ import static io.a2a.common.MediaType.APPLICATION_JSON; import static io.a2a.server.util.async.AsyncUtils.createTubeConfig; -import io.a2a.spec.A2AErrorCodes; import java.time.Instant; import java.time.format.DateTimeParseException; @@ -40,6 +39,7 @@ import io.a2a.server.version.A2AVersionValidator; import io.a2a.server.util.async.Internal; import io.a2a.spec.A2AError; +import io.a2a.spec.A2AErrorCodes; import io.a2a.spec.AgentCard; import io.a2a.spec.CancelTaskParams; import io.a2a.spec.ContentTypeNotSupportedError; @@ -122,13 +122,13 @@ public class RestHandler { private static final Logger log = Logger.getLogger(RestHandler.class.getName()); - private static final String TASK_STATE_PREFIX = "TASK_STATE_"; // Fields set by constructor injection cannot be final. We need a noargs constructor for // Jakarta compatibility, and it seems that making fields set by constructor injection // final, is not proxyable in all runtimes private AgentCard agentCard; - private @Nullable Instance extendedAgentCard; + private @Nullable + Instance extendedAgentCard; private AgentCardCacheMetadata cacheMetadata; private RequestHandler requestHandler; private Executor executor; @@ -377,7 +377,7 @@ public HTTPRestResponse createTaskPushNotificationConfiguration(ServerCallContex if (!taskIdFromBody.isEmpty() && !taskIdFromBody.equals(taskId)) { throw new InvalidParamsError("Task ID in request body (" + taskIdFromBody + ") does not match task ID in URL path (" + taskId + ")."); } - + builder.setTenant(tenant); builder.setTaskId(taskId); TaskPushNotificationConfig result = requestHandler.onCreateTaskPushNotificationConfig(ProtoUtils.FromProto.createTaskPushNotificationConfig(builder), context); @@ -766,29 +766,38 @@ private static int mapErrorToHttpStatus(A2AError error) { if (error instanceof InvalidParamsError) { return 422; } - if (error instanceof MethodNotFoundError || error instanceof TaskNotFoundError) { - return 404; + if (error instanceof MethodNotFoundError) { + return A2AErrorCodes.METHOD_NOT_FOUND.httpCode(); + } + if (error instanceof TaskNotFoundError) { + return A2AErrorCodes.TASK_NOT_FOUND.httpCode(); } if (error instanceof TaskNotCancelableError) { - return 409; + return A2AErrorCodes.TASK_NOT_CANCELABLE.httpCode(); } if (error instanceof UnsupportedOperationError) { - return 501; + return A2AErrorCodes.UNSUPPORTED_OPERATION.httpCode(); } if (error instanceof ContentTypeNotSupportedError) { - return 415; + return A2AErrorCodes.CONTENT_TYPE_NOT_SUPPORTED.httpCode(); } if (error instanceof InvalidAgentResponseError) { - return 502; + return A2AErrorCodes.INVALID_AGENT_RESPONSE.httpCode(); + } + if (error instanceof ExtendedAgentCardNotConfiguredError) { + return A2AErrorCodes.EXTENDED_AGENT_CARD_NOT_CONFIGURED.httpCode(); + } + if (error instanceof ExtensionSupportRequiredError) { + return A2AErrorCodes.EXTENSION_SUPPORT_REQUIRED.httpCode(); } - if (error instanceof ExtendedAgentCardNotConfiguredError - || error instanceof ExtensionSupportRequiredError - || error instanceof VersionNotSupportedError - || error instanceof PushNotificationNotSupportedError) { - return 400; + if (error instanceof VersionNotSupportedError) { + return A2AErrorCodes.VERSION_NOT_SUPPORTED.httpCode(); + } + if (error instanceof PushNotificationNotSupportedError) { + return A2AErrorCodes.PUSH_NOTIFICATION_NOT_SUPPORTED.httpCode(); } if (error instanceof InternalError) { - return 500; + return A2AErrorCodes.INTERNAL.httpCode(); } return 500; } @@ -827,7 +836,7 @@ public HTTPRestResponse getExtendedAgentCard(ServerCallContext context, String t } catch (A2AError e) { return createErrorResponse(e); } catch (Throwable t) { - return createErrorResponse(500, new InternalError(t.getMessage())); + return createErrorResponse(A2AErrorCodes.INTERNAL.httpCode(), new InternalError(t.getMessage())); } } @@ -1036,12 +1045,16 @@ public String toString() { return "HTTPRestErrorResponse{error=" + error + '}'; } - private record ErrorBody(int code, String status, String message, List details) {} + private record ErrorBody(int code, String status, String message, List details) { + + } private record ErrorDetail( @com.google.gson.annotations.SerializedName("@type") String type, String reason, String domain, - Map metadata) {} + Map metadata) { + + } } } diff --git a/transport/rest/src/test/java/io/a2a/transport/rest/handler/RestHandlerTest.java b/transport/rest/src/test/java/io/a2a/transport/rest/handler/RestHandlerTest.java index f5184b2f4..53875d698 100644 --- a/transport/rest/src/test/java/io/a2a/transport/rest/handler/RestHandlerTest.java +++ b/transport/rest/src/test/java/io/a2a/transport/rest/handler/RestHandlerTest.java @@ -798,7 +798,7 @@ public void testVersionNotSupportedErrorOnSendMessage() { RestHandler.HTTPRestResponse response = handler.sendMessage(contextWithVersion, "", requestBody); - assertProblemDetail(response, 400, + assertProblemDetail(response, 501, "VERSION_NOT_SUPPORTED", "Protocol version '2.0' is not supported. Supported versions: [1.0]"); }