Skip to content

Commit ed6f73a

Browse files
fjumaehsavoie
authored andcommitted
feat: add support for JSON+HTTP/REST
* Create JSON+HTTP/REST Client. * Integrate PR from @ronantakizawa using regexp for server routing. * Update server code to use proper JSON (de)serialization with Proto. * Create Reference Implementation * Removing the listTasks since it has been removed from the specs * Removing io.grpc:* as direct dependencies for a2a-java-sdk-spec-grpc. Signed-off-by: Emmanuel Hugonnet <[email protected]>
1 parent 0238c29 commit ed6f73a

File tree

47 files changed

+3723
-59
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+3723
-59
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,3 @@ nb-configuration.xml
4444
# TLS Certificates
4545
.certs/
4646
nbproject/
47-

README.md

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,18 @@ To use the reference implementation with the gRPC protocol, add the following de
8181
Note that you can add more than one of the above dependencies to your project depending on the transports
8282
you'd like to support.
8383

84-
Support for the HTTP+JSON/REST transport will be coming soon.
84+
To use the reference implementation with the HTTP+JSON/REST protocol, add the following dependency to your project:
85+
86+
> *⚠️ The `io.github.a2asdk` `groupId` below is temporary and will likely change for future releases.*
87+
88+
```xml
89+
<dependency>
90+
<groupId>io.github.a2asdk</groupId>
91+
<artifactId>a2a-java-sdk-reference-rest</artifactId>
92+
<!-- Use a released version from https://github.com/a2aproject/a2a-java/releases -->
93+
<version>${io.a2a.sdk.version}</version>
94+
</dependency>
95+
```
8596

8697
### 2. Add a class that creates an A2A Agent Card
8798

