Skip to content

Commit 7a6b6f3

Browse files
committed
WIP
Signed-off-by: Emmanuel Hugonnet <[email protected]>
1 parent da7ee20 commit 7a6b6f3

File tree

13 files changed

+700
-16
lines changed

13 files changed

+700
-16
lines changed

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ public MessageEvent(Message message) {
2121
public Message getMessage() {
2222
return message;
2323
}
24-
}
25-
2624

25+
@Override
26+
public String toString() {
27+
String messageAsString = "{"
28+
+ "role=" + message.getRole()
29+
+ ", parts=" + message.getParts()
30+
+ ", messageId=" + message.getMessageId()
31+
+ ", contextId=" + message.getContextId()
32+
+ ", taskId=" + message.getTaskId()
33+
+ ", metadata=" + message.getMetadata()
34+
+ ", kind=" + message.getKind()
35+
+ ", referenceTaskIds=" + message.getReferenceTaskIds()
36+
+ ", extensions=" + message.getExtensions() + '}';
37+
return "MessageEvent{" + "message=" + messageAsString + '}';
38+
}
39+
}

examples/helloworld/client/pom.xml

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

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

15-
<name>Java SDK A2A Examples</name>
15+
<name>Java SDK A2A HelloWorld Example - Client</name>
1616
<description>Examples for the Java SDK for the Agent2Agent Protocol (A2A)</description>
1717

1818
<dependencies>

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,18 @@
1212
import io.a2a.A2A;
1313

1414
import io.a2a.client.Client;
15-
import io.a2a.client.ClientBuilder;
1615
import io.a2a.client.ClientEvent;
1716
import io.a2a.client.MessageEvent;
1817
import io.a2a.client.http.A2ACardResolver;
1918
import io.a2a.client.transport.jsonrpc.JSONRPCTransport;
2019
import io.a2a.client.transport.jsonrpc.JSONRPCTransportConfig;
20+
import io.a2a.client.transport.spi.interceptors.ClientCallContext;
21+
import io.a2a.common.A2AHeaders;
2122
import io.a2a.spec.AgentCard;
2223
import io.a2a.spec.Message;
2324
import io.a2a.spec.Part;
2425
import io.a2a.spec.TextPart;
26+
import java.util.Collections;
2527

