1515 */
1616package com .google .adk .a2a .converters ;
1717
18+ import static com .google .common .base .Preconditions .checkNotNull ;
1819import static com .google .common .collect .ImmutableList .toImmutableList ;
1920
2021import com .fasterxml .jackson .core .JsonProcessingException ;
3940import io .a2a .spec .FileWithUri ;
4041import io .a2a .spec .Message ;
4142import io .a2a .spec .TextPart ;
43+ import java .nio .charset .StandardCharsets ;
4244import java .util .Base64 ;
4345import java .util .HashMap ;
4446import java .util .List ;
@@ -66,6 +68,9 @@ public final class PartConverter {
6668 public static final String PARTIAL_ARGS_KEY = "partialArgs" ;
6769 public static final String SCHEDULING_KEY = "scheduling" ;
6870 public static final String PARTS_KEY = "parts" ;
71+ public static final String A2A_DATA_PART_START_TAG = "<a2a_datapart_json>" ;
72+ public static final String A2A_DATA_PART_END_TAG = "</a2a_datapart_json>" ;
73+ public static final String A2A_DATA_PART_TEXT_MIME_TYPE = "text/plain" ;
6974
7075 public static Optional <TextPart > toTextPart (io .a2a .spec .Part <?> part ) {
7176 if (part instanceof TextPart textPart ) {
@@ -76,9 +81,7 @@ public static Optional<TextPart> toTextPart(io.a2a.spec.Part<?> part) {
7681
7782 /** Convert an A2A JSON part into a Google GenAI part representation. */
7883 public static com .google .genai .types .Part toGenaiPart (io .a2a .spec .Part <?> a2aPart ) {
79- if (a2aPart == null ) {
80- throw new IllegalArgumentException ("A2A part cannot be null" );
81- }
84+ checkNotNull (a2aPart , "A2A part cannot be null" );
8285
8386 if (a2aPart instanceof TextPart textPart ) {
8487 com .google .genai .types .Part .Builder partBuilder =
@@ -220,9 +223,13 @@ private static com.google.genai.types.Part convertDataPartToGenAiPart(DataPart d
220223 }
221224
222225 try {
223- String json = objectMapper .writeValueAsString (data );
226+ String json = objectMapper .writeValueAsString (dataPart );
227+ String wrappedJson = A2A_DATA_PART_START_TAG + json + A2A_DATA_PART_END_TAG ;
228+ byte [] bytes = wrappedJson .getBytes (StandardCharsets .UTF_8 );
224229 com .google .genai .types .Part .Builder builder =
225- com .google .genai .types .Part .builder ().text (json );
230+ com .google .genai .types .Part .builder ()
231+ .inlineData (
232+ Blob .builder ().data (bytes ).mimeType (A2A_DATA_PART_TEXT_MIME_TYPE ).build ());
226233 if (!metadata .isEmpty ()) {
227234 builder .partMetadata (metadata );
228235 }
@@ -334,6 +341,53 @@ private static DataPart createDataPartFromExecutableCode(
334341 return new DataPart (data .buildOrThrow (), metadata .buildOrThrow ());
335342 }
336343
344+ /**
345+ * {@return true if the given Blob contains inlineData that represents a serialized A2A DataPart,
346+ * false otherwise}
347+ *
348+ * <p>A DataPart in inlineData is expected to have a "text/plain" MIME type and the content
349+ * wrapped in {@link #A2A_DATA_PART_START_TAG} and {@link #A2A_DATA_PART_END_TAG}.
350+ *
351+ * @param blob The Blob to check.
352+ */
353+ private static boolean isDataPartInlineData (Blob blob ) {
354+ String mimeType = blob .mimeType ().orElse ("" );
355+ if (!mimeType .equals (A2A_DATA_PART_TEXT_MIME_TYPE )) {
356+ return false ;
357+ }
358+ byte [] data = blob .data ().orElse (null );
359+ if (data == null ) {
360+ return false ;
361+ }
362+ String str = new String (data , StandardCharsets .UTF_8 );
363+ return str .startsWith (A2A_DATA_PART_START_TAG ) && str .endsWith (A2A_DATA_PART_END_TAG );
364+ }
365+
366+ private static DataPart inlineDataToA2ADataPart (
367+ Blob blob , ImmutableMap <String , Object > metadata ) {
368+ byte [] data = blob .data ().orElse (null );
369+ if (data == null ) {
370+ throw new IllegalArgumentException ("Blob data cannot be null" );
371+ }
372+ String str = new String (data , StandardCharsets .UTF_8 );
373+ String jsonContent =
374+ str .substring (
375+ A2A_DATA_PART_START_TAG .length (), str .length () - A2A_DATA_PART_END_TAG .length ());
376+ try {
377+ DataPart deserialized = objectMapper .readValue (jsonContent , DataPart .class );
378+
379+ ImmutableMap .Builder <String , Object > mergedMetadata = ImmutableMap .builder ();
380+ if (deserialized .getMetadata () != null ) {
381+ mergedMetadata .putAll (deserialized .getMetadata ());
382+ }
383+ mergedMetadata .putAll (metadata );
384+
385+ return new DataPart (deserialized .getData (), mergedMetadata .buildKeepingLast ());
386+ } catch (Exception e ) {
387+ throw new IllegalArgumentException ("Failed to parse DataPart payload from inlineData" , e );
388+ }
389+ }
390+
337391 private PartConverter () {}
338392
339393 /** Convert a GenAI part into the A2A JSON representation. */
@@ -352,6 +406,10 @@ public static io.a2a.spec.Part<?> fromGenaiPart(Part part, boolean isPartial) {
352406 return new TextPart (part .text ().get (), metadata .buildKeepingLast ());
353407 }
354408
409+ if (part .inlineData ().isPresent () && isDataPartInlineData (part .inlineData ().get ())) {
410+ return inlineDataToA2ADataPart (part .inlineData ().get (), metadata .buildOrThrow ());
411+ }
412+
355413 if (part .fileData ().isPresent () || part .inlineData ().isPresent ()) {
356414 return filePartToA2A (part , metadata );
357415 }
0 commit comments