google-adk 1.5.0__py3-none-any.whl → 1.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. google/adk/a2a/converters/event_converter.py +257 -36
  2. google/adk/a2a/converters/part_converter.py +93 -25
  3. google/adk/a2a/converters/request_converter.py +12 -32
  4. google/adk/a2a/converters/utils.py +22 -4
  5. google/adk/a2a/executor/__init__.py +13 -0
  6. google/adk/a2a/executor/a2a_agent_executor.py +260 -0
  7. google/adk/a2a/executor/task_result_aggregator.py +71 -0
  8. google/adk/a2a/logs/__init__.py +13 -0
  9. google/adk/a2a/logs/log_utils.py +349 -0
  10. google/adk/agents/base_agent.py +54 -0
  11. google/adk/agents/llm_agent.py +15 -0
  12. google/adk/agents/remote_a2a_agent.py +532 -0
  13. google/adk/artifacts/in_memory_artifact_service.py +6 -3
  14. google/adk/cli/browser/chunk-EQDQRRRY.js +1 -0
  15. google/adk/cli/browser/chunk-TXJFAAIW.js +2 -0
  16. google/adk/cli/browser/index.html +4 -3
  17. google/adk/cli/browser/main-RXDVX3K6.js +3914 -0
  18. google/adk/cli/browser/polyfills-FFHMD2TL.js +17 -0
  19. google/adk/cli/cli_deploy.py +4 -1
  20. google/adk/cli/cli_eval.py +8 -6
  21. google/adk/cli/cli_tools_click.py +30 -10
  22. google/adk/cli/fast_api.py +120 -5
  23. google/adk/cli/utils/agent_loader.py +12 -0
  24. google/adk/evaluation/agent_evaluator.py +107 -10
  25. google/adk/evaluation/base_eval_service.py +157 -0
  26. google/adk/evaluation/constants.py +20 -0
  27. google/adk/evaluation/eval_case.py +3 -3
  28. google/adk/evaluation/eval_metrics.py +39 -0
  29. google/adk/evaluation/evaluation_generator.py +1 -1
  30. google/adk/evaluation/final_response_match_v2.py +230 -0
  31. google/adk/evaluation/llm_as_judge.py +141 -0
  32. google/adk/evaluation/llm_as_judge_utils.py +48 -0
  33. google/adk/evaluation/metric_evaluator_registry.py +89 -0
  34. google/adk/evaluation/response_evaluator.py +38 -211
  35. google/adk/evaluation/safety_evaluator.py +54 -0
  36. google/adk/evaluation/trajectory_evaluator.py +16 -2
  37. google/adk/evaluation/vertex_ai_eval_facade.py +147 -0
  38. google/adk/events/event.py +2 -4
  39. google/adk/flows/llm_flows/base_llm_flow.py +2 -0
  40. google/adk/memory/in_memory_memory_service.py +3 -2
  41. google/adk/models/lite_llm.py +50 -10
  42. google/adk/runners.py +27 -10
  43. google/adk/sessions/database_session_service.py +25 -7
  44. google/adk/sessions/in_memory_session_service.py +5 -1
  45. google/adk/sessions/vertex_ai_session_service.py +67 -42
  46. google/adk/tools/bigquery/config.py +11 -1
  47. google/adk/tools/bigquery/query_tool.py +306 -12
  48. google/adk/tools/enterprise_search_tool.py +2 -2
  49. google/adk/tools/function_tool.py +7 -1
  50. google/adk/tools/google_search_tool.py +1 -1
  51. google/adk/tools/mcp_tool/mcp_session_manager.py +44 -30
  52. google/adk/tools/mcp_tool/mcp_tool.py +44 -7
  53. google/adk/version.py +1 -1
  54. {google_adk-1.5.0.dist-info → google_adk-1.6.1.dist-info}/METADATA +6 -4
  55. {google_adk-1.5.0.dist-info → google_adk-1.6.1.dist-info}/RECORD +58 -42
  56. google/adk/cli/browser/main-JAAWEV7F.js +0 -92
  57. google/adk/cli/browser/polyfills-B6TNHZQ6.js +0 -17
  58. {google_adk-1.5.0.dist-info → google_adk-1.6.1.dist-info}/WHEEL +0 -0
  59. {google_adk-1.5.0.dist-info → google_adk-1.6.1.dist-info}/entry_points.txt +0 -0
  60. {google_adk-1.5.0.dist-info → google_adk-1.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -14,7 +14,8 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- import datetime
17
+ from datetime import datetime
18
+ from datetime import timezone
18
19
  import logging
19
20
  from typing import Any
20
21
  from typing import Dict
@@ -26,18 +27,24 @@ from a2a.server.events import Event as A2AEvent
26
27
  from a2a.types import Artifact
27
28
  from a2a.types import DataPart
28
29
  from a2a.types import Message
30
+ from a2a.types import Part as A2APart
29
31
  from a2a.types import Role
32
+ from a2a.types import Task
30
33
  from a2a.types import TaskArtifactUpdateEvent
31
34
  from a2a.types import TaskState
32
35
  from a2a.types import TaskStatus
33
36
  from a2a.types import TaskStatusUpdateEvent
34
37
  from a2a.types import TextPart
38
+ from google.genai import types as genai_types
35
39
 
36
40
  from ...agents.invocation_context import InvocationContext
37
41
  from ...events.event import Event
38
- from ...utils.feature_decorator import working_in_progress
42
+ from ...flows.llm_flows.functions import REQUEST_EUC_FUNCTION_CALL_NAME
43
+ from ...utils.feature_decorator import experimental
44
+ from .part_converter import A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY
39
45
  from .part_converter import A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
40
46
  from .part_converter import A2A_DATA_PART_METADATA_TYPE_KEY
47
+ from .part_converter import convert_a2a_part_to_genai_part
41
48
  from .part_converter import convert_genai_part_to_a2a_part
42
49
  from .utils import _get_adk_metadata_key
43
50
 
@@ -143,6 +150,8 @@ def _convert_artifact_to_a2a_events(
143
150
  invocation_context: InvocationContext,
144
151
  filename: str,
145
152
  version: int,
153
+ task_id: Optional[str] = None,
154
+ context_id: Optional[str] = None,
146
155
  ) -> TaskArtifactUpdateEvent:
147
156
  """Converts a new artifact version to an A2A TaskArtifactUpdateEvent.
148
157
 
@@ -151,6 +160,7 @@ def _convert_artifact_to_a2a_events(
151
160
  invocation_context: The invocation context.
152
161
  filename: The name of the artifact file.
153
162
  version: The version number of the artifact.
163
+ task_id: Optional task ID to use for generated events. If not provided, new UUIDs will be generated.
154
164
 
155
165
  Returns:
156
166
  A TaskArtifactUpdateEvent representing the artifact update.
@@ -186,9 +196,9 @@ def _convert_artifact_to_a2a_events(
186
196
  )
187
197
 
188
198
  return TaskArtifactUpdateEvent(
189
- taskId=str(uuid.uuid4()),
199
+ taskId=task_id,
190
200
  append=False,
191
- contextId=invocation_context.session.id,
201
+ contextId=context_id,
192
202
  lastChunk=True,
193
203
  artifact=Artifact(
194
204
  artifactId=artifact_id,
@@ -210,7 +220,7 @@ def _convert_artifact_to_a2a_events(
210
220
  raise RuntimeError(f"Artifact conversion failed: {e}") from e
211
221
 
212
222
 
213
- def _process_long_running_tool(a2a_part, event: Event) -> None:
223
+ def _process_long_running_tool(a2a_part: A2APart, event: Event) -> None:
214
224
  """Processes long-running tool metadata for an A2A part.
215
225
 
216
226
  Args:
@@ -220,18 +230,172 @@ def _process_long_running_tool(a2a_part, event: Event) -> None:
220
230
  if (
221
231
  isinstance(a2a_part.root, DataPart)
222
232
  and event.long_running_tool_ids
233
+ and a2a_part.root.metadata
223
234
  and a2a_part.root.metadata.get(
224
235
  _get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)
225
236
  )
226
237
  == A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
227
- and a2a_part.root.metadata.get("id") in event.long_running_tool_ids
238
+ and a2a_part.root.data.get("id") in event.long_running_tool_ids
228
239
  ):
