solace-agent-mesh 1.6.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.

Potentially problematic release.


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

Files changed (79) hide show
  1. solace_agent_mesh/agent/protocol/event_handlers.py +173 -30
  2. solace_agent_mesh/agent/proxies/base/component.py +35 -4
  3. solace_agent_mesh/agent/sac/app.py +54 -7
  4. solace_agent_mesh/agent/sac/component.py +84 -73
  5. solace_agent_mesh/agent/sac/task_execution_context.py +46 -0
  6. solace_agent_mesh/assets/docs/404.html +3 -3
  7. solace_agent_mesh/assets/docs/assets/js/{e3d9abda.2b916f9e.js → e3d9abda.6b9493d0.js} +1 -1
  8. solace_agent_mesh/assets/docs/assets/js/{main.20feee82.js → main.b12eac43.js} +2 -2
  9. solace_agent_mesh/assets/docs/assets/js/{runtime~main.0d198646.js → runtime~main.e268214e.js} +1 -1
  10. solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +3 -3
  11. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +3 -3
  12. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +3 -3
  13. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +3 -3
  14. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +3 -3
  15. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +3 -3
  16. solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +3 -3
  17. solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +3 -3
  18. solace_agent_mesh/assets/docs/docs/documentation/components/index.html +3 -3
  19. solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +3 -3
  20. solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +3 -3
  21. solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +3 -3
  22. solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +3 -3
  23. solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +3 -3
  24. solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +3 -3
  25. solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +3 -3
  26. solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +3 -3
  27. solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +3 -3
  28. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +3 -3
  29. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +3 -3
  30. solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +3 -3
  31. solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +3 -3
  32. solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +3 -3
  33. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +3 -3
  34. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +3 -3
  35. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +3 -3
  36. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +3 -3
  37. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +3 -3
  38. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +3 -3
  39. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +3 -3
  40. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +3 -3
  41. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +3 -3
  42. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +3 -3
  43. solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +3 -3
  44. solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +3 -3
  45. solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +3 -3
  46. solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +3 -3
  47. solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +3 -3
  48. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
  49. solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +3 -3
  50. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +3 -3
  51. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +3 -3
  52. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +3 -3
  53. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +3 -3
  54. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +3 -3
  55. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
  56. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +3 -3
  57. solace_agent_mesh/assets/docs/lunr-index-1761248203150.json +1 -0
  58. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  59. solace_agent_mesh/assets/docs/search-doc-1761248203150.json +1 -0
  60. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  61. solace_agent_mesh/cli/__init__.py +1 -1
  62. solace_agent_mesh/client/webui/frontend/static/assets/main-B32noGmR.js +342 -0
  63. solace_agent_mesh/client/webui/frontend/static/index.html +1 -1
  64. solace_agent_mesh/common/a2a/protocol.py +78 -0
  65. solace_agent_mesh/common/sac/sam_component_base.py +383 -4
  66. solace_agent_mesh/gateway/base/app.py +15 -0
  67. solace_agent_mesh/gateway/base/component.py +104 -38
  68. solace_agent_mesh/gateway/http_sse/component.py +1 -1
  69. solace_agent_mesh/gateway/http_sse/main.py +2 -2
  70. solace_agent_mesh/gateway/http_sse/routers/users.py +47 -1
  71. {solace_agent_mesh-1.6.0.dist-info → solace_agent_mesh-1.6.1.dist-info}/METADATA +1 -1
  72. {solace_agent_mesh-1.6.0.dist-info → solace_agent_mesh-1.6.1.dist-info}/RECORD +76 -76
  73. solace_agent_mesh/assets/docs/lunr-index-1761165361160.json +0 -1
  74. solace_agent_mesh/assets/docs/search-doc-1761165361160.json +0 -1
  75. solace_agent_mesh/client/webui/frontend/static/assets/main-BGTaW0uv.js +0 -342
  76. /solace_agent_mesh/assets/docs/assets/js/{main.20feee82.js.LICENSE.txt → main.b12eac43.js.LICENSE.txt} +0 -0
  77. {solace_agent_mesh-1.6.0.dist-info → solace_agent_mesh-1.6.1.dist-info}/WHEEL +0 -0
  78. {solace_agent_mesh-1.6.0.dist-info → solace_agent_mesh-1.6.1.dist-info}/entry_points.txt +0 -0
  79. {solace_agent_mesh-1.6.0.dist-info → solace_agent_mesh-1.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -49,6 +49,7 @@ from google.adk.agents.run_config import StreamingMode
49
49
 
50
50
  log = logging.getLogger(__name__)
51
51
 
52
+
52
53
  def _register_peer_artifacts_in_parent_context(
53
54
  parent_task_context: "TaskExecutionContext",
54
55
  peer_task_object: Task,
@@ -123,6 +124,30 @@ async def process_event(component, event: Event):
123
124
  agent_status_sub_prefix
124
125
  ):
125
126
  await handle_a2a_response(component, message)
127
+ elif hasattr(component, "trust_manager") and component.trust_manager:
128
+ # Check if this is a trust card message (enterprise feature)
129
+ try:
130
+ if component.trust_manager.is_trust_card_topic(topic):
131
+ await component.trust_manager.handle_trust_card_message(
132
+ message, topic
133
+ )
134
+ message.call_acknowledgements()
135
+ return
136
+ except Exception as e:
137
+ log.error(
138
+ "%s Error handling trust card message: %s",
139
+ component.log_identifier,
140
+ e,
141
+ )
142
+ message.call_acknowledgements()
143
+ return
144
+
145
+ log.warning(
146
+ "%s Received message on unhandled topic: %s",
147
+ component.log_identifier,
148
+ topic,
149
+ )
150
+ message.call_acknowledgements()
126
151
  else:
127
152
  log.warning(
128
153
  "%s Received message on unhandled topic: %s",
@@ -229,12 +254,11 @@ async def handle_a2a_request(component, message: SolaceMessage):
229
254
  payload_dict = message.get_payload()
230
255
  if not isinstance(payload_dict, dict):
231
256
  raise ValueError("Payload is not a dictionary.")
232
-
233
-
257
+
234
258
  a2a_request: A2ARequest = A2ARequest.model_validate(payload_dict)
235
259
  jsonrpc_request_id = a2a.get_request_id(a2a_request)
236
260
 
237
- # Extract properties from message user properties
261
+ # Extract properties from message user properties
238
262
  client_id = message.get_user_properties().get("clientId", "default_client")
239
263
  status_topic_from_peer = message.get_user_properties().get("a2aStatusTopic")
240
264
  reply_topic_from_peer = message.get_user_properties().get("replyTo")
@@ -248,6 +272,90 @@ async def handle_a2a_request(component, message: SolaceMessage):
248
272
  # For Send, we will generate it.
249
273
  logical_task_id = None
250
274
  method = a2a.get_request_method(a2a_request)
275
+
276
+ # Enterprise feature: Verify user authentication if trust manager enabled
277
+ verified_user_identity = None
278
+ if hasattr(component, "trust_manager") and component.trust_manager:
279
+ # Determine task_id for verification
280
+ if method == "tasks/cancel":
281
+ verification_task_id = a2a.get_task_id_from_cancel_request(a2a_request)
282
+ elif method in ["message/send", "message/stream"]:
283
+ verification_task_id = str(a2a.get_request_id(a2a_request))
284
+ else:
285
+ verification_task_id = None
286
+
287
+ if verification_task_id:
288
+ try:
289
+ # Enterprise handles all verification logic
290
+ verified_user_identity = (
291
+ component.trust_manager.verify_request_authentication(
292
+ message=message,
293
+ task_id=verification_task_id,
294
+ namespace=namespace,
295
+ jsonrpc_request_id=jsonrpc_request_id,
296
+ )
297
+ )
298
+
299
+ if verified_user_identity:
300
+ log.info(
301
+ "%s Successfully authenticated user '%s' for task %s",
302
+ component.log_identifier,
303
+ verified_user_identity.get("user_id"),
304
+ verification_task_id,
305
+ )
306
+
307
+ except Exception as e:
308
+ # Authentication failed - enterprise provides error details
309
+ log.error(
310
+ "%s Authentication failed for task %s: %s",
311
+ component.log_identifier,
312
+ verification_task_id,
313
+ e,
314
+ )
315
+
316
+ # Build error response using enterprise exception data if available
317
+ error_data = {
318
+ "reason": "authentication_failed",
319
+ "task_id": verification_task_id,
320
+ }
321
+ if hasattr(e, "create_error_response_data"):
322
+ error_data = e.create_error_response_data()
323
+
324
+ error_response = a2a.create_invalid_request_error_response(
325
+ message="Authentication failed",
326
+ request_id=jsonrpc_request_id,
327
+ data=error_data,
328
+ )
329
+
330
+ # Determine reply topic
331
+ reply_topic = message.get_user_properties().get("replyTo")
332
+ if not reply_topic:
333
+ client_id = message.get_user_properties().get(
334
+ "clientId", "default_client"
335
+ )
336
+ reply_topic = a2a.get_client_response_topic(
337
+ namespace, client_id
338
+ )
339
+
340
+ component.publish_a2a_message(
341
+ payload=error_response.model_dump(exclude_none=True),
342
+ topic=reply_topic,
343
+ )
344
+
345
+ try:
346
+ message.call_acknowledgements()
347
+ log.debug(
348
+ "%s ACKed message with failed authentication",
349
+ component.log_identifier,
350
+ )
351
+ except Exception as ack_e:
352
+ log.error(
353
+ "%s Failed to ACK message after authentication failure: %s",
354
+ component.log_identifier,
355
+ ack_e,
356
+ )
357
+ return None
358
+
251
359
  if method == "tasks/cancel":
252
360
  logical_task_id = a2a.get_task_id_from_cancel_request(a2a_request)
253
361
  log.info(
@@ -537,6 +645,15 @@ async def handle_a2a_request(component, message: SolaceMessage):
537
645
  "response_format": response_format,
538
646
  "host_agent_name": agent_name,
539
647
  }
648
+
649
+ # Store verified user identity claims in a2a_context (not the raw token)
650
+ if verified_user_identity:
651
+ a2a_context["verified_user_identity"] = verified_user_identity
652
+ log.debug(
653
+ "%s Stored verified user identity in a2a_context for task %s",
654
+ component.log_identifier,
655
+ logical_task_id,
656
+ )
540
657
  log.debug(
541
658
  "%s A2A Context (shared service model): %s",
542
659
  component.log_identifier,
@@ -547,6 +664,18 @@ async def handle_a2a_request(component, message: SolaceMessage):
547
664
  task_context = TaskExecutionContext(
548
665
  task_id=logical_task_id, a2a_context=a2a_context
549
666
  )
667
+
668
+ # Store auth token for peer delegation using generic security storage
669
+ if hasattr(component, "trust_manager") and component.trust_manager:
670
+ auth_token = message.get_user_properties().get("authToken")
671
+ if auth_token:
672
+ task_context.set_security_data("auth_token", auth_token)
673
+ log.debug(
674
+ "%s Stored authentication token in TaskExecutionContext security storage for task %s",
675
+ component.log_identifier,
676
+ logical_task_id,
677
+ )
678
+
550
679
  with component.active_tasks_lock:
551
680
  component.active_tasks[logical_task_id] = task_context
552
681
  log.info(
@@ -1581,81 +1710,95 @@ def handle_sam_event(component, message, topic):
1581
1710
  """Handle incoming SAM system events."""
1582
1711
  try:
1583
1712
  payload = message.get_payload()
1584
-
1713
+
1585
1714
  if not isinstance(payload, dict):
1586
1715
  log.warning("Invalid SAM event payload - not a dict")
1587
1716
  message.call_acknowledgements()
1588
1717
  return
1589
-
1718
+
1590
1719
  event_type = payload.get("event_type")
1591
1720
  if not event_type:
1592
1721
  log.warning("SAM event missing event_type field")
1593
1722
  message.call_acknowledgements()
1594
1723
  return
1595
-
1724
+
1596
1725
  log.info("%s Received SAM event: %s", component.log_identifier, event_type)
1597
-
1726
+
1598
1727
  if event_type == "session.deleted":
1599
1728
  data = payload.get("data", {})
1600
1729
  session_id = data.get("session_id")
1601
1730
  user_id = data.get("user_id")
1602
1731
  agent_id = data.get("agent_id")
1603
-
1732
+
1604
1733
  if not all([session_id, user_id, agent_id]):
1605
1734
  log.warning("Missing required fields in session.deleted event")
1606
1735
  message.call_acknowledgements()
1607
1736
  return
1608
-
1737
+
1609
1738
  current_agent = component.get_config("agent_name")
1610
-
1739
+
1611
1740
  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))
1741
+ log.info(
1742
+ "%s Processing session.deleted event for session %s",
1743
+ component.log_identifier,
1744
+ session_id,
1745
+ )
1746
+ asyncio.create_task(
1747
+ cleanup_agent_session(component, session_id, user_id)
1748
+ )
1615
1749
  else:
1616
- log.debug("Session deletion event for different agent: %s != %s", agent_id, current_agent)
1750
+ log.debug(
1751
+ "Session deletion event for different agent: %s != %s",
1752
+ agent_id,
1753
+ current_agent,
1754
+ )
1617
1755
  else:
1618
1756
  log.debug("Unhandled SAM event type: %s", event_type)
1619
-
1757
+
1620
1758
  message.call_acknowledgements()
1621
-
1759
+
1622
1760
  except Exception as e:
1623
1761
  log.error("Error handling SAM event %s: %s", topic, e)
1624
1762
  message.call_acknowledgements()
1625
1763
 
1626
1764
 
1627
-
1628
1765
  async def cleanup_agent_session(component, session_id: str, user_id: str):
1629
1766
  """Clean up agent-side session data."""
1630
1767
  try:
1631
1768
  log.info("Starting cleanup for session %s, user %s", session_id, user_id)
1632
-
1633
- if hasattr(component, 'session_service') and component.session_service:
1769
+
1770
+ if hasattr(component, "session_service") and component.session_service:
1634
1771
  agent_name = component.get_config("agent_name")
1635
- log.info("Deleting session %s from agent %s session service", session_id, agent_name)
1772
+ log.info(
1773
+ "Deleting session %s from agent %s session service",
1774
+ session_id,
1775
+ agent_name,
1776
+ )
1636
1777
  await component.session_service.delete_session(
1637
- app_name=agent_name,
1638
- user_id=user_id,
1639
- session_id=session_id
1778
+ app_name=agent_name, user_id=user_id, session_id=session_id
1640
1779
  )
1641
1780
  log.info("Successfully deleted session %s from session service", session_id)
1642
1781
  else:
1643
1782
  log.info("No session service available for cleanup")
1644
-
1783
+
1645
1784
  with component.active_tasks_lock:
1646
1785
  tasks_to_cancel = []
1647
1786
  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):
1787
+ if (
1788
+ hasattr(context, "a2a_context")
1789
+ and context.a2a_context.get("session_id") == session_id
1790
+ ):
1650
1791
  tasks_to_cancel.append(task_id)
1651
-
1792
+
1652
1793
  for task_id in tasks_to_cancel:
1653
1794
  context = component.active_tasks.get(task_id)
1654
1795
  if context:
1655
1796
  context.cancel()
1656
- log.info("Cancelled task %s for deleted session %s", task_id, session_id)
1657
-
1797
+ log.info(
1798
+ "Cancelled task %s for deleted session %s", task_id, session_id
1799
+ )
1800
+
1658
1801
  log.info("Session cleanup completed for session %s", session_id)
1659
-
1802
+
1660
1803
  except Exception as e:
1661
1804
  log.error("Error cleaning up session %s: %s", session_id, e)
@@ -140,12 +140,23 @@ class BaseProxyComponent(ComponentBase, ABC):
140
140
  future = asyncio.run_coroutine_threadsafe(
141
141
  self._process_event_async(event), self._async_loop
142
142
  )
143
- future.add_done_callback(self._handle_scheduled_task_completion)
143
+ # Pass the event to the completion handler so it can NACK on failure
144
+ future.add_done_callback(
145
+ lambda f: self._handle_scheduled_task_completion(f, event)
146
+ )
144
147
 
145
148
  async def _process_event_async(self, event: Event):
146
149
  """Asynchronous event processing logic."""
147
150
  if event.event_type == EventType.MESSAGE:
148
- await self._handle_a2a_request(event.data)
151
+ message_handled = False
152
+ try:
153
+ await self._handle_a2a_request(event.data)
154
+ message_handled = True
155
+ finally:
156
+ # Mark that we attempted to handle the message
157
+ # (success/failure ack/nack is done inside _handle_a2a_request)
158
+ if not hasattr(event, "_proxy_message_handled"):
159
+ event._proxy_message_handled = message_handled
149
160
  elif event.event_type == EventType.TIMER:
150
161
  if event.data.get("timer_id") == self._discovery_timer_id:
151
162
  await self._discover_and_publish_agents()
@@ -510,8 +521,10 @@ class BaseProxyComponent(ComponentBase, ABC):
510
521
  self._async_init_future.set_exception, e
511
522
  )
