Skip to content

Commit c9c34a1

Browse files
committed
Add details about the A2A server implementation to the README and update the project version to 0.2.3-SNAPSHOT
1 parent a486a1f commit c9c34a1

File tree

9 files changed

+252
-12
lines changed

9 files changed

+252
-12
lines changed

README.md

Lines changed: 244 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,250 @@
1-
# Java A2A SDK (WIP)
1+
# A2A Java SDK
22

3-
This project (currently WIP) will provide a Java SDK implementation of Google's [Agent2Agent protocol (A2A)](https://google.github.io/A2A/).
3+
<html>
4+
<h3 align="center">A Java library that helps run agentic applications as A2AServers following Google's <a href="https://google-a2a.github.io/A2A">Agent2Agent (A2A) Protocol</a>.</h3>
5+
</html>
46

5-
## Specification
7+
## Installation
68

7-
The majority of the classes required by the specification can currently be found in the [src/main/java/io/a2a/spec](https://github.com/fjuma/a2a-java-sdk/tree/main/src/main/java/io/a2a/spec) directory.
9+
You can build the A2A Java SDK using `mvn`:
10+
11+
```bash
12+
mvn clean install
13+
```
14+
15+
## Examples
16+
17+
You can find an example of how to use the A2A Java SDK [here](https://github.com/fjuma/a2a-samples/tree/java-sdk-example/samples/multi_language/python_and_java_multiagent/weather_agent).
18+
19+
More examples will be added soon.
20+
21+
## A2A Server
22+
23+
The A2A Java SDK provides a Java server implementation of the [Agent2Agent (A2A) Protocol](https://google-a2a.github.io/A2A). To run your agentic Java application as an A2A server, simply follow the steps below.
24+
25+
- [Add the A2A Java SDK Core Maven dependency to your project](#1-add-the-a2a-java-sdk-core-maven-dependency-to-your-project)
26+
- [Add a class that creates an A2A Agent Card](#2-add-a-class-that-creates-an-a2a-agent-card)
27+
- [Add a class that creates an A2A Agent Executor](#3-add-a-class-that-creates-an-a2a-agent-executor)
28+
- [Add an A2A Java SDK Server Maven dependency to your project](#4-add-an-a2a-java-sdk-server-maven-dependency-to-your-project)
29+
30+
### 1. Add the A2A Java SDK Core Maven dependency to your project
31+
32+
```xml
33+
<dependency>
34+
<groupId>io.a2a.sdk</groupId>
35+
<artifactId>a2a-java-sdk-core</artifactId>
36+
<version>${io.a2a.sdk.version}</version>
37+
</dependency>
38+
```
39+
40+
### 2. Add a class that creates an A2A Agent Card
41+
42+
```java
43+
import io.a2a.spec.AgentCapabilities;
44+
import io.a2a.spec.AgentCard;
45+
import io.a2a.spec.AgentSkill;
46+
import io.a2a.spec.PublicAgentCard;
47+
...
48+
49+
@ApplicationScoped
50+
public class WeatherAgentCardProducer {
51+
52+
@Produces
53+
@PublicAgentCard
54+
public AgentCard agentCard() {
55+
return new AgentCard.Builder()
56+
.name("Weather Agent")
57+
.description("Helps with weather")
58+
.url("http://localhost:10001")
59+
.version("1.0.0")
60+
.capabilities(new AgentCapabilities.Builder()
61+
.streaming(true)
62+
.pushNotifications(false)
63+
.stateTransitionHistory(false)
64+
.build())
65+
.defaultInputModes(Collections.singletonList("text"))
66+
.defaultOutputModes(Collections.singletonList("text"))
67+
.skills(Collections.singletonList(new AgentSkill.Builder()
68+
.id("weather_search")
69+
.name("Search weather")
70+
.description("Helps with weather in city, or states")
71+
.tags(Collections.singletonList("weather"))
72+
.examples(List.of("weather in LA, CA"))
73+
.build()))
74+
.build();
75+
}
76+
}
77+
```
78+
79+
### 3. Add a class that creates an A2A Agent Executor
80+
81+
```java
82+
import io.a2a.server.agentexecution.AgentExecutor;
83+
import io.a2a.server.agentexecution.RequestContext;
84+
import io.a2a.server.events.EventQueue;
85+
import io.a2a.server.tasks.TaskUpdater;
86+
import io.a2a.spec.JSONRPCError;
87+
import io.a2a.spec.Message;
88+
import io.a2a.spec.Part;
89+
import io.a2a.spec.TextPart;
90+
...
91+
92+
@ApplicationScoped
93+
public class WeatherAgentExecutorProducer {
94+
95+
@Inject
96+
WeatherAgent weatherAgent;
97+
98+
// Thread pool for background execution
99+
private final Executor taskExecutor = Executors.newCachedThreadPool();
100+
101+
// Track active sessions for potential cancellation
102+
private final ConcurrentHashMap<String, CompletableFuture<Void>> activeSessions = new ConcurrentHashMap<>();
103+
104+
@Produces
105+
public AgentExecutor agentExecutor() {
106+
return new WeatherAgentExecutor(weatherAgent, taskExecutor, activeSessions);
107+
}
108+
109+
private static class WeatherAgentExecutor implements AgentExecutor {
110+
111+
private final WeatherAgent weatherAgent;
112+
private final Executor taskExecutor;
113+
private final ConcurrentHashMap<String, CompletableFuture<Void>> activeSessions;
114+
115+
public WeatherAgentExecutor(WeatherAgent weatherAgent, Executor taskExecutor,
116+
ConcurrentHashMap<String, CompletableFuture<Void>> activeSessions) {
117+
this.weatherAgent = weatherAgent;
118+
this.taskExecutor = taskExecutor;
119+
this.activeSessions = activeSessions;
120+
}
121+
122+
@Override
123+
public void execute(RequestContext context, EventQueue eventQueue) throws JSONRPCError {
124+
TaskUpdater updater = new TaskUpdater(context, eventQueue);
125+
126+
// Immediately notify that the task is submitted
127+
if (context.getTask() == null) {
128+
updater.submit();
129+
}
130+
updater.startWork();
131+
132+
CompletableFuture<Void> taskFuture = CompletableFuture.runAsync(() -> {
133+
try {
134+
processRequest(context, updater);
135+
} catch (Exception e) {
136+
System.err.println("Weather agent execution failed: " + e.getMessage());
137+
e.printStackTrace();
138+
}
139+
}, taskExecutor);
140+
141+
// Track the active session
142+
activeSessions.put(context.getContextId(), taskFuture);
143+
taskFuture.join();
144+
}
145+
146+
private void processRequest(RequestContext context, TaskUpdater updater) {
147+
String contextId = context.getContextId();
148+
149+
try {
150+
// Check for interruption before starting work
151+
if (Thread.currentThread().isInterrupted()) {
152+
return;
153+
}
154+
155+
// Extract text from message parts
156+
String userMessage = extractTextFromMessage(context.getMessage());
157+
158+
// Call the weather agent with the user's message
159+
String response = weatherAgent.chat(userMessage);
160+
161+
// Check for interruption after agent call
162+
if (Thread.currentThread().isInterrupted()) {
163+
return;
164+
}
165+
166+
// Create response part
167+
TextPart responsePart = new TextPart(response, null);
168+
List<Part<?>> parts = List.of(responsePart);
169+
170+
// Add response as artifact and complete the task
171+
updater.addArtifact(parts, null, null, null);
172+
updater.complete();
173+
174+
} catch (Exception e) {
175+
// Task failed
176+
System.err.println("Weather agent task failed: " + contextId);
177+
e.printStackTrace();
178+
179+
// Mark task as failed using TaskUpdater
180+
updater.fail();
181+
182+
} finally {
183+
// Clean up active session
184+
activeSessions.remove(contextId);
185+
}
186+
}
187+
188+
private String extractTextFromMessage(Message message) {
189+
StringBuilder textBuilder = new StringBuilder();
190+
191+
if (message.getParts() != null) {
192+
for (Part part : message.getParts()) {
193+
if (part instanceof TextPart textPart) {
194+
textBuilder.append(textPart.getText());
195+
}
196+
}
197+
}
198+
199+
return textBuilder.toString();
200+
}
201+
202+
@Override
203+
public void cancel(RequestContext context, EventQueue eventQueue) throws JSONRPCError {
204+
String contextId = context.getContextId();
205+
CompletableFuture<Void> taskFuture = activeSessions.get(contextId);
206+
207+
if (taskFuture != null) {
208+
// Cancel the future
209+
taskFuture.cancel(true);
210+
activeSessions.remove(contextId);
211+
212+
// Update task status to cancelled using TaskUpdater
213+
TaskUpdater updater = new TaskUpdater(context, eventQueue);
214+
updater.cancel();
215+
} else {
216+
System.out.println("Cancellation requested for inactive weather session: " + contextId);
217+
}
218+
}
219+
}
220+
}
221+
```
222+
223+
### 4. Add an A2A Java SDK Server Maven dependency to your project
224+
225+
Adding a dependency on an A2A Java SDK Server will allow you to run your agentic Java application as an A2A server.
226+
227+
The A2A Java SDK provides two A2A server endpoint implementations, one based on Jakarta REST (`a2a-java-sdk-server-jakarta`) and one based on Quarkus Reactive Routes (`a2a-java-sdk-server-quarkus`). You can choose the one that best fits your application.
228+
229+
Add **one** of the following dependencies to your project:
230+
231+
```xml
232+
<dependency>
233+
<groupId>io.a2a.sdk</groupId>
234+
<artifactId>a2a-java-sdk-server-jakarta</artifactId>
235+
<version>${io.a2a.sdk.version}</version>
236+
</dependency>
237+
```
238+
239+
OR
240+
241+
```xml
242+
<dependency>
243+
<groupId>io.a2a.sdk</groupId>
244+
<artifactId>a2a-java-sdk-server-quarkus</artifactId>
245+
<version>${io.a2a.sdk.version}</version>
246+
</dependency>
247+
```
8248

9249
## Client
10250

core/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>io.a2a.sdk</groupId>
99
<artifactId>a2a-java-sdk-parent</artifactId>
10-
<version>1.0.0-SNAPSHOT</version>
10+
<version>0.2.3-SNAPSHOT</version>
1111
</parent>
1212
<artifactId>a2a-java-sdk-core</artifactId>
1313

examples/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>io.a2a.sdk</groupId>
99
<artifactId>a2a-java-sdk-parent</artifactId>
10-
<version>1.0.0-SNAPSHOT</version>
10+
<version>0.2.3-SNAPSHOT</version>
1111
</parent>
1212

1313
<artifactId>a2a-java-sdk-examples</artifactId>

examples/src/main/java/io/a2a/examples/helloworld/client/HelloWorldRunner.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
///usr/bin/env jbang "$0" "$@" ; exit $?
22
//REPOS file://~/.m2/repository/
3-
//DEPS io.a2a.sdk:a2a-java-sdk:1.0.0-SNAPSHOT
3+
//DEPS io.a2a.sdk:a2a-java-sdk:0.2.3-SNAPSHOT
44
//SOURCES HelloWorldClient.java
55

66
/**

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>io.a2a.sdk</groupId>
88
<artifactId>a2a-java-sdk-parent</artifactId>
9-
<version>1.0.0-SNAPSHOT</version>
9+
<version>0.2.3-SNAPSHOT</version>
1010

1111
<packaging>pom</packaging>
1212

sdk-jakarta/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>io.a2a.sdk</groupId>
99
<artifactId>a2a-java-sdk-parent</artifactId>
10-
<version>1.0.0-SNAPSHOT</version>
10+
<version>0.2.3-SNAPSHOT</version>
1111
</parent>
1212
<artifactId>a2a-java-sdk-server-jakarta</artifactId>
1313

sdk-quarkus/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>io.a2a.sdk</groupId>
99
<artifactId>a2a-java-sdk-parent</artifactId>
10-
<version>1.0.0-SNAPSHOT</version>
10+
<version>0.2.3-SNAPSHOT</version>
1111
</parent>
1212
<artifactId>a2a-java-sdk-server-quarkus</artifactId>
1313

tck/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>io.a2a.sdk</groupId>
99
<artifactId>a2a-java-sdk-parent</artifactId>
10-
<version>1.0.0-SNAPSHOT</version>
10+
<version>0.2.3-SNAPSHOT</version>
1111
</parent>
1212

1313
<artifactId>a2a-tck-server</artifactId>

tests/server-common/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>io.a2a.sdk</groupId>
99
<artifactId>a2a-java-sdk-parent</artifactId>
10-
<version>1.0.0-SNAPSHOT</version>
10+
<version>0.2.3-SNAPSHOT</version>
1111
<relativePath>../../pom.xml</relativePath>
1212
</parent>
1313
<artifactId>a2a-java-sdk-tests-server-common</artifactId>

0 commit comments

Comments
 (0)