229
- a2a_part.root.metadata[_get_adk_metadata_key("is_long_running")] = True
240
+ a2a_part.root.metadata[
241
+ _get_adk_metadata_key(A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY)
242
+ ] = True
230
243
 
231
244
 
232
- @working_in_progress
233
- def convert_event_to_a2a_status_message(
234
- event: Event, invocation_context: InvocationContext
245
+ def convert_a2a_task_to_event(
246
+ a2a_task: Task,
247
+ author: Optional[str] = None,
248
+ invocation_context: Optional[InvocationContext] = None,
249
+ ) -> Event:
250
+ """Converts an A2A task to an ADK event.
251
+
252
+ Args:
253
+ a2a_task: The A2A task to convert. Must not be None.
254
+ author: The author of the event. Defaults to "a2a agent" if not provided.
255
+ invocation_context: The invocation context containing session information.
256
+ If provided, the branch will be set from the context.
257
+
258
+ Returns:
259
+ An ADK Event object representing the converted task.
260
+
261
+ Raises:
262
+ ValueError: If a2a_task is None.
263
+ RuntimeError: If conversion of the underlying message fails.
264
+ """
265
+ if a2a_task is None:
266
+ raise ValueError("A2A task cannot be None")
267
+
268
+ try:
269
+ # Extract message from task status or history
270
+ message = None
271
+ if a2a_task.status and a2a_task.status.message:
272
+ message = a2a_task.status.message
273
+ elif a2a_task.history:
274
+ message = a2a_task.history[-1]
275
+
276
+ # Convert message if available
277
+ if message:
278
+ try:
279
+ return convert_a2a_message_to_event(message, author, invocation_context)
280
+ except Exception as e:
281
+ logger.error("Failed to convert A2A task message to event: %s", e)
282
+ raise RuntimeError(f"Failed to convert task message: {e}") from e
283
+
284
+ # Create minimal event if no message is available
285
+ return Event(
286
+ invocation_id=(
287
+ invocation_context.invocation_id
288
+ if invocation_context
289
+ else str(uuid.uuid4())
290
+ ),
291
+ author=author or "a2a agent",
292
+ branch=invocation_context.branch if invocation_context else None,
293
+ )
294
+
295
+ except Exception as e:
296
+ logger.error("Failed to convert A2A task to event: %s", e)
297
+ raise
298
+
299
+
300
+ @experimental
301
+ def convert_a2a_message_to_event(
302
+ a2a_message: Message,
303
+ author: Optional[str] = None,
304
+ invocation_context: Optional[InvocationContext] = None,
305
+ ) -> Event:
306
+ """Converts an A2A message to an ADK event.
307
+
308
+ Args:
309
+ a2a_message: The A2A message to convert. Must not be None.
310
+ author: The author of the event. Defaults to "a2a agent" if not provided.
311
+ invocation_context: The invocation context containing session information.
312
+ If provided, the branch will be set from the context.
313
+
314
+ Returns:
315
+ An ADK Event object with converted content and long-running tool metadata.
316
+
317
+ Raises:
318
+ ValueError: If a2a_message is None.
319
+ RuntimeError: If conversion of message parts fails.
320
+ """
321
+ if a2a_message is None:
322
+ raise ValueError("A2A message cannot be None")
323
+
324
+ if not a2a_message.parts:
325
+ logger.warning(
326
+ "A2A message has no parts, creating event with empty content"
327
+ )
328
+ return Event(
329
+ invocation_id=(
330
+ invocation_context.invocation_id
331
+ if invocation_context
332
+ else str(uuid.uuid4())
333
+ ),
334
+ author=author or "a2a agent",
335
+ branch=invocation_context.branch if invocation_context else None,
336
+ content=genai_types.Content(role="model", parts=[]),
337
+ )
338
+
339
+ try:
340
+ parts = []
341
+ long_running_tool_ids = set()
342
+
343
+ for a2a_part in a2a_message.parts:
344
+ try:
345
+ part = convert_a2a_part_to_genai_part(a2a_part)
346
+ if part is None:
347
+ logger.warning("Failed to convert A2A part, skipping: %s", a2a_part)
348
+ continue
349
+
350
+ # Check for long-running tools
351
+ if (
352
+ a2a_part.root.metadata
353
+ and a2a_part.root.metadata.get(
354
+ _get_adk_metadata_key(
355
+ A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY
356
+ )
357
+ )
358
+ is True
359
+ ):
360
+ long_running_tool_ids.add(part.function_call.id)
361
+
362
+ parts.append(part)
363
+
364
+ except Exception as e:
365
+ logger.error("Failed to convert A2A part: %s, error: %s", a2a_part, e)
366
+ # Continue processing other parts instead of failing completely
367
+ continue
368
+
369
+ if not parts:
370
+ logger.warning(
371
+ "No parts could be converted from A2A message %s", a2a_message
372
+ )
373
+
374
+ return Event(
375
+ invocation_id=(
376
+ invocation_context.invocation_id
377
+ if invocation_context
378
+ else str(uuid.uuid4())
379
+ ),
380
+ author=author or "a2a agent",
381
+ branch=invocation_context.branch if invocation_context else None,
382
+ long_running_tool_ids=long_running_tool_ids
383
+ if long_running_tool_ids
384
+ else None,
385
+ content=genai_types.Content(
386
+ role="model",
387
+ parts=parts,
388
+ ),
389
+ )
390
+
391
+ except Exception as e:
392
+ logger.error("Failed to convert A2A message to event: %s", e)
393
+ raise RuntimeError(f"Failed to convert message: {e}") from e
394
+
395
+
396
+ @experimental
397
+ def convert_event_to_a2a_message(
398
+ event: Event, invocation_context: InvocationContext, role: Role = Role.agent
235
399
  ) -> Optional[Message]:
236
400
  """Converts an ADK event to an A2A message.
237
401
 
@@ -262,9 +426,7 @@ def convert_event_to_a2a_status_message(
262
426
  _process_long_running_tool(a2a_part, event)
263
427
 
264
428
  if a2a_parts:
265
- return Message(
266
- messageId=str(uuid.uuid4()), role=Role.agent, parts=a2a_parts
267
- )
429
+ return Message(messageId=str(uuid.uuid4()), role=role, parts=a2a_parts)
268
430
 
269
431
  except Exception as e:
270
432
  logger.error("Failed to convert event to status message: %s", e)
@@ -274,38 +436,57 @@ def convert_event_to_a2a_status_message(
274
436
 
275
437
 
276
438
  def _create_error_status_event(
277
- event: Event, invocation_context: InvocationContext
439
+ event: Event,
440
+ invocation_context: InvocationContext,
441
+ task_id: Optional[str] = None,
442
+ context_id: Optional[str] = None,
278
443
  ) -> TaskStatusUpdateEvent:
279
444
  """Creates a TaskStatusUpdateEvent for error scenarios.
280
445
 
281
446
  Args:
282
447
  event: The ADK event containing error information.
283
448
  invocation_context: The invocation context.
449
+ task_id: Optional task ID to use for generated events.
450
+ context_id: Optional Context ID to use for generated events.
284
451
 
285
452
  Returns:
286
453
  A TaskStatusUpdateEvent with FAILED state.
287
454
  """
288
455
  error_message = getattr(event, "error_message", None) or DEFAULT_ERROR_MESSAGE
289
456
 
457
+ # Get context metadata and add error code
458
+ event_metadata = _get_context_metadata(event, invocation_context)
459
+ if event.error_code:
460
+ event_metadata[_get_adk_metadata_key("error_code")] = str(event.error_code)
461
+
290
462
  return TaskStatusUpdateEvent(
291
- taskId=str(uuid.uuid4()),
292
- contextId=invocation_context.session.id,
293
- final=False,
294
- metadata=_get_context_metadata(event, invocation_context),
463
+ taskId=task_id,
464
+ contextId=context_id,
465
+ metadata=event_metadata,
295
466
  status=TaskStatus(
296
467
  state=TaskState.failed,
297
468
  message=Message(
298
469
  messageId=str(uuid.uuid4()),
299
470
  role=Role.agent,
300
471
  parts=[TextPart(text=error_message)],
472
+ metadata={
473
+ _get_adk_metadata_key("error_code"): str(event.error_code)
474
+ }
475
+ if event.error_code
476
+ else {},
301
477
  ),
302
- timestamp=datetime.datetime.now().isoformat(),
478
+ timestamp=datetime.now(timezone.utc).isoformat(),
303
479
  ),
480
+ final=False,
304
481
  )
305
482
 
306
483
 
307
- def _create_running_status_event(
308
- message: Message, invocation_context: InvocationContext, event: Event
484
+ def _create_status_update_event(
485
+ message: Message,
486
+ invocation_context: InvocationContext,
487
+ event: Event,
488
+ task_id: Optional[str] = None,
489
+ context_id: Optional[str] = None,
309
490
  ) -> TaskStatusUpdateEvent:
310
491
  """Creates a TaskStatusUpdateEvent for running scenarios.
311
492
 
@@ -313,32 +494,70 @@ def _create_running_status_event(
313
494
  message: The A2A message to include.
314
495
  invocation_context: The invocation context.
315
496
  event: The ADK event.
497
+ task_id: Optional task ID to use for generated events.
498
+ context_id: Optional Context ID to use for generated events.
499
+
316
500
 
317
501
  Returns:
318
502
  A TaskStatusUpdateEvent with RUNNING state.
319
503
  """
504
+ status = TaskStatus(
505
+ state=TaskState.working,
506
+ message=message,
507
+ timestamp=datetime.now(timezone.utc).isoformat(),
508
+ )
509
+
510
+ if any(
511
+ part.root.metadata.get(
512
+ _get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)
513
+ )
514
+ == A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
515
+ and part.root.metadata.get(
516
+ _get_adk_metadata_key(A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY)
517
+ )
518
+ is True
519
+ and part.root.data.get("name") == REQUEST_EUC_FUNCTION_CALL_NAME
520
+ for part in message.parts
521
+ if part.root.metadata
522
+ ):
523
+ status.state = TaskState.auth_required
524
+ elif any(
525
+ part.root.metadata.get(
526
+ _get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)
527
+ )
528
+ == A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
529
+ and part.root.metadata.get(
530
+ _get_adk_metadata_key(A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY)
531
+ )
532
+ is True
533
+ for part in message.parts
534
+ if part.root.metadata
535
+ ):
536
+ status.state = TaskState.input_required
537
+
320
538
  return TaskStatusUpdateEvent(
321
- taskId=str(uuid.uuid4()),
322
- contextId=invocation_context.session.id,
323
- final=False,
324
- status=TaskStatus(
325
- state=TaskState.working,
326
- message=message,
327
- timestamp=datetime.datetime.now().isoformat(),
328
- ),
539
+ taskId=task_id,
540
+ contextId=context_id,
541
+ status=status,
329
542
  metadata=_get_context_metadata(event, invocation_context),
543
+ final=False,
330
544
  )
331
545
 
332
546
 
333
- @working_in_progress
547
+ @experimental
334
548
  def convert_event_to_a2a_events(
335
- event: Event, invocation_context: InvocationContext
549
+ event: Event,
550
+ invocation_context: InvocationContext,
551
+ task_id: Optional[str] = None,
552
+ context_id: Optional[str] = None,
336
553
  ) -> List[A2AEvent]:
337
554
  """Converts a GenAI event to a list of A2A events.
338
555
 
339
556
  Args:
340
557
  event: The ADK event to convert.
341
558
  invocation_context: The invocation context.
559
+ task_id: Optional task ID to use for generated events.
560
+ context_id: Optional Context ID to use for generated events.
342
561
 
343
562
  Returns:
344
563
  A list of A2A events representing the converted ADK event.
@@ -358,20 +577,22 @@ def convert_event_to_a2a_events(
358
577
  if event.actions.artifact_delta:
359
578
  for filename, version in event.actions.artifact_delta.items():
360
579
  artifact_event = _convert_artifact_to_a2a_events(
361
- event, invocation_context, filename, version
580
+ event, invocation_context, filename, version, task_id, context_id
362
581
  )
363
582
  a2a_events.append(artifact_event)
364
583
 
365
584
  # Handle error scenarios
366
585
  if event.error_code:
367
- error_event = _create_error_status_event(event, invocation_context)
586
+ error_event = _create_error_status_event(
587
+ event, invocation_context, task_id, context_id
588
+ )
368
589
  a2a_events.append(error_event)
369
590
 
370
591
  # Handle regular message content
371
- message = convert_event_to_a2a_status_message(event, invocation_context)
592
+ message = convert_event_to_a2a_message(event, invocation_context)
372
593
  if message:
373
- running_event = _create_running_status_event(
374
- message, invocation_context, event
594
+ running_event = _create_status_update_event(
595
+ message, invocation_context, event, task_id, context_id
375
596
  )
376
597
  a2a_events.append(running_event)
377
598
 
@@ -18,9 +18,9 @@ module containing utilities for conversion betwen A2A Part and Google GenAI Part
18
18
 
19
19
  from __future__ import annotations
20
20
 
21
+ import base64
21
22
  import json
22
23
  import logging
23
- import sys
24
24
  from typing import Optional
25
25
 
26
26
  from .utils import _get_adk_metadata_key
@@ -28,26 +28,30 @@ from .utils import _get_adk_metadata_key
28
28
  try:
29
29
  from a2a import types as a2a_types
30
30
  except ImportError as e:
31
+ import sys
32
+
31
33
  if sys.version_info < (3, 10):
32
34
  raise ImportError(
33
- 'A2A Tool requires Python 3.10 or above. Please upgrade your Python'
34
- ' version.'
35
+ 'A2A requires Python 3.10 or above. Please upgrade your Python version.'
35
36
  ) from e
36
37
  else:
37
38
  raise e
38
39
 
39
40
  from google.genai import types as genai_types
40
41
 
41
- from ...utils.feature_decorator import working_in_progress
42
+ from ...utils.feature_decorator import experimental
42
43
 
43
44
  logger = logging.getLogger('google_adk.' + __name__)
44
45
 
45
46
  A2A_DATA_PART_METADATA_TYPE_KEY = 'type'
47
+ A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY = 'is_long_running'
46
48
  A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL = 'function_call'
47
49
  A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE = 'function_response'
50
+ A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT = 'code_execution_result'
51
+ A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE = 'executable_code'
48
52
 
49
53
 
50
- @working_in_progress
54
+ @experimental
51
55
  def convert_a2a_part_to_genai_part(
52
56
  a2a_part: a2a_types.Part,
53
57
  ) -> Optional[genai_types.Part]:
@@ -67,7 +71,8 @@ def convert_a2a_part_to_genai_part(
67
71
  elif isinstance(part.file, a2a_types.FileWithBytes):
68
72
  return genai_types.Part(
69
73
  inline_data=genai_types.Blob(
70
- data=part.file.bytes.encode('utf-8'), mime_type=part.file.mimeType
74
+ data=base64.b64decode(part.file.bytes),
75
+ mime_type=part.file.mimeType,
71
76
  )
72
77
  )
73
78
  else:
@@ -84,7 +89,11 @@ def convert_a2a_part_to_genai_part(
84
89
  # response.
85
90
  # TODO once A2A defined how to suervice such information, migrate below
86
91
  # logic accordinlgy
87
- if part.metadata and A2A_DATA_PART_METADATA_TYPE_KEY in part.metadata:
92
+ if (
93
+ part.metadata
94
+ and _get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)
95
+ in part.metadata
96
+ ):
88
97
  if (
89
98
  part.metadata[_get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)]
90
99
  == A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
@@ -103,6 +112,24 @@ def convert_a2a_part_to_genai_part(
103
112
  part.data, by_alias=True
104
113
  )
105
114
  )
115
+ if (
116
+ part.metadata[_get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)]
117
+ == A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT
118
+ ):
119
+ return genai_types.Part(
120
+ code_execution_result=genai_types.CodeExecutionResult.model_validate(
121
+ part.data, by_alias=True
122
+ )
123
+ )
124
+ if (
125
+ part.metadata[_get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)]
126
+ == A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE
127
+ ):
128
+ return genai_types.Part(
129
+ executable_code=genai_types.ExecutableCode.model_validate(
130
+ part.data, by_alias=True
131
+ )
132
+ )
106
133
  return genai_types.Part(text=json.dumps(part.data))
107
134
 
108
135
  logger.warning(
@@ -113,32 +140,45 @@ def convert_a2a_part_to_genai_part(
113
140
  return None
114
141
 
115
142
 
116
- @working_in_progress
143
+ @experimental
117
144
  def convert_genai_part_to_a2a_part(
118
145
  part: genai_types.Part,
119
146
  ) -> Optional[a2a_types.Part]:
120
147
  """Convert a Google GenAI Part to an A2A Part."""
148
+
121
149
  if part.text:
122
- return a2a_types.TextPart(text=part.text)
150
+ a2a_part = a2a_types.TextPart(text=part.text)
151
+ if part.thought is not None:
152
+ a2a_part.metadata = {_get_adk_metadata_key('thought'): part.thought}
153
+ return a2a_types.Part(root=a2a_part)
123
154
 
124
155
  if part.file_data:
125
- return a2a_types.FilePart(
126
- file=a2a_types.FileWithUri(
127
- uri=part.file_data.file_uri,
128
- mimeType=part.file_data.mime_type,
156
+ return a2a_types.Part(
157
+ root=a2a_types.FilePart(
158
+ file=a2a_types.FileWithUri(
159
+ uri=part.file_data.file_uri,
160
+ mimeType=part.file_data.mime_type,
161
+ )
129
162
  )
130
163
  )
131
164
 
132
165
  if part.inline_data:
133
- return a2a_types.Part(
134
- root=a2a_types.FilePart(
135
- file=a2a_types.FileWithBytes(
136
- bytes=part.inline_data.data,
137
- mimeType=part.inline_data.mime_type,
138
- )
166
+ a2a_part = a2a_types.FilePart(
167
+ file=a2a_types.FileWithBytes(
168
+ bytes=base64.b64encode(part.inline_data.data).decode('utf-8'),
169
+ mimeType=part.inline_data.mime_type,
139
170
  )
140
171
  )
141
172
 
173
+ if part.video_metadata:
174
+ a2a_part.metadata = {
175
+ _get_adk_metadata_key(
176
+ 'video_metadata'
177
+ ): part.video_metadata.model_dump(by_alias=True, exclude_none=True)
178
+ }
179
+
180
+ return a2a_types.Part(root=a2a_part)
181
+
142
182
  # Conver the funcall and function reponse to A2A DataPart.
143
183
  # This is mainly for converting human in the loop and auth request and
144
184
  # response.
@@ -151,9 +191,9 @@ def convert_genai_part_to_a2a_part(
151
191
  by_alias=True, exclude_none=True
152
192
  ),
153
193
  metadata={
154
- A2A_DATA_PART_METADATA_TYPE_KEY: (
155
- A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
156
- )
194
+ _get_adk_metadata_key(
195
+ A2A_DATA_PART_METADATA_TYPE_KEY
196
+ ): A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
157
197
  },
158
198
  )
159
199
  )
@@ -165,9 +205,37 @@ def convert_genai_part_to_a2a_part(
165
205
  by_alias=True, exclude_none=True
166
206
  ),
167
207
  metadata={
168
- A2A_DATA_PART_METADATA_TYPE_KEY: (
169
- A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE
170
- )
208
+ _get_adk_metadata_key(
209
+ A2A_DATA_PART_METADATA_TYPE_KEY
210
+ ): A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE
211
+ },
212
+ )
213
+ )
214
+
215
+ if part.code_execution_result:
216
+ return a2a_types.Part(
217
+ root=a2a_types.DataPart(
218
+ data=part.code_execution_result.model_dump(
219
+ by_alias=True, exclude_none=True
220
+ ),
221
+ metadata={
222
+ _get_adk_metadata_key(
223
+ A2A_DATA_PART_METADATA_TYPE_KEY
224
+ ): A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT
225
+ },
226
+ )
227
+ )
228
+
229
+ if part.executable_code:
230
+ return a2a_types.Part(
231
+ root=a2a_types.DataPart(
232
+ data=part.executable_code.model_dump(
233
+ by_alias=True, exclude_none=True
234
+ ),
235
+ metadata={
236
+ _get_adk_metadata_key(
237
+ A2A_DATA_PART_METADATA_TYPE_KEY
238
+ ): A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE
171
239
  },
172
240
  )
173
241
  )