solace-agent-mesh 1.6.0__py3-none-any.whl → 1.6.2__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 (127) hide show
  1. solace_agent_mesh/agent/adk/app_llm_agent.py +26 -0
  2. solace_agent_mesh/agent/adk/artifacts/filesystem_artifact_service.py +1 -1
  3. solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +135 -31
  4. solace_agent_mesh/agent/adk/models/lite_llm.py +5 -0
  5. solace_agent_mesh/agent/adk/runner.py +10 -12
  6. solace_agent_mesh/agent/adk/services.py +50 -14
  7. solace_agent_mesh/agent/adk/setup.py +66 -38
  8. solace_agent_mesh/agent/protocol/event_handlers.py +416 -152
  9. solace_agent_mesh/agent/proxies/a2a/app.py +3 -2
  10. solace_agent_mesh/agent/proxies/base/app.py +3 -2
  11. solace_agent_mesh/agent/proxies/base/component.py +35 -4
  12. solace_agent_mesh/agent/sac/app.py +97 -9
  13. solace_agent_mesh/agent/sac/component.py +284 -145
  14. solace_agent_mesh/agent/sac/task_execution_context.py +79 -2
  15. solace_agent_mesh/agent/tools/tool_config_types.py +3 -0
  16. solace_agent_mesh/agent/utils/artifact_helpers.py +1 -1
  17. solace_agent_mesh/assets/docs/404.html +3 -3
  18. solace_agent_mesh/assets/docs/assets/js/240a0364.c39f8388.js +1 -0
  19. solace_agent_mesh/assets/docs/assets/js/631738c7.7c4594c9.js +1 -0
  20. solace_agent_mesh/assets/docs/assets/js/66d4869e.830d443f.js +1 -0
  21. solace_agent_mesh/assets/docs/assets/js/71da7b71.ddbdfbe2.js +1 -0
  22. solace_agent_mesh/assets/docs/assets/js/{e3d9abda.2b916f9e.js → e3d9abda.6b9493d0.js} +1 -1
  23. solace_agent_mesh/assets/docs/assets/js/e92d0134.4f395c6b.js +1 -0
  24. solace_agent_mesh/assets/docs/assets/js/f284c35a.720d2ef2.js +1 -0
  25. solace_agent_mesh/assets/docs/assets/js/main.d1643f0b.js +2 -0
  26. solace_agent_mesh/assets/docs/assets/js/runtime~main.97f920d4.js +1 -0
  27. solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +3 -3
  28. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +3 -3
  29. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +3 -3
  30. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +3 -3
  31. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +3 -3
  32. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +3 -3
  33. solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +3 -3
  34. solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +3 -3
  35. solace_agent_mesh/assets/docs/docs/documentation/components/index.html +3 -3
  36. solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +3 -3
  37. solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +3 -3
  38. solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +3 -3
  39. solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +4 -25
  40. solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +4 -4
  41. solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +4 -4
  42. solace_agent_mesh/assets/docs/docs/documentation/deploying/logging/index.html +76 -0
  43. solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +5 -4
  44. solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +3 -3
  45. solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +3 -3
  46. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +3 -3
  47. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +3 -3
  48. solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +3 -3
  49. solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +3 -3
  50. solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +3 -3
  51. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +3 -3
  52. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +3 -3
  53. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +3 -3
  54. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +3 -3
  55. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +3 -3
  56. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +3 -3
  57. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +3 -3
  58. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +3 -3
  59. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +3 -3
  60. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +3 -3
  61. solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +3 -3
  62. solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +3 -3
  63. solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +3 -3
  64. solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +3 -3
  65. solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +3 -3
  66. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
  67. solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +3 -3
  68. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +3 -6
  69. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +3 -3
  70. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +3 -3
  71. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +3 -3
  72. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +3 -3
  73. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
  74. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +3 -3
  75. solace_agent_mesh/assets/docs/lunr-index-1761663789856.json +1 -0
  76. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  77. solace_agent_mesh/assets/docs/search-doc-1761663789856.json +1 -0
  78. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  79. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  80. solace_agent_mesh/cli/__init__.py +1 -1
  81. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-BTf6dqwp.js → authCallback-D4_RMYRh.js} +1 -1
  82. solace_agent_mesh/client/webui/frontend/static/assets/{client-CaY59VuC.js → client-UZ3qU6Bq.js} +1 -1
  83. solace_agent_mesh/client/webui/frontend/static/assets/main--3yJYl7S.css +1 -0
  84. solace_agent_mesh/client/webui/frontend/static/assets/main-DojKHS49.js +342 -0
  85. solace_agent_mesh/client/webui/frontend/static/assets/{vendor-BEmvJSYz.js → vendor-DSqhjwq_.js} +1 -1
  86. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  87. solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
  88. solace_agent_mesh/common/a2a/events.py +2 -1
  89. solace_agent_mesh/common/a2a/protocol.py +78 -0
  90. solace_agent_mesh/common/sac/sam_component_base.py +406 -21
  91. solace_agent_mesh/common/utils/pydantic_utils.py +90 -3
  92. solace_agent_mesh/gateway/base/app.py +15 -0
  93. solace_agent_mesh/gateway/base/component.py +116 -46
  94. solace_agent_mesh/gateway/http_sse/app.py +7 -0
  95. solace_agent_mesh/gateway/http_sse/component.py +18 -10
  96. solace_agent_mesh/gateway/http_sse/dependencies.py +83 -59
  97. solace_agent_mesh/gateway/http_sse/main.py +5 -4
  98. solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +1 -1
  99. solace_agent_mesh/gateway/http_sse/routers/auth.py +103 -6
  100. solace_agent_mesh/gateway/http_sse/routers/config.py +1 -1
  101. solace_agent_mesh/gateway/http_sse/routers/sessions.py +1 -1
  102. solace_agent_mesh/gateway/http_sse/routers/sse.py +15 -5
  103. solace_agent_mesh/gateway/http_sse/routers/tasks.py +3 -3
  104. solace_agent_mesh/gateway/http_sse/routers/users.py +47 -1
  105. solace_agent_mesh/gateway/http_sse/routers/visualization.py +90 -8
  106. solace_agent_mesh/gateway/http_sse/services/session_service.py +1 -1
  107. solace_agent_mesh/gateway/http_sse/session_manager.py +15 -15
  108. solace_agent_mesh/gateway/http_sse/shared/exception_handlers.py +16 -1
  109. solace_agent_mesh/gateway/http_sse/sse_manager.py +15 -6
  110. solace_agent_mesh/templates/logging_config_template.ini +2 -2
  111. {solace_agent_mesh-1.6.0.dist-info → solace_agent_mesh-1.6.2.dist-info}/METADATA +2 -2
  112. {solace_agent_mesh-1.6.0.dist-info → solace_agent_mesh-1.6.2.dist-info}/RECORD +116 -114
  113. solace_agent_mesh/assets/docs/assets/js/240a0364.7eac6021.js +0 -1
  114. solace_agent_mesh/assets/docs/assets/js/631738c7.a8b1ef8b.js +0 -1
  115. solace_agent_mesh/assets/docs/assets/js/71da7b71.38583438.js +0 -1
  116. solace_agent_mesh/assets/docs/assets/js/e92d0134.cf6d6522.js +0 -1
  117. solace_agent_mesh/assets/docs/assets/js/f284c35a.42f59cdd.js +0 -1
  118. solace_agent_mesh/assets/docs/assets/js/main.20feee82.js +0 -2
  119. solace_agent_mesh/assets/docs/assets/js/runtime~main.0d198646.js +0 -1
  120. solace_agent_mesh/assets/docs/lunr-index-1761165361160.json +0 -1
  121. solace_agent_mesh/assets/docs/search-doc-1761165361160.json +0 -1
  122. solace_agent_mesh/client/webui/frontend/static/assets/main-BGTaW0uv.js +0 -342
  123. solace_agent_mesh/client/webui/frontend/static/assets/main-DHJKSW1S.css +0 -1
  124. /solace_agent_mesh/assets/docs/assets/js/{main.20feee82.js.LICENSE.txt → main.d1643f0b.js.LICENSE.txt} +0 -0
  125. {solace_agent_mesh-1.6.0.dist-info → solace_agent_mesh-1.6.2.dist-info}/WHEEL +0 -0
  126. {solace_agent_mesh-1.6.0.dist-info → solace_agent_mesh-1.6.2.dist-info}/entry_points.txt +0 -0
  127. {solace_agent_mesh-1.6.0.dist-info → solace_agent_mesh-1.6.2.dist-info}/licenses/LICENSE +0 -0
