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 70185f32d..d2ce6e720 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 @@ -199,8 +199,6 @@ public void sendMessage(org.a2aproject.sdk.grpc.SendMessageRequest request, StreamObserver responseObserver) { try { ServerCallContext context = createCallContext(responseObserver); - A2AVersionValidator.validateProtocolVersion(getAgentCardInternal(), context); - A2AExtensions.validateRequiredExtensions(getAgentCardInternal(), context); MessageSendParams params = FromProto.messageSendParams(request); EventKind taskOrMessage = getRequestHandler().onMessageSend(params, context); org.a2aproject.sdk.grpc.SendMessageResponse response = ToProto.taskOrMessage(taskOrMessage); @@ -395,8 +393,6 @@ public void sendStreamingMessage(org.a2aproject.sdk.grpc.SendMessageRequest requ try { ServerCallContext context = createCallContext(responseObserver); installForkedContextWrapper(context); - A2AVersionValidator.validateProtocolVersion(getAgentCardInternal(), context); - A2AExtensions.validateRequiredExtensions(getAgentCardInternal(), context); MessageSendParams params = FromProto.messageSendParams(request); Flow.Publisher publisher = getRequestHandler().onMessageSendStream(params, context); convertToStreamResponse(publisher, responseObserver, context); @@ -568,6 +564,7 @@ public void getExtendedAgentCard(org.a2aproject.sdk.grpc.GetExtendedAgentCardReq handleError(responseObserver, new UnsupportedOperationError()); return; } + ServerCallContext context = createCallContext(responseObserver); AgentCard extendedAgentCard = getExtendedAgentCard(); if (extendedAgentCard != null) { responseObserver.onNext(ToProto.agentCard(extendedAgentCard)); @@ -643,6 +640,7 @@ public void deleteTaskPushNotificationConfig(org.a2aproject.sdk.grpc.DeleteTaskP */ private ServerCallContext createCallContext(StreamObserver responseObserver) { CallContextFactory factory = getCallContextFactory(); + ServerCallContext context; if (factory == null) { // Default implementation when no custom CallContextFactory is provided // This handles both CDI injection scenarios and test scenarios where callContextFactory is null @@ -699,12 +697,16 @@ private ServerCallContext createCallContext(StreamObserver responseObserv requestedExtensions = A2AExtensions.getRequestedExtensions(List.of(extensionsHeader)); } - return new ServerCallContext(user, state, requestedExtensions, requestedVersion); + context = new ServerCallContext(user, state, requestedExtensions, requestedVersion); } else { // TODO: CallContextFactory interface expects ServerCall + Metadata, but we only have StreamObserver // This is another manifestation of the architectural limitation mentioned above - return factory.create(responseObserver); // Fall back to basic create() method for now + context = factory.create(responseObserver); // Fall back to basic create() method for now } + + A2AVersionValidator.validateProtocolVersion(getAgentCardInternal(), context); + A2AExtensions.validateRequiredExtensions(getAgentCardInternal(), context); + return context; } /** 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 66c620164..2578340ee 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 @@ -1073,6 +1073,52 @@ public ServerCallContext create(StreamObserver streamObserver) { assertGrpcError(streamRecorder, Status.Code.UNIMPLEMENTED); } + @Test + public void testVersionNotSupportedErrorOnGetTask() throws Exception { + // Regression test: getTask previously skipped A2A protocol version + // and extension validation, unlike sendMessage/sendStreamingMessage. + AgentCard agentCard = AgentCard.builder() + .name("test-card") + .description("Test card with version 1.0") + .supportedInterfaces(Collections.singletonList(new AgentInterface("GRPC", "http://localhost:9999"))) + .version("1.0.0") + .capabilities(AgentCapabilities.builder() + .streaming(true) + .pushNotifications(false) + .build()) + .defaultInputModes(List.of("text")) + .defaultOutputModes(List.of("text")) + .skills(List.of()) + .build(); + + // Create handler that provides incompatible version 2.0 in the context + GrpcHandler handler = new TestGrpcHandler(agentCard, requestHandler, internalExecutor) { + @Override + protected CallContextFactory getCallContextFactory() { + return new CallContextFactory() { + @Override + public ServerCallContext create(StreamObserver streamObserver) { + return new ServerCallContext( + UnauthenticatedUser.INSTANCE, + Map.of("grpc_response_observer", streamObserver), + new HashSet<>(), + "2.0" // Incompatible version + ); + } + }; + } + }; + + GetTaskRequest request = GetTaskRequest.newBuilder() + .setId(AbstractA2ARequestHandlerTest.MINIMAL_TASK.id()) + .build(); + StreamRecorder streamRecorder = StreamRecorder.create(); + handler.getTask(request, streamRecorder); + streamRecorder.awaitCompletion(5, TimeUnit.SECONDS); + + assertGrpcError(streamRecorder, Status.Code.UNIMPLEMENTED); + } + @Test public void testCompatibleVersionSuccess() throws Exception { // Create AgentCard with protocol version 1.0