Skip to content

Commit 4e8de90

Browse files
google-genai-botcopybara-github
authored andcommitted
feat: add response converters to support multiple A2A client events
PiperOrigin-RevId: 856551560
1 parent f91077c commit 4e8de90

9 files changed

Lines changed: 597 additions & 137 deletions

File tree

a2a/pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<properties>
1717
<java.version>17</java.version>
1818
<maven.compiler.release>${java.version}</maven.compiler.release>
19-
<a2a.sdk.version>0.3.0.Beta1</a2a.sdk.version>
19+
<a2a.sdk.version>0.3.2.Final</a2a.sdk.version>
2020
<google.adk.version>${project.version}</google.adk.version>
2121
<guava.version>33.0.0-jre</guava.version>
2222
<jackson.version>2.19.0</jackson.version>
@@ -89,6 +89,11 @@
8989
<artifactId>a2a-java-sdk-http-client</artifactId>
9090
<version>${a2a.sdk.version}</version>
9191
</dependency>
92+
<dependency>
93+
<groupId>io.github.a2asdk</groupId>
94+
<artifactId>a2a-java-sdk-client</artifactId>
95+
<version>${a2a.sdk.version}</version>
96+
</dependency>
9297
<dependency>
9398
<groupId>junit</groupId>
9499
<artifactId>junit</artifactId>

a2a/src/main/java/com/google/adk/a2a/RemoteA2AAgent.java

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.google.adk.a2a;
22

33
import static com.google.common.base.Strings.isNullOrEmpty;
4-
import static com.google.common.base.Strings.nullToEmpty;
54

