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
7 changes: 6 additions & 1 deletion a2a/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<properties>
<java.version>17</java.version>
<maven.compiler.release>${java.version}</maven.compiler.release>
<a2a.sdk.version>0.3.0.Beta1</a2a.sdk.version>
<a2a.sdk.version>0.3.2.Final</a2a.sdk.version>
<google.adk.version>${project.version}</google.adk.version>
<guava.version>33.0.0-jre</guava.version>
<jackson.version>2.19.0</jackson.version>
Expand Down Expand Up @@ -89,6 +89,11 @@
<artifactId>a2a-java-sdk-http-client</artifactId>
<version>${a2a.sdk.version}</version>
</dependency>
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-client</artifactId>
<version>${a2a.sdk.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
48 changes: 2 additions & 46 deletions a2a/src/main/java/com/google/adk/a2a/RemoteA2AAgent.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.google.adk.a2a;

import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.base.Strings.nullToEmpty;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.adk.a2a.converters.EventConverter;
Expand All @@ -10,16 +9,13 @@
import com.google.adk.agents.Callbacks;
import com.google.adk.agents.InvocationContext;
import com.google.adk.events.Event;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Resources;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.a2a.spec.AgentCard;
import io.a2a.spec.EventKind;
import io.a2a.spec.Message;
import io.a2a.spec.MessageSendParams;
import io.a2a.spec.SendMessageRequest;
import io.a2a.spec.SendMessageResponse;
import io.a2a.spec.Task;
import io.a2a.spec.TaskStatus;
import io.reactivex.rxjava3.core.Flowable;
import java.io.IOException;
import java.net.URL;
Expand Down Expand Up @@ -258,7 +254,7 @@ protected Flowable<Event> runAsyncImpl(InvocationContext invocationContext) {
Message a2aMessage = new Message.Builder(originalMessage).contextId(sessionId).build();

Map<String, Object> metadata =
originalMessage.getMetadata() == null ? Map.of() : originalMessage.getMetadata();
originalMessage.getMetadata() == null ? ImmutableMap.of() : originalMessage.getMetadata();

MessageSendParams params = new MessageSendParams(a2aMessage, null, metadata);
SendMessageRequest request = new SendMessageRequest(invocationContext.invocationId(), params);
Expand All @@ -268,7 +264,6 @@ protected Flowable<Event> runAsyncImpl(InvocationContext invocationContext) {
.sendMessage(request)
.flatMap(
response -> {
String responseContextId = extractContextId(response);
List<Event> events =
ResponseConverter.sendMessageResponseToEvents(
response,
Expand Down Expand Up @@ -296,34 +291,6 @@ protected Flowable<Event> runLiveImpl(InvocationContext invocationContext) {
"_run_live_impl for " + getClass() + " via A2A is not implemented.");
}

private static String extractContextId(SendMessageResponse response) {
if (response == null) {
return "";
}

EventKind result = response.getResult();
if (result instanceof Message message) {
String contextId = nullToEmpty(message.getContextId());
if (!contextId.isEmpty()) {
return contextId;
}
}
if (result instanceof Task task) {
String contextId = nullToEmpty(task.getContextId());
if (!contextId.isEmpty()) {
return contextId;
}
TaskStatus status = task.getStatus();
if (status != null && status.message() != null) {
String statusContext = nullToEmpty(status.message().getContextId());
if (!statusContext.isEmpty()) {
return statusContext;
}
}
}
return "";
}

/** Exception thrown when the agent card cannot be resolved. */
public static class AgentCardResolutionError extends RuntimeException {
public AgentCardResolutionError(String message) {
Expand All @@ -335,17 +302,6 @@ public AgentCardResolutionError(String message, Throwable cause) {
}
}

/** Exception thrown when the A2A client encounters an error. */
public static class A2AClientError extends RuntimeException {
public A2AClientError(String message) {
super(message);
}

public A2AClientError(String message, Throwable cause) {
super(message, cause);
}
}

/** Exception thrown when a type error occurs. */
public static class TypeError extends RuntimeException {
public TypeError(String message) {
Expand Down
12 changes: 12 additions & 0 deletions a2a/src/main/java/com/google/adk/a2a/common/A2AClientError.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.google.adk.a2a.common;

/** Exception thrown when the A2A client encounters an error. */
public class A2AClientError extends RuntimeException {
public A2AClientError(String message) {
super(message);
}

public A2AClientError(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ public final class EventConverter {

private EventConverter() {}

/**
* Aggregation mode for converting events to A2A messages.
*
* <p>AS_IS: Parts are aggregated as-is.
*
* <p>EXTERNAL_HANDOFF: Parts are aggregated as-is, except for function responses, which are
* converted to text parts with the function name and response map.
*/
public enum AggregationMode {
AS_IS,
EXTERNAL_HANDOFF
Expand Down
33 changes: 23 additions & 10 deletions a2a/src/main/java/com/google/adk/a2a/converters/PartConverter.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.google.adk.a2a.converters;

import static java.util.stream.Collectors.toCollection;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import com.google.genai.types.Blob;
import com.google.genai.types.FileData;
import com.google.genai.types.FunctionCall;
Expand All @@ -13,8 +16,10 @@
import io.a2a.spec.FileWithBytes;
import io.a2a.spec.FileWithUri;
import io.a2a.spec.TextPart;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.slf4j.Logger;
Expand Down Expand Up @@ -60,6 +65,13 @@ public static Optional<com.google.genai.types.Part> toGenaiPart(io.a2a.spec.Part
return Optional.empty();
}

public static List<com.google.genai.types.Part> toGenaiParts(List<io.a2a.spec.Part<?>> a2aParts) {
return a2aParts.stream()
.map(PartConverter::toGenaiPart)
.flatMap(Optional::stream)
.collect(toCollection(ArrayList::new));
}

/**
* Convert a Google GenAI Part to an A2A Part.
*
Expand Down Expand Up @@ -129,8 +141,8 @@ private static Optional<com.google.genai.types.Part> convertDataPartToGenAiPart(

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

if (data.containsKey("name") && data.containsKey("args")
|| A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL.equals(metadataType)) {
if ((data.containsKey("name") && data.containsKey("args"))
|| metadataType.equals(A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL)) {
String functionName = String.valueOf(data.getOrDefault("name", ""));
String functionId = String.valueOf(data.getOrDefault("id", ""));
Map<String, Object> args = coerceToMap(data.get("args"));
Expand All @@ -141,8 +153,8 @@ private static Optional<com.google.genai.types.Part> convertDataPartToGenAiPart(
.build());
}

if (data.containsKey("name") && data.containsKey("response")
|| A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE.equals(metadataType)) {
if ((data.containsKey("name") && data.containsKey("response"))
|| metadataType.equals(A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE)) {
String functionName = String.valueOf(data.getOrDefault("name", ""));
String functionId = String.valueOf(data.getOrDefault("id", ""));
Map<String, Object> response = coerceToMap(data.get("response"));
Expand Down Expand Up @@ -175,10 +187,10 @@ private static Optional<DataPart> createDataPartFromFunctionCall(FunctionCall fu
Map<String, Object> data = new HashMap<>();
data.put("name", functionCall.name().orElse(""));
data.put("id", functionCall.id().orElse(""));
data.put("args", functionCall.args().orElse(Map.of()));
data.put("args", functionCall.args().orElse(ImmutableMap.of()));

Map<String, Object> metadata =
Map.of(A2A_DATA_PART_METADATA_TYPE_KEY, A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL);
ImmutableMap<String, Object> metadata =
ImmutableMap.of(A2A_DATA_PART_METADATA_TYPE_KEY, A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL);

return Optional.of(new DataPart(data, metadata));
}
Expand All @@ -194,10 +206,11 @@ private static Optional<DataPart> createDataPartFromFunctionResponse(
Map<String, Object> data = new HashMap<>();
data.put("name", functionResponse.name().orElse(""));
data.put("id", functionResponse.id().orElse(""));
data.put("response", functionResponse.response().orElse(Map.of()));
data.put("response", functionResponse.response().orElse(ImmutableMap.of()));

Map<String, Object> metadata =
Map.of(A2A_DATA_PART_METADATA_TYPE_KEY, A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE);
ImmutableMap<String, Object> metadata =
ImmutableMap.of(
A2A_DATA_PART_METADATA_TYPE_KEY, A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE);

return Optional.of(new DataPart(data, metadata));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.google.adk.events.Event;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.genai.types.Content;
import io.a2a.spec.DataPart;
import io.a2a.spec.Message;
Expand Down Expand Up @@ -159,16 +160,17 @@ public static ImmutableList<Event> convertAggregatedA2aMessageToAdkEvents(

private static String extractAuthorFromMetadata(Part<?> a2aPart) {
if (a2aPart instanceof DataPart dataPart) {
Map<String, Object> metadata = Optional.ofNullable(dataPart.getMetadata()).orElse(Map.of());
Map<String, Object> metadata =
Optional.ofNullable(dataPart.getMetadata()).orElse(ImmutableMap.of());
String type =
metadata.getOrDefault(PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY, "").toString();
if (PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL.equals(type)) {
if (type.equals(PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL)) {
return "model";
}
if (PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE.equals(type)) {
if (type.equals(PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE)) {
return "user";
}
Map<String, Object> data = Optional.ofNullable(dataPart.getData()).orElse(Map.of());
Map<String, Object> data = Optional.ofNullable(dataPart.getData()).orElse(ImmutableMap.of());
if (data.containsKey("args")) {
return "model";
}
Expand All @@ -193,14 +195,4 @@ private static Event createEvent(
.timestamp(Instant.now().toEpochMilli())
.build();
}

/**
* Convert an A2A Part to a GenAI Part.
*
* @param a2aPart The A2A Part to convert.
* @return Optional containing the converted GenAI Part, or empty if conversion fails.
*/
private static Optional<com.google.genai.types.Part> convertA2aPartToGenAiPart(Part<?> a2aPart) {
return PartConverter.toGenaiPart(a2aPart);
}
}
Loading