512
523
 
513
- def _handle_scheduled_task_completion(self, future: concurrent.futures.Future):
514
- """Callback to log exceptions from tasks scheduled on the async loop."""
524
+ def _handle_scheduled_task_completion(
525
+ self, future: concurrent.futures.Future, event: Event
526
+ ):
527
+ """Callback to log exceptions from tasks scheduled on the async loop and NACK messages on failure."""
515
528
  if future.done() and future.exception():
516
529
  log.error(
517
530
  "%s Coroutine scheduled on async loop failed: %s",
@@ -519,6 +532,24 @@ class BaseProxyComponent(ComponentBase, ABC):
519
532
  future.exception(),
520
533
  exc_info=future.exception(),
521
534
  )
535
+ # NACK the message if this was a MESSAGE event that failed before being handled
536
+ # The _proxy_message_handled flag is set in _process_event_async to track
537
+ # whether _handle_a2a_request was entered (where ack/nack is normally done)
538
+ if event.event_type == EventType.MESSAGE:
539
+ message_handled = getattr(event, "_proxy_message_handled", False)
540
+ if not message_handled:
541
+ try:
542
+ event.data.call_negative_acknowledgements()
543
+ log.warning(
544
+ "%s NACKed message due to async processing failure before entering request handler.",
545
+ self.log_identifier,
546
+ )
547
+ except Exception as nack_e:
548
+ log.error(
549
+ "%s Failed to NACK message after async processing failure: %s",
550
+ self.log_identifier,
551
+ nack_e,
552
+ )
522
553
 