65
import com.fasterxml.jackson.databind.ObjectMapper;
76
import com.google.adk.a2a.converters.EventConverter;
@@ -10,16 +9,13 @@
109
import com.google.adk.agents.Callbacks;
1110
import com.google.adk.agents.InvocationContext;
1211
import com.google.adk.events.Event;
12+
import com.google.common.collect.ImmutableMap;
1313
import com.google.common.io.Resources;
1414
import com.google.errorprone.annotations.CanIgnoreReturnValue;
1515
import io.a2a.spec.AgentCard;
16-
import io.a2a.spec.EventKind;
1716
import io.a2a.spec.Message;
1817
import io.a2a.spec.MessageSendParams;
1918
import io.a2a.spec.SendMessageRequest;
20-
import io.a2a.spec.SendMessageResponse;
21-
import io.a2a.spec.Task;
22-
import io.a2a.spec.TaskStatus;
2319
import io.reactivex.rxjava3.core.Flowable;
2420
import java.io.IOException;
2521
import java.net.URL;
@@ -258,7 +254,7 @@ protected Flowable<Event> runAsyncImpl(InvocationContext invocationContext) {
258254
Message a2aMessage = new Message.Builder(originalMessage).contextId(sessionId).build();
259255

260256
Map<String, Object> metadata =
261-
originalMessage.getMetadata() == null ? Map.of() : originalMessage.getMetadata();
257+
originalMessage.getMetadata() == null ? ImmutableMap.of() : originalMessage.getMetadata();
262258

263259
MessageSendParams params = new MessageSendParams(a2aMessage, null, metadata);
264260
SendMessageRequest request = new SendMessageRequest(invocationContext.invocationId(), params);
@@ -268,7 +264,6 @@ protected Flowable<Event> runAsyncImpl(InvocationContext invocationContext) {
268264
.sendMessage(request)
269265
.flatMap(
270266
response -> {
271-
String responseContextId = extractContextId(response);
272267
List<Event> events =
273268
ResponseConverter.sendMessageResponseToEvents(
274269
response,
@@ -296,34 +291,6 @@ protected Flowable<Event> runLiveImpl(InvocationContext invocationContext) {
296291
"_run_live_impl for " + getClass() + " via A2A is not implemented.");
297292
}
298293

299-
private static String extractContextId(SendMessageResponse response) {
300-
if (response == null) {
301-
return "";
302-
}
303-
304-
EventKind result = response.getResult();
305-
if (result instanceof Message message) {
306-
String contextId = nullToEmpty(message.getContextId());
307-
if (!contextId.isEmpty()) {
308-
return contextId;
309-
}
310-
}
311-
if (result instanceof Task task) {
312-
String contextId = nullToEmpty(task.getContextId());
313-
if (!contextId.isEmpty()) {
314-
return contextId;
315-
}
316-
TaskStatus status = task.getStatus();
317-
if (status != null && status.message() != null) {
318-
String statusContext = nullToEmpty(status.message().getContextId());
319-
if (!statusContext.isEmpty()) {
320-
return statusContext;
321-
}
322-
}
323-
}
324-
return "";
325-
}
326-
327294
/** Exception thrown when the agent card cannot be resolved. */
328295
public static class AgentCardResolutionError extends RuntimeException {
329296
public AgentCardResolutionError(String message) {
@@ -335,17 +302,6 @@ public AgentCardResolutionError(String message, Throwable cause) {
335302
}
336303
}
337304

338-
/** Exception thrown when the A2A client encounters an error. */
339-
public static class A2AClientError extends RuntimeException {
340-
public A2AClientError(String message) {
341-
super(message);
342-
}
343-
344-
public A2AClientError(String message, Throwable cause) {
345-
super(message, cause);
346-
}
347-
}
348-
349305
/** Exception thrown when a type error occurs. */
350306
public static class TypeError extends RuntimeException {
351307
public TypeError(String message) {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.google.adk.a2a.common;
2+
3+
/** Exception thrown when the A2A client encounters an error. */
4+
public class A2AClientError extends RuntimeException {
5+
public A2AClientError(String message) {
6+
super(message);
7+
}
8+
9+
public A2AClientError(String message, Throwable cause) {
10+
super(message, cause);
11+
}
12+
}

a2a/src/main/java/com/google/adk/a2a/converters/EventConverter.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ public final class EventConverter {
2424

2525
private EventConverter() {}
2626

27+
/**
28+
* Aggregation mode for converting events to A2A messages.
29+
*
30+
* <p>AS_IS: Parts are aggregated as-is.
31+
*
32+
* <p>EXTERNAL_HANDOFF: Parts are aggregated as-is, except for function responses, which are
33+
* converted to text parts with the function name and response map.
34+
*/
2735
public enum AggregationMode {
2836
AS_IS,
2937
EXTERNAL_HANDOFF

a2a/src/main/java/com/google/adk/a2a/converters/PartConverter.java

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package com.google.adk.a2a.converters;
22

3+
import static com.google.common.collect.ImmutableList.toImmutableList;
4+
35
import com.fasterxml.jackson.core.JsonProcessingException;
46
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import com.google.common.collect.ImmutableList;
8+
import com.google.common.collect.ImmutableMap;
59
import com.google.genai.types.Blob;
610
import com.google.genai.types.FileData;
711
import com.google.genai.types.FunctionCall;
@@ -15,6 +19,7 @@
1519
import io.a2a.spec.TextPart;
1620
import java.util.Base64;
1721
import java.util.HashMap;
22+
import java.util.List;
1823
import java.util.Map;
1924
import java.util.Optional;
2025
import org.slf4j.Logger;
@@ -60,6 +65,14 @@ public static Optional<com.google.genai.types.Part> toGenaiPart(io.a2a.spec.Part
6065
return Optional.empty();
6166
}
6267

68+
public static ImmutableList<com.google.genai.types.Part> toGenaiParts(
69+
List<io.a2a.spec.Part<?>> a2aParts) {
70+
return a2aParts.stream()
71+
.map(PartConverter::toGenaiPart)
72+
.flatMap(Optional::stream)
73+
.collect(toImmutableList());
74+
}
75+
6376
/**
6477
* Convert a Google GenAI Part to an A2A Part.
6578
*
@@ -129,8 +142,8 @@ private static Optional<com.google.genai.types.Part> convertDataPartToGenAiPart(
129142

130143
String metadataType = metadata.getOrDefault(A2A_DATA_PART_METADATA_TYPE_KEY, "").toString();
131144

132-
if (data.containsKey("name") && data.containsKey("args")
133-
|| A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL.equals(metadataType)) {
145+
if ((data.containsKey("name") && data.containsKey("args"))
146+
|| metadataType.equals(A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL)) {
134147
String functionName = String.valueOf(data.getOrDefault("name", ""));
135148
String functionId = String.valueOf(data.getOrDefault("id", ""));
136149
Map<String, Object> args = coerceToMap(data.get("args"));
@@ -141,8 +154,8 @@ private static Optional<com.google.genai.types.Part> convertDataPartToGenAiPart(
141154
.build());
142155
}
143156

144-
if (data.containsKey("name") && data.containsKey("response")
145-
|| A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE.equals(metadataType)) {
157+
if ((data.containsKey("name") && data.containsKey("response"))
158+
|| metadataType.equals(A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE)) {
146159
String functionName = String.valueOf(data.getOrDefault("name", ""));
147160
String functionId = String.valueOf(data.getOrDefault("id", ""));
148161
Map<String, Object> response = coerceToMap(data.get("response"));
@@ -175,10 +188,10 @@ private static Optional<DataPart> createDataPartFromFunctionCall(FunctionCall fu
175188
Map<String, Object> data = new HashMap<>();
176189
data.put("name", functionCall.name().orElse(""));
177190
data.put("id", functionCall.id().orElse(""));
178-
data.put("args", functionCall.args().orElse(Map.of()));
191+
data.put("args", functionCall.args().orElse(ImmutableMap.of()));
179192

180-
Map<String, Object> metadata =
181-
Map.of(A2A_DATA_PART_METADATA_TYPE_KEY, A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL);
193+
ImmutableMap<String, Object> metadata =
194+
ImmutableMap.of(A2A_DATA_PART_METADATA_TYPE_KEY, A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL);
182195

183196
return Optional.of(new DataPart(data, metadata));
184197
}
@@ -194,10 +207,11 @@ private static Optional<DataPart> createDataPartFromFunctionResponse(
194207
Map<String, Object> data = new HashMap<>();
195208
data.put("name", functionResponse.name().orElse(""));
196209
data.put("id", functionResponse.id().orElse(""));
197-
data.put("response", functionResponse.response().orElse(Map.of()));
210+
data.put("response", functionResponse.response().orElse(ImmutableMap.of()));
198211

199-
Map<String, Object> metadata =
200-
Map.of(A2A_DATA_PART_METADATA_TYPE_KEY, A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE);
212+
ImmutableMap<String, Object> metadata =
213+
ImmutableMap.of(
214+
A2A_DATA_PART_METADATA_TYPE_KEY, A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE);
201215

202216
return Optional.of(new DataPart(data, metadata));
203217
}

a2a/src/main/java/com/google/adk/a2a/converters/RequestConverter.java

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.google.adk.events.Event;
44
import com.google.common.collect.ImmutableList;
5+
import com.google.common.collect.ImmutableMap;
56
import com.google.genai.types.Content;
67
import io.a2a.spec.DataPart;
78
import io.a2a.spec.Message;
@@ -159,16 +160,17 @@ public static ImmutableList<Event> convertAggregatedA2aMessageToAdkEvents(
159160

160161
private static String extractAuthorFromMetadata(Part<?> a2aPart) {
161162
if (a2aPart instanceof DataPart dataPart) {
162-
Map<String, Object> metadata = Optional.ofNullable(dataPart.getMetadata()).orElse(Map.of());
163+
Map<String, Object> metadata =
164+
Optional.ofNullable(dataPart.getMetadata()).orElse(ImmutableMap.of());
163165
String type =
164166
metadata.getOrDefault(PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY, "").toString();
165-
if (PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL.equals(type)) {
167+
if (type.equals(PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL)) {
166168
return "model";
167169
}
168-
if (PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE.equals(type)) {
170+
if (type.equals(PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE)) {
169171
return "user";
170172
}
171-
Map<String, Object> data = Optional.ofNullable(dataPart.getData()).orElse(Map.of());
173+
Map<String, Object> data = Optional.ofNullable(dataPart.getData()).orElse(ImmutableMap.of());
172174
if (data.containsKey("args")) {
173175
return "model";
174176
}
@@ -193,14 +195,4 @@ private static Event createEvent(
193195
.timestamp(Instant.now().toEpochMilli())
194196
.build();
195197
}
196-
197-
/**
198-
* Convert an A2A Part to a GenAI Part.
199-
*
200-
* @param a2aPart The A2A Part to convert.
201-
* @return Optional containing the converted GenAI Part, or empty if conversion fails.
202-
*/
203-
private static Optional<com.google.genai.types.Part> convertA2aPartToGenAiPart(Part<?> a2aPart) {
204-
return PartConverter.toGenaiPart(a2aPart);
205-
}
206198
}

0 commit comments

Comments
 (0)