Skip to content

feat: Add 0.3 protocol version compatibility layer#805

Open
kabir wants to merge 13 commits intoa2aproject:mainfrom
kabir:add-0.3-compat-reference
Open

feat: Add 0.3 protocol version compatibility layer#805
kabir wants to merge 13 commits intoa2aproject:mainfrom
kabir:add-0.3-compat-reference

Conversation

@kabir
Copy link
Copy Markdown
Collaborator

@kabir kabir commented Apr 21, 2026

This passes the 0.3 TCK, and all unit tests.
At the moment 0.3 and 1.0 are NOT co-hosted, you need to select one

@ehsavoie
Copy link
Copy Markdown
Collaborator

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive backward compatibility layer for A2A Protocol v0.3, enabling interoperability between v1.0 SDK components and v0.3 agents. The implementation includes dedicated spec types, a conversion layer using MapStruct, and transport handlers for JSON-RPC, gRPC, and REST. Key feedback includes the need to handle authentication errors in the JDK HTTP client's GET method, fixing misleading error messages in asynchronous requests, and enhancing the SSE parser's robustness. Furthermore, the REST transport requires consistent stream completion signaling, and lazy initialization of the agent card must be made thread-safe to prevent race conditions.

Comment on lines +215 to +221
.build();
HttpResponse<String> response =
httpClient.send(request, BodyHandlers.ofString(StandardCharsets.UTF_8));
return new JdkHttpResponse(response);
}

@Override
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The get() method does not check for HTTP_UNAUTHORIZED (401) or HTTP_FORBIDDEN (403) status codes, unlike the post() method (lines 270-274). This inconsistency means authentication errors during GET requests (like fetching the agent card) might not be reported with the standard A2A error messages, violating the A2A protocol specification for error code mappings.

        @Override
        public A2AHttpResponse_v0_3 get() throws IOException, InterruptedException {
            HttpRequest request = createRequestBuilder(false)
                    .build();
            HttpResponse<String> response =
                    httpClient.send(request, BodyHandlers.ofString(StandardCharsets.UTF_8));

            if (response.statusCode() == HTTP_UNAUTHORIZED) {
                throw new IOException(A2AErrorMessages.AUTHENTICATION_FAILED);
            } else if (response.statusCode() == HTTP_FORBIDDEN) {
                throw new IOException(A2AErrorMessages.AUTHORIZATION_FAILED);
            }

            return new JdkHttpResponse(response);
        }
References
  1. Adhere to the A2A protocol specification for error code mappings, even if other mappings seem more internally consistent.

response.statusCode() != HTTP_FORBIDDEN) {
subscriber.onError(new IOException("Request failed with status " + response.statusCode() + ":" + response.body()));
}
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In asyncRequest, the bodyHandler is defined as BodyHandler<Void>, which means response.body() will always return null. Including it in the error message is misleading and provides no useful information.

                            subscriber.onError(new IOException("Request failed with status " + response.statusCode()));

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive backward compatibility layer for A2A protocol version 0.3, enabling interoperability between v1.0 SDK components and v0.3 agents. The changes include a dedicated module structure for spec types, a server-side conversion layer using MapStruct, and transport-specific client and server implementations. Feedback highlights several critical issues: a missing checked exception declaration in the REST transport, incorrect resource name patterns and stubbed methods in the gRPC transport, and inconsistent field initialization. Additionally, a potential null pointer risk was identified in the SSE event listener's error handling logic.