523
554
  def _publish_discovered_cards(self):
524
555
  """Publishes all agent cards currently in the registry."""
@@ -23,7 +23,13 @@ from ...common.a2a import (
23
23
  get_agent_status_subscription_topic,
24
24
  get_sam_events_subscription_topic,
25
25
  )
26
- from ...common.constants import DEFAULT_COMMUNICATION_TIMEOUT, TEXT_ARTIFACT_CONTEXT_MAX_LENGTH_CAPACITY, TEXT_ARTIFACT_CONTEXT_DEFAULT_LENGTH, HEALTH_CHECK_TTL_SECONDS, HEALTH_CHECK_INTERVAL_SECONDS
26
+ from ...common.constants import (
27
+ DEFAULT_COMMUNICATION_TIMEOUT,
28
+ TEXT_ARTIFACT_CONTEXT_MAX_LENGTH_CAPACITY,
29
+ TEXT_ARTIFACT_CONTEXT_DEFAULT_LENGTH,
30
+ HEALTH_CHECK_TTL_SECONDS,
31
+ HEALTH_CHECK_INTERVAL_SECONDS,
32
+ )
27
33
  from ...agent.sac.component import SamAgentComponent
28
34
  from ...agent.utils.artifact_helpers import DEFAULT_SCHEMA_MAX_KEYS
