Skip to content

Commit ca14abd

Browse files
committed
feat(http-client): add Android support and implement SPI for A2AHttpClient
- Introduce `AndroidA2AHttpClient` using `HttpURLConnection` for Android compatibility. - Implement `A2AHttpClientFactory` and `A2AHttpClientProvider` using `ServiceLoader` to decouple implementations. - Update `A2A` and transport providers to use the factory instead of hardcoding `JdkA2AHttpClient`. - Add `@JsonProperty` annotations to spec records to prevent parsing failures on Android.
1 parent c2551bc commit ca14abd

28 files changed

Lines changed: 462 additions & 50 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ Different transport protocols can be configured with specific settings using spe
394394

395395
##### JSON-RPC Transport Configuration
396396

397-
For the JSON-RPC transport, to use the default `JdkA2AHttpClient`, provide a `JSONRPCTransportConfig` created with its default constructor.
397+
For the JSON-RPC transport, to use the default HTTP client (resolved automatically by `A2AHttpClientFactory`), provide a `JSONRPCTransportConfig` created with its default constructor.
398398

399399
To use a custom HTTP client implementation, simply create a `JSONRPCTransportConfig` as follows:
400400

@@ -441,7 +441,7 @@ Client client = Client
441441

442442
##### HTTP+JSON/REST Transport Configuration
443443

444-
For the HTTP+JSON/REST transport, if you'd like to use the default `JdkA2AHttpClient`, provide a `RestTransportConfig` created with its default constructor.
444+
For the HTTP+JSON/REST transport, to use the default HTTP client (resolved automatically by `A2AHttpClientFactory`), provide a `RestTransportConfig` created with its default constructor.
445445

446446
To use a custom HTTP client implementation, simply create a `RestTransportConfig` as follows:
447447

client/base/src/main/java/io/a2a/A2A.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
import java.util.Collections;
44
import java.util.List;
55
import java.util.Map;
6-
import java.util.UUID;
76