Comment on lines +269 to +272
public AgentCard_v0_3 getAgentCard(@Nullable ClientCallContext_v0_3 context) throws A2AClientException_v0_3 {
// TODO: Determine how to handle retrieving the authenticated extended agent card
return agentCard;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The getAgentCard method is currently a stub that just returns the cached agentCard. Unlike the JSON-RPC and REST transports, it does not attempt to fetch the card from the server or handle the authenticated extended card logic. It should be implemented using the gRPC GetAgentCard method to ensure consistency across transports.

// Signal normal stream completion (null error means successful completion)
log.fine("SSEEventListener.onComplete() called - signaling successful stream completion");
if (errorHandler != null) {
log.fine("Calling errorHandler.accept(null) to signal successful completion");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Passing null to the errorHandler to signal completion is risky. If the consumer implementation of errorHandler (which is a Consumer<Throwable>) does not explicitly check for null, it will throw a NullPointerException. It is better to use a separate completion callback or ensure the error handler handles nulls.

@kabir kabir force-pushed the add-0.3-compat-reference branch from 1f52f3c to 8be6e40 Compare April 29, 2026 13:48
@kabir kabir force-pushed the add-0.3-compat-reference branch 2 times, most recently from 245fb86 to f205fc7 Compare May 1, 2026 16:32
kabir and others added 13 commits May 5, 2026 13:53
This passes the 0.3 TCK, and all unit tests.
At the moment 0.3 and 1.0 are NOT co-hosted, you need to select one
…s to Vert.x Web Router

Replace Quarkus Reactive Routes (@route annotations) with Vert.x Web Router
(@observes Router) in the compat-0.3 JSONRPC reference module, matching the
pattern established in the main reference modules (commit 8be6e40).

Key changes:
- Replace quarkus-reactive-routes dependency with a2a-java-sdk-reference-common
  in both JSONRPC and REST POMs (REST POM change is prep for next commit)
- Use VertxSecurityHelper for authentication instead of @authenticated annotation
- Fix SSE race condition: subscribe synchronously instead of wrapping in
  executor.execute(), preventing 100-600ms delays that caused event loss
- Replace MultiSseSupport inner class with improved version: close handler for
  client disconnect detection, setWriteQueueMaxSize(1) for anti-buffering,
  Cache-Control/X-Accel-Buffering headers
- Use per-route BodyHandler instead of global to avoid ordering issues with
  test routes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…o Vert.x Web Router

Replace @route annotations with @observes @priority(10) Router pattern,
add VertxSecurityHelper for manual authentication, fix SSE race condition
with synchronous subscription, and add per-route BodyHandler.

Move compat-0.3 reference modules from SDK BOM to Reference BOM since
they depend on reference-common (VertxSecurityHelper), and update both
BOM verifiers accordingly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add HTTP Basic auth support to AuthInterceptor_v0_3 (matching v1.0)
- Add @authenticated annotations to compat-0.3 JSONRPC and REST route
  handler methods for security enforcement
- Update AgentCardProducer_v0_3 to conditionally include security
  schemes when test.agent.security.enabled=true
- Create AbstractA2AServerWithAuthTest_v0_3 base class in
  server-conversion with v0.3 client types
- Add JSONRPC auth test (QuarkusA2AJSONRPC_v0_3_WithAuthTest) with
  AuthTestProfile, TestIdentityProvider, and security dependencies
- Add security config to JSONRPC application.properties

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add authentication test infrastructure for the v0.3 REST reference
module, mirroring the v1.0 auth test structure. Fix missing 401/403
error handling in v0.3 JDK HTTP client's get() and delete() methods
that prevented proper authentication failure reporting.

- Add AuthTestProfile_v0_3 with embedded user store and HTTP Basic auth
- Add TestIdentityProvider_v0_3 for auto-auth in regular tests
- Add QuarkusA2ARest_v0_3_WithAuthTest extending AbstractA2AServerWithAuthTest_v0_3
- Add quarkus-security, quarkus-elytron-security-properties-file,
  quarkus-test-security test dependencies
- Add test security configuration to application.properties
- Fix JdkA2AHttpClient_v0_3 get()/delete() to throw IOException on
  401/403 responses, matching post() and v1.0 behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add authentication test infrastructure for the v0.3 gRPC reference
module, mirroring the v1.0 gRPC auth test structure.

- Add TestAuthorizationController_v0_3 to disable @authenticated
  enforcement for regular tests via AuthorizationController
- Add AuthTestProfile_v0_3 to enable real auth for auth-specific tests
- Add QuarkusA2AGrpc_v0_3_WithAuthTest extending
  AbstractA2AServerWithAuthTest_v0_3
- Add quarkus-security, quarkus-elytron-security-properties-file,
  quarkus-test-security test dependencies
- Fix GrpcHandler_v0_3 to catch SecurityException and map to
  UNAUTHENTICATED/PERMISSION_DENIED gRPC status codes instead of
  falling through to INTERNAL error handling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…d handling

- Switch JSONRPCErrorTypeAdapter to TypeAdapterFactory for correct subclass resolution
- Migrate Convert_v0_3_To10RequestHandler to constructor injection
- Extract writeJsonRpcId() utility to deduplicate id serialization logic
- Always write JSON-RPC id field (null when unknown) per spec
- Fix spurious leading slash in listTaskPushNotificationConfigurations URL
- Add AuthenticatedExtendedCardNotConfiguredError to error code mapping
- Remove debug log statement from RestHandler

Signed-off-by: Emmanuel Hugonnet <ehugonne@redhat.com>
…ultiplexing

Add VersionRouter in reference-common that resolves A2A protocol version
from the A2A-Version header or query param. Per spec Section 3.6.2,
missing/empty version defaults to 0.3 for backward compatibility.

Add PublicAgentCard.Literal for programmatic CDI lookup. Modify both
v0.3 A2AServerRoutes_v0_3 (JSON-RPC and REST) to skip registering
/.well-known/agent-card.json when a non-@DefaultBean v1.0 AgentCard
producer is present, so v1.0 takes precedence in dual-version mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create reference/multiversion-jsonrpc with MultiVersionJSONRPCRoutes and
reference/multiversion-rest with MultiVersionRestRoutes. These are
reusable production modules that provide version-dispatching routes for
dual v1.0/v0.3 deployments.

JSON-RPC uses Vert.x route order(-1) to intercept POST / before the
standalone v1.0 and v0.3 handlers, dispatching based on A2A-Version
header. REST intercepts /v1/... paths that overlap between v0.3 literal
routes and v1.0 regex routes, with path param bridging between naming
conventions. Both include proper VersionNotSupportedError handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…pport

Add tests/reference/jsonrpc module that verifies v1.0 and v0.3 JSON-RPC
clients both work when both protocol versions are provisioned on the same
server. Version routing uses A2A-Version header per spec Section 3.6.1.

Also adds A2A-Version: 1.0 header to all v1.0 client transports (JSON-RPC,
REST, gRPC) per spec requirement that clients MUST send this header. This
is needed for multiversion routing and is a spec-compliance fix.

Other changes:
- Add META-INF/beans.xml to multiversion modules for CDI discovery
- Make setStreamingMultiSseSupportSubscribedRunnable public in both
  v1.0 and v0.3 route classes for cross-bean access

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add test modules at tests/reference/rest/ and tests/reference/grpc/ that
verify both v1.0 and v0.3 protocol clients work simultaneously against a
single server instance.

Each module includes v1.0 tests (AbstractA2AServerTest), v0.3 tests
(AbstractA2AServerServerTest_v0_3), and authentication test variants for
both versions.

The REST streaming callback method setStreamingMultiSseSupportSubscribedRunnable
is made public in both A2AServerRoutes and A2AServerRoutes_v0_3 so the
multiversion test infrastructure can register callbacks from a different
package.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The spec requires that agents interpret empty/missing A2A-Version
as 0.3 for backward compatibility. Previously defaulted to the
current protocol version (1.0). All transport handler tests updated
to explicitly pass version "1.0" in their test contexts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@kabir kabir force-pushed the add-0.3-compat-reference branch from f205fc7 to aa02eaa Compare May 5, 2026 13:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants