From a82bec32b10f9441b6486ff42fa96109e184131a Mon Sep 17 00:00:00 2001 From: Jeff Mesnil Date: Fri, 6 Mar 2026 12:59:14 +0100 Subject: [PATCH] fix!: Update the name of A2A headers A2A-Version and A2A-Extensions are specified by the spec (as of dec790a) instead of the X-A2A-*. Updates the gRPC context keys to be derived from the HTTP headers and in lower case. I did not change the `X-A2A-Notification-Token` header that is still present in the spec but I'll open a PR to verify if that's not an omission Signed-off-by: Jeff Mesnil --- .../client/transport/grpc/GrpcTransport.java | 16 ++++++------- .../main/java/io/a2a/common/A2AHeaders.java | 4 ++-- .../quarkus/A2AExtensionsInterceptor.java | 24 ++++++++++--------- .../server/apps/quarkus/A2AServerRoutes.java | 8 +++---- .../server/rest/quarkus/A2AServerRoutes.java | 8 +++---- .../grpc/context/GrpcContextKeys.java | 12 ++++++---- .../grpc/handler/CallContextFactory.java | 4 ++-- .../transport/grpc/handler/GrpcHandler.java | 8 +++---- 8 files changed, 45 insertions(+), 39 deletions(-) diff --git a/client/transport/grpc/src/main/java/io/a2a/client/transport/grpc/GrpcTransport.java b/client/transport/grpc/src/main/java/io/a2a/client/transport/grpc/GrpcTransport.java index 474b7b784..351c3543a 100644 --- a/client/transport/grpc/src/main/java/io/a2a/client/transport/grpc/GrpcTransport.java +++ b/client/transport/grpc/src/main/java/io/a2a/client/transport/grpc/GrpcTransport.java @@ -61,10 +61,10 @@ public class GrpcTransport implements ClientTransport { AuthInterceptor.AUTHORIZATION, Metadata.ASCII_STRING_MARSHALLER); private static final Metadata.Key EXTENSIONS_KEY = Metadata.Key.of( - A2AHeaders.X_A2A_EXTENSIONS, + A2AHeaders.A2A_EXTENSIONS.toLowerCase(), Metadata.ASCII_STRING_MARSHALLER); private static final Metadata.Key VERSION_KEY = Metadata.Key.of( - A2AHeaders.X_A2A_VERSION, + A2AHeaders.A2A_VERSION.toLowerCase(), Metadata.ASCII_STRING_MARSHALLER); private final A2AServiceBlockingV2Stub blockingStub; private final A2AServiceStub asyncStub; @@ -380,8 +380,8 @@ private io.a2a.grpc.SendMessageRequest createGrpcSendMessageRequest(MessageSendP /** * Creates gRPC metadata from ClientCallContext headers. - * Extracts headers like X-A2A-Extensions and sets them as gRPC metadata. - * + * Extracts headers like a2a-extensions and sets them as gRPC metadata. + * The headers are lower-cased (compared to the HTTP headers). * @param context the client call context containing headers, may be null * @param payloadAndHeaders the payload and headers wrapper, may be null * @return the gRPC metadata @@ -390,14 +390,14 @@ private Metadata createGrpcMetadata(@Nullable ClientCallContext context, @Nullab Metadata metadata = new Metadata(); if (context != null && context.getHeaders() != null) { - // Set X-A2A-Version header if present - String versionHeader = context.getHeaders().get(A2AHeaders.X_A2A_VERSION); + // Set a2a-version header if present + String versionHeader = context.getHeaders().get(A2AHeaders.A2A_VERSION.toLowerCase()); if (versionHeader != null) { metadata.put(VERSION_KEY, versionHeader); } - // Set X-A2A-Extensions header if present - String extensionsHeader = context.getHeaders().get(A2AHeaders.X_A2A_EXTENSIONS); + // Set a2a-extensions header if present + String extensionsHeader = context.getHeaders().get(A2AHeaders.A2A_EXTENSIONS.toLowerCase()); if (extensionsHeader != null) { metadata.put(EXTENSIONS_KEY, extensionsHeader); } diff --git a/common/src/main/java/io/a2a/common/A2AHeaders.java b/common/src/main/java/io/a2a/common/A2AHeaders.java index f050333aa..ffbbebda9 100644 --- a/common/src/main/java/io/a2a/common/A2AHeaders.java +++ b/common/src/main/java/io/a2a/common/A2AHeaders.java @@ -9,13 +9,13 @@ public final class A2AHeaders { * HTTP header name for A2A protocol version. * Used to communicate the protocol version that the client is using. */ - public static final String X_A2A_VERSION = "X-A2A-Version"; + public static final String A2A_VERSION = "A2A-Version"; /** * HTTP header name for A2A extensions. * Used to communicate which extensions are requested by the client. */ - public static final String X_A2A_EXTENSIONS = "X-A2A-Extensions"; + public static final String A2A_EXTENSIONS = "A2A-Extensions"; /** * HTTP header name for a push notification token. diff --git a/reference/grpc/src/main/java/io/a2a/server/grpc/quarkus/A2AExtensionsInterceptor.java b/reference/grpc/src/main/java/io/a2a/server/grpc/quarkus/A2AExtensionsInterceptor.java index c024a92f0..f985e7cd1 100644 --- a/reference/grpc/src/main/java/io/a2a/server/grpc/quarkus/A2AExtensionsInterceptor.java +++ b/reference/grpc/src/main/java/io/a2a/server/grpc/quarkus/A2AExtensionsInterceptor.java @@ -21,8 +21,8 @@ * *

Captured Information

*
    - *
  • A2A Protocol Version: {@code X-A2A-Version} header
  • - *
  • A2A Extensions: {@code X-A2A-Extensions} header
  • + *
  • A2A Protocol Version: {@code a2a-version} header
  • + *
  • A2A Extensions: {@code a2a-extensions} header
  • *
  • Complete Metadata: All request headers via {@link io.grpc.Metadata}
  • *
  • Method Name: gRPC method being invoked
  • *
  • Peer Information: Client connection details
  • @@ -60,6 +60,13 @@ @ApplicationScoped public class A2AExtensionsInterceptor implements ServerInterceptor { + private static final Metadata.Key EXTENSIONS_KEY = Metadata.Key.of( + A2AHeaders.A2A_EXTENSIONS.toLowerCase(), + Metadata.ASCII_STRING_MARSHALLER); + private static final Metadata.Key VERSION_KEY = Metadata.Key.of( + A2AHeaders.A2A_VERSION.toLowerCase(), + Metadata.ASCII_STRING_MARSHALLER); + /** * Intercepts incoming gRPC calls to capture metadata and context information. * @@ -68,8 +75,8 @@ public class A2AExtensionsInterceptor implements ServerInterceptor { * *

    Extraction Process: *

      - *
    1. Extract {@code X-A2A-Version} header from metadata
    2. - *
    3. Extract {@code X-A2A-Extensions} header from metadata
    4. + *
    5. Extract {@code a2a-version} header from metadata
    6. + *
    7. Extract {@code a2a-extensions} header from metadata
    8. *
    9. Capture complete {@link Metadata} object
    10. *
    11. Capture gRPC method name from {@link ServerCall}
    12. *
    13. Map gRPC method to A2A protocol method name
    14. @@ -92,14 +99,9 @@ public ServerCall.Listener interceptCall( ServerCallHandler serverCallHandler) { // Extract A2A protocol version header - Metadata.Key versionKey = - Metadata.Key.of(A2AHeaders.X_A2A_VERSION, Metadata.ASCII_STRING_MARSHALLER); - String version = metadata.get(versionKey); - + String version = metadata.get(VERSION_KEY); // Extract A2A extensions header - Metadata.Key extensionsKey = - Metadata.Key.of(A2AHeaders.X_A2A_EXTENSIONS, Metadata.ASCII_STRING_MARSHALLER); - String extensions = metadata.get(extensionsKey); + String extensions = metadata.get(EXTENSIONS_KEY); // Create enhanced context with rich information (equivalent to Python's ServicerContext) Context context = Context.current() diff --git a/reference/jsonrpc/src/main/java/io/a2a/server/apps/quarkus/A2AServerRoutes.java b/reference/jsonrpc/src/main/java/io/a2a/server/apps/quarkus/A2AServerRoutes.java index a7b9f93e1..dc03f8a03 100644 --- a/reference/jsonrpc/src/main/java/io/a2a/server/apps/quarkus/A2AServerRoutes.java +++ b/reference/jsonrpc/src/main/java/io/a2a/server/apps/quarkus/A2AServerRoutes.java @@ -513,11 +513,11 @@ public String getUsername() { state.put(TENANT_KEY, extractTenant(rc)); state.put(TRANSPORT_KEY, TransportProtocol.JSONRPC); - // Extract requested protocol version from X-A2A-Version header - String requestedVersion = rc.request().getHeader(A2AHeaders.X_A2A_VERSION); + // Extract requested protocol version from A2A-Version header + String requestedVersion = rc.request().getHeader(A2AHeaders.A2A_VERSION); - // Extract requested extensions from X-A2A-Extensions header - List extensionHeaderValues = rc.request().headers().getAll(A2AHeaders.X_A2A_EXTENSIONS); + // Extract requested extensions from A2A-Extensions header + List extensionHeaderValues = rc.request().headers().getAll(A2AHeaders.A2A_EXTENSIONS); Set requestedExtensions = A2AExtensions.getRequestedExtensions(extensionHeaderValues); return new ServerCallContext(user, state, requestedExtensions, requestedVersion); diff --git a/reference/rest/src/main/java/io/a2a/server/rest/quarkus/A2AServerRoutes.java b/reference/rest/src/main/java/io/a2a/server/rest/quarkus/A2AServerRoutes.java index 05e352a18..a2f81e1f7 100644 --- a/reference/rest/src/main/java/io/a2a/server/rest/quarkus/A2AServerRoutes.java +++ b/reference/rest/src/main/java/io/a2a/server/rest/quarkus/A2AServerRoutes.java @@ -731,11 +731,11 @@ public String getUsername() { state.put(TENANT_KEY, extractTenant(rc)); state.put(TRANSPORT_KEY, TransportProtocol.HTTP_JSON); - // Extract requested protocol version from X-A2A-Version header - String requestedVersion = rc.request().getHeader(A2AHeaders.X_A2A_VERSION); + // Extract requested protocol version from A2A-Version header + String requestedVersion = rc.request().getHeader(A2AHeaders.A2A_VERSION); - // Extract requested extensions from X-A2A-Extensions header - List extensionHeaderValues = rc.request().headers().getAll(A2AHeaders.X_A2A_EXTENSIONS); + // Extract requested extensions from A2A-Extensions header + List extensionHeaderValues = rc.request().headers().getAll(A2AHeaders.A2A_EXTENSIONS); Set requestedExtensions = A2AExtensions.getRequestedExtensions(extensionHeaderValues); return new ServerCallContext(user, state, requestedExtensions, requestedVersion); diff --git a/transport/grpc/src/main/java/io/a2a/transport/grpc/context/GrpcContextKeys.java b/transport/grpc/src/main/java/io/a2a/transport/grpc/context/GrpcContextKeys.java index 58878c7a3..99062f6fe 100644 --- a/transport/grpc/src/main/java/io/a2a/transport/grpc/context/GrpcContextKeys.java +++ b/transport/grpc/src/main/java/io/a2a/transport/grpc/context/GrpcContextKeys.java @@ -1,8 +1,12 @@ package io.a2a.transport.grpc.context; +import static java.util.Locale.ROOT; + +import java.util.Locale; import java.util.Map; +import io.a2a.common.A2AHeaders; import io.a2a.spec.A2AMethods; import io.grpc.Context; @@ -40,18 +44,18 @@ public final class GrpcContextKeys { /** - * Context key for storing the X-A2A-Version header value. + * Context key for storing the a2a-version header value. * Set by server interceptors and accessed by service handlers. */ public static final Context.Key VERSION_HEADER_KEY = - Context.key("x-a2a-version"); + Context.key(A2AHeaders.A2A_VERSION.toLowerCase(ROOT)); /** - * Context key for storing the X-A2A-Extensions header value. + * Context key for storing the a2a-extensions header value. * Set by server interceptors and accessed by service handlers. */ public static final Context.Key EXTENSIONS_HEADER_KEY = - Context.key("x-a2a-extensions"); + Context.key(A2AHeaders.A2A_EXTENSIONS.toLowerCase(ROOT)); /** * Context key for storing the complete gRPC Metadata object. diff --git a/transport/grpc/src/main/java/io/a2a/transport/grpc/handler/CallContextFactory.java b/transport/grpc/src/main/java/io/a2a/transport/grpc/handler/CallContextFactory.java index 1a52f41bb..11a8edfaf 100644 --- a/transport/grpc/src/main/java/io/a2a/transport/grpc/handler/CallContextFactory.java +++ b/transport/grpc/src/main/java/io/a2a/transport/grpc/handler/CallContextFactory.java @@ -18,8 +18,8 @@ *
    15. User authentication from security context
    16. *
    17. gRPC metadata (headers)
    18. *
    19. Method name and peer information
    20. - *
    21. A2A protocol version from {@code X-A2A-Version} header
    22. - *
    23. Required extensions from {@code X-A2A-Extensions} header
    24. + *
    25. A2A protocol version from {@code A2A-Version} header
    26. + *
    27. Required extensions from {@code A2A-Extensions} header
    28. *
    29. Response observer for gRPC streaming
    30. *
* 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 cf58e9da9..159d687c2 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 @@ -600,8 +600,8 @@ public void deleteTaskPushNotificationConfig(io.a2a.grpc.DeleteTaskPushNotificat *
  • HTTP headers extracted from metadata
  • *
  • gRPC method name
  • *
  • Peer information (client connection details)
  • - *
  • A2A protocol version from {@code X-A2A-Version} header (via context)
  • - *
  • Required extensions from {@code X-A2A-Extensions} header (via context)
  • + *
  • A2A protocol version from {@code A2A-Version} header (via context)
  • + *
  • Required extensions from {@code A2A-Extensions} header (via context)
  • * * *

    Custom Context Creation: @@ -901,7 +901,7 @@ public static void setStreamingSubscribedRunnable(Runnable runnable) { protected abstract Executor getExecutor(); /** - * Attempts to extract the X-A2A-Version header from the current gRPC context. + * Attempts to extract the A2A-Version header from the current gRPC context. * This will only work if a server interceptor has been configured to capture * the metadata and store it in the context. * @@ -917,7 +917,7 @@ public static void setStreamingSubscribedRunnable(Runnable runnable) { } /** - * Attempts to extract the X-A2A-Extensions header from the current gRPC context. + * Attempts to extract the A2A-Extensions header from the current gRPC context. * This will only work if a server interceptor has been configured to capture * the metadata and store it in the context. *