@@ -117,7 +128,7 @@ public class WeatherAgentCardProducer {
117128
.tags(Collections.singletonList("weather"))
118129
.examples(List.of("weather in LA, CA"))
119130
.build()))
120-
.protocolVersion("0.2.5")
131+
.protocolVersion("0.3.0")
121132
.build();
122133
}
123134
}
@@ -247,7 +258,7 @@ By default, the sdk-client is coming with the JSONRPC transport dependency. Desp
247258
dependency is included by default, you still need to add the transport to the Client as described in [JSON-RPC Transport section](#json-rpc-transport-configuration).
248259

249260

250-
If you want to use another transport (such as GRPC or HTTP+JSON), you'll need to add a relevant dependency:
261+
If you want to use GRPC transport, you'll need to add a relevant dependency:
251262

252263
----
253264
> *⚠️ The `io.github.a2asdk` `groupId` below is temporary and will likely change for future releases.*
@@ -262,7 +273,21 @@ If you want to use another transport (such as GRPC or HTTP+JSON), you'll need to
262273
</dependency>
263274
```
264275

265-
Support for the HTTP+JSON/REST transport will be coming soon.
276+
277+
If you want to use HTTP+JSON/REST transport, you'll need to add a relevant dependency:
278+
279+
----
280+
> *⚠️ The `io.github.a2asdk` `groupId` below is temporary and will likely change for future releases.*
281+
----
282+
283+
```xml
284+
<dependency>
285+
<groupId>io.github.a2asdk</groupId>
286+
<artifactId>a2a-java-sdk-client-transport-rest</artifactId>
287+
<!-- Use a released version from https://github.com/a2aproject/a2a-java/releases -->
288+
<version>${io.a2a.sdk.version}</version>
289+
</dependency>
290+
```
266291

267292
### Sample Usage
268293

@@ -360,6 +385,30 @@ Client client = Client
360385
.build();
361386
```
362387

388+
389+
##### HTTP+JSON/REST Transport Configuration
390+
391+
For
392+
For the HTTP+JSON/REST transport, if you'd like to use the default `JdkA2AHttpClient`, no additional
393+
configuration is needed. To use a custom HTTP client implementation, simply create a `RestTransportConfig`
394+
as follows:
395+
396+
```java
397+
// Create a custom HTTP client
398+
A2AHttpClient customHttpClient = ...
399+
400+
// Configure the client settings
401+
ClientConfig clientConfig = new ClientConfig.Builder()
402+
.setAcceptedOutputModes(List.of("text"))
403+
.build();
404+
405+
Client client = Client
406+
.builder(agentCard)
407+
.clientConfig(clientConfig)
408+
.withTransport(RestTransport.class, new RestTransportConfig(customHttpClient))
409+
.build();
410+
```
411+
363412
##### Multiple Transport Configurations
364413

365414
You can specify configuration for multiple transports, the appropriate configuration
@@ -371,6 +420,7 @@ Client client = Client
371420
.builder(agentCard)
372421
.withTransport(GrpcTransport.class, new GrpcTransportConfig(channelFactory))
373422
.withTransport(JSONRPCTransport.class, new JSONRPCTransportConfig())
423+
.withTransport(RestTransport.class, new RestTransportConfig())
374424
.build();
375425
```
376426

client/base/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@
5454
<artifactId>mockserver-netty</artifactId>
5555
<scope>test</scope>
5656
</dependency>
57+
<dependency>
58+
<groupId>org.slf4j</groupId>
59+
<artifactId>slf4j-jdk14</artifactId>
60+
<scope>test</scope>
61+
</dependency>
5762
</dependencies>
5863

5964
</project>

client/transport/grpc/pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@
3333
<groupId>${project.groupId}</groupId>
3434
<artifactId>a2a-java-sdk-client-transport-spi</artifactId>
3535
</dependency>
36+
<dependency>
37+
<groupId>io.grpc</groupId>
38+
<artifactId>grpc-protobuf</artifactId>
39+
</dependency>
40+
<dependency>
41+
<groupId>io.grpc</groupId>
42+
<artifactId>grpc-stub</artifactId>
43+
</dependency>
3644
<dependency>
3745
<groupId>org.junit.jupiter</groupId>
3846
<artifactId>junit-jupiter-api</artifactId>

client/transport/jsonrpc/src/test/java/io/a2a/client/transport/jsonrpc/JSONRPCTransportTest.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import static io.a2a.client.transport.jsonrpc.JsonMessages.AGENT_CARD;
44
import static io.a2a.client.transport.jsonrpc.JsonMessages.AGENT_CARD_SUPPORTS_EXTENDED;
5-
import static io.a2a.client.transport.jsonrpc.JsonMessages.AUTHENTICATION_EXTENDED_AGENT_CARD;
65
import static io.a2a.client.transport.jsonrpc.JsonMessages.CANCEL_TASK_TEST_REQUEST;
76
import static io.a2a.client.transport.jsonrpc.JsonMessages.CANCEL_TASK_TEST_RESPONSE;
87
import static io.a2a.client.transport.jsonrpc.JsonMessages.GET_AUTHENTICATED_EXTENDED_AGENT_CARD_REQUEST;
@@ -50,7 +49,6 @@
5049
import io.a2a.spec.FilePart;
5150
import io.a2a.spec.FileWithBytes;
5251
import io.a2a.spec.FileWithUri;
53-
import io.a2a.spec.GetAuthenticatedExtendedCardResponse;
5452
import io.a2a.spec.GetTaskPushNotificationConfigParams;
5553
import io.a2a.spec.Message;
5654
import io.a2a.spec.MessageSendConfiguration;

client/transport/rest/pom.xml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?xml version="1.0"?>
2+
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
3+
xmlns="http://maven.apache.org/POM/4.0.0"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>io.github.a2asdk</groupId>
9+
<artifactId>a2a-java-sdk-parent</artifactId>
10+
<version>0.3.0.Beta1-SNAPSHOT</version>
11+
<relativePath>../../../pom.xml</relativePath>
12+
</parent>
13+
<artifactId>a2a-java-sdk-client-transport-rest</artifactId>
14+
<packaging>jar</packaging>
15+
16+
<name>Java SDK A2A Client Transport: JSON+HTTP/REST</name>
17+
<description>Java SDK for the Agent2Agent Protocol (A2A) - JSON+HTTP/REST Client Transport</description>
18+
19+
<dependencies>
20+
<dependency>
21+
<groupId>${project.groupId}</groupId>
22+
<artifactId>a2a-java-sdk-common</artifactId>
23+
</dependency>
24+
<dependency>
25+
<groupId>${project.groupId}</groupId>
26+
<artifactId>a2a-java-sdk-spec</artifactId>
27+
</dependency>
28+
<dependency>
29+
<groupId>${project.groupId}</groupId>
30+
<artifactId>a2a-java-sdk-spec-grpc</artifactId>
31+
</dependency>
32+
<dependency>
33+
<groupId>${project.groupId}</groupId>
34+
<artifactId>a2a-java-sdk-client-transport-spi</artifactId>
35+
</dependency>
36+
<dependency>
37+
<groupId>io.github.a2asdk</groupId>
38+
<artifactId>a2a-java-sdk-http-client</artifactId>
39+
</dependency>
40+
<dependency>
41+
<groupId>com.google.protobuf</groupId>
42+
<artifactId>protobuf-java-util</artifactId>
43+
</dependency>
44+
<dependency>
45+
<groupId>org.junit.jupiter</groupId>
46+
<artifactId>junit-jupiter-api</artifactId>
47+
<scope>test</scope>
48+
</dependency>
49+
50+
<dependency>
51+
<groupId>org.mock-server</groupId>
52+
<artifactId>mockserver-netty</artifactId>
53+
<scope>test</scope>
54+
</dependency>
55+
<dependency>
56+
<groupId>org.slf4j</groupId>
57+
<artifactId>slf4j-jdk14</artifactId>
58+
<scope>test</scope>
59+
</dependency>
60+
</dependencies>
61+
62+
</project>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package io.a2a.client.transport.rest;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.JsonNode;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
7+
import io.a2a.client.http.A2AHttpResponse;
8+
import io.a2a.spec.A2AClientException;
9+
import io.a2a.spec.AuthenticatedExtendedCardNotConfiguredError;
10+
import io.a2a.spec.ContentTypeNotSupportedError;
11+
import io.a2a.spec.InternalError;
12+
import io.a2a.spec.InvalidAgentResponseError;
13+
import io.a2a.spec.InvalidParamsError;
14+
import io.a2a.spec.InvalidRequestError;
15+
import io.a2a.spec.JSONParseError;
16+
import io.a2a.spec.MethodNotFoundError;
17+
import io.a2a.spec.PushNotificationNotSupportedError;
18+
import io.a2a.spec.TaskNotCancelableError;
19+
import io.a2a.spec.TaskNotFoundError;
20+
import io.a2a.spec.UnsupportedOperationError;
21+
import java.util.logging.Level;
22+
import java.util.logging.Logger;
23+
24+
/**
25+
* Utility class to A2AHttpResponse to appropriate A2A error types
26+
*/
27+
public class RestErrorMapper {
28+
29+
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().registerModule(new JavaTimeModule());
30+
31+
public static A2AClientException mapRestError(A2AHttpResponse response) {
32+
return RestErrorMapper.mapRestError(response.body(), response.status());
33+
}
34+
35+
public static A2AClientException mapRestError(String body, int code) {
36+
try {
37+
if (body != null && !body.isBlank()) {
38+
JsonNode node = OBJECT_MAPPER.readTree(body);
39+
String className = node.findValue("error").asText();
40+
String errorMessage = node.findValue("message").asText();
41+
return mapRestError(className, errorMessage, code);
42+
}
43+
return mapRestError("", "", code);
44+
} catch (JsonProcessingException ex) {
45+
Logger.getLogger(RestErrorMapper.class.getName()).log(Level.SEVERE, null, ex);
46+
return new A2AClientException("Failed to parse error response: " + ex.getMessage());
47+
}
48+
}
49+
50+
public static A2AClientException mapRestError(String className, String errorMessage, int code) {
51+
switch (className) {
52+
case "io.a2a.spec.TaskNotFoundError":
53+
return new A2AClientException(errorMessage, new TaskNotFoundError());
54+
case "io.a2a.spec.AuthenticatedExtendedCardNotConfiguredError":
55+
return new A2AClientException(errorMessage, new AuthenticatedExtendedCardNotConfiguredError());
56+
case "io.a2a.spec.ContentTypeNotSupportedError":
57+
return new A2AClientException(errorMessage, new ContentTypeNotSupportedError(null, null, errorMessage));
58+
case "io.a2a.spec.InternalError":
59+
return new A2AClientException(errorMessage, new InternalError(errorMessage));
60+
case "io.a2a.spec.InvalidAgentResponseError":
61+
return new A2AClientException(errorMessage, new InvalidAgentResponseError(null, null, errorMessage));
62+
case "io.a2a.spec.InvalidParamsError":
63+
return new A2AClientException(errorMessage, new InvalidParamsError());
64+
case "io.a2a.spec.InvalidRequestError":
65+
return new A2AClientException(errorMessage, new InvalidRequestError());
66+
case "io.a2a.spec.JSONParseError":
67+
return new A2AClientException(errorMessage, new JSONParseError());
68+
case "io.a2a.spec.MethodNotFoundError":
69+
return new A2AClientException(errorMessage, new MethodNotFoundError());
70+
case "io.a2a.spec.PushNotificationNotSupportedError":
71+
return new A2AClientException(errorMessage, new PushNotificationNotSupportedError());
72+
case "io.a2a.spec.TaskNotCancelableError":
73+
return new A2AClientException(errorMessage, new TaskNotCancelableError());
74+
case "io.a2a.spec.UnsupportedOperationError":
75+
return new A2AClientException(errorMessage, new UnsupportedOperationError());
76+
default:
77+
return new A2AClientException(errorMessage);
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)