diff --git a/a2a/src/main/java/com/google/adk/a2a/converters/PartConverter.java b/a2a/src/main/java/com/google/adk/a2a/converters/PartConverter.java
index 2e957695b..8464dbe50 100644
--- a/a2a/src/main/java/com/google/adk/a2a/converters/PartConverter.java
+++ b/a2a/src/main/java/com/google/adk/a2a/converters/PartConverter.java
@@ -39,6 +39,7 @@
import io.a2a.spec.FileWithUri;
import io.a2a.spec.Message;
import io.a2a.spec.TextPart;
+import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
@@ -66,6 +67,9 @@ public final class PartConverter {
public static final String PARTIAL_ARGS_KEY = "partialArgs";
public static final String SCHEDULING_KEY = "scheduling";
public static final String PARTS_KEY = "parts";
+ public static final String A2A_DATA_PART_START_TAG = "";
+ public static final String A2A_DATA_PART_END_TAG = "";
+ public static final String A2A_DATA_PART_TEXT_MIME_TYPE = "text/plain";
public static Optional toTextPart(io.a2a.spec.Part> part) {
if (part instanceof TextPart textPart) {
@@ -190,7 +194,11 @@ private static com.google.genai.types.Part convertDataPartToGenAiPart(DataPart d
try {
String json = objectMapper.writeValueAsString(data);
- return com.google.genai.types.Part.builder().text(json).build();
+ String wrappedJson = A2A_DATA_PART_START_TAG + json + A2A_DATA_PART_END_TAG;
+ byte[] bytes = wrappedJson.getBytes(StandardCharsets.UTF_8);
+ return com.google.genai.types.Part.builder()
+ .inlineData(Blob.builder().data(bytes).mimeType(A2A_DATA_PART_TEXT_MIME_TYPE).build())
+ .build();
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Failed to serialize DataPart payload", e);
}
@@ -298,6 +306,37 @@ private static DataPart createDataPartFromExecutableCode(
return new DataPart(data.buildOrThrow(), metadata.buildOrThrow());
}
+ private static boolean isDataPartInlineData(Blob blob) {
+ if (!blob.mimeType().orElse("").equals(A2A_DATA_PART_TEXT_MIME_TYPE)) {
+ return false;
+ }
+ byte[] data = blob.data().orElse(null);
+ if (data == null) {
+ return false;
+ }
+ String str = new String(data, StandardCharsets.UTF_8);
+ return str.startsWith(A2A_DATA_PART_START_TAG) && str.endsWith(A2A_DATA_PART_END_TAG);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static DataPart inlineDataToDataPart(
+ Blob blob, ImmutableMap.Builder metadata) {
+ byte[] data = blob.data().orElse(null);
+ if (data == null) {
+ throw new IllegalArgumentException("Blob data cannot be null");
+ }
+ String str = new String(data, StandardCharsets.UTF_8);
+ String jsonContent =
+ str.substring(
+ A2A_DATA_PART_START_TAG.length(), str.length() - A2A_DATA_PART_END_TAG.length());
+ try {
+ Map dataMap = objectMapper.readValue(jsonContent, Map.class);
+ return new DataPart(dataMap, metadata.buildOrThrow());
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Failed to parse DataPart payload from inlineData", e);
+ }
+ }
+
private PartConverter() {}
/** Convert a GenAI part into the A2A JSON representation. */
@@ -315,6 +354,10 @@ public static io.a2a.spec.Part> fromGenaiPart(Part part, boolean isPartial) {
return new TextPart(part.text().get(), metadata.buildOrThrow());
}
+ if (part.inlineData().isPresent() && isDataPartInlineData(part.inlineData().get())) {
+ return inlineDataToDataPart(part.inlineData().get(), metadata);
+ }
+
if (part.fileData().isPresent() || part.inlineData().isPresent()) {
return filePartToA2A(part, metadata);
}
diff --git a/a2a/src/test/java/com/google/adk/a2a/converters/PartConverterTest.java b/a2a/src/test/java/com/google/adk/a2a/converters/PartConverterTest.java
index 563a9afdc..bbbb47467 100644
--- a/a2a/src/test/java/com/google/adk/a2a/converters/PartConverterTest.java
+++ b/a2a/src/test/java/com/google/adk/a2a/converters/PartConverterTest.java
@@ -193,13 +193,17 @@ public void toGenaiPart_withDataPartFunctionResponse_returnsGenaiFunctionRespons
}
@Test
- public void toGenaiPart_withOtherDataPart_returnsGenaiTextPartWithJson() {
+ public void toGenaiPart_withOtherDataPart_returnsGenaiInlineDataPartWithWrappedJson() {
ImmutableMap data = ImmutableMap.of("key", "value");
DataPart dataPart = new DataPart(data, null);
Part result = PartConverter.toGenaiPart(dataPart);
- assertThat(result.text()).hasValue("{\"key\":\"value\"}");
+ assertThat(result.inlineData()).isPresent();
+ Blob blob = result.inlineData().get();
+ assertThat(blob.mimeType()).hasValue("text/plain");
+ String expectedContent = "{\"key\":\"value\"}";
+ assertThat(new String(blob.data().get(), UTF_8)).isEqualTo(expectedContent);
}
@Test
@@ -374,4 +378,20 @@ public void toGenaiPart_dataPartWithNonMapCoercedToMap() {
assertThat(result.functionCall()).isPresent();
assertThat(result.functionCall().get().args()).hasValue(ImmutableMap.of("value", 123));
}
+
+ @Test
+ public void fromGenaiPart_withDataPartInlineData_returnsDataPart() {
+ String wrappedJson = "{\"key\":\"value\"}";
+ Part part =
+ Part.builder()
+ .inlineData(
+ Blob.builder().mimeType("text/plain").data(wrappedJson.getBytes(UTF_8)).build())
+ .build();
+
+ io.a2a.spec.Part> result = PartConverter.fromGenaiPart(part, false);
+
+ assertThat(result).isInstanceOf(DataPart.class);
+ DataPart dataPart = (DataPart) result;
+ assertThat(dataPart.getData()).containsExactly("key", "value");
+ }
}