2628
/**
2729
* A simple example of using the A2A Java SDK to communicate with an A2A server.
@@ -61,6 +63,7 @@ public static void main(String[] args) {
6163
List<BiConsumer<ClientEvent, AgentCard>> consumers = new ArrayList<>();
6264
consumers.add((event, agentCard) -> {
6365
if (event instanceof MessageEvent messageEvent) {
66+
System.out.println("Received client MessageEvent: " + messageEvent);
6467
Message responseMessage = messageEvent.getMessage();
6568
StringBuilder textBuilder = new StringBuilder();
6669
if (responseMessage.getParts() != null) {
@@ -89,11 +92,11 @@ public static void main(String[] args) {
8992
.streamingErrorHandler(streamingErrorHandler)
9093
.withTransport(JSONRPCTransport.class, new JSONRPCTransportConfig())
9194
.build();
95+
ClientCallContext clientContext = new ClientCallContext(Collections.emptyMap(), Map.of(A2AHeaders.X_A2A_EXTENSIONS, "https://github.com/a2aproject/a2a-samples/extensions/timestamp/v1"));
9296

9397
Message message = A2A.toUserMessage(MESSAGE_TEXT); // the message ID will be automatically generated for you
94-
9598
System.out.println("Sending message: " + MESSAGE_TEXT);
96-
client.sendMessage(message);
99+
client.sendMessage(message, clientContext);
97100
System.out.println("Message sent successfully. Responses will be handled by the configured consumers.");
98101

99102
try {

examples/helloworld/server/pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,19 @@
1212

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

15-
<name>Java SDK A2A Examples</name>
15+
<name>Java SDK A2A HelloWorld Example - Server</name>
1616
<description>Examples for the Java SDK for the Agent2Agent Protocol (A2A)</description>
1717

1818
<dependencies>
1919
<dependency>
2020
<groupId>io.github.a2asdk</groupId>
2121
<artifactId>a2a-java-sdk-client</artifactId>
2222
</dependency>
23+
<dependency>
24+
<groupId>io.github.a2asdk</groupId>
25+
<artifactId>a2a-java-timestamp-extension</artifactId>
26+
<version>${project.version}</version>
27+
</dependency>
2328
<dependency>
2429
<groupId>io.github.a2asdk</groupId>
2530
<artifactId>a2a-java-sdk-reference-jsonrpc</artifactId>

examples/helloworld/server/src/main/java/io/a2a/examples/helloworld/AgentExecutorProducer.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import io.a2a.server.agentexecution.RequestContext;
88
import io.a2a.server.events.EventQueue;
99
import io.a2a.A2A;
10+
import io.a2a.extension.timestamp.TimeStampAgentExecutorWrapper;
1011
import io.a2a.spec.JSONRPCError;
1112
import io.a2a.spec.UnsupportedOperationError;
1213

@@ -15,7 +16,7 @@ public class AgentExecutorProducer {
1516

1617
@Produces
1718
public AgentExecutor agentExecutor() {
18-
return new AgentExecutor() {
19+
return new TimeStampAgentExecutorWrapper(new AgentExecutor() {
1920
@Override
2021
public void execute(RequestContext context, EventQueue eventQueue) throws JSONRPCError {
2122
eventQueue.enqueueEvent(A2A.toAgentMessage("Hello World"));
@@ -25,6 +26,6 @@ public void execute(RequestContext context, EventQueue eventQueue) throws JSONRP
2526
public void cancel(RequestContext context, EventQueue eventQueue) throws JSONRPCError {
2627
throw new UnsupportedOperationError();
2728
}
28-
};
29+
});
2930
}
3031
}

extensions/timestamp/pom.xml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
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.4.0.Alpha1-SNAPSHOT</version>
11+
<relativePath>../../pom.xml</relativePath>
12+
</parent>
13+
14+
<artifactId>a2a-java-timestamp-extension</artifactId>
15+
<name>A2A Java SDK :: Extension :: Timestamp</name>
16+
<description>Simple Timestamp Extension</description>
17+
18+
<dependencies>
19+
<dependency>
20+
<groupId>io.github.a2asdk</groupId>
21+
<artifactId>a2a-java-sdk-server-common</artifactId>
22+
</dependency>
23+
<!-- Test dependencies -->
24+
<dependency>
25+
<groupId>org.junit.jupiter</groupId>
26+
<artifactId>junit-jupiter</artifactId>
27+
<scope>test</scope>
28+
</dependency>
29+
<dependency>
30+
<groupId>org.mockito</groupId>
31+
<artifactId>mockito-core</artifactId>
32+
<scope>test</scope>
33+
</dependency>
34+
<dependency>
35+
<groupId>org.mockito</groupId>
36+
<artifactId>mockito-junit-jupiter</artifactId>
37+
<scope>test</scope>
38+
</dependency>
39+
</dependencies>
40+
</project>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.a2a.extension.timestamp;
2+
3+
import io.a2a.server.agentexecution.AgentExecutor;
4+
import io.a2a.server.agentexecution.RequestContext;
5+
import io.a2a.server.events.EventQueue;
6+
import io.a2a.spec.JSONRPCError;
7+
import java.util.logging.Logger;
8+
9+
public class TimeStampAgentExecutorWrapper implements AgentExecutor {
10+
11+
public static final String CORE_PATH = "github.com/a2aproject/a2a-samples/extensions/timestamp/v1";
12+
public static final String URI = "https://" + CORE_PATH;
13+
public static final String TIMESTAMP_FIELD = CORE_PATH + "/timestamp";
14+
15+
private static final Logger logger = Logger.getLogger(TimeStampAgentExecutorWrapper.class.getName());
16+
private final AgentExecutor delegate;
17+
18+
public TimeStampAgentExecutorWrapper(AgentExecutor delegate) {
19+
this.delegate = delegate;
20+
}
21+
22+
@Override
23+
public void execute(RequestContext context, EventQueue eventQueue) throws JSONRPCError {
24+
if(isActivated(context)) {
25+
delegate.execute(context, new TimeStampEventQueue(eventQueue));
26+
} else {
27+
delegate.execute(context, eventQueue);
28+
}
29+
}
30+
31+
@Override
32+
public void cancel(RequestContext context, EventQueue eventQueue) throws JSONRPCError {
33+
if(isActivated(context)) {
34+
delegate.cancel(context, new TimeStampEventQueue(eventQueue));
35+
} else {
36+
delegate.cancel(context, eventQueue);
37+
}
38+
}
39+
40+
private boolean isActivated(final RequestContext context) {
41+
if (context.getCallContext().isExtensionActivated(URI)) {
42+
return true;
43+
}
44+
if (context.getCallContext().isExtensionRequested(URI)) {
45+
logger.info("Activated extension: " + URI);
46+
context.getCallContext().activateExtension(URI);
47+
return true;
48+
}
49+
return false;
50+
}
51+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package io.a2a.extension.timestamp;
2+
3+
import static io.a2a.extension.timestamp.TimeStampAgentExecutorWrapper.TIMESTAMP_FIELD;
4+
import static io.a2a.extension.timestamp.TimeStampAgentExecutorWrapper.URI;
5+
6+
import io.a2a.server.events.EventQueue;
7+
import io.a2a.spec.Artifact;
8+
import io.a2a.spec.Event;
9+
import io.a2a.spec.Message;
10+
import io.a2a.spec.Task;
11+
import io.a2a.spec.TaskArtifactUpdateEvent;
12+
import io.a2a.spec.TaskStatusUpdateEvent;
13+
import java.time.OffsetDateTime;
14+
import java.time.ZoneOffset;
15+
import java.util.ArrayList;
16+
import java.util.HashMap;
17+
import java.util.List;
18+
import java.util.Map;
19+
20+
public class TimeStampEventQueue extends EventQueue {
21+
22+
private final EventQueue delegate;
23+
24+
public TimeStampEventQueue(EventQueue delegate) {
25+
this.delegate = delegate;
26+
}
27+
28+
@Override
29+
public void enqueueEvent(Event event) {
30+
this.delegate.enqueueEvent(timestampEvent(event));
31+
}
32+
33+
private Event timestampEvent(Event event) {
34+
if (event instanceof Message message) {
35+
return processMessage(message);
36+
}
37+
if (event instanceof TaskArtifactUpdateEvent taskArtifactUpdateEvent) {
38+
return processTaskArtifactUpdateEvent(taskArtifactUpdateEvent);
39+
}
40+
if (event instanceof TaskStatusUpdateEvent taskStatusUpdateEvent) {
41+
return processTaskStatusUpdateEvent(taskStatusUpdateEvent);
42+
}
43+
if (event instanceof Task task) {
44+
return processTask(task);
45+
}
46+
return event;
47+
}
48+
49+
private Message processMessage(Message message) {
50+
Map<String, Object> metadata = message.getMetadata() == null ? new HashMap<>() : new HashMap<>(message.getMetadata());
51+
if (!metadata.containsKey(TIMESTAMP_FIELD)) {
52+
metadata.put(TIMESTAMP_FIELD, OffsetDateTime.now(ZoneOffset.UTC));
53+
}
54+
List<String> extensions = message.getExtensions() == null ? new ArrayList<>() : new ArrayList<>(message.getExtensions());
55+
if (!extensions.contains(URI)) {
56+
extensions.add(URI);
57+
}
58+
return new Message.Builder(message).metadata(metadata).extensions(extensions).build();
59+
}
60+
61+
private Task processTask(Task task) {
62+
Map<String, Object> metadata = task.getMetadata() == null ? new HashMap<>() : new HashMap<>(task.getMetadata());
63+
if (!metadata.containsKey(TIMESTAMP_FIELD)) {
64+
metadata.put(TIMESTAMP_FIELD, OffsetDateTime.now(ZoneOffset.UTC));
65+
}
66+
List<Artifact> artifacts = new ArrayList<>();
67+
for (Artifact artifact : task.getArtifacts()) {
68+
artifacts.add(processArtifact(artifact));
69+
}
70+
return new Task.Builder(task).artifacts(artifacts).metadata(metadata).build();
71+
}
72+
73+
private TaskStatusUpdateEvent processTaskStatusUpdateEvent(TaskStatusUpdateEvent taskStatusUpdateEvent) {
74+
Map<String, Object> metadata = taskStatusUpdateEvent.getMetadata() == null ? new HashMap<>() : new HashMap<>(taskStatusUpdateEvent.getMetadata());
75+
if (!metadata.containsKey(TIMESTAMP_FIELD)) {
76+
metadata.put(TIMESTAMP_FIELD, OffsetDateTime.now(ZoneOffset.UTC));
77+
}
78+
return new TaskStatusUpdateEvent.Builder(taskStatusUpdateEvent).metadata(metadata).build();
79+
}
80+
81+
private TaskArtifactUpdateEvent processTaskArtifactUpdateEvent(TaskArtifactUpdateEvent taskArtifactUpdateEvent) {
82+
Map<String, Object> metadata = taskArtifactUpdateEvent.getMetadata() == null ? new HashMap<>() : new HashMap<>(taskArtifactUpdateEvent.getMetadata());
83+
if (!metadata.containsKey(TIMESTAMP_FIELD)) {
84+
metadata.put(TIMESTAMP_FIELD, OffsetDateTime.now(ZoneOffset.UTC));
85+
}
86+
if (taskArtifactUpdateEvent.getArtifact() != null) {
87+
return new TaskArtifactUpdateEvent.Builder(taskArtifactUpdateEvent).artifact(processArtifact(taskArtifactUpdateEvent.getArtifact())).metadata(metadata).build();
88+
}
89+
return new TaskArtifactUpdateEvent.Builder(taskArtifactUpdateEvent).metadata(metadata).build();
90+
}
91+
92+
private Artifact processArtifact(Artifact artifact) {
93+
Map<String, Object> metadata = artifact.metadata() == null ? new HashMap<>() : new HashMap<>(artifact.metadata());
94+
if (!metadata.containsKey(TIMESTAMP_FIELD)) {
95+
metadata.put(TIMESTAMP_FIELD, OffsetDateTime.now(ZoneOffset.UTC));
96+
}
97+
List<String> extensions = artifact.extensions() == null ? new ArrayList<>() : new ArrayList<>(artifact.extensions());
98+
if (!extensions.contains(URI)) {
99+
extensions.add(URI);
100+
}
101+
return new Artifact.Builder(artifact).metadata(metadata).extensions(extensions).build();
102+
}
103+
104+
@Override
105+
public void awaitQueuePollerStart() throws InterruptedException {
106+
this.delegate.awaitQueuePollerStart();
107+
}
108+
109+
@Override
110+
public void close() {
111+
this.delegate.close();
112+
}
113+
114+
@Override
115+
public void close(boolean immediate) {
116+
this.delegate.close(immediate);
117+
}
118+
119+
@Override
120+
public void close(boolean immediate, boolean notifyParent) {
121+
this.delegate.close(immediate, notifyParent);
122+
}
123+
124+
@Override
125+
public void signalQueuePollerStarted() {
126+
this.delegate.signalQueuePollerStarted();
127+
}
128+
129+
@Override
130+
public EventQueue tap() {
131+
return this;
132+
}
133+
}

0 commit comments

Comments
 (0)