29
35
  from ...common.utils.pydantic_utils import SamConfigBase
@@ -31,6 +37,14 @@ from ..tools.tool_config_types import AnyToolConfig
31
37
 
32
38
  log = logging.getLogger(__name__)
33
39
 
40
+ # Try to import TrustManagerConfig from enterprise repo
41
+ try:
42
+ from solace_agent_mesh_enterprise.common.trust.config import TrustManagerConfig
43
+
44
+ except ImportError:
45
+ # Enterprise features not available - create a placeholder type
46
+ TrustManagerConfig = Dict[str, Any] # type: ignore
47
+
34
48
  info = {
35
49
  "class_name": "SamAgentApp",
36
50
  "description": "Custom App class for SAM Agent Host with namespace prefixing and automatic subscription generation.",
@@ -80,10 +94,12 @@ class AgentDiscoveryConfig(SamConfigBase):
80
94
  default=True, description="Enable discovery and instruction injection."
81
95
  )
82
96
  health_check_ttl_seconds: int = Field(
83
- default=HEALTH_CHECK_TTL_SECONDS, description="Time-to-live in seconds after which an unresponsive agent is de-registered."
97
+ default=HEALTH_CHECK_TTL_SECONDS,
98
+ description="Time-to-live in seconds after which an unresponsive agent is de-registered.",
84
99
  )
