Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ nb-configuration.xml
/.quarkus/cli/plugins/
# TLS Certificates
.certs/

#Apache NetBeans
nbproject/
nbactions.xml

# Private Claude config
.claude/
Expand Down
5 changes: 5 additions & 0 deletions boms/extras/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
<artifactId>a2a-java-extras-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-sdk-http-client-vertx</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-extras-task-store-database-jpa</artifactId>
Expand Down
4 changes: 4 additions & 0 deletions boms/extras/src/it/extras-usage-test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-extras-common</artifactId>
</dependency>
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-http-client-vertx</artifactId>
</dependency>
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-extras-task-store-database-jpa</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions client/base/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-sdk-http-client</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-sdk-http-client-vertx</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-sdk-client-transport-spi</artifactId>
Expand Down
6 changes: 3 additions & 3 deletions client/base/src/main/java/io/a2a/A2A.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import io.a2a.client.http.A2ACardResolver;
import io.a2a.client.http.A2AHttpClient;
import io.a2a.client.http.JdkA2AHttpClient;
import io.a2a.client.http.A2AHttpClientFactory;
import io.a2a.spec.A2AClientError;
import io.a2a.spec.A2AClientJSONError;
import io.a2a.spec.AgentCard;
Expand Down Expand Up @@ -286,7 +286,7 @@ private static Message toMessage(List<Part<?>> parts, Message.Role role, String
* @see AgentCard
*/
public static AgentCard getAgentCard(String agentUrl) throws A2AClientError, A2AClientJSONError {
return getAgentCard(new JdkA2AHttpClient(), agentUrl);
return getAgentCard(A2AHttpClientFactory.create(), agentUrl);
}

/**
Expand Down Expand Up @@ -357,7 +357,7 @@ public static AgentCard getAgentCard(A2AHttpClient httpClient, String agentUrl)
* @throws io.a2a.spec.A2AClientJSONError if the response body cannot be decoded as JSON or validated against the AgentCard schema
*/
public static AgentCard getAgentCard(String agentUrl, String relativeCardPath, Map<String, String> authHeaders) throws A2AClientError, A2AClientJSONError {
return getAgentCard(new JdkA2AHttpClient(), agentUrl, relativeCardPath, authHeaders);
return getAgentCard(A2AHttpClientFactory.create(), agentUrl, relativeCardPath, authHeaders);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import java.util.List;

import io.a2a.client.config.ClientConfig;
import io.a2a.client.http.JdkA2AHttpClient;
import io.a2a.client.http.A2AHttpClientFactory;
import io.a2a.client.transport.grpc.GrpcTransport;
import io.a2a.client.transport.grpc.GrpcTransportConfigBuilder;
import io.a2a.client.transport.jsonrpc.JSONRPCTransport;
Expand Down Expand Up @@ -89,7 +89,7 @@ public void shouldCreateClient_differentConfigurations() throws A2AClientExcepti
Client client = Client
.builder(card)
.withTransport(JSONRPCTransport.class, new JSONRPCTransportConfigBuilder())
.withTransport(JSONRPCTransport.class, new JSONRPCTransportConfig(new JdkA2AHttpClient()))
.withTransport(JSONRPCTransport.class, new JSONRPCTransportConfig(A2AHttpClientFactory.create()))
.build();

Assertions.assertNotNull(client);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
import com.google.protobuf.MessageOrBuilder;
import io.a2a.client.http.A2ACardResolver;
import io.a2a.client.http.A2AHttpClient;
import io.a2a.client.http.A2AHttpClientFactory;
import io.a2a.client.http.A2AHttpResponse;
import io.a2a.client.http.JdkA2AHttpClient;
import io.a2a.client.transport.jsonrpc.sse.SSEEventListener;
import io.a2a.client.transport.spi.ClientTransport;
import io.a2a.client.transport.spi.interceptors.ClientCallContext;
Expand Down Expand Up @@ -84,7 +84,7 @@ public JSONRPCTransport(AgentCard agentCard) {

public JSONRPCTransport(@Nullable A2AHttpClient httpClient, @Nullable AgentCard agentCard,
AgentInterface agentInterface, @Nullable List<ClientCallInterceptor> interceptors) {
this.httpClient = httpClient == null ? new JdkA2AHttpClient() : httpClient;
this.httpClient = httpClient == null ? A2AHttpClientFactory.create() : httpClient;
this.agentCard = agentCard;
this.agentInterface = agentInterface;
this.interceptors = interceptors;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.a2a.client.transport.jsonrpc;

import io.a2a.client.http.A2AHttpClient;
import io.a2a.client.http.JdkA2AHttpClient;
import io.a2a.client.http.A2AHttpClientFactory;
import io.a2a.client.transport.spi.ClientTransportConfigBuilder;
import org.jspecify.annotations.Nullable;

Expand All @@ -11,7 +11,7 @@
* This builder provides a fluent API for configuring the JSON-RPC transport protocol.
* All configuration options are optional - if not specified, sensible defaults are used:
* <ul>
* <li><b>HTTP client:</b> {@link JdkA2AHttpClient} (JDK's built-in HTTP client)</li>
* <li><b>HTTP client:</b> Auto-selected via {@link A2AHttpClientFactory} (prefers Vert.x, falls back to JDK)</li>
* <li><b>Interceptors:</b> None</li>
* </ul>
* <p>
Expand Down Expand Up @@ -78,7 +78,7 @@ public class JSONRPCTransportConfigBuilder extends ClientTransportConfigBuilder<
* <li>Custom header handling</li>
* </ul>
* <p>
* If not specified, the default {@link JdkA2AHttpClient} is used.
* If not specified, a client is auto-selected via {@link A2AHttpClientFactory}.
* <p>
* Example:
* <pre>{@code
Expand All @@ -101,16 +101,16 @@ public JSONRPCTransportConfigBuilder httpClient(A2AHttpClient httpClient) {
/**
* Build the JSON-RPC transport configuration.
* <p>
* If no HTTP client was configured, the default {@link JdkA2AHttpClient} is used.
* If no HTTP client was configured, one is auto-selected via {@link A2AHttpClientFactory}.
* Any configured interceptors are transferred to the configuration.
*
* @return the configured JSON-RPC transport configuration
*/
@Override
public JSONRPCTransportConfig build() {
// No HTTP client provided, fallback to the default one (JDK-based implementation)
// No HTTP client provided, use factory to get best available implementation
if (httpClient == null) {
httpClient = new JdkA2AHttpClient();
httpClient = A2AHttpClientFactory.create();
}

JSONRPCTransportConfig config = new JSONRPCTransportConfig(httpClient);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.a2a.client.transport.jsonrpc;

import io.a2a.client.http.JdkA2AHttpClient;
import io.a2a.client.http.A2AHttpClientFactory;
import io.a2a.client.transport.spi.ClientTransportProvider;
import io.a2a.spec.A2AClientException;
import io.a2a.spec.AgentCard;
Expand All @@ -14,7 +14,7 @@ public class JSONRPCTransportProvider implements ClientTransportProvider<JSONRPC
public JSONRPCTransport create(@Nullable JSONRPCTransportConfig clientTransportConfig, AgentCard agentCard, AgentInterface agentInterface) throws A2AClientException {
JSONRPCTransportConfig currentClientTransportConfig = clientTransportConfig;
if (currentClientTransportConfig == null) {
currentClientTransportConfig = new JSONRPCTransportConfig(new JdkA2AHttpClient());
currentClientTransportConfig = new JSONRPCTransportConfig(A2AHttpClientFactory.create());
}
return new JSONRPCTransport(currentClientTransportConfig.getHttpClient(), agentCard, agentInterface, currentClientTransportConfig.getInterceptors());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
import com.google.protobuf.util.JsonFormat;
import io.a2a.client.http.A2ACardResolver;
import io.a2a.client.http.A2AHttpClient;
import io.a2a.client.http.A2AHttpClientFactory;
import io.a2a.client.http.A2AHttpResponse;
import io.a2a.client.http.JdkA2AHttpClient;
import io.a2a.client.transport.rest.sse.RestSSEEventListener;
import io.a2a.client.transport.spi.ClientTransport;
import io.a2a.client.transport.spi.interceptors.ClientCallContext;
Expand Down Expand Up @@ -74,7 +74,7 @@ public RestTransport(AgentCard agentCard) {

public RestTransport(@Nullable A2AHttpClient httpClient, AgentCard agentCard,
AgentInterface agentInterface, @Nullable List<ClientCallInterceptor> interceptors) {
this.httpClient = httpClient == null ? new JdkA2AHttpClient() : httpClient;
this.httpClient = httpClient == null ? A2AHttpClientFactory.create() : httpClient;
this.agentCard = agentCard;
this.agentInterface = agentInterface;
this.interceptors = interceptors;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.a2a.client.transport.rest;

import io.a2a.client.http.A2AHttpClient;
import io.a2a.client.http.JdkA2AHttpClient;
import io.a2a.client.http.A2AHttpClientFactory;
import io.a2a.client.transport.spi.ClientTransportConfigBuilder;
import org.jspecify.annotations.Nullable;

Expand All @@ -11,7 +11,7 @@
* This builder provides a fluent API for configuring the REST transport protocol.
* All configuration options are optional - if not specified, sensible defaults are used:
* <ul>
* <li><b>HTTP client:</b> {@link JdkA2AHttpClient} (JDK's built-in HTTP client)</li>
* <li><b>HTTP client:</b> Auto-selected via {@link A2AHttpClientFactory} (prefers Vert.x, falls back to JDK)</li>
* <li><b>Interceptors:</b> None</li>
* </ul>
* <p>
Expand Down Expand Up @@ -78,7 +78,7 @@ public class RestTransportConfigBuilder extends ClientTransportConfigBuilder<Res
* <li>Custom header handling</li>
* </ul>
* <p>
* If not specified, the default {@link JdkA2AHttpClient} is used.
* If not specified, a client is auto-selected via {@link A2AHttpClientFactory}.
* <p>
* Example:
* <pre>{@code
Expand All @@ -101,16 +101,16 @@ public RestTransportConfigBuilder httpClient(A2AHttpClient httpClient) {
/**
* Build the REST transport configuration.
* <p>
* If no HTTP client was configured, the default {@link JdkA2AHttpClient} is used.
* If no HTTP client was configured, one is auto-selected via {@link A2AHttpClientFactory}.
* Any configured interceptors are transferred to the configuration.
*
* @return the configured REST transport configuration
*/
@Override
public RestTransportConfig build() {
// No HTTP client provided, fallback to the default one (JDK-based implementation)
// No HTTP client provided, use factory to get best available implementation
if (httpClient == null) {
httpClient = new JdkA2AHttpClient();
httpClient = A2AHttpClientFactory.create();
}

RestTransportConfig config = new RestTransportConfig(httpClient);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.a2a.client.transport.rest;

import io.a2a.client.http.JdkA2AHttpClient;
import io.a2a.client.http.A2AHttpClientFactory;
import io.a2a.client.transport.spi.ClientTransportProvider;
import io.a2a.spec.A2AClientException;
import io.a2a.spec.AgentCard;
Expand All @@ -18,9 +18,9 @@ public String getTransportProtocol() {
public RestTransport create(RestTransportConfig clientTransportConfig, AgentCard agentCard, AgentInterface agentInterface) throws A2AClientException {
RestTransportConfig transportConfig = clientTransportConfig;
if (transportConfig == null) {
transportConfig = new RestTransportConfig(new JdkA2AHttpClient());
transportConfig = new RestTransportConfig(A2AHttpClientFactory.create());
}
return new RestTransport(clientTransportConfig.getHttpClient(), agentCard, agentInterface, transportConfig.getInterceptors());
return new RestTransport(transportConfig.getHttpClient(), agentCard, agentInterface, transportConfig.getInterceptors());
}

@Override
Expand Down
16 changes: 13 additions & 3 deletions extras/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ This directory contains additions to what is provided by the default SDK impleme

Please see the README's of each child directory for more details.

[`task-store-database-jpa`](./task-store-database-jpa/README.md) - Replaces the default `InMemoryTaskStore` with a `TaskStore` backed by a RDBMS. It uses JPA to interact with the RDBMS.
[`push-notification-config-store-database-jpa`](./push-notification-config-store-database-jpa/README.md) - Replaces the default `InMemoryPushNotificationConfigStore` with a `PushNotificationConfigStore` backed by a RDBMS. It uses JPA to interact with the RDBMS.
[`queue-manager-replicated`](./queue-manager-replicated/README.md) - Replaces the default `InMemoryQueueManager` with a `QueueManager` supporting replication to other A2A servers implementing the same agent. You can write your own `ReplicationStrategy`, or use the provided `MicroProfile Reactive Messaging implementation`.
## HTTP Client

[`http-client-vertx`](./http-client-vertx/README.md) - Vert.x WebClient-based implementation of `A2AHttpClient` for reactive, high-performance HTTP communication. Replaces the default JDK HttpClient with a non-blocking, event-loop based client. Uses SPI for automatic discovery - simply add this library as a dependency to use it. Recommended for reactive applications, Quarkus, and high-throughput scenarios.

## Storage & Persistence

[`task-store-database-jpa`](./task-store-database-jpa/README.md) - Replaces the default `InMemoryTaskStore` with a `TaskStore` backed by a RDBMS. It uses JPA to interact with the RDBMS, providing persistence across application restarts and shared state in multi-instance deployments.

[`push-notification-config-store-database-jpa`](./push-notification-config-store-database-jpa/README.md) - Replaces the default `InMemoryPushNotificationConfigStore` with a `PushNotificationConfigStore` backed by a RDBMS. It uses JPA to interact with the RDBMS, ensuring push notification subscriptions survive restarts.

## Distributed Systems

[`queue-manager-replicated`](./queue-manager-replicated/README.md) - Replaces the default `InMemoryQueueManager` with a `QueueManager` supporting replication to other A2A servers implementing the same agent. Required for multi-instance deployments. You can write your own `ReplicationStrategy`, or use the provided MicroProfile Reactive Messaging implementation with Apache Kafka, Pulsar, or AMQP.
Loading