From d87ca593297adf7da9e118e56fef357a28f4edf0 Mon Sep 17 00:00:00 2001 From: Dang Zitou Date: Thu, 13 Aug 2026 02:43:36 +0800 Subject: [PATCH 1/2] fix(grpc): align unsupported operation status This fixes #1051 --- .../transport/grpc/GrpcErrorMapperTest.java | 2 +- .../org/a2aproject/sdk/spec/A2AErrorCodes.java | 2 +- .../sdk/transport/grpc/handler/GrpcHandler.java | 4 ++-- .../transport/grpc/handler/GrpcHandlerTest.java | 16 ++++++++-------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/client/transport/grpc/src/test/java/org/a2aproject/sdk/client/transport/grpc/GrpcErrorMapperTest.java b/client/transport/grpc/src/test/java/org/a2aproject/sdk/client/transport/grpc/GrpcErrorMapperTest.java index 421d92cbb..c7432a808 100644 --- a/client/transport/grpc/src/test/java/org/a2aproject/sdk/client/transport/grpc/GrpcErrorMapperTest.java +++ b/client/transport/grpc/src/test/java/org/a2aproject/sdk/client/transport/grpc/GrpcErrorMapperTest.java @@ -109,7 +109,7 @@ public void testTaskNotFoundErrorUnmarshalling() { public void testUnsupportedOperationErrorUnmarshalling() { String errorMessage = "Operation not supported"; StatusRuntimeException grpcException = createA2AStatusException( - Status.Code.UNIMPLEMENTED.value(), errorMessage, "UNSUPPORTED_OPERATION"); + Status.Code.FAILED_PRECONDITION.value(), errorMessage, "UNSUPPORTED_OPERATION"); A2AClientException result = GrpcErrorMapper.mapGrpcError(grpcException); diff --git a/spec/src/main/java/org/a2aproject/sdk/spec/A2AErrorCodes.java b/spec/src/main/java/org/a2aproject/sdk/spec/A2AErrorCodes.java index 79129944c..bbd84ccc2 100644 --- a/spec/src/main/java/org/a2aproject/sdk/spec/A2AErrorCodes.java +++ b/spec/src/main/java/org/a2aproject/sdk/spec/A2AErrorCodes.java @@ -26,7 +26,7 @@ public enum A2AErrorCodes { PUSH_NOTIFICATION_NOT_SUPPORTED(-32003, "UNIMPLEMENTED", 400), /** Error code indicating the requested operation is not supported (-32004). */ - UNSUPPORTED_OPERATION(-32004, "UNIMPLEMENTED", 400), + UNSUPPORTED_OPERATION(-32004, "FAILED_PRECONDITION", 400), /** Error code indicating the content type is not supported (-32005). */ CONTENT_TYPE_NOT_SUPPORTED(-32005, "INVALID_ARGUMENT", 415), diff --git a/transport/grpc/src/main/java/org/a2aproject/sdk/transport/grpc/handler/GrpcHandler.java b/transport/grpc/src/main/java/org/a2aproject/sdk/transport/grpc/handler/GrpcHandler.java index 3f739f182..ae222b5d4 100644 --- a/transport/grpc/src/main/java/org/a2aproject/sdk/transport/grpc/handler/GrpcHandler.java +++ b/transport/grpc/src/main/java/org/a2aproject/sdk/transport/grpc/handler/GrpcHandler.java @@ -119,7 +119,7 @@ *
  • {@link org.a2aproject.sdk.spec.MethodNotFoundError} → {@link Status#NOT_FOUND}
  • *
  • {@link org.a2aproject.sdk.spec.TaskNotFoundError} → {@link Status#NOT_FOUND}
  • *
  • {@link org.a2aproject.sdk.spec.InternalError} → {@link Status#INTERNAL}
  • - *
  • {@link org.a2aproject.sdk.spec.UnsupportedOperationError} → {@link Status#UNIMPLEMENTED}
  • + *
  • {@link org.a2aproject.sdk.spec.UnsupportedOperationError} → {@link Status#FAILED_PRECONDITION}
  • *
  • {@link SecurityException} → {@link Status#UNAUTHENTICATED} or {@link Status#PERMISSION_DENIED}
  • * * @@ -746,7 +746,7 @@ private void installForkedContextWrapper(ServerCallContext context) { *
  • {@link TaskNotFoundError} → {@code NOT_FOUND}
  • *
  • {@link TaskNotCancelableError} → {@code FAILED_PRECONDITION}
  • *
  • {@link PushNotificationNotSupportedError} → {@code UNIMPLEMENTED}
  • - *
  • {@link UnsupportedOperationError} → {@code UNIMPLEMENTED}
  • + *
  • {@link UnsupportedOperationError} → {@code FAILED_PRECONDITION}
  • *
  • {@link JSONParseError} → {@code INTERNAL}
  • *
  • {@link ContentTypeNotSupportedError} → {@code INVALID_ARGUMENT}
  • *
  • {@link InvalidAgentResponseError} → {@code INTERNAL}
  • diff --git a/transport/grpc/src/test/java/org/a2aproject/sdk/transport/grpc/handler/GrpcHandlerTest.java b/transport/grpc/src/test/java/org/a2aproject/sdk/transport/grpc/handler/GrpcHandlerTest.java index 974d99e00..f88186fc5 100644 --- a/transport/grpc/src/test/java/org/a2aproject/sdk/transport/grpc/handler/GrpcHandlerTest.java +++ b/transport/grpc/src/test/java/org/a2aproject/sdk/transport/grpc/handler/GrpcHandlerTest.java @@ -161,7 +161,7 @@ public void testOnCancelTaskNotSupported() throws Exception { handler.cancelTask(request, streamRecorder); streamRecorder.awaitCompletion(5, TimeUnit.SECONDS); - assertGrpcError(streamRecorder, Status.Code.UNIMPLEMENTED); + assertGrpcError(streamRecorder, Status.Code.FAILED_PRECONDITION); } @Test @@ -218,7 +218,7 @@ public void testOnMessageError() throws Exception { agentEmitter.fail(new UnsupportedOperationError()); }; StreamRecorder streamRecorder = sendMessageRequest(handler); - assertGrpcError(streamRecorder, Status.Code.UNIMPLEMENTED); + assertGrpcError(streamRecorder, Status.Code.FAILED_PRECONDITION); } @Test @@ -286,7 +286,7 @@ public void testOnGetPushNotificationNoPushNotifierConfig() throws Exception { GrpcHandler handler = new TestGrpcHandler(card, requestHandler, internalExecutor); StreamRecorder streamRecorder = getTaskPushNotificationConfigRequest(handler, AbstractA2ARequestHandlerTest.MINIMAL_TASK.id(), AbstractA2ARequestHandlerTest.MINIMAL_TASK.id()); - assertGrpcError(streamRecorder, Status.Code.UNIMPLEMENTED); + assertGrpcError(streamRecorder, Status.Code.FAILED_PRECONDITION); } @Test @@ -301,7 +301,7 @@ public void testOnSetPushNotificationNoPushNotifierConfig() 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 @@ -625,7 +625,7 @@ public void testStreamingNotSupportedError() throws Exception { AgentCard card = AbstractA2ARequestHandlerTest.createAgentCard(false, true); GrpcHandler handler = new TestGrpcHandler(card, requestHandler, internalExecutor); StreamRecorder streamRecorder = sendStreamingMessageRequest(handler); - assertGrpcError(streamRecorder, Status.Code.UNIMPLEMENTED); + assertGrpcError(streamRecorder, Status.Code.FAILED_PRECONDITION); } @Test @@ -639,7 +639,7 @@ public void testStreamingNotSupportedErrorOnSubscribeToTask() throws Exception { StreamRecorder streamRecorder = StreamRecorder.create(); handler.subscribeToTask(request, streamRecorder); streamRecorder.awaitCompletion(5, TimeUnit.SECONDS); - assertGrpcError(streamRecorder, Status.Code.UNIMPLEMENTED); + assertGrpcError(streamRecorder, Status.Code.FAILED_PRECONDITION); } @Test @@ -735,7 +735,7 @@ public void testListPushNotificationConfigNoPushConfigStore() { .build(); StreamRecorder streamRecorder = StreamRecorder.create(); handler.listTaskPushNotificationConfigs(request, streamRecorder); - assertGrpcError(streamRecorder, Status.Code.UNIMPLEMENTED); + assertGrpcError(streamRecorder, Status.Code.FAILED_PRECONDITION); } @Test @@ -806,7 +806,7 @@ public void testDeletePushNotificationConfigNoPushConfigStore() { .build(); StreamRecorder streamRecorder = StreamRecorder.create(); handler.deleteTaskPushNotificationConfig(request, streamRecorder); - assertGrpcError(streamRecorder, Status.Code.UNIMPLEMENTED); + assertGrpcError(streamRecorder, Status.Code.FAILED_PRECONDITION); } @Disabled From 4b28625b47d05029174202aa6ffb062ddb059dbf Mon Sep 17 00:00:00 2001 From: Dang Zitou Date: Fri, 14 Aug 2026 22:57:20 +0800 Subject: [PATCH 2/2] fix(grpc): map failed precondition fallback Signed-off-by: Dang Zitou --- .../sdk/client/transport/grpc/GrpcErrorMapper.java | 3 ++- .../client/transport/grpc/GrpcErrorMapperTest.java | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/client/transport/grpc/src/main/java/org/a2aproject/sdk/client/transport/grpc/GrpcErrorMapper.java b/client/transport/grpc/src/main/java/org/a2aproject/sdk/client/transport/grpc/GrpcErrorMapper.java index cbcb05752..816151f1b 100644 --- a/client/transport/grpc/src/main/java/org/a2aproject/sdk/client/transport/grpc/GrpcErrorMapper.java +++ b/client/transport/grpc/src/main/java/org/a2aproject/sdk/client/transport/grpc/GrpcErrorMapper.java @@ -74,7 +74,8 @@ public static A2AClientException mapGrpcError(Throwable e, String errorPrefix) { String desc = message != null ? message : e.getMessage() == null ? "" : e.getMessage(); return switch (code) { case NOT_FOUND -> new A2AClientException(errorPrefix + desc, new TaskNotFoundError()); - case UNIMPLEMENTED -> new A2AClientException(errorPrefix + desc, new UnsupportedOperationError()); + case UNIMPLEMENTED, FAILED_PRECONDITION -> + new A2AClientException(errorPrefix + desc, new UnsupportedOperationError()); case INVALID_ARGUMENT -> new A2AClientException(errorPrefix + desc, new InvalidParamsError()); case INTERNAL -> new A2AClientException(errorPrefix + desc, new org.a2aproject.sdk.spec.InternalError(null, desc, null)); case UNAUTHENTICATED -> new A2AClientException(errorPrefix + A2AErrorMessages.AUTHENTICATION_FAILED); diff --git a/client/transport/grpc/src/test/java/org/a2aproject/sdk/client/transport/grpc/GrpcErrorMapperTest.java b/client/transport/grpc/src/test/java/org/a2aproject/sdk/client/transport/grpc/GrpcErrorMapperTest.java index c7432a808..1f5d0da75 100644 --- a/client/transport/grpc/src/test/java/org/a2aproject/sdk/client/transport/grpc/GrpcErrorMapperTest.java +++ b/client/transport/grpc/src/test/java/org/a2aproject/sdk/client/transport/grpc/GrpcErrorMapperTest.java @@ -118,6 +118,20 @@ public void testUnsupportedOperationErrorUnmarshalling() { assertInstanceOf(UnsupportedOperationError.class, result.getCause()); } + @Test + public void testUnsupportedOperationErrorFallbackUsesFailedPrecondition() { + String errorMessage = "Operation not supported"; + StatusRuntimeException grpcException = Status.FAILED_PRECONDITION + .withDescription(errorMessage) + .asRuntimeException(); + + A2AClientException result = GrpcErrorMapper.mapGrpcError(grpcException); + + assertNotNull(result); + assertNotNull(result.getCause()); + assertInstanceOf(UnsupportedOperationError.class, result.getCause()); + } + @Test public void testInvalidParamsErrorUnmarshalling() { String errorMessage = "Invalid parameters provided";