@@ -2,52 +2,106 @@
2
2
  Contains event handling logic for the A2A_ADK_HostComponent.
3
3
  """
4
4
 
5
- import logging
6
- import json
7
5
  import asyncio
8
- from typing import TYPE_CHECKING, Dict, Any
9
6
  import fnmatch
10
- from solace_ai_connector.common.message import Message as SolaceMessage
11
- from ...agent.adk.callbacks import _publish_data_part_status_update
12
- from ...common.data_parts import ToolResultData
13
- from ...common.a2a.types import ToolsExtensionParams
14
- from solace_ai_connector.common.event import Event, EventType
7
+ import json
8
+ import logging
9
+ from typing import TYPE_CHECKING, Any, Dict
10
+
15
11
  from a2a.types import (
16
12
  A2ARequest,
17
- AgentCard,
18
13
  AgentCapabilities,
14
+ AgentCard,
19
15
  AgentExtension,
20
16
  DataPart,
21
17
  JSONRPCResponse,
22
18
  Task,
23
19
  TaskArtifactUpdateEvent,
20
+ TaskState,
24
21
  TaskStatusUpdateEvent,
25
22
  TextPart,
26
23
  )
24
+ from google.adk.agents import RunConfig
25
+ from google.adk.agents.run_config import StreamingMode
26
+ from solace_ai_connector.common.event import Event, EventType
27
+ from solace_ai_connector.common.message import Message as SolaceMessage
28
+
29
+ from ...agent.adk.callbacks import _publish_data_part_status_update
30
+ from ...agent.adk.runner import run_adk_async_task_thread_wrapper
31
+ from ...agent.utils.artifact_helpers import generate_artifact_metadata_summary
27
32
  from ...common import a2a
28
33
  from ...common.a2a import (
29
34
  get_agent_request_topic,
30
- get_discovery_topic,
31
- translate_a2a_to_adk_content,
32
- get_client_response_topic,
33
35
  get_agent_response_subscription_topic,
34
36
  get_agent_status_subscription_topic,
37
+ get_client_response_topic,
38
+ get_discovery_topic,
35
39
  get_sam_events_subscription_topic,
36
40
  get_text_from_message,
37
41
  topic_matches_subscription,
42
+ translate_a2a_to_adk_content,
38
43
  )
39
- from ...agent.utils.artifact_helpers import (
40
- generate_artifact_metadata_summary,
41
- )
42
- from ...agent.adk.runner import run_adk_async_task_thread_wrapper
44
+ from ...common.a2a.types import ToolsExtensionParams
45
+ from ...common.data_parts import ToolResultData
43
46
  from ..sac.task_execution_context import TaskExecutionContext
44
- from google.adk.agents import RunConfig
45
47
 
46
48
  if TYPE_CHECKING:
47
49
  from ..sac.component import SamAgentComponent
48
- from google.adk.agents.run_config import StreamingMode
49
50
 
50
51
  log = logging.getLogger(__name__)
52
+ trace_logger = logging.getLogger("sam_trace")
53
+
54
+
55
+ def _forward_jsonrpc_response(
56
+ component: "SamAgentComponent",
57
+ original_jsonrpc_request_id: str,
58
+ result_data: Any,
59
+ target_topic: str,
60
+ main_logical_task_id: str,
61
+ peer_agent_name: str,
62
+ message: SolaceMessage,
63
+ ) -> None:
64
+ """
65
+ Utility method to forward a JSONRPCResponse with the given result data.
66
+
67
+ Args:
68
+ component: The SamAgentComponent instance
69
+ original_jsonrpc_request_id: The original JSONRPC request ID
70
+ result_data: The data to include in the response result
71
+ target_topic: The topic to publish to
72
+ main_logical_task_id: The main logical task ID for logging
73
+ peer_agent_name: The peer agent name for logging
74
+ message: The original message to acknowledge
75
+ """
76
+ forwarded_rpc_response = JSONRPCResponse(
77
+ id=original_jsonrpc_request_id,
78
+ result=result_data,
79
+ )
80
+ payload_to_publish = forwarded_rpc_response.model_dump(
81
+ by_alias=True, exclude_none=True
82
+ )
83
+
84
+ try:
85
+ component.publish_a2a_message(
86
+ payload_to_publish,
87
+ target_topic,
88
+ )
89
+ log.debug(
90
+ "%s Forwarded DataPart signal for main task %s (from peer %s) to %s.",
91
+ component.log_identifier,
92
+ main_logical_task_id,
93
+ peer_agent_name,
94
+ target_topic,
95
+ )
96
+ except Exception as pub_err:
97
+ log.exception(
98
+ "%s Failed to publish forwarded status signal for main task %s: %s",
99
+ component.log_identifier,
100
+ main_logical_task_id,
101
+ pub_err,
102
+ )
103
+ message.call_acknowledgements()
104
+
51
105
 
52
106
  def _register_peer_artifacts_in_parent_context(
53
107
  parent_task_context: "TaskExecutionContext",
@@ -123,6 +177,30 @@ async def process_event(component, event: Event):
123
177
  agent_status_sub_prefix
124
178
  ):
125
179
  await handle_a2a_response(component, message)
180
+ elif hasattr(component, "trust_manager") and component.trust_manager:
181
+ # Check if this is a trust card message (enterprise feature)
182
+ try:
183
+ if component.trust_manager.is_trust_card_topic(topic):
184
+ await component.trust_manager.handle_trust_card_message(
185
+ message, topic
186
+ )
187
+ message.call_acknowledgements()
188
+ return
189
+ except Exception as e:
190
+ log.error(
191
+ "%s Error handling trust card message: %s",
192
+ component.log_identifier,
193
+ e,
194
+ )
195
+ message.call_acknowledgements()
196
+ return
197
+
198
+ log.warning(
199
+ "%s Received message on unhandled topic: %s",
200
+ component.log_identifier,
201
+ topic,
202
+ )
203
+ message.call_acknowledgements()
126
204
  else:
127
205
  log.warning(
128
206
  "%s Received message on unhandled topic: %s",
@@ -171,7 +249,7 @@ async def process_event(component, event: Event):
171
249
 
172
250
  async def _publish_peer_tool_result_notification(
173
251
  component: "SamAgentComponent",
174
- correlation_data: Dict[str, Any],
252
+ correlation_data: dict[str, Any],
175
253
  payload_to_queue: Any,
176
254
  log_identifier: str,
177
255
  ):
@@ -229,12 +307,11 @@ async def handle_a2a_request(component, message: SolaceMessage):
229
307
  payload_dict = message.get_payload()
230
308
  if not isinstance(payload_dict, dict):
231
309
  raise ValueError("Payload is not a dictionary.")
232
-
233
-
310
+
234
311
  a2a_request: A2ARequest = A2ARequest.model_validate(payload_dict)
235
312
  jsonrpc_request_id = a2a.get_request_id(a2a_request)
236
313
 
237
- # Extract properties from message user properties
314
+ # Extract properties from message user properties
238
315
  client_id = message.get_user_properties().get("clientId", "default_client")
239
316
  status_topic_from_peer = message.get_user_properties().get("a2aStatusTopic")
240
317
  reply_topic_from_peer = message.get_user_properties().get("replyTo")
@@ -248,6 +325,90 @@ async def handle_a2a_request(component, message: SolaceMessage):
248
325
  # For Send, we will generate it.
249
326
  logical_task_id = None
250
327
  method = a2a.get_request_method(a2a_request)
328
+
329
+ # Enterprise feature: Verify user authentication if trust manager enabled
330
+ verified_user_identity = None
331
+ if hasattr(component, "trust_manager") and component.trust_manager:
332
+ # Determine task_id for verification
333
+ if method == "tasks/cancel":
334
+ verification_task_id = a2a.get_task_id_from_cancel_request(a2a_request)
335
+ elif method in ["message/send", "message/stream"]:
336
+ verification_task_id = str(a2a.get_request_id(a2a_request))
337
+ else:
338
+ verification_task_id = None
339
+
340
+ if verification_task_id:
341
+ try:
342
+ # Enterprise handles all verification logic
343
+ verified_user_identity = (
344
+ component.trust_manager.verify_request_authentication(
345
+ message=message,
346
+ task_id=verification_task_id,
347
+ namespace=namespace,
348
+ jsonrpc_request_id=jsonrpc_request_id,
349
+ )
350
+ )
351
+
352
+ if verified_user_identity:
353
+ log.info(
354
+ "%s Successfully authenticated user '%s' for task %s",
355
+ component.log_identifier,
356
+ verified_user_identity.get("user_id"),
357
+ verification_task_id,
358
+ )
359
+
360
+ except Exception as e:
361
+ # Authentication failed - enterprise provides error details
362
+ log.error(
363
+ "%s Authentication failed for task %s: %s",
364
+ component.log_identifier,
365
+ verification_task_id,
366
+ e,
367
+ )
368
+
369
+ # Build error response using enterprise exception data if available
370
+ error_data = {
371
+ "reason": "authentication_failed",
372
+ "task_id": verification_task_id,
373
+ }
374
+ if hasattr(e, "create_error_response_data"):
375
+ error_data = e.create_error_response_data()
376
+
377
+ error_response = a2a.create_invalid_request_error_response(
378
+ message="Authentication failed",
379
+ request_id=jsonrpc_request_id,
380
+ data=error_data,
381
+ )
382
+
383
+ # Determine reply topic
384
+ reply_topic = message.get_user_properties().get("replyTo")
385
+ if not reply_topic:
386
+ client_id = message.get_user_properties().get(
387
+ "clientId", "default_client"
388
+ )
389
+ reply_topic = a2a.get_client_response_topic(
390
+ namespace, client_id
391
+ )
392
+
393
+ component.publish_a2a_message(
394
+ payload=error_response.model_dump(exclude_none=True),
395
+ topic=reply_topic,
396
+ )
397
+
398
+ try:
399
+ message.call_acknowledgements()
400
+ log.debug(
401
+ "%s ACKed message with failed authentication",
402
+ component.log_identifier,
403
+ )
404
+ except Exception as ack_e:
405
+ log.error(
406
+ "%s Failed to ACK message after authentication failure: %s",
407
+ component.log_identifier,
408
+ ack_e,
409
+ )
410
+ return None
411
+
251
412
  if method == "tasks/cancel":
252
413
  logical_task_id = a2a.get_task_id_from_cancel_request(a2a_request)
253
414
  log.info(
@@ -357,6 +518,35 @@ async def handle_a2a_request(component, message: SolaceMessage):
357
518
  # The gateway/client is the source of truth for the task ID.
358
519
  # The agent adopts the ID from the JSON-RPC request envelope.
359
520
  logical_task_id = str(a2a.get_request_id(a2a_request))
521
+
522
+ try:
523
+ from solace_agent_mesh_enterprise.auth.input_required import (
524
+ a2a_auth_message_handler,
525
+ )
526
+
527
+ try:
528
+ message_handled = await a2a_auth_message_handler(
529
+ component, a2a_message, logical_task_id
530
+ )
531
+ if message_handled:
532
+ message.call_acknowledgements()
533
+ log.debug(
534
+ "%s ACKed message handled by input-required auth handler.",
535
+ component.log_identifier,
536
+ )
537
+ return None
538
+ except Exception as auth_import_err:
539
+ log.error(
540
+ "%s Error in input-required auth handler: %s",
541
+ component.log_identifier,
542
+ auth_import_err,
543
+ )
544
+ message.call_acknowledgements()
545
+ return None
546
+
547
+ except ImportError:
548
+ pass
549
+
360
550
  # The session id is now contextId on the message
361
551
  original_session_id = a2a_message.context_id
362
552
  message_id = a2a_message.message_id
@@ -382,7 +572,7 @@ async def handle_a2a_request(component, message: SolaceMessage):
382
572
  )
383
573
  else:
384
574
  session_behavior = component.default_session_behavior
385
- log.info(
575
+ log.debug(
386
576
  "%s No 'sessionBehavior' in task metadata. Using component default: '%s'.",
387
577
  component.log_identifier,
388
578
  session_behavior,
@@ -524,7 +714,6 @@ async def handle_a2a_request(component, message: SolaceMessage):
524
714
  "is_streaming": is_streaming_request,
525
715
  "statusTopic": status_topic_from_peer,
526
716
  "replyToTopic": reply_topic_from_peer,
527
- "original_solace_message": message,
528
717
  "a2a_user_config": a2a_user_config,
529
718
  "effective_session_id": effective_session_id,
530
719
  "is_run_based_session": is_run_based_session,
@@ -537,16 +726,48 @@ async def handle_a2a_request(component, message: SolaceMessage):
537
726
  "response_format": response_format,
538
727
  "host_agent_name": agent_name,
539
728
  }
540
- log.debug(
541
- "%s A2A Context (shared service model): %s",
542
- component.log_identifier,
543
- a2a_context,
544
- )
729
+
730
+ # Store verified user identity claims in a2a_context (not the raw token)
731
+ if verified_user_identity:
732
+ a2a_context["verified_user_identity"] = verified_user_identity
733
+ log.debug(
734
+ "%s Stored verified user identity in a2a_context for task %s",
735
+ component.log_identifier,
736
+ logical_task_id,
737
+ )
738
+ if trace_logger.isEnabledFor(logging.DEBUG):
739
+ trace_logger.debug(
740
+ "%s A2A Context (shared service model): %s",
741
+ component.log_identifier,
742
+ a2a_context,
743
+ )
744
+ else:
745
+ log.debug(
746
+ "%s A2A Context prepared for task %s",
747
+ component.log_identifier,
748
+ a2a_context.get("logical_task_id", "unknown"),
749
+ )
545
750
 
546
751
  # Create and store the execution context for this task
547
752
  task_context = TaskExecutionContext(
548
753
  task_id=logical_task_id, a2a_context=a2a_context
549
754
  )
755
+
756
+ # Store the original Solace message in TaskExecutionContext instead of a2a_context
757
+ # This avoids serialization issues when a2a_context is stored in ADK session state
758
+ task_context.set_original_solace_message(message)
759
+
760
+ # Store auth token for peer delegation using generic security storage
761
+ if hasattr(component, "trust_manager") and component.trust_manager:
762
+ auth_token = message.get_user_properties().get("authToken")
763
+ if auth_token:
764
+ task_context.set_security_data("auth_token", auth_token)
765
+ log.debug(
766
+ "%s Stored authentication token in TaskExecutionContext security storage for task %s",
767
+ component.log_identifier,
768
+ logical_task_id,
769
+ )
770
+
550
771
  with component.active_tasks_lock:
551
772
  component.active_tasks[logical_task_id] = task_context
552
773
  log.info(
@@ -631,7 +852,7 @@ async def handle_a2a_request(component, message: SolaceMessage):
631
852
  streaming_mode = StreamingMode.SSE
632
853
 
633
854
  max_llm_calls_per_task = component.get_config("max_llm_calls_per_task", 20)
634
- log.info(
855
+ log.debug(
635
856
  "%s Using max_llm_calls_per_task: %s",
636
857
  component.log_identifier,
637
858
  max_llm_calls_per_task,
@@ -797,13 +1018,13 @@ def handle_agent_card_message(component, message: SolaceMessage):
797
1018
  break
798
1019
 
799
1020
  if is_allowed:
800
-
1021
+
801
1022
  # Also store in peer_agents for backward compatibility
802
1023
  component.peer_agents[agent_name] = agent_card
803
1024
 
804
1025
  # Store the agent card in the registry for health tracking
805
1026
  is_new = component.agent_registry.add_or_update_agent(agent_card)
806
-
1027
+
807
1028
  if is_new:
808
1029
  log.info(
809
1030
  "%s Registered new agent '%s' in registry.",
@@ -924,93 +1145,143 @@ async def handle_a2a_response(component, message: SolaceMessage):
924
1145
  if isinstance(payload_data, TaskStatusUpdateEvent):
925
1146
  try:
926
1147
  status_event = payload_data
1148
+
927
1149
  data_parts = a2a.get_data_parts_from_status_update(
928
1150
  status_event
929
1151
  )
930
1152
  if data_parts:
931
- for data_part in data_parts:
932
- log.info(
933
- "%s Received DataPart signal from peer for sub-task %s. Forwarding...",
934
- component.log_identifier,
935
- sub_task_id,
1153
+
1154
+ peer_agent_name = (
1155
+ status_event.metadata.get(
1156
+ "agent_name", "UnknownPeer"
936
1157
  )
937
- correlation_data = await component._get_correlation_data_for_sub_task(
1158
+ if status_event.metadata
1159
+ else "UnknownPeer"
1160
+ )
1161
+
1162
+ correlation_data = (
1163
+ await component._get_correlation_data_for_sub_task(
938
1164
  sub_task_id
939
1165
  )
940
- if not correlation_data:
941
- log.warning(
942
- "%s Correlation data not found for sub-task %s. Cannot forward status signal.",
943
- component.log_identifier,
944
- sub_task_id,
945
- )
946
- message.call_acknowledgements()
947
- return
948
-
949
- original_task_context = correlation_data.get(
950
- "original_task_context"
1166
+ )
1167
+ if not correlation_data:
1168
+ log.warning(
1169
+ "%s Correlation data not found for sub-task %s. Cannot forward status signal.",
1170
+ component.log_identifier,
1171
+ sub_task_id,
951
1172
  )
952
- if not original_task_context:
953
- log.warning(
954
- "%s original_task_context not found in correlation data for sub-task %s. Cannot forward status signal.",
955
- component.log_identifier,
956
- sub_task_id,
957
- )
958
- message.call_acknowledgements()
959
- return
1173
+ message.call_acknowledgements()
1174
+ return
960
1175
 
961
- main_logical_task_id = original_task_context.get(
962
- "logical_task_id"
963
- )
964
- original_jsonrpc_request_id = (
965
- original_task_context.get("jsonrpc_request_id")
1176
+ original_task_context = correlation_data.get(
1177
+ "original_task_context"
1178
+ )
1179
+ if not original_task_context:
1180
+ log.warning(
1181
+ "%s original_task_context not found in correlation data for sub-task %s. Cannot forward status signal.",
1182
+ component.log_identifier,
1183
+ sub_task_id,
966
1184
  )
967
- main_context_id = original_task_context.get(
968
- "contextId"
1185
+ message.call_acknowledgements()
1186
+ return
1187
+
1188
+ main_logical_task_id = original_task_context.get(
1189
+ "logical_task_id"
1190
+ )
1191
+ original_jsonrpc_request_id = original_task_context.get(
1192
+ "jsonrpc_request_id"
1193
+ )
1194
+ main_context_id = original_task_context.get("contextId")
1195
+
1196
+ target_topic_for_forward = original_task_context.get(
1197
+ "statusTopic"
1198
+ )
1199
+
1200
+ if (
1201
+ not main_logical_task_id
1202
+ or not original_jsonrpc_request_id
1203
+ or not target_topic_for_forward
1204
+ ):
1205
+ log.error(
1206
+ "%s Missing critical info (main_task_id, original_rpc_id, or target_status_topic) in context for sub-task %s. Cannot forward. Context: %s",
1207
+ component.log_identifier,
1208
+ sub_task_id,
1209
+ original_task_context,
969
1210
  )
1211
+ message.call_acknowledgements()
1212
+ return
970
1213
 
971
- target_topic_for_forward = (
972
- original_task_context.get("statusTopic")
1214
+ event_metadata = {
1215
+ "agent_name": component.agent_name,
1216
+ "forwarded_from_peer": peer_agent_name,
1217
+ "original_peer_event_taskId": status_event.task_id,
1218
+ "original_peer_event_timestamp": (
1219
+ status_event.status.timestamp
1220
+ if status_event.status
1221
+ and status_event.status.timestamp
1222
+ else None
1223
+ ),
1224
+ "function_call_id": correlation_data.get(
1225
+ "adk_function_call_id", None
1226
+ ),
1227
+ }
1228
+
1229
+ if (
1230
+ status_event.status.state
1231
+ == TaskState.input_required
1232
+ ):
1233
+ log.debug(
1234
+ "%s Received input-required status for sub-task %s. Requesting user input. Forwarding to target.",
1235
+ component.log_identifier,
1236
+ sub_task_id,
973
1237
  )
974
1238
 
975
1239
  if (
976
- not main_logical_task_id
977
- or not original_jsonrpc_request_id
978
- or not target_topic_for_forward
1240
+ status_event.metadata
1241
+ and "task_call_stack" in status_event.metadata
1242
+ and isinstance(
1243
+ status_event.metadata["task_call_stack"],
1244
+ list,
1245
+ )
979
1246
  ):
980
- log.error(
981
- "%s Missing critical info (main_task_id, original_rpc_id, or target_status_topic) in context for sub-task %s. Cannot forward. Context: %s",
982
- component.log_identifier,
983
- sub_task_id,
984
- original_task_context,
1247
+ task_call_stack = status_event.metadata[
1248
+ "task_call_stack"
1249
+ ].copy()
1250
+ task_call_stack.insert(0, sub_task_id)
1251
+ event_metadata["task_call_stack"] = (
1252
+ task_call_stack
985
1253
  )
986
- message.call_acknowledgements()
987
- return
1254
+ else:
1255
+ event_metadata["task_call_stack"] = [
1256
+ sub_task_id
1257
+ ]
1258
+
1259
+ status_event.metadata = event_metadata
1260
+ status_event.task_id = main_logical_task_id
1261
+
1262
+ _forward_jsonrpc_response(
1263
+ component=component,
1264
+ original_jsonrpc_request_id=original_jsonrpc_request_id,
1265
+ result_data=status_event,
1266
+ target_topic=target_topic_for_forward,
1267
+ main_logical_task_id=main_logical_task_id,
1268
+ peer_agent_name=peer_agent_name,
1269
+ message=message,
1270
+ )
1271
+ return
988
1272
 
989
- peer_agent_name = (
990
- status_event.metadata.get(
991
- "agent_name", "UnknownPeer"
992
- )
993
- if status_event.metadata
994
- else "UnknownPeer"
1273
+ for data_part in data_parts:
1274
+ log.info(
1275
+ "%s Received DataPart signal from peer for sub-task %s. Forwarding...",
1276
+ component.log_identifier,
1277
+ sub_task_id,
995
1278
  )
996
1279
 
997
1280
  forwarded_message = a2a.create_agent_parts_message(
998
1281
  parts=[data_part],
999
- metadata={
1000
- "agent_name": component.agent_name,
1001
- "forwarded_from_peer": peer_agent_name,
1002
- "original_peer_event_taskId": status_event.task_id,
1003
- "original_peer_event_timestamp": (
1004
- status_event.status.timestamp
1005
- if status_event.status
1006
- and status_event.status.timestamp
1007
- else None
1008
- ),
1009
- "function_call_id": correlation_data.get(
1010
- "adk_function_call_id", None
1011
- ),
1012
- },
1282
+ metadata=event_metadata,
1013
1283
  )
1284
+
1014
1285
  forwarded_event = a2a.create_status_update(
1015
1286
  task_id=main_logical_task_id,
1016
1287
  context_id=main_context_id,
@@ -1024,36 +1295,15 @@ async def handle_a2a_response(component, message: SolaceMessage):
1024
1295
  forwarded_event.status.timestamp = (
1025
1296
  status_event.status.timestamp
1026
1297
  )
1027
- forwarded_rpc_response = JSONRPCResponse(
1028
- id=original_jsonrpc_request_id,
1029
- result=forwarded_event,
1030
- )
1031
- payload_to_publish = (
1032
- forwarded_rpc_response.model_dump(
1033
- by_alias=True, exclude_none=True
1034
- )
1298
+ _forward_jsonrpc_response(
1299
+ component=component,
1300
+ original_jsonrpc_request_id=original_jsonrpc_request_id,
1301
+ result_data=forwarded_event,
1302
+ target_topic=target_topic_for_forward,
1303
+ main_logical_task_id=main_logical_task_id,
1304
+ peer_agent_name=peer_agent_name,
1305
+ message=message,
1035
1306
  )
1036
-
1037
- try:
1038
- component.publish_a2a_message(
1039
- payload_to_publish,
1040
- target_topic_for_forward,
1041
- )
1042
- log.info(
1043
- "%s Forwarded DataPart signal for main task %s (from peer %s) to %s.",
1044
- component.log_identifier,
1045
- main_logical_task_id,
1046
- peer_agent_name,
1047
- target_topic_for_forward,
1048
- )
1049
- except Exception as pub_err:
1050
- log.exception(
1051
- "%s Failed to publish forwarded status signal for main task %s: %s",
1052
- component.log_identifier,
1053
- main_logical_task_id,
1054
- pub_err,
1055
- )
1056
- message.call_acknowledgements()
1057
1307
  return
1058
1308
 
1059
1309
  payload_to_queue = status_event.model_dump(
@@ -1581,81 +1831,95 @@ def handle_sam_event(component, message, topic):
1581
1831
  """Handle incoming SAM system events."""
1582
1832
  try:
1583
1833
  payload = message.get_payload()
1584
-
1834
+
1585
1835
  if not isinstance(payload, dict):
1586
1836
  log.warning("Invalid SAM event payload - not a dict")
1587
1837
  message.call_acknowledgements()
1588
1838
  return
1589
-
1839
+
1590
1840
  event_type = payload.get("event_type")
1591
1841
  if not event_type:
1592
1842
  log.warning("SAM event missing event_type field")
1593
1843
  message.call_acknowledgements()
1594
1844
  return
1595
-
1845
+
1596
1846
  log.info("%s Received SAM event: %s", component.log_identifier, event_type)
1597
-
1847
+
1598
1848
  if event_type == "session.deleted":
1599
1849
  data = payload.get("data", {})
1600
1850
  session_id = data.get("session_id")
1601
1851
  user_id = data.get("user_id")
1602
1852
  agent_id = data.get("agent_id")
1603
-
1853
+
1604
1854
  if not all([session_id, user_id, agent_id]):
1605
1855
  log.warning("Missing required fields in session.deleted event")
1606
1856
  message.call_acknowledgements()
1607
1857
  return
1608
-
1858
+
1609
1859
  current_agent = component.get_config("agent_name")
1610
-
1860
+
1611
1861
  if agent_id == current_agent:
1612
- log.info("%s Processing session.deleted event for session %s",
1613
- component.log_identifier, session_id)
1614
- asyncio.create_task(cleanup_agent_session(component, session_id, user_id))
1862
+ log.info(
1863
+ "%s Processing session.deleted event for session %s",
1864
+ component.log_identifier,
1865
+ session_id,
1866
+ )
1867
+ asyncio.create_task(
1868
+ cleanup_agent_session(component, session_id, user_id)
1869
+ )
1615
1870
  else:
1616
- log.debug("Session deletion event for different agent: %s != %s", agent_id, current_agent)
1871
+ log.debug(
1872
+ "Session deletion event for different agent: %s != %s",
1873
+ agent_id,
1874
+ current_agent,
1875
+ )
1617
1876
  else:
1618
1877
  log.debug("Unhandled SAM event type: %s", event_type)
1619
-
1878
+
1620
1879
  message.call_acknowledgements()
1621
-
1880
+
1622
1881
  except Exception as e:
1623
1882
  log.error("Error handling SAM event %s: %s", topic, e)
1624
1883
  message.call_acknowledgements()
1625
1884
 
1626
1885
 
1627
-
1628
1886
  async def cleanup_agent_session(component, session_id: str, user_id: str):
1629
1887
  """Clean up agent-side session data."""
1630
1888
  try:
1631
1889
  log.info("Starting cleanup for session %s, user %s", session_id, user_id)
1632
-
1633
- if hasattr(component, 'session_service') and component.session_service:
1890
+
1891
+ if hasattr(component, "session_service") and component.session_service:
1634
1892
  agent_name = component.get_config("agent_name")
1635
- log.info("Deleting session %s from agent %s session service", session_id, agent_name)
1893
+ log.info(
1894
+ "Deleting session %s from agent %s session service",
1895
+ session_id,
1896
+ agent_name,
1897
+ )
1636
1898
  await component.session_service.delete_session(
1637
- app_name=agent_name,
1638
- user_id=user_id,
1639
- session_id=session_id
1899
+ app_name=agent_name, user_id=user_id, session_id=session_id
1640
1900
  )
1641
1901
  log.info("Successfully deleted session %s from session service", session_id)
1642
1902
  else:
1643
1903
  log.info("No session service available for cleanup")
1644
-
1904
+
1645
1905
  with component.active_tasks_lock:
1646
1906
  tasks_to_cancel = []
1647
1907
  for task_id, context in component.active_tasks.items():
1648
- if (hasattr(context, 'a2a_context') and
1649
- context.a2a_context.get('session_id') == session_id):
1908
+ if (
1909
+ hasattr(context, "a2a_context")
1910
+ and context.a2a_context.get("session_id") == session_id
1911
+ ):
1650
1912
  tasks_to_cancel.append(task_id)
1651
-
1913
+
1652
1914
  for task_id in tasks_to_cancel:
1653
1915
  context = component.active_tasks.get(task_id)
1654
1916
  if context:
1655
1917
  context.cancel()
1656
- log.info("Cancelled task %s for deleted session %s", task_id, session_id)
1657
-
1918
+ log.info(
1919
+ "Cancelled task %s for deleted session %s", task_id, session_id
1920
+ )
1921
+
1658
1922
  log.info("Session cleanup completed for session %s", session_id)
1659
-
1923
+
1660
1924
  except Exception as e:
1661
1925
  log.error("Error cleaning up session %s: %s", session_id, e)