85
100
  health_check_interval_seconds: int = Field(
86
- default=HEALTH_CHECK_INTERVAL_SECONDS, description="Interval in seconds between health checks."
101
+ default=HEALTH_CHECK_INTERVAL_SECONDS,
102
+ description="Interval in seconds between health checks.",
87
103
  )
88
104
 
89
105
 
@@ -218,7 +234,9 @@ class ArtifactServiceConfig(SamConfigBase):
218
234
  class SessionServiceConfig(SamConfigBase):
219
235
  """Configuration for the ADK Session Service."""
220
236
 
221
- type: str = Field(..., description="Service type (e.g., 'memory', 'sql', 'vertex_rag').")
237
+ type: str = Field(
238
+ ..., description="Service type (e.g., 'memory', 'sql', 'vertex_rag')."
239
+ )
222
240
  default_behavior: Literal["PERSISTENT", "RUN_BASED"] = Field(
223
241
  default="PERSISTENT", description="Default behavior for session service."
224
242
  )
@@ -235,10 +253,21 @@ class SamAgentAppConfig(SamConfigBase):
235
253
  description="Absolute topic prefix for A2A communication (e.g., 'myorg/dev').",
236
254
  )
237
255
  agent_name: str = Field(..., description="Unique name for this ADK agent instance.")
