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
6 changes: 4 additions & 2 deletions .github/workflows/run-tck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ env:
UV_SYSTEM_PYTHON: 1
# SUT_JSONRPC_URL to use for the TCK and the server agent
SUT_JSONRPC_URL: http://localhost:9999
# Slow system on CI
TCK_STREAMING_TIMEOUT: 5.0

# Only run the latest job
concurrency:
Expand Down Expand Up @@ -55,7 +57,7 @@ jobs:
- name: Build with Maven, skipping tests
run: mvn -B install -DskipTests
- name: Start SUT
run: SUT_GRPC_URL=${{ env.SUT_JSONRPC_URL }} mvn -B quarkus:dev & #SUT_JSONRPC_URL already set
run: SUT_GRPC_URL=${{ env.SUT_JSONRPC_URL }} SUT_REST_URL=${{ env.SUT_JSONRPC_URL }} mvn -B quarkus:dev & #SUT_JSONRPC_URL already set
working-directory: tck
- name: Wait for SUT to start
run: |
Expand Down Expand Up @@ -93,5 +95,5 @@ jobs:

- name: Run TCK
run: |
./run_tck.py --sut-url ${{ env.SUT_JSONRPC_URL }} --category all --transports jsonrpc,grpc --compliance-report report.json
./run_tck.py --sut-url ${{ env.SUT_JSONRPC_URL }} --category all --transports jsonrpc,grpc,rest --compliance-report report.json
working-directory: tck/a2a-tck
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,3 @@ nb-configuration.xml
# TLS Certificates
.certs/
nbproject/

57 changes: 53 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,18 @@ To use the reference implementation with the gRPC protocol, add the following de
Note that you can add more than one of the above dependencies to your project depending on the transports
you'd like to support.

Support for the HTTP+JSON/REST transport will be coming soon.
To use the reference implementation with the HTTP+JSON/REST protocol, add the following dependency to your project:

> *⚠️ The `io.github.a2asdk` `groupId` below is temporary and will likely change for future releases.*

```xml
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-reference-rest</artifactId>
<!-- Use a released version from https://github.com/a2aproject/a2a-java/releases -->
<version>${io.a2a.sdk.version}</version>
</dependency>
```

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

Expand Down Expand Up @@ -117,7 +128,7 @@ public class WeatherAgentCardProducer {
.tags(Collections.singletonList("weather"))
.examples(List.of("weather in LA, CA"))
.build()))
.protocolVersion("0.2.5")
.protocolVersion("0.3.0")
.build();
}
}
Expand Down Expand Up @@ -247,7 +258,7 @@ By default, the sdk-client is coming with the JSONRPC transport dependency. Desp
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).


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

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

Support for the HTTP+JSON/REST transport will be coming soon.

If you want to use the HTTP+JSON/REST transport, you'll need to add a relevant dependency:

----
> *⚠️ The `io.github.a2asdk` `groupId` below is temporary and will likely change for future releases.*
----

```xml
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-client-transport-rest</artifactId>
<!-- Use a released version from https://github.com/a2aproject/a2a-java/releases -->
<version>${io.a2a.sdk.version}</version>
</dependency>
```

### Sample Usage

Expand Down Expand Up @@ -360,6 +385,29 @@ Client client = Client
.build();
```


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

For the HTTP+JSON/REST transport, if you'd like to use the default `JdkA2AHttpClient`, provide a `RestTransportConfig` created with its default constructor.

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

```java
// Create a custom HTTP client
A2AHttpClient customHttpClient = ...

// Configure the client settings
ClientConfig clientConfig = new ClientConfig.Builder()
.setAcceptedOutputModes(List.of("text"))
.build();

Client client = Client
.builder(agentCard)
.clientConfig(clientConfig)
.withTransport(RestTransport.class, new RestTransportConfig(customHttpClient))
.build();
```

##### Multiple Transport Configurations

You can specify configuration for multiple transports, the appropriate configuration
Expand All @@ -371,6 +419,7 @@ Client client = Client
.builder(agentCard)
.withTransport(GrpcTransport.class, new GrpcTransportConfig(channelFactory))
.withTransport(JSONRPCTransport.class, new JSONRPCTransportConfig())
.withTransport(RestTransport.class, new RestTransportConfig())
.build();
```

