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
109 changes: 74 additions & 35 deletions a2a/src/main/java/com/google/adk/a2a/converters/PartConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ public static com.google.genai.types.Part toGenaiPart(io.a2a.spec.Part<?> a2aPar
if (a2aPart instanceof TextPart textPart) {
com.google.genai.types.Part.Builder partBuilder =
com.google.genai.types.Part.builder().text(textPart.getText());
if (textPart.getMetadata() != null
&& Objects.equals(textPart.getMetadata().get("thought"), Boolean.TRUE)) {
partBuilder.thought(true);
if (textPart.getMetadata() != null) {
partBuilder.partMetadata(textPart.getMetadata());
if (Objects.equals(textPart.getMetadata().get("thought"), Boolean.TRUE)) {
partBuilder.thought(true);
}
}
return partBuilder.build();
}
Expand All @@ -108,14 +110,19 @@ public static ImmutableList<com.google.genai.types.Part> toGenaiParts(

private static com.google.genai.types.Part convertFilePartToGenAiPart(FilePart filePart) {
FileContent fileContent = filePart.getFile();
Map<String, Object> metadata = filePart.getMetadata();
if (fileContent instanceof FileWithUri fileWithUri) {
return com.google.genai.types.Part.builder()
.fileData(
FileData.builder()
.fileUri(fileWithUri.uri())
.mimeType(fileWithUri.mimeType())
.build())
.build();
com.google.genai.types.Part.Builder builder =
com.google.genai.types.Part.builder()
.fileData(
FileData.builder()
.fileUri(fileWithUri.uri())
.mimeType(fileWithUri.mimeType())
.build());
if (metadata != null) {
builder.partMetadata(metadata);
}
return builder.build();
}

if (fileContent instanceof FileWithBytes fileWithBytes) {
Expand All @@ -124,9 +131,13 @@ private static com.google.genai.types.Part convertFilePartToGenAiPart(FilePart f
throw new GenAiFieldMissingException("FileWithBytes missing byte content");
}
byte[] decoded = Base64.getDecoder().decode(bytesString);
return com.google.genai.types.Part.builder()
.inlineData(Blob.builder().data(decoded).mimeType(fileWithBytes.mimeType()).build())
.build();
com.google.genai.types.Part.Builder builder =
com.google.genai.types.Part.builder()
.inlineData(Blob.builder().data(decoded).mimeType(fileWithBytes.mimeType()).build());
if (metadata != null) {
builder.partMetadata(metadata);
}
return builder.build();
}

throw new IllegalArgumentException("Unsupported FilePart content: " + fileContent.getClass());
Expand All @@ -145,24 +156,33 @@ private static com.google.genai.types.Part convertDataPartToGenAiPart(DataPart d
String functionName = String.valueOf(data.getOrDefault(NAME_KEY, ""));
String functionId = String.valueOf(data.getOrDefault(ID_KEY, ""));
Map<String, Object> args = coerceToMap(data.get(ARGS_KEY));
return com.google.genai.types.Part.builder()
.functionCall(FunctionCall.builder().name(functionName).id(functionId).args(args).build())
.build();
com.google.genai.types.Part.Builder builder =
com.google.genai.types.Part.builder()
.functionCall(
FunctionCall.builder().name(functionName).id(functionId).args(args).build());
if (!metadata.isEmpty()) {
builder.partMetadata(metadata);
}
return builder.build();
}

if ((data.containsKey(NAME_KEY) && data.containsKey(RESPONSE_KEY))
|| metadataType.equals(A2ADataPartMetadataType.FUNCTION_RESPONSE.getType())) {
String functionName = String.valueOf(data.getOrDefault(NAME_KEY, ""));
String functionId = String.valueOf(data.getOrDefault(ID_KEY, ""));
Map<String, Object> response = coerceToMap(data.get(RESPONSE_KEY));
return com.google.genai.types.Part.builder()
.functionResponse(
FunctionResponse.builder()
.name(functionName)
.id(functionId)
.response(response)
.build())
.build();
com.google.genai.types.Part.Builder builder =
com.google.genai.types.Part.builder()
.functionResponse(
FunctionResponse.builder()
.name(functionName)
.id(functionId)
.response(response)
.build());
if (!metadata.isEmpty()) {
builder.partMetadata(metadata);
}
return builder.build();
}

if ((data.containsKey(CODE_KEY) && data.containsKey(LANGUAGE_KEY))
Expand All @@ -171,26 +191,42 @@ private static com.google.genai.types.Part convertDataPartToGenAiPart(DataPart d
String language =
String.valueOf(
data.getOrDefault(LANGUAGE_KEY, Language.Known.LANGUAGE_UNSPECIFIED.toString()));
return com.google.genai.types.Part.builder()
.executableCode(
ExecutableCode.builder().code(code).language(new Language(language)).build())
.build();
com.google.genai.types.Part.Builder builder =
com.google.genai.types.Part.builder()
.executableCode(
ExecutableCode.builder().code(code).language(new Language(language)).build());
if (!metadata.isEmpty()) {
builder.partMetadata(metadata);
}
return builder.build();
}

if ((data.containsKey(OUTCOME_KEY) && data.containsKey(OUTPUT_KEY))
|| metadataType.equals(A2ADataPartMetadataType.CODE_EXECUTION_RESULT.getType())) {
String outcome =
String.valueOf(data.getOrDefault(OUTCOME_KEY, Outcome.Known.OUTCOME_OK).toString());
String output = String.valueOf(data.getOrDefault(OUTPUT_KEY, ""));
return com.google.genai.types.Part.builder()
.codeExecutionResult(
CodeExecutionResult.builder().outcome(new Outcome(outcome)).output(output).build())
.build();
com.google.genai.types.Part.Builder builder =
com.google.genai.types.Part.builder()
.codeExecutionResult(
CodeExecutionResult.builder()
.outcome(new Outcome(outcome))
.output(output)
.build());
if (!metadata.isEmpty()) {
builder.partMetadata(metadata);
}
return builder.build();
}

try {
String json = objectMapper.writeValueAsString(data);
return com.google.genai.types.Part.builder().text(json).build();
com.google.genai.types.Part.Builder builder =
com.google.genai.types.Part.builder().text(json);
if (!metadata.isEmpty()) {
builder.partMetadata(metadata);
}
return builder.build();
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Failed to serialize DataPart payload", e);
}
Expand Down Expand Up @@ -305,10 +341,13 @@ public static io.a2a.spec.Part<?> fromGenaiPart(Part part, boolean isPartial) {
if (part == null) {
throw new GenAiFieldMissingException("GenAI part cannot be null");
}
ImmutableMap.Builder<String, Object> metadata = ImmutableMap.builder();
Map<String, Object> baseMetadata = new HashMap<>();
if (isPartial) {
metadata.put(A2AMetadataKey.PARTIAL.getType(), true);
baseMetadata.put(A2AMetadataKey.PARTIAL.getType(), true);
}
part.partMetadata().ifPresent(baseMetadata::putAll);
ImmutableMap.Builder<String, Object> metadata =
ImmutableMap.<String, Object>builder().putAll(baseMetadata);

if (part.text().isPresent()) {
addValueIfPresent(metadata, "thought", part.thought());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public void toGenaiPart_withTextPartMetadataWithoutThought_returnsGenaiTextPartW

assertThat(result.text()).hasValue("Thinking process");
assertThat(result.thought()).isEmpty();
assertThat(result.partMetadata()).hasValue(ImmutableMap.of("otherKey", "value"));
}

@Test
Expand Down Expand Up @@ -374,4 +375,34 @@ public void toGenaiPart_dataPartWithNonMapCoercedToMap() {
assertThat(result.functionCall()).isPresent();
assertThat(result.functionCall().get().args()).hasValue(ImmutableMap.of("value", 123));
}

@Test
public void toGenaiPart_withTextPartMetadata_propagatesMetadata() {
TextPart textPart = new TextPart("Hello", ImmutableMap.of("key", "value"));

Part result = PartConverter.toGenaiPart(textPart);

assertThat(result.partMetadata()).hasValue(ImmutableMap.of("key", "value"));
}

@Test
public void toGenaiPart_withFilePartMetadata_propagatesMetadata() {
FilePart filePart =
new FilePart(
new FileWithUri("text/plain", "file.txt", "http://file.txt"),
ImmutableMap.of("key", "value"));

Part result = PartConverter.toGenaiPart(filePart);

assertThat(result.partMetadata()).hasValue(ImmutableMap.of("key", "value"));
}

@Test
public void fromGenaiPart_withPartMetadata_propagatesMetadata() {
Part part = Part.builder().text("Hello").partMetadata(ImmutableMap.of("key", "value")).build();

io.a2a.spec.Part<?> result = PartConverter.fromGenaiPart(part, false);

assertThat(result.getMetadata()).containsExactly("key", "value");
}
}