solace-agent-mesh 1.0.7__py3-none-any.whl → 1.1.0__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.

Potentially problematic release.


This version of solace-agent-mesh might be problematic. Click here for more details.

Files changed (163) hide show
  1. solace_agent_mesh/agent/adk/adk_llm.txt +182 -42
  2. solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +171 -0
  3. solace_agent_mesh/agent/adk/callbacks.py +165 -104
  4. solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +0 -18
  5. solace_agent_mesh/agent/adk/models/models_llm.txt +104 -55
  6. solace_agent_mesh/agent/adk/runner.py +7 -5
  7. solace_agent_mesh/agent/adk/services.py +9 -1
  8. solace_agent_mesh/agent/adk/setup.py +11 -0
  9. solace_agent_mesh/agent/adk/stream_parser.py +8 -1
  10. solace_agent_mesh/agent/adk/tool_wrapper.py +10 -3
  11. solace_agent_mesh/agent/agent_llm.txt +355 -18
  12. solace_agent_mesh/agent/protocol/event_handlers.py +433 -296
  13. solace_agent_mesh/agent/protocol/protocol_llm.txt +54 -7
  14. solace_agent_mesh/agent/sac/app.py +1 -1
  15. solace_agent_mesh/agent/sac/component.py +212 -517
  16. solace_agent_mesh/agent/sac/sac_llm.txt +133 -63
  17. solace_agent_mesh/agent/testing/testing_llm.txt +25 -58
  18. solace_agent_mesh/agent/tools/peer_agent_tool.py +15 -11
  19. solace_agent_mesh/agent/tools/tools_llm.txt +234 -69
  20. solace_agent_mesh/agent/utils/artifact_helpers.py +35 -1
  21. solace_agent_mesh/agent/utils/utils_llm.txt +90 -105
  22. solace_agent_mesh/assets/docs/404.html +3 -3
  23. solace_agent_mesh/assets/docs/assets/js/{3d406171.7d02a73b.js → 3d406171.0b9eeed1.js} +1 -1
  24. solace_agent_mesh/assets/docs/assets/js/6e0db977.39a79ca9.js +1 -0
  25. solace_agent_mesh/assets/docs/assets/js/{75384d09.ccd480c4.js → 75384d09.bf78fbdb.js} +1 -1
  26. solace_agent_mesh/assets/docs/assets/js/90dd9cf6.88f385ea.js +1 -0
  27. solace_agent_mesh/assets/docs/assets/js/f284c35a.fb68323a.js +1 -0
  28. solace_agent_mesh/assets/docs/assets/js/main.a75ecc0d.js +2 -0
  29. solace_agent_mesh/assets/docs/assets/js/runtime~main.458efb1d.js +1 -0
  30. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +4 -4
  31. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +4 -4
  32. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +4 -4
  33. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
  34. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +4 -4
  35. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +4 -4
  36. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +4 -4
  37. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +4 -4
  38. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +4 -4
  39. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +4 -4
  40. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +4 -4
  41. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +4 -4
  42. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +4 -4
  43. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +4 -4
  44. solace_agent_mesh/assets/docs/docs/documentation/migration-guides/a2a-upgrade-to-0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html +105 -0
  45. solace_agent_mesh/assets/docs/docs/documentation/migration-guides/a2a-upgrade-to-0.3.0/a2a-technical-migration-map/index.html +53 -0
  46. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
  47. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +8 -8
  48. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +4 -4
  49. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +4 -4
  50. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +4 -4
  51. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +4 -4
  52. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +4 -4
  53. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +4 -4
  54. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
  55. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +4 -4
  56. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +4 -4
  57. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +4 -4
  58. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +4 -4
  59. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +4 -4
  60. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +4 -4
  61. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +4 -4
  62. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +4 -4
  63. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +4 -4
  64. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +4 -4
  65. solace_agent_mesh/assets/docs/lunr-index-1756992446316.json +1 -0
  66. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  67. solace_agent_mesh/assets/docs/search-doc-1756992446316.json +1 -0
  68. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  69. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  70. solace_agent_mesh/cli/__init__.py +1 -1
  71. solace_agent_mesh/cli/commands/add_cmd/web_add_agent_step.py +12 -3
  72. solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +10 -14
  73. solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +2 -15
  74. solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +6 -2
  75. solace_agent_mesh/cli/utils.py +15 -0
  76. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-DvlO62me.js → authCallback-BmF2l6vg.js} +1 -1
  77. solace_agent_mesh/client/webui/frontend/static/assets/{client-bp6u3qVZ.js → client-D881Dttc.js} +4 -4
  78. solace_agent_mesh/client/webui/frontend/static/assets/main-C0jZjYa8.js +699 -0
  79. solace_agent_mesh/client/webui/frontend/static/assets/main-CCeG324-.css +1 -0
  80. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +2 -2
  81. solace_agent_mesh/client/webui/frontend/static/index.html +3 -3
  82. solace_agent_mesh/common/a2a/__init__.py +213 -0
  83. solace_agent_mesh/common/a2a/a2a_llm.txt +182 -0
  84. solace_agent_mesh/common/a2a/artifact.py +328 -0
  85. solace_agent_mesh/common/a2a/events.py +183 -0
  86. solace_agent_mesh/common/a2a/message.py +307 -0
  87. solace_agent_mesh/common/a2a/protocol.py +513 -0
  88. solace_agent_mesh/common/a2a/task.py +127 -0
  89. solace_agent_mesh/common/a2a/translation.py +653 -0
  90. solace_agent_mesh/common/a2a/types.py +54 -0
  91. solace_agent_mesh/common/a2a_spec/a2a.json +2576 -0
  92. solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +407 -0
  93. solace_agent_mesh/common/a2a_spec/schemas/agent_progress_update.json +18 -0
  94. solace_agent_mesh/common/a2a_spec/schemas/artifact_creation_progress.json +31 -0
  95. solace_agent_mesh/common/a2a_spec/schemas/llm_invocation.json +18 -0
  96. solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +235 -0
  97. solace_agent_mesh/common/a2a_spec/schemas/tool_invocation_start.json +26 -0
  98. solace_agent_mesh/common/a2a_spec/schemas/tool_result.json +25 -0
  99. solace_agent_mesh/common/agent_registry.py +1 -1
  100. solace_agent_mesh/common/common_llm.txt +192 -70
  101. solace_agent_mesh/common/data_parts.py +99 -0
  102. solace_agent_mesh/common/middleware/middleware_llm.txt +17 -17
  103. solace_agent_mesh/common/sac/__init__.py +0 -0
  104. solace_agent_mesh/common/sac/sac_llm.txt +71 -0
  105. solace_agent_mesh/common/sac/sam_component_base.py +252 -0
  106. solace_agent_mesh/common/services/providers/providers_llm.txt +51 -84
  107. solace_agent_mesh/common/services/services_llm.txt +206 -26
  108. solace_agent_mesh/common/utils/artifact_utils.py +29 -0
  109. solace_agent_mesh/common/utils/embeds/embeds_llm.txt +176 -80
  110. solace_agent_mesh/common/utils/utils_llm.txt +323 -42
  111. solace_agent_mesh/config_portal/backend/common.py +1 -1
  112. solace_agent_mesh/config_portal/frontend/static/client/assets/{_index-MqsrTd6g.js → _index-Bym6YkMd.js} +74 -24
  113. solace_agent_mesh/config_portal/frontend/static/client/assets/{components-B7lKcHVY.js → components-Rk0n-9cK.js} +1 -1
  114. solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-CEumGClk.js → entry.client-mvZjNKiz.js} +1 -1
  115. solace_agent_mesh/config_portal/frontend/static/client/assets/{index-DSo1AH_7.js → index-DzNKzXrc.js} +1 -1
  116. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-d845808d.js +1 -0
  117. solace_agent_mesh/config_portal/frontend/static/client/assets/{root-C4XmHinv.js → root-BWvk5-gF.js} +1 -1
  118. solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
  119. solace_agent_mesh/core_a2a/core_a2a_llm.txt +10 -8
  120. solace_agent_mesh/core_a2a/service.py +20 -44
  121. solace_agent_mesh/gateway/base/app.py +27 -1
  122. solace_agent_mesh/gateway/base/base_llm.txt +177 -72
  123. solace_agent_mesh/gateway/base/component.py +294 -523
  124. solace_agent_mesh/gateway/gateway_llm.txt +299 -58
  125. solace_agent_mesh/gateway/http_sse/component.py +156 -183
  126. solace_agent_mesh/gateway/http_sse/components/components_llm.txt +29 -29
  127. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +272 -36
  128. solace_agent_mesh/gateway/http_sse/main.py +8 -10
  129. solace_agent_mesh/gateway/http_sse/routers/agents.py +1 -1
  130. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +18 -4
  131. solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +231 -5
  132. solace_agent_mesh/gateway/http_sse/routers/sessions.py +12 -7
  133. solace_agent_mesh/gateway/http_sse/routers/tasks.py +116 -169
  134. solace_agent_mesh/gateway/http_sse/services/agent_service.py +1 -1
  135. solace_agent_mesh/gateway/http_sse/services/services_llm.txt +89 -135
  136. solace_agent_mesh/gateway/http_sse/services/task_service.py +2 -5
  137. solace_agent_mesh/solace_agent_mesh_llm.txt +362 -0
  138. solace_agent_mesh/templates/gateway_component_template.py +149 -98
  139. {solace_agent_mesh-1.0.7.dist-info → solace_agent_mesh-1.1.0.dist-info}/METADATA +5 -4
  140. {solace_agent_mesh-1.0.7.dist-info → solace_agent_mesh-1.1.0.dist-info}/RECORD +144 -127
  141. solace_agent_mesh/assets/docs/assets/js/f284c35a.731836ad.js +0 -1
  142. solace_agent_mesh/assets/docs/assets/js/main.d79f063b.js +0 -2
  143. solace_agent_mesh/assets/docs/assets/js/runtime~main.6415ad00.js +0 -1
  144. solace_agent_mesh/assets/docs/lunr-index-1756146501924.json +0 -1
  145. solace_agent_mesh/assets/docs/search-doc-1756146501924.json +0 -1
  146. solace_agent_mesh/client/webui/frontend/static/assets/main-BCpII1-0.css +0 -1
  147. solace_agent_mesh/client/webui/frontend/static/assets/main-BucUdn9m.js +0 -673
  148. solace_agent_mesh/common/a2a_protocol.py +0 -564
  149. solace_agent_mesh/common/client/__init__.py +0 -4
  150. solace_agent_mesh/common/client/card_resolver.py +0 -21
  151. solace_agent_mesh/common/client/client.py +0 -85
  152. solace_agent_mesh/common/client/client_llm.txt +0 -133
  153. solace_agent_mesh/common/server/__init__.py +0 -4
  154. solace_agent_mesh/common/server/server.py +0 -122
  155. solace_agent_mesh/common/server/server_llm.txt +0 -169
  156. solace_agent_mesh/common/server/task_manager.py +0 -291
  157. solace_agent_mesh/common/server/utils.py +0 -28
  158. solace_agent_mesh/common/types.py +0 -411
  159. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-28271392.js +0 -1
  160. /solace_agent_mesh/assets/docs/assets/js/{main.d79f063b.js.LICENSE.txt → main.a75ecc0d.js.LICENSE.txt} +0 -0
  161. {solace_agent_mesh-1.0.7.dist-info → solace_agent_mesh-1.1.0.dist-info}/WHEEL +0 -0
  162. {solace_agent_mesh-1.0.7.dist-info → solace_agent_mesh-1.1.0.dist-info}/entry_points.txt +0 -0
  163. {solace_agent_mesh-1.0.7.dist-info → solace_agent_mesh-1.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -7,11 +7,12 @@ import asyncio
7
7
  import functools
8
8
  import threading
9
9
  import concurrent.futures
10
+ import uuid
10
11
  import fnmatch
11
12
  import base64
12
13
  from datetime import datetime, timezone
13
14
  import json
14
- from solace_ai_connector.components.component_base import ComponentBase
15
+ import json
15
16
  from solace_ai_connector.common.message import (
16
17
  Message as SolaceMessage,
17
18
  )
@@ -37,39 +38,20 @@ from google.adk.agents.callback_context import CallbackContext
37
38
  from google.adk.models.llm_request import LlmRequest
38
39
  from google.genai import types as adk_types
39
40
  from google.adk.tools.mcp_tool import MCPToolset
40
- from ...common.types import (
41
+ from a2a.types import (
41
42
  AgentCard,
42
- Task,
43
- TaskStatus,
44
- TaskState,
45
- Message as A2AMessage,
46
- TextPart,
47
- FilePart,
48
- DataPart,
49
- FileContent,
50
43
  Artifact as A2AArtifact,
51
- JSONRPCResponse,
52
- InternalError,
44
+ Message as A2AMessage,
45
+ MessageSendParams,
46
+ SendMessageRequest,
47
+ TaskState,
48
+ TaskStatus,
53
49
  TaskStatusUpdateEvent,
54
- TaskArtifactUpdateEvent,
55
- SendTaskRequest,
56
- CancelTaskRequest,
57
- TaskIdParams,
58
- )
59
- from ...common.a2a_protocol import (
60
- get_a2a_base_topic,
61
- get_discovery_topic,
62
- get_agent_request_topic,
63
- get_agent_response_topic,
64
- get_client_response_topic,
65
- get_peer_agent_status_topic,
66
- format_and_route_adk_event,
67
- get_gateway_status_topic,
68
50
  )
51
+ from ...common import a2a
52
+ from ...common.data_parts import AgentProgressUpdateData
53
+ from ...common.a2a.translation import format_and_route_adk_event
69
54
  from ...agent.utils.config_parser import resolve_instruction_provider
70
- from ...agent.utils.artifact_helpers import (
71
- get_latest_artifact_version,
72
- )
73
55
  from ...agent.adk.services import (
74
56
  initialize_session_service,
75
57
  initialize_artifact_service,
@@ -94,8 +76,7 @@ from ...agent.adk.invocation_monitor import InvocationMonitor
94
76
  from ...common.middleware.registry import MiddlewareRegistry
95
77
  from ...common.constants import DEFAULT_COMMUNICATION_TIMEOUT
96
78
  from ...agent.tools.registry import tool_registry
97
- from ...common.utils.message_utils import validate_message_size
98
- from ...common.exceptions import MessageSizeExceededError
79
+ from ...common.sac.sam_component_base import SamComponentBase
99
80
 
100
81
  if TYPE_CHECKING:
101
82
  from .task_execution_context import TaskExecutionContext
@@ -123,7 +104,7 @@ info = {
123
104
  InstructionProvider = Callable[[ReadonlyContext], str]
124
105
 
125
106
 
126
- class SamAgentComponent(ComponentBase):
107
+ class SamAgentComponent(SamComponentBase):
127
108
  """
128
109
  A Solace AI Connector component that hosts a Google ADK agent,
129
110
  communicating via the A2A protocol over Solace.
@@ -257,8 +238,6 @@ class SamAgentComponent(ComponentBase):
257
238
  self.agent_card_tool_manifest: List[Dict[str, Any]] = []
258
239
  self.peer_agents: Dict[str, Any] = {}
259
240
  self._card_publish_timer_id: str = f"publish_card_{self.agent_name}"
260
- self._async_loop = None
261
- self._async_thread = None
262
241
  self._async_init_future = None
263
242
  self.peer_response_queues: Dict[str, asyncio.Queue] = {}
264
243
  self.peer_response_queue_lock = threading.Lock()
@@ -416,40 +395,11 @@ class SamAgentComponent(ComponentBase):
416
395
  raise RuntimeError(
417
396
  f"Failed to initialize synchronous ADK services: {service_err}"
418
397
  ) from service_err
419
- log.info(
420
- "%s Starting dedicated async thread for MCP/ADK initialization...",
421
- self.log_identifier,
422
- )
423
- self._async_loop = asyncio.new_event_loop()
398
+
399
+ # Async init is now handled by the base class `run` method.
400
+ # We still need a future to signal completion from the async thread.
424
401
  self._async_init_future = concurrent.futures.Future()
425
- self._async_thread = threading.Thread(
426
- target=self._start_async_loop, daemon=True
427
- )
428
- self._async_thread.start()
429
- init_coro_future = asyncio.run_coroutine_threadsafe(
430
- self._perform_async_init(), self._async_loop
431
- )
432
- log.info(
433
- "%s Waiting for async initialization to complete...",
434
- self.log_identifier,
435
- )
436
- try:
437
- init_coro_future.result(timeout=60)
438
- self._async_init_future.result(timeout=1)
439
- log.info(
440
- "%s Async initialization completed successfully.",
441
- self.log_identifier,
442
- )
443
- except Exception as init_err:
444
- log.error(
445
- "%s Async initialization failed during __init__: %s",
446
- self.log_identifier,
447
- init_err,
448
- )
449
- self.cleanup()
450
- raise RuntimeError(
451
- f"Failed to initialize component asynchronously: {init_err}"
452
- ) from init_err
402
+
453
403
  publish_interval_sec = self.agent_card_publishing_config.get(
454
404
  "interval_seconds"
455
405
  )
@@ -798,11 +748,12 @@ class SamAgentComponent(ComponentBase):
798
748
  sub_task_id,
799
749
  )
800
750
  task_id_for_peer = sub_task_id.replace(CORRELATION_DATA_PREFIX, "", 1)
801
- cancel_params = TaskIdParams(id=task_id_for_peer)
802
- cancel_request = CancelTaskRequest(params=cancel_params)
751
+ cancel_request = a2a.create_cancel_task_request(
752
+ task_id=task_id_for_peer
753
+ )
803
754
  user_props = {"clientId": self.agent_name}
804
755
  peer_topic = self._get_agent_request_topic(peer_agent_name)
805
- self._publish_a2a_message(
756
+ self.publish_a2a_message(
806
757
  payload=cancel_request.model_dump(exclude_none=True),
807
758
  topic=peer_topic,
808
759
  user_properties=user_props,
@@ -1286,17 +1237,17 @@ class SamAgentComponent(ComponentBase):
1286
1237
  return
1287
1238
 
1288
1239
  try:
1289
- a2a_message = A2AMessage(role="agent", parts=[TextPart(text=text_content)])
1290
- task_status = TaskStatus(
1291
- state=TaskState.WORKING,
1292
- message=a2a_message,
1293
- timestamp=datetime.now(timezone.utc),
1240
+ a2a_message = a2a.create_agent_text_message(
1241
+ text=text_content,
1242
+ task_id=logical_task_id,
1243
+ context_id=a2a_context.get("contextId"),
1294
1244
  )
1295
1245
  event_metadata = {"agent_name": self.agent_name}
1296
- status_update_event = TaskStatusUpdateEvent(
1297
- id=logical_task_id,
1298
- status=task_status,
1299
- final=is_stream_terminating_content,
1246
+ status_update_event = a2a.create_status_update(
1247
+ task_id=logical_task_id,
1248
+ context_id=a2a_context.get("contextId"),
1249
+ message=a2a_message,
1250
+ is_final=is_stream_terminating_content,
1300
1251
  metadata=event_metadata,
1301
1252
  )
1302
1253
 
@@ -1339,25 +1290,13 @@ class SamAgentComponent(ComponentBase):
1339
1290
  return
1340
1291
 
1341
1292
  try:
1342
- signal_data_part = DataPart(
1343
- data={
1344
- "a2a_signal_type": "agent_status_message",
1345
- "text": status_text,
1346
- },
1347
- metadata={"source_embed_type": "status_update"},
1348
- )
1349
- a2a_message = A2AMessage(role="agent", parts=[signal_data_part])
1350
- task_status = TaskStatus(
1351
- state=TaskState.WORKING,
1352
- message=a2a_message,
1353
- timestamp=datetime.now(timezone.utc),
1354
- )
1355
- event_metadata = {"agent_name": self.agent_name}
1356
- status_update_event = TaskStatusUpdateEvent(
1357
- id=logical_task_id,
1358
- status=task_status,
1359
- final=False,
1360
- metadata=event_metadata,
1293
+ progress_data = AgentProgressUpdateData(status_text=status_text)
1294
+ status_update_event = a2a.create_data_signal_event(
1295
+ task_id=logical_task_id,
1296
+ context_id=a2a_context.get("contextId"),
1297
+ signal_data=progress_data,
1298
+ agent_name=self.agent_name,
1299
+ part_metadata={"source_embed_type": "status_update"},
1361
1300
  )
1362
1301
 
1363
1302
  await self._publish_status_update_with_buffer_flush(
@@ -1514,16 +1453,27 @@ class SamAgentComponent(ComponentBase):
1514
1453
  )
1515
1454
 
1516
1455
  try:
1517
- rpc_response = JSONRPCResponse(
1518
- id=jsonrpc_request_id, result=status_update_event
1456
+ rpc_response = a2a.create_success_response(
1457
+ result=status_update_event, request_id=jsonrpc_request_id
1519
1458
  )
1520
1459
  payload_to_publish = rpc_response.model_dump(exclude_none=True)
1521
1460
 
1522
- target_topic = a2a_context.get("statusTopic") or get_gateway_status_topic(
1461
+ target_topic = a2a_context.get(
1462
+ "statusTopic"
1463
+ ) or a2a.get_gateway_status_topic(
1523
1464
  self.namespace, self.get_gateway_id(), logical_task_id
1524
1465
  )
1525
1466
 
1526
- self._publish_a2a_event(payload_to_publish, target_topic, a2a_context)
1467
+ # Construct user_properties to ensure ownership can be determined by gateways
1468
+ user_properties = {
1469
+ "a2aUserConfig": a2a_context.get("a2a_user_config"),
1470
+ "clientId": a2a_context.get("client_id"),
1471
+ "delegating_agent_name": self.get_config("agent_name"),
1472
+ }
1473
+
1474
+ self._publish_a2a_event(
1475
+ payload_to_publish, target_topic, a2a_context, user_properties
1476
+ )
1527
1477
 
1528
1478
  log.info(
1529
1479
  "%s Published %s status update to %s.",
@@ -1541,121 +1491,6 @@ class SamAgentComponent(ComponentBase):
1541
1491
  )
1542
1492
  raise
1543
1493
 
1544
- async def _translate_adk_part_to_a2a_filepart(
1545
- self,
1546
- adk_part: adk_types.Part,
1547
- filename: str,
1548
- a2a_context: Dict,
1549
- version: Optional[int] = None,
1550
- ) -> Optional[FilePart]:
1551
- """
1552
- Translates a loaded ADK Part (with inline_data) to an A2A FilePart
1553
- based on the configured artifact_handling_mode.
1554
- If version is not provided, it will be resolved to the latest.
1555
- """
1556
- if self.artifact_handling_mode == "ignore":
1557
- log.debug(
1558
- "%s Artifact handling mode is 'ignore'. Skipping translation for '%s'.",
1559
- self.log_identifier,
1560
- filename,
1561
- )
1562
- return None
1563
-
1564
- if not adk_part or not adk_part.inline_data:
1565
- log.warning(
1566
- "%s Cannot translate artifact '%s': ADK Part is missing or has no inline_data.",
1567
- self.log_identifier,
1568
- filename,
1569
- )
1570
- return None
1571
-
1572
- resolved_version = version
1573
- if resolved_version is None:
1574
- try:
1575
- resolved_version = await get_latest_artifact_version(
1576
- artifact_service=self.artifact_service,
1577
- app_name=self.get_config("agent_name"),
1578
- user_id=a2a_context.get("user_id"),
1579
- session_id=a2a_context.get("session_id"),
1580
- filename=filename,
1581
- )
1582
- if resolved_version is None:
1583
- log.error(
1584
- "%s Could not resolve latest version for artifact '%s'.",
1585
- self.log_identifier,
1586
- filename,
1587
- )
1588
- return None
1589
- except Exception as e:
1590
- log.exception(
1591
- "%s Failed to resolve latest version for artifact '%s': %s",
1592
- self.log_identifier,
1593
- filename,
1594
- e,
1595
- )
1596
- return None
1597
-
1598
- mime_type = adk_part.inline_data.mime_type
1599
- data_bytes = adk_part.inline_data.data
1600
- file_content: Optional[FileContent] = None
1601
-
1602
- try:
1603
- if self.artifact_handling_mode == "embed":
1604
- encoded_bytes = base64.b64encode(data_bytes).decode("utf-8")
1605
- file_content = FileContent(
1606
- name=filename, mimeType=mime_type, bytes=encoded_bytes
1607
- )
1608
- log.debug(
1609
- "%s Embedding artifact '%s' (size: %d bytes) for A2A message.",
1610
- self.log_identifier,
1611
- filename,
1612
- len(data_bytes),
1613
- )
1614
-
1615
- elif self.artifact_handling_mode == "reference":
1616
- adk_app_name = self.get_config("agent_name")
1617
- user_id = a2a_context.get("user_id")
1618
- original_session_id = a2a_context.get("session_id")
1619
-
1620
- if not all([adk_app_name, user_id, original_session_id]):
1621
- log.error(
1622
- "%s Cannot create artifact reference URI: missing context (app_name, user_id, or session_id).",
1623
- self.log_identifier,
1624
- )
1625
- return None
1626
-
1627
- artifact_uri = f"artifact://{adk_app_name}/{user_id}/{original_session_id}/{filename}?version={resolved_version}"
1628
-
1629
- log.info(
1630
- "%s Creating reference URI for artifact: %s",
1631
- self.log_identifier,
1632
- artifact_uri,
1633
- )
1634
- file_content = FileContent(
1635
- name=filename, mimeType=mime_type, uri=artifact_uri
1636
- )
1637
-
1638
- if file_content:
1639
- return FilePart(file=file_content)
1640
- else:
1641
- log.warning(
1642
- "%s No FileContent created for artifact '%s' despite mode '%s'.",
1643
- self.log_identifier,
1644
- filename,
1645
- self.artifact_handling_mode,
1646
- )
1647
- return None
1648
-
1649
- except Exception as e:
1650
- log.exception(
1651
- "%s Error translating artifact '%s' to A2A FilePart (mode: %s): %s",
1652
- self.log_identifier,
1653
- filename,
1654
- self.artifact_handling_mode,
1655
- e,
1656
- )
1657
- return None
1658
-
1659
1494
  async def _filter_text_from_final_streaming_event(
1660
1495
  self, adk_event: ADKEvent, a2a_context: Dict
1661
1496
  ) -> ADKEvent:
@@ -1972,7 +1807,7 @@ class SamAgentComponent(ComponentBase):
1972
1807
  namespace = self.get_config("namespace")
1973
1808
  gateway_id = self.get_gateway_id()
1974
1809
 
1975
- artifact_topic = peer_status_topic or get_gateway_status_topic(
1810
+ artifact_topic = peer_status_topic or a2a.get_gateway_status_topic(
1976
1811
  namespace, gateway_id, logical_task_id
1977
1812
  )
1978
1813
 
@@ -2012,18 +1847,36 @@ class SamAgentComponent(ComponentBase):
2012
1847
  )
2013
1848
  continue
2014
1849
 
2015
- a2a_file_part = await self._translate_adk_part_to_a2a_filepart(
2016
- loaded_adk_part, filename, a2a_context, version=version
1850
+ a2a_file_part = await a2a.translate_adk_part_to_a2a_filepart(
1851
+ adk_part=loaded_adk_part,
1852
+ filename=filename,
1853
+ a2a_context=a2a_context,
1854
+ artifact_service=self.artifact_service,
1855
+ artifact_handling_mode=self.artifact_handling_mode,
1856
+ adk_app_name=self.get_config("agent_name"),
1857
+ log_identifier=self.log_identifier,
1858
+ version=version,
2017
1859
  )
2018
1860
 
2019
1861
  if a2a_file_part:
2020
- a2a_artifact = A2AArtifact(name=filename, parts=[a2a_file_part])
2021
- artifact_update_event = TaskArtifactUpdateEvent(
2022
- id=logical_task_id, artifact=a2a_artifact
1862
+ a2a_message = a2a.create_agent_parts_message(
1863
+ parts=[a2a_file_part],
1864
+ task_id=logical_task_id,
1865
+ context_id=original_session_id,
2023
1866
  )
2024
- artifact_payload = JSONRPCResponse(
2025
- id=a2a_context.get("jsonrpc_request_id"),
2026
- result=artifact_update_event,
1867
+ task_status = a2a.create_task_status(
1868
+ state=TaskState.working, message=a2a_message
1869
+ )
1870
+ status_update_event = TaskStatusUpdateEvent(
1871
+ task_id=logical_task_id,
1872
+ context_id=original_session_id,
1873
+ status=task_status,
1874
+ final=False,
1875
+ kind="status-update",
1876
+ )
1877
+ artifact_payload = a2a.create_success_response(
1878
+ result=status_update_event,
1879
+ request_id=a2a_context.get("jsonrpc_request_id"),
2027
1880
  ).model_dump(exclude_none=True)
2028
1881
 
2029
1882
  self._publish_a2a_event(
@@ -2031,7 +1884,7 @@ class SamAgentComponent(ComponentBase):
2031
1884
  )
2032
1885
 
2033
1886
  log.info(
2034
- "%s Published TaskArtifactUpdateEvent for '%s' to %s",
1887
+ "%s Published TaskStatusUpdateEvent with FilePart for '%s' to %s",
2035
1888
  log_id,
2036
1889
  filename,
2037
1890
  artifact_topic,
@@ -2052,53 +1905,51 @@ class SamAgentComponent(ComponentBase):
2052
1905
  e,
2053
1906
  )
2054
1907
 
2055
- def _format_final_task_status(self, last_event: ADKEvent) -> TaskStatus:
1908
+ def _format_final_task_status(
1909
+ self, last_event: Optional[ADKEvent], override_text: Optional[str] = None
1910
+ ) -> TaskStatus:
2056
1911
  """Helper to format the final TaskStatus based on the last ADK event."""
2057
1912
  log.debug(
2058
1913
  "%s Formatting final task status from last ADK event %s",
2059
1914
  self.log_identifier,
2060
- last_event.id,
1915
+ last_event.id if last_event else "None",
2061
1916
  )
2062
- a2a_state = TaskState.COMPLETED
1917
+ a2a_state = TaskState.completed
2063
1918
  a2a_parts = []
2064
1919
 
2065
- if last_event.content and last_event.content.parts:
2066
- for part in last_event.content.parts:
2067
- if part.text:
2068
- a2a_parts.append(TextPart(text=part.text))
2069
- elif part.function_response:
2070
- try:
2071
- response_data = part.function_response.response
2072
- if isinstance(response_data, dict):
2073
- a2a_parts.append(
2074
- DataPart(
2075
- data=response_data,
2076
- metadata={"tool_name": part.function_response.name},
2077
- )
2078
- )
2079
- else:
2080
- a2a_parts.append(
2081
- TextPart(
2082
- text=f"Tool {part.function_response.name} result: {str(response_data)}"
2083
- )
2084
- )
2085
- except Exception:
2086
- a2a_parts.append(
2087
- TextPart(
2088
- text=f"[Tool {part.function_response.name} result omitted]"
1920
+ if override_text is not None:
1921
+ a2a_parts.append(a2a.create_text_part(text=override_text))
1922
+ # Add non-text parts from the last event
1923
+ if last_event and last_event.content and last_event.content.parts:
1924
+ for part in last_event.content.parts:
1925
+ if part.text is None:
1926
+ if part.function_response:
1927
+ a2a_parts.extend(
1928
+ a2a.translate_adk_function_response_to_a2a_parts(part)
2089
1929
  )
1930
+ else:
1931
+ # Original logic
1932
+ if last_event and last_event.content and last_event.content.parts:
1933
+ for part in last_event.content.parts:
1934
+ if part.text:
1935
+ a2a_parts.append(a2a.create_text_part(text=part.text))
1936
+ elif part.function_response:
1937
+ a2a_parts.extend(
1938
+ a2a.translate_adk_function_response_to_a2a_parts(part)
2090
1939
  )
2091
1940
 
2092
- elif last_event.actions:
1941
+ if last_event and last_event.actions:
2093
1942
  if last_event.actions.requested_auth_configs:
2094
- a2a_state = TaskState.INPUT_REQUIRED
2095
- a2a_parts.append(TextPart(text="[Agent requires input/authentication]"))
1943
+ a2a_state = TaskState.input_required
1944
+ a2a_parts.append(
1945
+ a2a.create_text_part(text="[Agent requires input/authentication]")
1946
+ )
2096
1947
 
2097
1948
  if not a2a_parts:
2098
- a2a_parts.append(TextPart(text=""))
2099
-
2100
- a2a_message = A2AMessage(role="agent", parts=a2a_parts)
2101
- return TaskStatus(state=a2a_state, message=a2a_message)
1949
+ a2a_message = a2a.create_agent_text_message(text="")
1950
+ else:
1951
+ a2a_message = a2a.create_agent_parts_message(parts=a2a_parts)
1952
+ return a2a.create_task_status(state=a2a_state, message=a2a_message)
2102
1953
 
2103
1954
  async def finalize_task_success(self, a2a_context: Dict):
2104
1955
  """
@@ -2156,55 +2007,16 @@ class SamAgentComponent(ComponentBase):
2156
2007
  logical_task_id,
2157
2008
  len(aggregated_text.encode("utf-8")),
2158
2009
  )
2159
-
2160
- final_a2a_parts = []
2161
- if aggregated_text:
2162
- final_a2a_parts.append(TextPart(text=aggregated_text))
2163
-
2164
- if last_event and last_event.content and last_event.content.parts:
2165
- for part in last_event.content.parts:
2166
- if part.text is None:
2167
- if part.function_response:
2168
- try:
2169
- response_data = part.function_response.response
2170
- if isinstance(response_data, dict):
2171
- final_a2a_parts.append(
2172
- DataPart(
2173
- data=response_data,
2174
- metadata={
2175
- "tool_name": part.function_response.name
2176
- },
2177
- )
2178
- )
2179
- else:
2180
- final_a2a_parts.append(
2181
- TextPart(
2182
- text=f"Tool {part.function_response.name} result: {str(response_data)}"
2183
- )
2184
- )
2185
- except Exception:
2186
- final_a2a_parts.append(
2187
- TextPart(
2188
- text=f"[Tool {part.function_response.name} result omitted]"
2189
- )
2190
- )
2191
-
2192
- if not final_a2a_parts:
2193
- final_a2a_parts.append(TextPart(text=""))
2194
-
2195
- final_status = TaskStatus(
2196
- state=TaskState.COMPLETED,
2197
- message=A2AMessage(role="agent", parts=final_a2a_parts),
2010
+ final_status = self._format_final_task_status(
2011
+ last_event, override_text=aggregated_text
2198
2012
  )
2199
2013
  else:
2200
2014
  if last_event:
2201
2015
  final_status = self._format_final_task_status(last_event)
2202
2016
  else:
2203
- final_status = TaskStatus(
2204
- state=TaskState.COMPLETED,
2205
- message=A2AMessage(
2206
- role="agent", parts=[TextPart(text="Task completed.")]
2207
- ),
2017
+ final_status = a2a.create_task_status(
2018
+ state=TaskState.completed,
2019
+ message=a2a.create_agent_text_message(text="Task completed."),
2208
2020
  )
2209
2021
 
2210
2022
  final_a2a_artifacts: List[A2AArtifact] = []
@@ -2224,16 +2036,18 @@ class SamAgentComponent(ComponentBase):
2224
2036
  len(task_context.produced_artifacts),
2225
2037
  )
2226
2038
 
2227
- final_task = Task(
2228
- id=logical_task_id,
2229
- sessionId=original_session_id,
2230
- status=final_status,
2039
+ final_task = a2a.create_final_task(
2040
+ task_id=logical_task_id,
2041
+ context_id=original_session_id,
2042
+ final_status=final_status,
2231
2043
  artifacts=(final_a2a_artifacts if final_a2a_artifacts else None),
2232
2044
  metadata=final_task_metadata,
2233
2045
  )
2234
- final_response = JSONRPCResponse(id=jsonrpc_request_id, result=final_task)
2046
+ final_response = a2a.create_success_response(
2047
+ result=final_task, request_id=jsonrpc_request_id
2048
+ )
2235
2049
  a2a_payload = final_response.model_dump(exclude_none=True)
2236
- target_topic = peer_reply_topic or get_client_response_topic(
2050
+ target_topic = peer_reply_topic or a2a.get_client_response_topic(
2237
2051
  namespace, client_id
2238
2052
  )
2239
2053
 
@@ -2300,17 +2114,15 @@ class SamAgentComponent(ComponentBase):
2300
2114
  client_id = a2a_context.get("client_id")
2301
2115
  peer_reply_topic = a2a_context.get("replyToTopic")
2302
2116
  namespace = self.get_config("namespace")
2303
- error_response = JSONRPCResponse(
2304
- id=jsonrpc_request_id,
2305
- error=InternalError(
2306
- message=f"Failed to finalize successful task: {e}",
2307
- data={"taskId": logical_task_id},
2308
- ),
2309
- )
2310
- target_topic = peer_reply_topic or get_client_response_topic(
2117
+ error_response = a2a.create_internal_error_response(
2118
+ message=f"Failed to finalize successful task: {e}",
2119
+ request_id=jsonrpc_request_id,
2120
+ data={"taskId": logical_task_id},
2121
+ )
2122
+ target_topic = peer_reply_topic or a2a.get_client_response_topic(
2311
2123
  namespace, client_id
2312
2124
  )
2313
- self._publish_a2a_message(
2125
+ self.publish_a2a_message(
2314
2126
  error_response.model_dump(exclude_none=True), target_topic
2315
2127
  )
2316
2128
  except Exception as report_err:
@@ -2340,23 +2152,24 @@ class SamAgentComponent(ComponentBase):
2340
2152
  peer_reply_topic = a2a_context.get("replyToTopic")
2341
2153
  namespace = self.get_config("namespace")
2342
2154
 
2343
- canceled_status = TaskStatus(
2344
- state=TaskState.CANCELED,
2345
- message=A2AMessage(
2346
- role="agent",
2347
- parts=[TextPart(text="Task cancelled by request.")],
2155
+ canceled_status = a2a.create_task_status(
2156
+ state=TaskState.canceled,
2157
+ message=a2a.create_agent_text_message(
2158
+ text="Task cancelled by request."
2348
2159
  ),
2349
2160
  )
2350
2161
  agent_name = self.get_config("agent_name")
2351
- final_task = Task(
2352
- id=logical_task_id,
2353
- sessionId=a2a_context.get("session_id"),
2354
- status=canceled_status,
2162
+ final_task = a2a.create_final_task(
2163
+ task_id=logical_task_id,
2164
+ context_id=a2a_context.get("contextId"),
2165
+ final_status=canceled_status,
2355
2166
  metadata={"agent_name": agent_name},
2356
2167
  )
2357
- final_response = JSONRPCResponse(id=jsonrpc_request_id, result=final_task)
2168
+ final_response = a2a.create_success_response(
2169
+ result=final_task, request_id=jsonrpc_request_id
2170
+ )
2358
2171
  a2a_payload = final_response.model_dump(exclude_none=True)
2359
- target_topic = peer_reply_topic or get_client_response_topic(
2172
+ target_topic = peer_reply_topic or a2a.get_client_response_topic(
2360
2173
  namespace, client_id
2361
2174
  )
2362
2175
 
@@ -2416,7 +2229,7 @@ class SamAgentComponent(ComponentBase):
2416
2229
  )
2417
2230
  try:
2418
2231
  # Create the status update event
2419
- tool_error_data_part = DataPart(
2232
+ tool_error_data_part = a2a.create_data_part(
2420
2233
  data={
2421
2234
  "a2a_signal_type": "tool_execution_error",
2422
2235
  "error_message": str(exception),
@@ -2424,17 +2237,16 @@ class SamAgentComponent(ComponentBase):
2424
2237
  }
2425
2238
  )
2426
2239
 
2427
- status_message = A2AMessage(role="agent", parts=[tool_error_data_part])
2428
- intermediate_status = TaskStatus(
2429
- state=TaskState.WORKING,
2430
- message=status_message,
2431
- timestamp=datetime.now(timezone.utc),
2240
+ status_message = a2a.create_agent_parts_message(
2241
+ parts=[tool_error_data_part],
2242
+ task_id=logical_task_id,
2243
+ context_id=a2a_context.get("contextId"),
2432
2244
  )
2433
-
2434
- status_update_event = TaskStatusUpdateEvent(
2435
- id=logical_task_id,
2436
- status=intermediate_status,
2437
- final=False,
2245
+ status_update_event = a2a.create_status_update(
2246
+ task_id=logical_task_id,
2247
+ context_id=a2a_context.get("contextId"),
2248
+ message=status_message,
2249
+ is_final=False,
2438
2250
  metadata={"agent_name": self.get_config("agent_name")},
2439
2251
  )
2440
2252
 
@@ -2554,15 +2366,14 @@ class SamAgentComponent(ComponentBase):
2554
2366
  "Otherwise, you can start a new topic."
2555
2367
  )
2556
2368
 
2557
- error_payload = InternalError(
2369
+ final_response = a2a.create_internal_error_response(
2558
2370
  message=limit_message_text,
2371
+ request_id=jsonrpc_request_id,
2559
2372
  data={"taskId": logical_task_id, "reason": "llm_call_limit_reached"},
2560
2373
  )
2561
-
2562
- final_response = JSONRPCResponse(id=jsonrpc_request_id, error=error_payload)
2563
2374
  a2a_payload = final_response.model_dump(exclude_none=True)
2564
2375
 
2565
- target_topic = peer_reply_topic or get_client_response_topic(
2376
+ target_topic = peer_reply_topic or a2a.get_client_response_topic(
2566
2377
  namespace, client_id
2567
2378
  )
2568
2379
 
@@ -2631,28 +2442,25 @@ class SamAgentComponent(ComponentBase):
2631
2442
  peer_reply_topic = a2a_context.get("replyToTopic")
2632
2443
  namespace = self.get_config("namespace")
2633
2444
 
2634
- failed_status = TaskStatus(
2635
- state=TaskState.FAILED,
2636
- message=A2AMessage(
2637
- role="agent",
2638
- parts=[
2639
- TextPart(
2640
- text="An unexpected error occurred during tool execution. Please try your request again. If the problem persists, contact an administrator."
2641
- )
2642
- ],
2445
+ failed_status = a2a.create_task_status(
2446
+ state=TaskState.failed,
2447
+ message=a2a.create_agent_text_message(
2448
+ text="An unexpected error occurred during tool execution. Please try your request again. If the problem persists, contact an administrator."
2643
2449
  ),
2644
2450
  )
2645
2451
 
2646
- final_task = Task(
2647
- id=logical_task_id,
2648
- sessionId=a2a_context.get("session_id"),
2649
- status=failed_status,
2452
+ final_task = a2a.create_final_task(
2453
+ task_id=logical_task_id,
2454
+ context_id=a2a_context.get("contextId"),
2455
+ final_status=failed_status,
2650
2456
  metadata={"agent_name": self.get_config("agent_name")},
2651
2457
  )
2652
2458
 
2653
- final_response = JSONRPCResponse(id=jsonrpc_request_id, result=final_task)
2459
+ final_response = a2a.create_success_response(
2460
+ result=final_task, request_id=jsonrpc_request_id
2461
+ )
2654
2462
  a2a_payload = final_response.model_dump(exclude_none=True)
2655
- target_topic = peer_reply_topic or get_client_response_topic(
2463
+ target_topic = peer_reply_topic or a2a.get_client_response_topic(
2656
2464
  namespace, client_id
2657
2465
  )
2658
2466
 
@@ -2871,21 +2679,21 @@ class SamAgentComponent(ComponentBase):
2871
2679
 
2872
2680
  def _get_a2a_base_topic(self) -> str:
2873
2681
  """Returns the base topic prefix using helper."""
2874
- return get_a2a_base_topic(self.namespace)
2682
+ return a2a.get_a2a_base_topic(self.namespace)
2875
2683
 
2876
2684
  def _get_discovery_topic(self) -> str:
2877
2685
  """Returns the discovery topic using helper."""
2878
- return get_discovery_topic(self.namespace)
2686
+ return a2a.get_discovery_topic(self.namespace)
2879
2687
 
2880
2688
  def _get_agent_request_topic(self, agent_id: str) -> str:
2881
2689
  """Returns the agent request topic using helper."""
2882
- return get_agent_request_topic(self.namespace, agent_id)
2690
+ return a2a.get_agent_request_topic(self.namespace, agent_id)
2883
2691
 
2884
2692
  def _get_agent_response_topic(
2885
2693
  self, delegating_agent_name: str, sub_task_id: str
2886
2694
  ) -> str:
2887
2695
  """Returns the agent response topic using helper."""
2888
- return get_agent_response_topic(
2696
+ return a2a.get_agent_response_topic(
2889
2697
  self.namespace, delegating_agent_name, sub_task_id
2890
2698
  )
2891
2699
 
@@ -2893,92 +2701,41 @@ class SamAgentComponent(ComponentBase):
2893
2701
  self, delegating_agent_name: str, sub_task_id: str
2894
2702
  ) -> str:
2895
2703
  """Returns the peer agent status topic using helper."""
2896
- return get_peer_agent_status_topic(
2704
+ return a2a.get_peer_agent_status_topic(
2897
2705
  self.namespace, delegating_agent_name, sub_task_id
2898
2706
  )
2899
2707
 
2900
2708
  def _get_client_response_topic(self, client_id: str) -> str:
2901
2709
  """Returns the client response topic using helper."""
2902
- return get_client_response_topic(self.namespace, client_id)
2710
+ return a2a.get_client_response_topic(self.namespace, client_id)
2903
2711
 
2904
- def _publish_a2a_message(
2905
- self, payload: Dict, topic: str, user_properties: Optional[Dict] = None
2712
+ def _publish_a2a_event(
2713
+ self,
2714
+ payload: Dict,
2715
+ topic: str,
2716
+ a2a_context: Dict,
2717
+ user_properties_override: Optional[Dict] = None,
2906
2718
  ):
2907
- """Helper to publish A2A messages via the SAC App."""
2908
- try:
2909
- max_size_bytes = self.max_message_size_bytes
2910
-
2911
- # Validate message size
2912
- is_valid, actual_size = validate_message_size(
2913
- payload, max_size_bytes, self.log_identifier
2914
- )
2915
-
2916
- if not is_valid:
2917
- error_msg = (
2918
- f"Message size validation failed: payload size ({actual_size} bytes) "
2919
- f"exceeds maximum allowed size ({max_size_bytes} bytes)"
2920
- )
2921
- log.error("%s %s", self.log_identifier, error_msg)
2922
- raise MessageSizeExceededError(actual_size, max_size_bytes, error_msg)
2923
-
2924
- # Debug logging to show message size when publishing
2925
- log.debug(
2926
- "%s Publishing message to topic %s (size: %d bytes)",
2927
- self.log_identifier,
2928
- topic,
2929
- actual_size,
2930
- )
2931
-
2932
- app = self.get_app()
2933
- if app:
2934
- if self.invocation_monitor:
2935
- self.invocation_monitor.log_message_event(
2936
- direction="PUBLISHED",
2937
- topic=topic,
2938
- payload=payload,
2939
- component_identifier=self.log_identifier,
2940
- )
2941
- app.send_message(
2942
- payload=payload, topic=topic, user_properties=user_properties
2943
- )
2944
- else:
2945
- log.error(
2946
- "%s Cannot publish message: Not running within a SAC App context.",
2947
- self.log_identifier,
2948
- )
2949
- except MessageSizeExceededError:
2950
- # Re-raise MessageSizeExceededError without wrapping
2951
- raise
2952
- except Exception as e:
2953
- log.exception(
2954
- "%s Failed to publish A2A message to topic %s: %s",
2955
- self.log_identifier,
2956
- topic,
2957
- e,
2958
- )
2959
- raise
2960
-
2961
- def _publish_a2a_event(self, payload: Dict, topic: str, a2a_context: Dict):
2962
2719
  """
2963
2720
  Centralized helper to publish an A2A event, ensuring user properties
2964
- are consistently attached from the a2a_context.
2721
+ are consistently attached from the a2a_context or an override.
2965
2722
  """
2966
- user_properties = {}
2967
- if a2a_context.get("a2a_user_config"):
2968
- user_properties["a2aUserConfig"] = a2a_context["a2a_user_config"]
2723
+ if user_properties_override is not None:
2724
+ user_properties = user_properties_override
2725
+ else:
2726
+ user_properties = {}
2727
+ if a2a_context.get("a2a_user_config"):
2728
+ user_properties["a2aUserConfig"] = a2a_context["a2a_user_config"]
2969
2729
 
2970
- self._publish_a2a_message(payload, topic, user_properties)
2730
+ self.publish_a2a_message(payload, topic, user_properties)
2971
2731
 
2972
2732
  def submit_a2a_task(
2973
2733
  self,
2974
2734
  target_agent_name: str,
2975
2735
  a2a_message: A2AMessage,
2976
- original_session_id: str,
2977
- main_logical_task_id: str,
2978
2736
  user_id: str,
2979
2737
  user_config: Dict[str, Any],
2980
2738
  sub_task_id: str,
2981
- function_call_id: Optional[str] = None,
2982
2739
  ) -> str:
2983
2740
  """
2984
2741
  Submits a task to a peer agent in a non-blocking way.
@@ -2987,25 +2744,18 @@ class SamAgentComponent(ComponentBase):
2987
2744
  log_identifier_helper = (
2988
2745
  f"{self.log_identifier}[SubmitA2ATask:{target_agent_name}]"
2989
2746
  )
2747
+ main_task_id = a2a_message.metadata.get("parentTaskId", "unknown_parent")
2990
2748
  log.debug(
2991
2749
  "%s Submitting non-blocking task for main task %s",
2992
2750
  log_identifier_helper,
2993
- main_logical_task_id,
2751
+ main_task_id,
2994
2752
  )
2995
2753
 
2996
2754
  peer_request_topic = self._get_agent_request_topic(target_agent_name)
2997
2755
 
2998
- a2a_request_params = {
2999
- "id": sub_task_id,
3000
- "sessionId": original_session_id,
3001
- "message": a2a_message.model_dump(exclude_none=True),
3002
- "metadata": {
3003
- "sessionBehavior": "RUN_BASED",
3004
- "parentTaskId": main_logical_task_id,
3005
- "function_call_id": function_call_id,
3006
- },
3007
- }
3008
- a2a_request = SendTaskRequest(params=a2a_request_params)
2756
+ # Create a compliant SendMessageRequest
2757
+ send_params = MessageSendParams(message=a2a_message)
2758
+ a2a_request = SendMessageRequest(id=sub_task_id, params=send_params)
3009
2759
 
3010
2760
  delegating_agent_name = self.get_config("agent_name")
3011
2761
  reply_to_topic = self._get_agent_response_topic(
@@ -3025,8 +2775,8 @@ class SamAgentComponent(ComponentBase):
3025
2775
  if isinstance(user_config, dict):
3026
2776
  user_properties["a2aUserConfig"] = user_config
3027
2777
 
3028
- self._publish_a2a_message(
3029
- payload=a2a_request.model_dump(exclude_none=True),
2778
+ self.publish_a2a_message(
2779
+ payload=a2a_request.model_dump(by_alias=True, exclude_none=True),
3030
2780
  topic=peer_request_topic,
3031
2781
  user_properties=user_properties,
3032
2782
  )
@@ -3072,25 +2822,6 @@ class SamAgentComponent(ComponentBase):
3072
2822
  exc_info=e,
3073
2823
  )
3074
2824
 
3075
- def _start_async_loop(self):
3076
- """Target method for the dedicated async thread."""
3077
- log.info("%s Dedicated async thread started.", self.log_identifier)
3078
- try:
3079
- asyncio.set_event_loop(self._async_loop)
3080
- self._async_loop.run_forever()
3081
- except Exception as e:
3082
- log.exception(
3083
- "%s Exception in dedicated async thread loop: %s",
3084
- self.log_identifier,
3085
- e,
3086
- )
3087
- if self._async_init_future and not self._async_init_future.done():
3088
- self._async_init_future.set_exception(e)
3089
- finally:
3090
- log.info("%s Dedicated async thread loop finishing.", self.log_identifier)
3091
- if self._async_loop.is_running():
3092
- self._async_loop.call_soon_threadsafe(self._async_loop.stop)
3093
-
3094
2825
  async def _perform_async_init(self):
3095
2826
  """Coroutine executed on the dedicated loop to perform async initialization."""
3096
2827
  try:
@@ -3249,58 +2980,8 @@ class SamAgentComponent(ComponentBase):
3249
2980
  im_clean_e,
3250
2981
  )
3251
2982
 
3252
- if self._async_loop and self._async_loop.is_running():
3253
- log.info(
3254
- "%s Performing async cleanup via dedicated thread...",
3255
- self.log_identifier,
3256
- )
3257
-
3258
- async def _perform_async_cleanup():
3259
- log.debug("%s Entering async cleanup coroutine...", self.log_identifier)
3260
- pass
3261
-
3262
- try:
3263
- cleanup_future = asyncio.run_coroutine_threadsafe(
3264
- _perform_async_cleanup(), self._async_loop
3265
- )
3266
- cleanup_future.result(timeout=30)
3267
- log.info("%s Async cleanup completed.", self.log_identifier)
3268
- except Exception as e:
3269
- log.exception(
3270
- "%s Error during async cleanup: %s", self.log_identifier, e
3271
- )
3272
- finally:
3273
- if self._async_loop and self._async_loop.is_running():
3274
- log.info(
3275
- "%s Cleanup: Stopping dedicated async loop...",
3276
- self.log_identifier,
3277
- )
3278
- self._async_loop.call_soon_threadsafe(self._async_loop.stop)
3279
- else:
3280
- log.info(
3281
- "%s Cleanup: Dedicated async loop is None or not running, no need to stop.",
3282
- self.log_identifier,
3283
- )
3284
- if self._async_thread and self._async_thread.is_alive():
3285
- log.info(
3286
- "%s Cleanup: Joining dedicated async thread...",
3287
- self.log_identifier,
3288
- )
3289
- self._async_thread.join(timeout=5)
3290
- if self._async_thread.is_alive():
3291
- log.warning(
3292
- "%s Dedicated async thread did not exit cleanly.",
3293
- self.log_identifier,
3294
- )
3295
- log.info(
3296
- "%s Dedicated async thread stopped and joined.", self.log_identifier
3297
- )
3298
- else:
3299
- log.info(
3300
- "%s Dedicated async loop not running, skipping async cleanup.",
3301
- self.log_identifier,
3302
- )
3303
-
2983
+ # The base class cleanup() will handle stopping the async loop and joining the thread.
2984
+ # We just need to cancel any active tasks before that happens.
3304
2985
  with self.active_tasks_lock:
3305
2986
  if self._async_loop and self._async_loop.is_running():
3306
2987
  for task_context in self.active_tasks.values():
@@ -3458,3 +3139,17 @@ class SamAgentComponent(ComponentBase):
3458
3139
  "%s Error during embed resolution: %s", method_context_log_identifier, e
3459
3140
  )
3460
3141
  return raw_text, [], ""
3142
+
3143
+ async def _async_setup_and_run(self) -> None:
3144
+ """
3145
+ Main async logic for the agent component.
3146
+ This is called by the base class's `_run_async_operations`.
3147
+ """
3148
+ await self._perform_async_init()
3149
+
3150
+ def _pre_async_cleanup(self) -> None:
3151
+ """
3152
+ Pre-cleanup actions for the agent component.
3153
+ Called by the base class before stopping the async loop.
3154
+ """
3155
+ pass