Expand Down
10 changes: 10 additions & 0 deletions client/base/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@
<artifactId>a2a-java-sdk-client-transport-grpc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-sdk-client-transport-rest</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-sdk-common</artifactId>
Expand All @@ -54,6 +59,11 @@
<artifactId>mockserver-netty</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
8 changes: 8 additions & 0 deletions client/transport/grpc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-sdk-client-transport-spi</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import static io.a2a.client.transport.jsonrpc.JsonMessages.AGENT_CARD;
import static io.a2a.client.transport.jsonrpc.JsonMessages.AGENT_CARD_SUPPORTS_EXTENDED;
import static io.a2a.client.transport.jsonrpc.JsonMessages.AUTHENTICATION_EXTENDED_AGENT_CARD;
import static io.a2a.client.transport.jsonrpc.JsonMessages.CANCEL_TASK_TEST_REQUEST;
import static io.a2a.client.transport.jsonrpc.JsonMessages.CANCEL_TASK_TEST_RESPONSE;
import static io.a2a.client.transport.jsonrpc.JsonMessages.GET_AUTHENTICATED_EXTENDED_AGENT_CARD_REQUEST;
Expand Down Expand Up @@ -50,7 +49,6 @@
import io.a2a.spec.FilePart;
import io.a2a.spec.FileWithBytes;
import io.a2a.spec.FileWithUri;
import io.a2a.spec.GetAuthenticatedExtendedCardResponse;
import io.a2a.spec.GetTaskPushNotificationConfigParams;
import io.a2a.spec.Message;
import io.a2a.spec.MessageSendConfiguration;
Expand Down
62 changes: 62 additions & 0 deletions client/transport/rest/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-parent</artifactId>
<version>0.3.0.Beta1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<artifactId>a2a-java-sdk-client-transport-rest</artifactId>
<packaging>jar</packaging>

<name>Java SDK A2A Client Transport: JSON+HTTP/REST</name>
<description>Java SDK for the Agent2Agent Protocol (A2A) - JSON+HTTP/REST Client Transport</description>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-sdk-common</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-sdk-spec</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-sdk-spec-grpc</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-sdk-client-transport-spi</artifactId>
</dependency>
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-http-client</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package io.a2a.client.transport.rest;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import io.a2a.client.http.A2AHttpResponse;
import io.a2a.spec.A2AClientException;
import io.a2a.spec.AuthenticatedExtendedCardNotConfiguredError;
import io.a2a.spec.ContentTypeNotSupportedError;
import io.a2a.spec.InternalError;
import io.a2a.spec.InvalidAgentResponseError;
import io.a2a.spec.InvalidParamsError;
import io.a2a.spec.InvalidRequestError;
import io.a2a.spec.JSONParseError;
import io.a2a.spec.MethodNotFoundError;
import io.a2a.spec.PushNotificationNotSupportedError;
import io.a2a.spec.TaskNotCancelableError;
import io.a2a.spec.TaskNotFoundError;
import io.a2a.spec.UnsupportedOperationError;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Utility class to A2AHttpResponse to appropriate A2A error types
*/
public class RestErrorMapper {

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().registerModule(new JavaTimeModule());

public static A2AClientException mapRestError(A2AHttpResponse response) {
return RestErrorMapper.mapRestError(response.body(), response.status());
}

public static A2AClientException mapRestError(String body, int code) {
try {
if (body != null && !body.isBlank()) {
JsonNode node = OBJECT_MAPPER.readTree(body);
String className = node.findValue("error").asText();
String errorMessage = node.findValue("message").asText();
return mapRestError(className, errorMessage, code);
}
return mapRestError("", "", code);
} catch (JsonProcessingException ex) {
Logger.getLogger(RestErrorMapper.class.getName()).log(Level.SEVERE, null, ex);
return new A2AClientException("Failed to parse error response: " + ex.getMessage());
}
}

public static A2AClientException mapRestError(String className, String errorMessage, int code) {
switch (className) {
case "io.a2a.spec.TaskNotFoundError":
return new A2AClientException(errorMessage, new TaskNotFoundError());
case "io.a2a.spec.AuthenticatedExtendedCardNotConfiguredError":
return new A2AClientException(errorMessage, new AuthenticatedExtendedCardNotConfiguredError());
case "io.a2a.spec.ContentTypeNotSupportedError":
return new A2AClientException(errorMessage, new ContentTypeNotSupportedError(null, null, errorMessage));
case "io.a2a.spec.InternalError":
return new A2AClientException(errorMessage, new InternalError(errorMessage));
case "io.a2a.spec.InvalidAgentResponseError":
return new A2AClientException(errorMessage, new InvalidAgentResponseError(null, null, errorMessage));
case "io.a2a.spec.InvalidParamsError":
return new A2AClientException(errorMessage, new InvalidParamsError());
case "io.a2a.spec.InvalidRequestError":
return new A2AClientException(errorMessage, new InvalidRequestError());
case "io.a2a.spec.JSONParseError":
return new A2AClientException(errorMessage, new JSONParseError());
case "io.a2a.spec.MethodNotFoundError":
return new A2AClientException(errorMessage, new MethodNotFoundError());
case "io.a2a.spec.PushNotificationNotSupportedError":
return new A2AClientException(errorMessage, new PushNotificationNotSupportedError());
case "io.a2a.spec.TaskNotCancelableError":
return new A2AClientException(errorMessage, new TaskNotCancelableError());
case "io.a2a.spec.UnsupportedOperationError":
return new A2AClientException(errorMessage, new UnsupportedOperationError());
default:
return new A2AClientException(errorMessage);
}
}
}
Loading