Skip to content

Commit 1d36bdc

Browse files
committed
bug: Encapsulate the payload of Push notifications
The payload of push notifications is encapsulated by the `kind` of payload as specified in [§ 4.3.3. Push Notification Payload](https://a2a-protocol.org/latest/specification/#433-push-notification-payload) This commit only works for `task` payload and will required more work to support additional payloads (tracked by #490). This fixes #491 Signed-off-by: Jeff Mesnil <[email protected]>
1 parent 686e40a commit 1d36bdc

File tree

4 files changed

+38
-12
lines changed

4 files changed

+38
-12
lines changed

server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ private boolean dispatchNotification(Task task, PushNotificationConfig pushInfo)
8080

8181
String body;
8282
try {
83-
body = Utils.OBJECT_MAPPER.writeValueAsString(task);
83+
body = Utils.marshalFrom(task);
8484
} catch (JsonProcessingException e) {
8585
LOGGER.debug("Error writing value as string: {}", e.getMessage(), e);
8686
return false;

server-common/src/test/java/io/a2a/server/requesthandlers/AbstractA2ARequestHandlerTest.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
import java.util.ArrayList;
77
import java.util.Collections;
88
import java.util.List;
9+
import java.util.Map;
910
import java.util.Properties;
1011
import java.util.concurrent.CompletableFuture;
1112
import java.util.concurrent.CountDownLatch;
1213
import java.util.concurrent.Executor;
1314
import java.util.concurrent.Executors;
1415
import java.util.function.Consumer;
1516

16-
import jakarta.enterprise.context.Dependent;
17-
17+
import com.fasterxml.jackson.databind.JsonNode;
1818
import io.a2a.client.http.A2AHttpClient;
1919
import io.a2a.client.http.A2AHttpResponse;
2020
import io.a2a.server.agentexecution.AgentExecutor;
@@ -30,17 +30,16 @@
3030
import io.a2a.server.tasks.TaskStore;
3131
import io.a2a.spec.AgentCapabilities;
3232
import io.a2a.spec.AgentCard;
33+
import io.a2a.spec.Event;
3334
import io.a2a.spec.JSONRPCError;
3435
import io.a2a.spec.Message;
3536
import io.a2a.spec.Task;
3637
import io.a2a.spec.TaskState;
3738
import io.a2a.spec.TaskStatus;
38-
import io.a2a.spec.Event;
3939
import io.a2a.spec.TextPart;
4040
import io.a2a.util.Utils;
4141
import io.quarkus.arc.profile.IfBuildProfile;
42-
import java.util.Map;
43-
42+
import jakarta.enterprise.context.Dependent;
4443
import org.junit.jupiter.api.AfterEach;
4544
import org.junit.jupiter.api.Assertions;
4645
import org.junit.jupiter.api.BeforeEach;
@@ -199,7 +198,12 @@ public PostBuilder body(String body) {
199198

200199
@Override
201200
public A2AHttpResponse post() throws IOException, InterruptedException {
202-
tasks.add(Utils.OBJECT_MAPPER.readValue(body, Task.TYPE_REFERENCE));
201+
JsonNode root = Utils.OBJECT_MAPPER.readTree(body);
202+
// This assumes there is always one field in the outer JSON object.
203+
// This will need to be updated per the FIXME in #490
204+
JsonNode taskNode = root.elements().next();
205+
Task task = Utils.OBJECT_MAPPER.treeToValue(taskNode, Task.TYPE_REFERENCE);
206+
tasks.add(task);
203207
try {
204208
return new A2AHttpResponse() {
205209
@Override

server-common/src/test/java/io/a2a/server/tasks/PushNotificationSenderTest.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@
1616
import java.util.concurrent.TimeUnit;
1717
import java.util.function.Consumer;
1818

19-
import org.junit.jupiter.api.BeforeEach;
20-
import org.junit.jupiter.api.Test;
21-
19+
import com.fasterxml.jackson.databind.JsonNode;
2220
import io.a2a.client.http.A2AHttpClient;
2321
import io.a2a.client.http.A2AHttpResponse;
2422
import io.a2a.common.A2AHeaders;
25-
import io.a2a.util.Utils;
2623
import io.a2a.spec.PushNotificationConfig;
2724
import io.a2a.spec.Task;
2825
import io.a2a.spec.TaskState;
2926
import io.a2a.spec.TaskStatus;
27+
import io.a2a.util.Utils;
28+
import org.junit.jupiter.api.BeforeEach;
29+
import org.junit.jupiter.api.Test;
3030

3131
public class PushNotificationSenderTest {
3232

@@ -77,7 +77,11 @@ public A2AHttpResponse post() throws IOException, InterruptedException {
7777
}
7878

7979
try {
80-
Task task = Utils.OBJECT_MAPPER.readValue(body, Task.TYPE_REFERENCE);
80+
JsonNode root = Utils.OBJECT_MAPPER.readTree(body);
81+
// This assumes there is always one field in the outer JSON object.
82+
// This will need to be updated per the FIXME in #490
83+
JsonNode taskNode = root.elements().next();
84+
Task task = Utils.OBJECT_MAPPER.treeToValue(taskNode, Task.TYPE_REFERENCE);
8185
tasks.add(task);
8286
urls.add(url);
8387
headers.add(new java.util.HashMap<>(requestHeaders));

spec/src/main/java/io/a2a/util/Utils.java

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

33
import java.util.ArrayList;
44
import java.util.List;
5+
import java.util.Map;
56
import java.util.logging.Logger;
67

78
import com.fasterxml.jackson.core.JsonProcessingException;
89
import com.fasterxml.jackson.core.type.TypeReference;
910
import com.fasterxml.jackson.databind.ObjectMapper;
11+
import com.fasterxml.jackson.databind.node.ObjectNode;
1012
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
1113

1214
import io.a2a.spec.Artifact;
1315
import io.a2a.spec.Part;
16+
import io.a2a.spec.StreamingEventKind;
1417
import io.a2a.spec.Task;
1518
import io.a2a.spec.TaskArtifactUpdateEvent;
1619

@@ -69,6 +72,21 @@ public static <T> T unmarshalFrom(String data, TypeReference<T> typeRef) throws
6972
return OBJECT_MAPPER.readValue(data, typeRef);
7073
}
7174

75+
/**
76+
* Serializes a StreamingEventKind in a JSON string
77+
* <p>
78+
* The StreamingEventKind object is wrapped in a JSON field named from its kind (e.g. "task") before
79+
* it is serialized
80+
*
81+
* @param kind the StreamingEventKind to deserialize
82+
* @return a JSON String
83+
* @throws JsonProcessingException if JSON parsing fails
84+
*/
85+
public static String marshalFrom(StreamingEventKind kind) throws JsonProcessingException {
86+
Map<String, StreamingEventKind> wrapper = Map.of(kind.getKind(), kind);
87+
return OBJECT_MAPPER.writeValueAsString(wrapper);
88+
}
89+
7290
/**
7391
* Returns the provided value if non-null, otherwise returns the default value.
7492
* <p>

0 commit comments

Comments
 (0)