87
import io.a2a.client.http.A2ACardResolver;
98
import io.a2a.client.http.A2AHttpClient;
10-
import io.a2a.client.http.JdkA2AHttpClient;
9+
import io.a2a.client.http.A2AHttpClientFactory;
1110
import io.a2a.spec.A2AClientError;
1211
import io.a2a.spec.A2AClientJSONError;
1312
import io.a2a.spec.AgentCard;
@@ -139,7 +138,7 @@ private static Message toMessage(List<Part<?>> parts, Message.Role role, String
139138
* @throws A2AClientJSONError If the response body cannot be decoded as JSON or validated against the AgentCard schema
140139
*/
141140
public static AgentCard getAgentCard(String agentUrl) throws A2AClientError, A2AClientJSONError {
142-
return getAgentCard(new JdkA2AHttpClient(), agentUrl);
141+
return getAgentCard(A2AHttpClientFactory.create(), agentUrl);
143142
}
144143

145144
/**
@@ -167,7 +166,7 @@ public static AgentCard getAgentCard(A2AHttpClient httpClient, String agentUrl)
167166
* @throws A2AClientJSONError If the response body cannot be decoded as JSON or validated against the AgentCard schema
168167
*/
169168
public static AgentCard getAgentCard(String agentUrl, String relativeCardPath, Map<String, String> authHeaders) throws A2AClientError, A2AClientJSONError {
170-
return getAgentCard(new JdkA2AHttpClient(), agentUrl, relativeCardPath, authHeaders);
169+
return getAgentCard(A2AHttpClientFactory.create(), agentUrl, relativeCardPath, authHeaders);
171170
}
172171

173172
/**

client/transport/jsonrpc/src/main/java/io/a2a/client/transport/jsonrpc/JSONRPCTransport.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
import io.a2a.client.transport.spi.interceptors.ClientCallInterceptor;
1616
import io.a2a.client.transport.spi.interceptors.PayloadAndHeaders;
1717
import io.a2a.client.http.A2AHttpClient;
18+
import io.a2a.client.http.A2AHttpClientFactory;
1819
import io.a2a.client.http.A2AHttpResponse;
19-
import io.a2a.client.http.JdkA2AHttpClient;
2020
import io.a2a.client.transport.spi.ClientTransport;
2121
import io.a2a.spec.A2AClientError;
2222
import io.a2a.spec.A2AClientException;
@@ -87,7 +87,7 @@ public JSONRPCTransport(AgentCard agentCard) {
8787

8888
public JSONRPCTransport(A2AHttpClient httpClient, AgentCard agentCard,
8989
String agentUrl, List<ClientCallInterceptor> interceptors) {
90-
this.httpClient = httpClient == null ? new JdkA2AHttpClient() : httpClient;
90+
this.httpClient = httpClient == null ? A2AHttpClientFactory.create() : httpClient;
9191
this.agentCard = agentCard;
9292
this.agentUrl = agentUrl;
9393
this.interceptors = interceptors;

client/transport/jsonrpc/src/main/java/io/a2a/client/transport/jsonrpc/JSONRPCTransportConfigBuilder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.a2a.client.transport.jsonrpc;
22

33
import io.a2a.client.http.A2AHttpClient;
4+
import io.a2a.client.http.A2AHttpClientFactory;
45
import io.a2a.client.http.JdkA2AHttpClient;
56
import io.a2a.client.transport.spi.ClientTransportConfigBuilder;
67

@@ -18,7 +19,7 @@ public JSONRPCTransportConfigBuilder httpClient(A2AHttpClient httpClient) {
1819
public JSONRPCTransportConfig build() {
1920
// No HTTP client provided, fallback to the default one (JDK-based implementation)
2021
if (httpClient == null) {
21-
httpClient = new JdkA2AHttpClient();
22+
httpClient = A2AHttpClientFactory.create();
2223
}
2324

2425
JSONRPCTransportConfig config = new JSONRPCTransportConfig(httpClient);

client/transport/jsonrpc/src/main/java/io/a2a/client/transport/jsonrpc/JSONRPCTransportProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.a2a.client.transport.jsonrpc;
22

3-
import io.a2a.client.http.JdkA2AHttpClient;
3+
import io.a2a.client.http.A2AHttpClientFactory;
44
import io.a2a.client.transport.spi.ClientTransportProvider;
55
import io.a2a.spec.A2AClientException;
66
import io.a2a.spec.AgentCard;
@@ -11,7 +11,7 @@ public class JSONRPCTransportProvider implements ClientTransportProvider<JSONRPC
1111
@Override
1212
public JSONRPCTransport create(JSONRPCTransportConfig clientTransportConfig, AgentCard agentCard, String agentUrl) throws A2AClientException {
1313
if (clientTransportConfig == null) {
14-
clientTransportConfig = new JSONRPCTransportConfig(new JdkA2AHttpClient());
14+
clientTransportConfig = new JSONRPCTransportConfig(A2AHttpClientFactory.create());
1515
}
1616

1717
return new JSONRPCTransport(clientTransportConfig.getHttpClient(), agentCard, agentUrl, clientTransportConfig.getInterceptors());

client/transport/rest/src/main/java/io/a2a/client/transport/rest/RestTransport.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
import com.google.protobuf.util.JsonFormat;
99
import io.a2a.client.http.A2ACardResolver;
1010
import io.a2a.client.http.A2AHttpClient;
11+
import io.a2a.client.http.A2AHttpClientFactory;
1112
import io.a2a.client.http.A2AHttpResponse;
12-
import io.a2a.client.http.JdkA2AHttpClient;
1313
import io.a2a.client.transport.rest.sse.RestSSEEventListener;
1414
import io.a2a.client.transport.spi.ClientTransport;
1515
import io.a2a.client.transport.spi.interceptors.ClientCallContext;
@@ -62,7 +62,7 @@ public RestTransport(AgentCard agentCard) {
6262

6363
public RestTransport(@Nullable A2AHttpClient httpClient, AgentCard agentCard,
6464
String agentUrl, @Nullable List<ClientCallInterceptor> interceptors) {
65-
this.httpClient = httpClient == null ? new JdkA2AHttpClient() : httpClient;
65+
this.httpClient = httpClient == null ? A2AHttpClientFactory.create() : httpClient;
6666
this.agentCard = agentCard;
6767
this.agentUrl = agentUrl.endsWith("/") ? agentUrl.substring(0, agentUrl.length() - 1) : agentUrl;
6868
this.interceptors = interceptors;

client/transport/rest/src/main/java/io/a2a/client/transport/rest/RestTransportConfigBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.a2a.client.transport.rest;
22

33
import io.a2a.client.http.A2AHttpClient;
4+
import io.a2a.client.http.A2AHttpClientFactory;
45
import io.a2a.client.http.JdkA2AHttpClient;
56
import io.a2a.client.transport.spi.ClientTransportConfigBuilder;
67
import org.jspecify.annotations.Nullable;
@@ -16,9 +17,8 @@ public RestTransportConfigBuilder httpClient(A2AHttpClient httpClient) {
1617

1718
@Override
1819
public RestTransportConfig build() {
19-
// No HTTP client provided, fallback to the default one (JDK-based implementation)
2020
if (httpClient == null) {
21-
httpClient = new JdkA2AHttpClient();
21+
httpClient = A2AHttpClientFactory.create();
2222
}
2323

2424
RestTransportConfig config = new RestTransportConfig(httpClient);

client/transport/rest/src/main/java/io/a2a/client/transport/rest/RestTransportProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.a2a.client.transport.rest;
22

3-
import io.a2a.client.http.JdkA2AHttpClient;
3+
import io.a2a.client.http.A2AHttpClientFactory;
44
import io.a2a.client.transport.spi.ClientTransportProvider;
55
import io.a2a.spec.A2AClientException;
66
import io.a2a.spec.AgentCard;
@@ -17,7 +17,7 @@ public String getTransportProtocol() {
1717
public RestTransport create(RestTransportConfig clientTransportConfig, AgentCard agentCard, String agentUrl) throws A2AClientException {
1818
RestTransportConfig transportConfig = clientTransportConfig;
1919
if (transportConfig == null) {
20-
transportConfig = new RestTransportConfig(new JdkA2AHttpClient());
20+
transportConfig = new RestTransportConfig(A2AHttpClientFactory.create());
2121
}
2222
return new RestTransport(clientTransportConfig.getHttpClient(), agentCard, agentUrl, transportConfig.getInterceptors());
2323
}

examples/cloud-deployment/k8s/02-kafka.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ metadata:
3333
strimzi.io/kraft: enabled
3434
spec:
3535
kafka:
36-
version: 4.0.0
36+
version: 4.1.0
3737
metadataVersion: 4.0-IV0
3838
listeners:
3939
- name: plain

http-client/src/main/java/io/a2a/client/http/A2ACardResolver.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,16 @@ public class A2ACardResolver {
2323

2424
private static final TypeReference<AgentCard> AGENT_CARD_TYPE_REFERENCE = new TypeReference<>() {};
2525

26-
/**
27-
* Get the agent card for an A2A agent.
28-
* The {@code JdkA2AHttpClient} will be used to fetch the agent card.
29-
*
30-
* @param baseUrl the base URL for the agent whose agent card we want to retrieve
31-
* @throws A2AClientError if the URL for the agent is invalid
32-
*/
33-
public A2ACardResolver(String baseUrl) throws A2AClientError {
34-
this(new JdkA2AHttpClient(), baseUrl, null, null);
35-
}
26+
/**
27+
* Get the agent card for an A2A agent.
28+
* The {@link A2AHttpClientFactory#create()} will be used to fetch the agent card if available.
29+
*
30+
* @param baseUrl the base URL for the agent whose agent card we want to retrieve
31+
* @throws A2AClientError if the URL for the agent is invalid
32+
*/
33+
public A2ACardResolver(String baseUrl) throws A2AClientError {
34+
this(A2AHttpClientFactory.create(), baseUrl, null, null);
35+
}
3636

3737
/**
3838
* Constructs an A2ACardResolver with a specific HTTP client and base URL.

0 commit comments

Comments
 (0)