Skip to content

Commit 671ed42

Browse files
google-genai-botcopybara-github
authored andcommitted
ADK changes
PiperOrigin-RevId: 931331353
1 parent 987ef4e commit 671ed42

2 files changed

Lines changed: 66 additions & 3 deletions

File tree

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

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import io.a2a.spec.FileWithUri;
4040
import io.a2a.spec.Message;
4141
import io.a2a.spec.TextPart;
42+
import java.nio.charset.StandardCharsets;
4243
import java.util.Base64;
4344
import java.util.HashMap;
4445
import java.util.List;
@@ -66,6 +67,9 @@ public final class PartConverter {
6667
public static final String PARTIAL_ARGS_KEY = "partialArgs";
6768
public static final String SCHEDULING_KEY = "scheduling";
6869
public static final String PARTS_KEY = "parts";
70+
public static final String A2A_DATA_PART_START_TAG = "<a2a_datapart_json>";
71+
public static final String A2A_DATA_PART_END_TAG = "</a2a_datapart_json>";
72+
public static final String A2A_DATA_PART_TEXT_MIME_TYPE = "text/plain";
6973

7074
public static Optional<TextPart> toTextPart(io.a2a.spec.Part<?> part) {
7175
if (part instanceof TextPart textPart) {
@@ -190,7 +194,11 @@ private static com.google.genai.types.Part convertDataPartToGenAiPart(DataPart d
190194

191195
try {
192196
String json = objectMapper.writeValueAsString(data);
193-
return com.google.genai.types.Part.builder().text(json).build();
197+
String wrappedJson = A2A_DATA_PART_START_TAG + json + A2A_DATA_PART_END_TAG;
198+
byte[] bytes = wrappedJson.getBytes(StandardCharsets.UTF_8);
199+
return com.google.genai.types.Part.builder()
200+
.inlineData(Blob.builder().data(bytes).mimeType(A2A_DATA_PART_TEXT_MIME_TYPE).build())
201+
.build();
194202
} catch (JsonProcessingException e) {
195203
throw new IllegalArgumentException("Failed to serialize DataPart payload", e);
196204
}
@@ -298,6 +306,37 @@ private static DataPart createDataPartFromExecutableCode(
298306
return new DataPart(data.buildOrThrow(), metadata.buildOrThrow());
299307
}
300308

309+
private static boolean isDataPartInlineData(Blob blob) {
310+
if (!blob.mimeType().orElse("").equals(A2A_DATA_PART_TEXT_MIME_TYPE)) {
311+
return false;
312+
}
313+
byte[] data = blob.data().orElse(null);
314+
if (data == null) {
315+
return false;
316+
}
317+
String str = new String(data, StandardCharsets.UTF_8);
318+
return str.startsWith(A2A_DATA_PART_START_TAG) && str.endsWith(A2A_DATA_PART_END_TAG);
319+
}
320+
321+
@SuppressWarnings("unchecked")
322+
private static DataPart inlineDataToDataPart(
323+
Blob blob, ImmutableMap.Builder<String, Object> metadata) {
324+
byte[] data = blob.data().orElse(null);
325+
if (data == null) {
326+
throw new IllegalArgumentException("Blob data cannot be null");
327+
}
328+
String str = new String(data, StandardCharsets.UTF_8);
329+
String jsonContent =
330+
str.substring(
331+
A2A_DATA_PART_START_TAG.length(), str.length() - A2A_DATA_PART_END_TAG.length());
332+
try {
333+
Map<String, Object> dataMap = objectMapper.readValue(jsonContent, Map.class);
334+
return new DataPart(dataMap, metadata.buildOrThrow());
335+
} catch (Exception e) {
336+
throw new IllegalArgumentException("Failed to parse DataPart payload from inlineData", e);
337+
}
338+
}
339+
301340
private PartConverter() {}
302341

303342
/** Convert a GenAI part into the A2A JSON representation. */
@@ -315,6 +354,10 @@ public static io.a2a.spec.Part<?> fromGenaiPart(Part part, boolean isPartial) {
315354
return new TextPart(part.text().get(), metadata.buildOrThrow());
316355
}
317356

357+
if (part.inlineData().isPresent() && isDataPartInlineData(part.inlineData().get())) {
358+
return inlineDataToDataPart(part.inlineData().get(), metadata);
359+
}
360+
318361
if (part.fileData().isPresent() || part.inlineData().isPresent()) {
319362
return filePartToA2A(part, metadata);
320363
}

a2a/src/test/java/com/google/adk/a2a/converters/PartConverterTest.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,13 +193,17 @@ public void toGenaiPart_withDataPartFunctionResponse_returnsGenaiFunctionRespons
193193
}
194194

195195
@Test
196-
public void toGenaiPart_withOtherDataPart_returnsGenaiTextPartWithJson() {
196+
public void toGenaiPart_withOtherDataPart_returnsGenaiInlineDataPartWithWrappedJson() {
197197
ImmutableMap<String, Object> data = ImmutableMap.of("key", "value");
198198
DataPart dataPart = new DataPart(data, null);
199199

200200
Part result = PartConverter.toGenaiPart(dataPart);
201201

202-
assertThat(result.text()).hasValue("{\"key\":\"value\"}");
202+
assertThat(result.inlineData()).isPresent();
203+
Blob blob = result.inlineData().get();
204+
assertThat(blob.mimeType()).hasValue("text/plain");
205+
String expectedContent = "<a2a_datapart_json>{\"key\":\"value\"}</a2a_datapart_json>";
206+
assertThat(new String(blob.data().get(), UTF_8)).isEqualTo(expectedContent);
203207
}
204208

205209
@Test
@@ -374,4 +378,20 @@ public void toGenaiPart_dataPartWithNonMapCoercedToMap() {
374378
assertThat(result.functionCall()).isPresent();
375379
assertThat(result.functionCall().get().args()).hasValue(ImmutableMap.of("value", 123));
376380
}
381+
382+
@Test
383+
public void fromGenaiPart_withDataPartInlineData_returnsDataPart() {
384+
String wrappedJson = "<a2a_datapart_json>{\"key\":\"value\"}</a2a_datapart_json>";
385+
Part part =
386+
Part.builder()
387+
.inlineData(
388+
Blob.builder().mimeType("text/plain").data(wrappedJson.getBytes(UTF_8)).build())
389+
.build();
390+
391+
io.a2a.spec.Part<?> result = PartConverter.fromGenaiPart(part, false);
392+
393+
assertThat(result).isInstanceOf(DataPart.class);
394+
DataPart dataPart = (DataPart) result;
395+
assertThat(dataPart.getData()).containsExactly("key", "value");
396+
}
377397
}

0 commit comments

Comments
 (0)