Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ public void testTaskSerialization() throws JsonProcessingException {
.status(status)
.build();

// Test serialization as Event
String json = JsonUtil.toJson(originalTask);
// Test streaming-event serialization
String json = JsonUtil.toJsonStreamingEvent(originalTask);
assertTrue(json.contains("\"task\""), "JSON should contain task wrapper");
assertTrue(json.contains("\"id\":\"test-task-123\""), "JSON should contain task ID");

Expand Down Expand Up @@ -82,8 +82,8 @@ public void testMessageSerialization() throws JsonProcessingException {
.contextId("test-context-123")
.build();

// Test serialization as Event
String json = JsonUtil.toJson(originalMessage);
// Test streaming-event serialization
String json = JsonUtil.toJsonStreamingEvent(originalMessage);
assertTrue(json.contains("\"message\""), "JSON should contain message wrapper");
assertTrue(json.contains("\"taskId\":\"test-task-789\""), "JSON should contain task ID");

Expand Down Expand Up @@ -112,8 +112,8 @@ public void testTaskStatusUpdateEventSerialization() throws JsonProcessingExcept
.status(status)
.build();

// Test serialization as Event
String json = JsonUtil.toJson((Event) originalEvent);
// Test streaming-event serialization
String json = JsonUtil.toJsonStreamingEvent(originalEvent);
assertTrue(json.contains("\"statusUpdate\""), "JSON should contain statusUpdate wrapper");
assertTrue(json.contains("\"taskId\":\"test-task-abc\""), "JSON should contain task ID");
assertFalse(json.contains("\"final\""), "JSON should not contain final field");
Expand Down Expand Up @@ -145,8 +145,8 @@ public void testTaskArtifactUpdateEventSerialization() throws JsonProcessingExce
.artifact(artifact)
.build();

// Test serialization as Event
String json = JsonUtil.toJson((Event) originalEvent);
// Test streaming-event serialization
String json = JsonUtil.toJsonStreamingEvent(originalEvent);
assertTrue(json.contains("\"artifactUpdate\""), "JSON should contain artifactUpdate wrapper");
assertTrue(json.contains("\"taskId\":\"test-task-xyz\""), "JSON should contain task ID");
assertTrue(json.contains("\"test-artifact-123\""), "JSON should contain artifact ID");
Expand Down Expand Up @@ -328,4 +328,4 @@ public void testQueueClosedEventSerialization() throws JsonProcessingException {
QueueClosedEvent retrievedClosedEvent = (QueueClosedEvent) retrievedEvent;
assertEquals(taskId, retrievedClosedEvent.getTaskId(), "Reconstructed event should have correct task ID");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.a2aproject.sdk.spec.APIKeySecurityScheme;
import org.a2aproject.sdk.spec.ContentTypeNotSupportedError;
import org.a2aproject.sdk.spec.DataPart;
import org.a2aproject.sdk.spec.EventKind;
import org.a2aproject.sdk.spec.ExtendedAgentCardNotConfiguredError;
import org.a2aproject.sdk.spec.ExtensionSupportRequiredError;
import org.a2aproject.sdk.spec.FileContent;
Expand Down Expand Up @@ -93,7 +94,8 @@ private static GsonBuilder createBaseGsonBuilder() {
*/
public static final Gson OBJECT_MAPPER = createBaseGsonBuilder()
.registerTypeHierarchyAdapter(Part.class, new PartTypeAdapter())
.registerTypeHierarchyAdapter(StreamingEventKind.class, new StreamingEventKindTypeAdapter())
.registerTypeAdapter(EventKind.class, new EventKindTypeAdapter())
.registerTypeAdapter(StreamingEventKind.class, new StreamingEventKindTypeAdapter())
.registerTypeHierarchyAdapter(SecurityScheme.class, new SecuritySchemeTypeAdapter())
.create();

Expand Down Expand Up @@ -149,6 +151,21 @@ public static String toJson(Object data) throws JsonProcessingException {
}
}

/**
* Serializes a streaming event using the A2A streaming wrapper format.
*
* @param data the streaming event to serialize
* @return JSON string representation of the wrapped streaming event
* @throws JsonProcessingException if conversion fails
*/
public static String toJsonStreamingEvent(StreamingEventKind data) throws JsonProcessingException {
try {
return OBJECT_MAPPER.toJson(data, StreamingEventKind.class);
} catch (JsonSyntaxException e) {
throw new JsonProcessingException("Failed to generate JSON", e);
}
}

/**
* Writes a JSON-RPC {@code id} field. Handles null, String, and Number values,
* preserving fractional precision for non-integer numeric IDs.
Expand Down Expand Up @@ -623,6 +640,36 @@ private String stringOrEmpty(com.google.gson.JsonObject obj, String key) {
}
}

/**
* Gson TypeAdapter for serializing and deserializing {@link EventKind} in oneof wrapper format.
*/
static class EventKindTypeAdapter extends TypeAdapter<EventKind> {

private final Gson delegateGson = createBaseGsonBuilder()
.registerTypeHierarchyAdapter(Part.class, new PartTypeAdapter())
.create();
private final StreamingEventKindTypeAdapter streamingEventKindTypeAdapter =
new StreamingEventKindTypeAdapter();

@Override
public void write(JsonWriter out, EventKind value) throws java.io.IOException {
if (value == null) {
out.nullValue();
return;
}
out.beginObject();
out.name(value.kind());
delegateGson.toJson(value, value.getClass(), out);
out.endObject();
}

@Override
public @Nullable EventKind read(JsonReader in) throws java.io.IOException {
StreamingEventKind event = streamingEventKindTypeAdapter.read(in);
return event == null ? null : (EventKind) event;
}
}

/**
* Gson TypeAdapter for serializing and deserializing {@link StreamingEventKind} and its implementations.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ void testTaskSerialization() throws JsonProcessingException {
.build();

// Serialize as StreamingEventKind
String json = JsonUtil.toJson((StreamingEventKind) task);
String json = JsonUtil.toJsonStreamingEvent(task);

// Verify JSON contains task wrapper, not "kind" field
assertNotNull(json);
Expand Down Expand Up @@ -69,7 +69,7 @@ void testMessageSerialization() throws JsonProcessingException {
.build();

// Serialize as StreamingEventKind
String json = JsonUtil.toJson((StreamingEventKind) message);
String json = JsonUtil.toJsonStreamingEvent(message);

// Verify JSON contains message wrapper, not "kind" field
assertNotNull(json);
Expand Down Expand Up @@ -100,7 +100,7 @@ void testTaskStatusUpdateEventSerialization() throws JsonProcessingException {
.build();

// Serialize as StreamingEventKind
String json = JsonUtil.toJson((StreamingEventKind) statusEvent);
String json = JsonUtil.toJsonStreamingEvent(statusEvent);

// Verify JSON contains statusUpdate wrapper, not "kind" field
assertNotNull(json);
Expand Down Expand Up @@ -137,7 +137,7 @@ void testTaskArtifactUpdateEventSerialization() throws JsonProcessingException {
.build();

// Serialize as StreamingEventKind
String json = JsonUtil.toJson((StreamingEventKind) artifactEvent);
String json = JsonUtil.toJsonStreamingEvent(artifactEvent);

// Verify JSON contains artifactUpdate wrapper, not "kind" field
assertNotNull(json);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
import java.util.List;
import java.util.Map;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.a2aproject.sdk.jsonrpc.common.wrappers.ListTasksResponse;
import org.a2aproject.sdk.jsonrpc.common.wrappers.ListTasksResult;
import org.a2aproject.sdk.jsonrpc.common.wrappers.SendMessageResponse;
import org.a2aproject.sdk.spec.Artifact;
import org.a2aproject.sdk.spec.DataPart;
import org.a2aproject.sdk.spec.FileContent;
Expand Down Expand Up @@ -49,6 +54,8 @@ void testBasicTaskSerialization() throws JsonProcessingException {

// Verify JSON contains expected fields
assertNotNull(json);
JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject();
assertFalse(jsonObject.has(Task.STREAMING_EVENT_ID));
assertTrue(json.contains("\"id\":\"task-123\""));
assertTrue(json.contains("\"state\":\"TASK_STATE_SUBMITTED\""));

Expand All @@ -60,6 +67,60 @@ void testBasicTaskSerialization() throws JsonProcessingException {
assertEquals(task.status().state(), deserialized.status().state());
}

@Test
void testListTasksSerializationDoesNotWrapTaskItems() throws JsonProcessingException {
Task task = Task.builder()
.id("task-123")
.contextId("context-456")
.status(new TaskStatus(TaskState.TASK_STATE_SUBMITTED))
.build();

ListTasksResult result = new ListTasksResult(List.of(task));
String resultJson = JsonUtil.toJson(result);
JsonObject resultTaskJson = JsonParser.parseString(resultJson)
.getAsJsonObject()
.getAsJsonArray("tasks")
.get(0)
.getAsJsonObject();

assertFalse(resultTaskJson.has(Task.STREAMING_EVENT_ID));
assertEquals(task.id(), resultTaskJson.get("id").getAsString());
assertEquals(task.contextId(), resultTaskJson.get("contextId").getAsString());

ListTasksResponse response = new ListTasksResponse("request-1", result);
String responseJson = JsonUtil.toJson(response);
JsonObject responseTaskJson = JsonParser.parseString(responseJson)
.getAsJsonObject()
.getAsJsonObject("result")
.getAsJsonArray("tasks")
.get(0)
.getAsJsonObject();

assertFalse(responseTaskJson.has(Task.STREAMING_EVENT_ID));
assertEquals(task.id(), responseTaskJson.get("id").getAsString());
assertEquals(task.contextId(), responseTaskJson.get("contextId").getAsString());
}

@Test
void testSendMessageResponseSerializationKeepsEventKindWrapper() throws JsonProcessingException {
Task task = Task.builder()
.id("task-123")
.contextId("context-456")
.status(new TaskStatus(TaskState.TASK_STATE_SUBMITTED))
.build();

SendMessageResponse response = new SendMessageResponse("request-1", task);
String responseJson = JsonUtil.toJson(response);
JsonObject responseResultJson = JsonParser.parseString(responseJson)
.getAsJsonObject()
.getAsJsonObject("result");

assertTrue(responseResultJson.has(Task.STREAMING_EVENT_ID));
JsonObject taskJson = responseResultJson.getAsJsonObject(Task.STREAMING_EVENT_ID);
assertEquals(task.id(), taskJson.get("id").getAsString());
assertEquals(task.contextId(), taskJson.get("contextId").getAsString());
}

@Test
void testTaskWithTimestamp() throws JsonProcessingException {
OffsetDateTime timestamp = OffsetDateTime.now();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ private boolean dispatchNotification(StreamingEventKind event,
}
} else {
try {
body = JsonUtil.toJson(event);
body = JsonUtil.toJsonStreamingEvent(event);
} catch (Throwable throwable) {
LOGGER.error("Error serializing StreamingEventKind to JSON: {}", throwable.getMessage(), throwable);
return false;
Expand Down
Loading