238
- display_name: str = Field(default=None, description="Human-friendly display name for this ADK agent instance.")
256
+ display_name: str = Field(
257
+ default=None,
258
+ description="Human-friendly display name for this ADK agent instance.",
259
+ )
260
+ deployment: Optional[Dict[str, Any]] = Field(
261
+ default=None,
262
+ description="Deployment tracking information for rolling updates and version control.",
263
+ )
239
264
  model: Union[str, Dict[str, Any]] = Field(
240
265
  ..., description="ADK model name (string) or BaseLlm config dict."
241
266
  )
267
+ trust_manager: Optional[Union[TrustManagerConfig, Dict[str, Any]]] = Field(
268
+ default=None,
269
+ description="Configuration for the Trust Manager (enterprise feature)",
270
+ )
242
271
  instruction: Any = Field(
243
272
  default="",
244
273
  description="User-provided instructions for the ADK agent (string or invoke block).",
@@ -429,6 +458,20 @@ class SamAgentApp(App):
429
458
  get_agent_status_subscription_topic(namespace, agent_name),
430
459
  get_sam_events_subscription_topic(namespace, "session"),
431
460
  ]
461
+
462
+ # Add trust card subscription if trust manager is enabled
463
+ trust_config = app_config.get("trust_manager")
464
+ if trust_config and trust_config.get("enabled", False):
465
+ from ...common.a2a.protocol import get_trust_card_subscription_topic
466
+
467
+ trust_card_topic = get_trust_card_subscription_topic(namespace)
468
+ required_topics.append(trust_card_topic)
469
+ log.info(
470
+ "Trust Manager enabled for agent '%s', added trust card subscription: %s",
471
+ agent_name,
472
+ trust_card_topic,
473
+ )
474
+
432
475
  generated_subs = [{"topic": topic} for topic in required_topics]
433
476
  log.info(
434
477
  "Automatically generated subscriptions for Agent '%s': %s",
@@ -458,8 +501,12 @@ class SamAgentApp(App):
458
501
  broker_config["queue_name"] = generated_queue_name
459
502
  log.debug("Injected generated broker.queue_name: %s", generated_queue_name)
460
503
 
461
- broker_config["temporary_queue"] = app_info.get("broker", {}).get("temporary_queue", True)
462
- log.debug("Set broker_config.temporary_queue = %s", broker_config["temporary_queue"])
504
+ broker_config["temporary_queue"] = app_info.get("broker", {}).get(
505
+ "temporary_queue", True
506
+ )
507
+ log.debug(
508
+ "Set broker_config.temporary_queue = %s", broker_config["temporary_queue"]
509
+ )
463
510
 
464
511
  super().__init__(app_info, **kwargs)
465
512
  log.debug("%s Agent initialization complete.", agent_name)