Skip to content

Commit 9700c25

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. Signed-off-by: Emmanuel Hugonnet <[email protected]>
1 parent 26ab76d commit 9700c25

File tree

43 files changed

+2938
-259
lines changed

Some content is hidden

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

43 files changed

+2938
-259
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-

client/base/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@
4949
<artifactId>mockserver-netty</artifactId>
5050
<scope>test</scope>
5151
</dependency>
52+
<dependency>
53+
<groupId>org.slf4j</groupId>
54+
<artifactId>slf4j-jdk14</artifactId>
55+
<scope>test</scope>
56+
</dependency>
5257
</dependencies>
5358

5459
</project>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public void sendMessage(Message request, PushNotificationConfig pushNotification
7272
.metadata(metatadata)
7373
.build();
7474

75+
7576
sendMessage(messageSendParams, null, null, context);
7677
}
7778

client/base/src/test/java/io/a2a/client/A2ACardResolverTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ public PostBuilder createPost() {
128128
return null;
129129
}
130130

131+
@Override
132+
public DeleteBuilder createDelete() {
133+
return null;
134+
}
135+
131136
class TestGetBuilder implements A2AHttpClient.GetBuilder {
132137

133138
@Override

client/transport/jsonrest/pom.xml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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-jsonrest</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-client-config</artifactId>
23+
</dependency>
24+
<dependency>
25+
<groupId>${project.groupId}</groupId>
26+
<artifactId>a2a-java-sdk-common</artifactId>
27+
</dependency>
28+
<dependency>
29+
<groupId>${project.groupId}</groupId>
30+
<artifactId>a2a-java-sdk-spec</artifactId>
31+
</dependency>
32+
<dependency>
33+
<groupId>${project.groupId}</groupId>
34+
<artifactId>a2a-java-sdk-spec-grpc</artifactId>
35+
</dependency>
36+
<dependency>
37+
<groupId>${project.groupId}</groupId>
38+
<artifactId>a2a-java-sdk-client-transport-spi</artifactId>
39+
</dependency>
40+
<dependency>
41+
<groupId>io.github.a2asdk</groupId>
42+
<artifactId>a2a-java-sdk-http-client</artifactId>
43+
</dependency>
44+
<dependency>
45+
<groupId>com.google.protobuf</groupId>
46+
<artifactId>protobuf-java-util</artifactId>
47+
</dependency>
48+
<dependency>
49+
<groupId>org.junit.jupiter</groupId>
50+
<artifactId>junit-jupiter-api</artifactId>
51+
<scope>test</scope>
52+
</dependency>
53+
54+
<dependency>
55+
<groupId>org.mock-server</groupId>
56+
<artifactId>mockserver-netty</artifactId>
57+
<scope>test</scope>
58+
</dependency>
59+
<dependency>
60+
<groupId>org.slf4j</groupId>
61+
<artifactId>slf4j-jdk14</artifactId>
62+
<scope>test</scope>
63+
</dependency>
64+
</dependencies>
65+
66+
</project>

client/transport/jsonrest/src/main/java/io/a2a/client/transport/jsonrest/JSONRestTransport.java

Lines changed: 355 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.a2a.client.transport.jsonrest;
2+
3+
import io.a2a.client.config.ClientTransportConfig;
4+
import io.a2a.client.http.A2AHttpClient;
5+
6+
public class JSONRestTransportConfig implements ClientTransportConfig {
7+
8+
private final A2AHttpClient httpClient;
9+
10+
public JSONRestTransportConfig(A2AHttpClient httpClient) {
11+
this.httpClient = httpClient;
12+
}
13+
14+
public A2AHttpClient getHttpClient() {
15+
return httpClient;
16+
}
17+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.a2a.client.transport.jsonrest;
2+
3+
import java.util.List;
4+
5+
import io.a2a.client.config.ClientCallInterceptor;
6+
import io.a2a.client.config.ClientConfig;
7+
import io.a2a.client.config.ClientTransportConfig;
8+
import io.a2a.client.http.A2AHttpClient;
9+
import io.a2a.client.transport.spi.ClientTransport;
10+
import io.a2a.client.transport.spi.ClientTransportProvider;
11+
import io.a2a.spec.AgentCard;
12+
import io.a2a.spec.TransportProtocol;
13+
14+
public class JSONRestTransportProvider implements ClientTransportProvider {
15+
16+
@Override
17+
public ClientTransport create(ClientConfig clientConfig, AgentCard agentCard,
18+
String agentUrl, List<ClientCallInterceptor> interceptors) {
19+
A2AHttpClient httpClient = null;
20+
List<ClientTransportConfig> clientTransportConfigs = clientConfig.getClientTransportConfigs();
21+
if (clientTransportConfigs != null) {
22+
for (ClientTransportConfig clientTransportConfig : clientTransportConfigs) {
23+
if (clientTransportConfig instanceof JSONRestTransportConfig jsonrestTransportConfig) {
24+
httpClient = jsonrestTransportConfig.getHttpClient();
25+
break;
26+
}
27+
}
28+
}
29+
return new JSONRestTransport(httpClient, agentCard, agentUrl, interceptors);
30+
}
31+
32+
@Override
33+
public String getTransportProtocol() {
34+
return TransportProtocol.HTTP_JSON.asString();
35+
}
36+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package io.a2a.client.transport.jsonrest.sse;
2+
3+
import static io.a2a.grpc.StreamResponse.PayloadCase.ARTIFACT_UPDATE;
4+
import static io.a2a.grpc.StreamResponse.PayloadCase.MSG;
5+
import static io.a2a.grpc.StreamResponse.PayloadCase.STATUS_UPDATE;
6+
import static io.a2a.grpc.StreamResponse.PayloadCase.TASK;
7+
8+
import java.util.concurrent.Future;
9+
import java.util.function.Consumer;
10+
import java.util.logging.Logger;
11+
12+
import com.google.protobuf.InvalidProtocolBufferException;
13+
import com.google.protobuf.util.JsonFormat;
14+
import io.a2a.grpc.StreamResponse;
15+
import io.a2a.grpc.utils.ProtoUtils;
16+
import io.a2a.spec.StreamingEventKind;
17+
import java.util.logging.Level;
18+
19+
public class JSONRestSSEEventListener {
20+
21+
private static final Logger log = Logger.getLogger(JSONRestSSEEventListener.class.getName());
22+
private final Consumer<StreamingEventKind> eventHandler;
23+
private final Consumer<Throwable> errorHandler;
24+
25+
public JSONRestSSEEventListener(Consumer<StreamingEventKind> eventHandler,
26+
Consumer<Throwable> errorHandler) {
27+
this.eventHandler = eventHandler;
28+
this.errorHandler = errorHandler;
29+
}
30+
31+
public void onMessage(String message, Future<Void> completableFuture) {
32+
try {
33+
io.a2a.grpc.StreamResponse.Builder builder = io.a2a.grpc.StreamResponse.newBuilder();
34+
JsonFormat.parser().merge(message, builder);
35+
handleMessage(builder.build(), completableFuture);
36+
} catch (InvalidProtocolBufferException e) {
37+
Logger.getLogger(JSONRestSSEEventListener.class.getName()).log(Level.SEVERE, null, e);
38+
errorHandler.accept(e);
39+
}
40+
}
41+
42+
public void onError(Throwable throwable, Future<Void> future) {
43+
if (errorHandler != null) {
44+
errorHandler.accept(throwable);
45+
}
46+
future.cancel(true); // close SSE channel
47+
}
48+
49+
private void handleMessage(StreamResponse response, Future<Void> future) {
50+
StreamingEventKind event;
51+
switch (response.getPayloadCase()) {
52+
case MSG:
53+
event = ProtoUtils.FromProto.message(response.getMsg());
54+
break;
55+
case TASK:
56+
event = ProtoUtils.FromProto.task(response.getTask());
57+
break;
58+
case STATUS_UPDATE:
59+
event = ProtoUtils.FromProto.taskStatusUpdateEvent(response.getStatusUpdate());
60+
break;
61+
case ARTIFACT_UPDATE:
62+
event = ProtoUtils.FromProto.taskArtifactUpdateEvent(response.getArtifactUpdate());
63+
break;
64+
default:
65+
log.warning("Invalid stream response " + response.getPayloadCase());
66+
errorHandler.accept(new IllegalStateException("Invalid stream response from server: " + response.getPayloadCase()));
67+
return;
68+
}
69+
eventHandler.accept(event);
70+
}
71+
72+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.a2a.client.transport.jsonrest.JSONRestTransportProvider

0 commit comments

Comments
 (0)