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
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/assets/favicon-BLgzUch9.ico" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Solace Agent Mesh</title>
8
- <script type="module" crossorigin src="/assets/main-BGTaW0uv.js"></script>
8
+ <script type="module" crossorigin src="/assets/main-B32noGmR.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/vendor-BEmvJSYz.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/client-CaY59VuC.js">
11
11
  <link rel="stylesheet" crossorigin href="/assets/main-DHJKSW1S.css">
@@ -197,6 +197,84 @@ def get_sam_events_subscription_topic(namespace: str, category: str) -> str:
197
197
  return f"{namespace.rstrip('/')}/sam/events/{category}/>"
198
198
 
199
199
 
200
+ def get_trust_card_topic(namespace: str, component_type: str, component_id: str) -> str:
201
+ """
202
+ Returns the topic for publishing a Trust Card.
203
+
204
+ IMPORTANT: The component_id parameter MUST be the exact broker client-username
205
+ that the component uses to authenticate with the Solace broker. This is critical
206
+ for trust verification - trust cards are validated against the actual broker
207
+ authentication identity.
208
+
209
+ Args:
210
+ namespace: SAM namespace
211
+ component_type: Type of component ("gateway", "agent", etc.)
212
+ component_id: MUST be the broker client-username (from broker_username config).
213
+ DO NOT use arbitrary IDs like agent_name or gateway_id unless they
214
+ match the broker_username exactly.
215
+
216
+ Returns:
217
+ Topic string: {namespace}/a2a/v1/trust/{component_type}/{component_id}
218
+
219
+ Raises:
220
+ ValueError: If any parameter is empty
221
+
222
+ Security Note:
223
+ Trust card verification relies on matching the topic component_id with the
224
+ authenticated broker client-username. Using a different value breaks the
225
+ security model and trust chain verification.
226
+ """
227
+ if not namespace:
228
+ raise ValueError("Namespace cannot be empty.")
229
+ if not component_type:
230
+ raise ValueError("Component type cannot be empty.")
231
+ if not component_id:
232
+ raise ValueError("Component ID cannot be empty.")
233
+ return f"{get_a2a_base_topic(namespace)}/trust/{component_type}/{component_id}"
234
+
235
+
236
+ def get_trust_card_subscription_topic(namespace: str, component_type: Optional[str] = None) -> str:
237
+ """
238
+ Returns subscription pattern for Trust Cards.
239
+
240
+ Args:
241
+ namespace: SAM namespace
242
+ component_type: Optional - subscribe to specific type, or None for all types
243
+
244
+ Returns:
245
+ Subscription pattern
246
+ """
247
+ if not namespace:
248
+ raise ValueError("Namespace cannot be empty.")
249
+
250
+ if component_type:
251
+ return f"{get_a2a_base_topic(namespace)}/trust/{component_type}/*"
252
+ else:
253
+ return f"{get_a2a_base_topic(namespace)}/trust/*/*"
254
+
255
+
256
+ def extract_trust_card_info_from_topic(topic: str) -> tuple[str, str]:
257
+ """
258
+ Extracts component type and ID from trust card topic.
259
+
260
+ Args:
261
+ topic: Trust card topic
262
+
263
+ Returns:
264
+ Tuple of (component_type, component_id)
265
+
266
+ Raises:
267
+ ValueError: If topic format is invalid
268
+ """
269
+ parts = topic.split('/')
270
+ if len(parts) < 6 or parts[1] != 'a2a' or parts[2] != 'v1' or parts[3] != 'trust':
271
+ raise ValueError(f"Invalid trust card topic format: {topic}")
272
+
273
+ component_type = parts[4]
274
+ component_id = parts[5]
275
+ return component_type, component_id
276
+
277
+
200
278
  def subscription_to_regex(subscription: str) -> str:
201
279
  """Converts a Solace topic subscription string to a regex pattern."""
202
280
  # Escape regex special characters except for Solace wildcards
@@ -6,7 +6,8 @@ import logging
6
6
  import abc
7
7
  import asyncio
8
8
  import threading
9
- from typing import Any
9
+ import functools
10
+ from typing import Any, Optional
10
11
 
11
12
  from solace_ai_connector.components.component_base import ComponentBase
12
13
 
@@ -15,6 +16,7 @@ from ..utils.message_utils import validate_message_size
15
16
 
16
17
  log = logging.getLogger(__name__)
17
18
 
19
+
18
20
  class SamComponentBase(ComponentBase, abc.ABC):
19
21
  """
20
22
  Abstract base class for high-level SAM components (Agents, Gateways).
@@ -54,8 +56,294 @@ class SamComponentBase(ComponentBase, abc.ABC):
54
56
 
55
57
  self._async_loop: asyncio.AbstractEventLoop | None = None
56
58
  self._async_thread: threading.Thread | None = None
59
+
60
+ # Timer callback registry
61
+ self._timer_callbacks: dict[str, Any] = {}
62
+ self._timer_callbacks_lock = threading.Lock()
63
+
64
+ # Trust Manager integration (enterprise feature) - initialized as part of _late_init
65
+ self.trust_manager: Optional[Any] = None
66
+
57
67
  log.info("%s SamComponentBase initialized successfully.", self.log_identifier)
58
68
 
69
+ def add_timer(
70
+ self,
71
+ delay_ms: int,
72
+ timer_id: str,
73
+ interval_ms: int = 0,
74
+ callback: Optional[Any] = None,
75
+ ):
76
+ """
77
+ Add a timer with optional callback.
78
+
79
+ Args:
80
+ delay_ms: Initial delay in milliseconds
81
+ timer_id: Unique timer identifier
82
+ interval_ms: Repeat interval in milliseconds (0 for one-shot)
83
+ callback: Optional callback function to invoke when timer fires.
84
+ If provided, callback will be invoked when timer event occurs.
85
+ Callback receives timer_data dict as argument.
86
+ Callback should be thread-safe or schedule work appropriately.
87
+ """
88
+ # Register callback if provided
89
+ if callback:
90
+ with self._timer_callbacks_lock:
91
+ if timer_id in self._timer_callbacks:
92
+ log.warning(
93
+ "%s Timer ID '%s' already has a registered callback. Overwriting.",
94
+ self.log_identifier,
95
+ timer_id,
96
+ )
97
+ self._timer_callbacks[timer_id] = callback
98
+ log.debug(
99
+ "%s Registered callback for timer: %s",
100
+ self.log_identifier,
101
+ timer_id,
102
+ )
103
+
104
+ # Call parent implementation to actually create the timer
105
+ super().add_timer(delay_ms=delay_ms, timer_id=timer_id, interval_ms=interval_ms)
106
+
107
+ def cancel_timer(self, timer_id: str):
108
+ """
109
+ Cancel a timer and remove its callback if registered.
110
+
111
+ Args:
112
+ timer_id: Timer identifier to cancel
113
+ """
114
+ # Remove callback registration
115
+ with self._timer_callbacks_lock:
116
+ if timer_id in self._timer_callbacks:
117
+ del self._timer_callbacks[timer_id]
118
+ log.debug(
119
+ "%s Unregistered callback for timer: %s",
120
+ self.log_identifier,
121
+ timer_id,
122
+ )
123
+
124
+ # Call parent implementation to actually cancel the timer
125
+ super().cancel_timer(timer_id)
126
+
127
+ def process_event(self, event):
128
+ """
129
+ Process incoming events by routing to appropriate handlers.
130
+
131
+ This base implementation handles MESSAGE and TIMER events:
132
+ - MESSAGE events are routed to _handle_message() abstract method
133
+ - TIMER events are routed to registered callbacks
134
+ - Other events are passed to parent class
135
+
136
+ Args:
137
+ event: Event object from SAC framework
138
+ """
139
+ from solace_ai_connector.common.event import Event, EventType
140
+ from solace_ai_connector.common.message import Message as SolaceMessage
141
+
142
+ if event.event_type == EventType.MESSAGE:
143
+ message: SolaceMessage = event.data
144
+ topic = message.get_topic()
145
+
146
+ if not topic:
147
+ log.warning(
148
+ "%s Received message without topic. Ignoring.",
149
+ self.log_identifier,
150
+ )
151
+ try:
152
+ message.call_negative_acknowledgements()
153
+ except Exception as nack_e:
154
+ log.error(
155
+ "%s Failed to NACK message without topic: %s",
156
+ self.log_identifier,
157
+ nack_e,
158
+ )
159
+ return
160
+
161
+ try:
162
+ # Delegate to abstract method implemented by subclass
163
+ self._handle_message(message, topic)
164
+ except Exception as e:
165
+ log.error(
166
+ "%s Error in _handle_message for topic %s: %s",
167
+ self.log_identifier,
168
+ topic,
169
+ e,
170
+ exc_info=True,
171
+ )
172
+ try:
173
+ message.call_negative_acknowledgements()
174
+ except Exception as nack_e:
175
+ log.error(
176
+ "%s Failed to NACK message after error: %s",
177
+ self.log_identifier,
178
+ nack_e,
179
+ )
180
+ self.handle_error(e, event)
181
+
182
+ elif event.event_type == EventType.TIMER:
183
+ # Handle timer events via callback registry
184
+ timer_data = event.data
185
+ timer_id = timer_data.get("timer_id")
186
+
187
+ if not timer_id:
188
+ log.warning(
189
+ "%s Timer event missing timer_id: %s",
190
+ self.log_identifier,
191
+ timer_data,
192
+ )
193
+ return
194
+
195
+ # Look up registered callback
196
+ with self._timer_callbacks_lock:
197
+ callback = self._timer_callbacks.get(timer_id)
198
+
199
+ if callback:
200
+ try:
201
+ log.debug(
202
+ "%s Invoking registered callback for timer: %s",
203
+ self.log_identifier,
204
+ timer_id,
205
+ )
206
+ callback(timer_data)
207
+ except Exception as e:
208
+ log.error(
209
+ "%s Error in timer callback for %s: %s",
210
+ self.log_identifier,
211
+ timer_id,
212
+ e,
213
+ exc_info=True,
214
+ )
215
+ else:
216
+ log.warning(
217
+ "%s No callback registered for timer: %s. Timer event ignored.",
218
+ self.log_identifier,
219
+ timer_id,
220
+ )
221
+ else:
222
+ # Pass other event types to parent class
223
+ super().process_event(event)
224
+
225
+ def _handle_message(self, message, topic: str) -> None:
226
+ """
227
+ Handle an incoming message by routing to async handler.
228
+
229
+ This base implementation schedules async processing on the component's
230
+ event loop. Subclasses can override this for custom sync handling,
231
+ or implement _handle_message_async() for async handling.
232
+
233
+ Args:
234
+ message: The Solace message (SolaceMessage instance)
235
+ topic: The topic the message was received on
236
+ """
237
+ loop = self.get_async_loop()
238
+ if loop and loop.is_running():
239
+ # Schedule async processing
240
+ coro = self._handle_message_async(message, topic)
241
+ future = asyncio.run_coroutine_threadsafe(coro, loop)
242
+ future.add_done_callback(
243
+ functools.partial(self._handle_async_message_completion, topic=topic)
244
+ )
245
+ else:
246
+ log.error(
247
+ "%s Async loop not available. Cannot process message on topic: %s",
248
+ self.log_identifier,
249
+ topic,
250
+ )
251
+ raise RuntimeError("Async loop not available for message processing")
252
+
253
+ def _handle_async_message_completion(self, future: asyncio.Future, topic: str):
254
+ """Callback to handle completion of async message processing."""
255
+ try:
256
+ if future.cancelled():
257
+ log.warning(
258
+ "%s Message processing for topic %s was cancelled.",
259
+ self.log_identifier,
260
+ topic,
261
+ )
262
+ elif future.done():
263
+ exception = future.exception()
264
+ if exception is not None:
265
+ log.error(
266
+ "%s Message processing for topic %s failed: %s",
267
+ self.log_identifier,
268
+ topic,
269
+ exception,
270
+ exc_info=exception,
271
+ )
272
+ else:
273
+ # Handle successful completion
274
+ try:
275
+ _ = future.result()
276
+ log.debug(
277
+ "%s Message processing for topic %s completed successfully.",
278
+ self.log_identifier,
279
+ topic,
280
+ )
281
+ # Optional: Process the result if needed
282
+ # self._process_successful_result(result, topic)
283
+ except Exception as result_exception:
284
+ # This catches exceptions that might occur when getting the result
285
+ log.error(
286
+ "%s Error retrieving result for topic %s: %s",
287
+ self.log_identifier,
288
+ topic,
289
+ result_exception,
290
+ exc_info=result_exception,
291
+ )
292
+ else:
293
+ # This case shouldn't normally occur in a completion callback,
294
+ # but it's good defensive programming
295
+ log.warning(
296
+ "%s Future for topic %s is not done in completion handler.",
297
+ self.log_identifier,
298
+ topic,
299
+ )
300
+ except Exception as e:
301
+ log.error(
302
+ "%s Error in async message completion handler for topic %s: %s",
303
+ self.log_identifier,
304
+ topic,
305
+ e,
306
+ exc_info=True,
307
+ )
308
+
309
+ @abc.abstractmethod
310
+ async def _handle_message_async(self, message, topic: str) -> None:
311
+ """
312
+ Async handler for incoming messages.
313
+
314
+ Subclasses must implement this to process messages asynchronously.
315
+ This runs on the component's dedicated async event loop.
316
+
317
+ Args:
318
+ message: The Solace message (SolaceMessage instance)
319
+ topic: The topic the message was received on
320
+ """
321
+ pass
322
+
323
+ def _late_init(self):
324
+ """Late initialization hook called after the component is fully set up."""
325
+
326
+ # Setup the Trust Manager if present (enterprise feature)
327
+ # NOTE: The Trust Manager should use component.get_broker_username() to retrieve
328
+ # the actual broker client-username for trust card topic construction. This is
329
+ # critical because trust cards MUST be published on topics that match the actual
330
+ # authentication identity (client-username) used to connect to the broker.
331
+ try:
332
+ from solace_agent_mesh_enterprise.common.trust import (
333
+ initialize_trust_manager,
334
+ )
335
+
336
+ trust_config = self.get_config("trust_manager")
337
+ if trust_config and trust_config.get("enabled", False):
338
+ self.trust_manager = initialize_trust_manager(self)
339
+ log.info("%s Enterprise Trust Manager initialized", self.log_identifier)
340
+ except ImportError:
341
+ log.debug("%s Enterprise Trust Manager not available", self.log_identifier)
342
+ except Exception as e:
343
+ log.error(
344
+ "%s Failed to initialize Trust Manager: %s", self.log_identifier, e
345
+ )
346
+
59
347
  def publish_a2a_message(
60
348
  self, payload: dict, topic: str, user_properties: dict | None = None
61
349
  ):
@@ -197,6 +485,10 @@ class SamComponentBase(ComponentBase, abc.ABC):
197
485
  def run(self):
198
486
  """Starts the component's dedicated async thread."""
199
487
  log.info("%s Starting SamComponentBase run method.", self.log_identifier)
488
+
489
+ # Do all initialization that needs to be done after we are fully setup
490
+ self._late_init()
491
+
200
492
  if not self._async_thread or not self._async_thread.is_alive():
201
493
  self._async_thread = threading.Thread(
202
494
  target=self._run_async_operations,
@@ -256,14 +548,101 @@ class SamComponentBase(ComponentBase, abc.ABC):
256
548
  """Returns the dedicated asyncio event loop for this component's async tasks."""
257
549
  return self._async_loop
258
550
 
551
+ def get_broker_username(self) -> Optional[str]:
552
+ """
553
+ Returns the broker username (client-username) that this component uses
554
+ to authenticate with the Solace broker.
555
+
556
+ This is critical for trust card publishing and verification, as the
557
+ trust card topic must match the actual authentication identity.
558
+
559
+ Returns:
560
+ The broker username if available, None otherwise.
561
+ """
562
+ try:
563
+ app = self.get_app()
564
+ if app and hasattr(app, "app_info"):
565
+ broker_config = app.app_info.get("broker", {})
566
+ broker_username = broker_config.get("broker_username")
567
+ if broker_username:
568
+ log.debug(
569
+ "%s Retrieved broker username: %s",
570
+ self.log_identifier,
571
+ broker_username,
572
+ )
573
+ return broker_username
574
+ else:
575
+ log.warning(
576
+ "%s Broker username not found in broker configuration",
577
+ self.log_identifier,
578
+ )
579
+ else:
580
+ log.warning(
581
+ "%s Unable to access app or app_info to retrieve broker username",
582
+ self.log_identifier,
583
+ )
584
+ except Exception as e:
585
+ log.error(
586
+ "%s Error retrieving broker username: %s",
587
+ self.log_identifier,
588
+ e,
589
+ exc_info=True,
590
+ )
591
+ return None
592
+
259
593
  @abc.abstractmethod
260
- async def _async_setup_and_run(self) -> None:
594
+ def _get_component_id(self) -> str:
595
+ """
596
+ Returns unique identifier for this component instance.
597
+ Must be implemented by subclasses.
598
+
599
+ Returns:
600
+ Unique component identifier (e.g., agent_name, gateway_id)
261
601
  """
262
- Abstract method for subclasses to implement their main asynchronous logic.
263
- This coroutine is executed within the managed event loop.
602
+ pass
603
+
604
+ @abc.abstractmethod
605
+ def _get_component_type(self) -> str:
606
+ """
607
+ Returns component type string.
608
+ Must be implemented by subclasses.
609
+
610
+ Returns:
611
+ Component type ("gateway", "agent", etc.)
264
612
  """
265
613
  pass
266
614
 
615
+ async def _async_setup_and_run(self) -> None:
616
+ """
617
+ Base async setup that initializes Trust Manager if present.
618
+ Subclasses should override and call super() first, then add their logic.
619
+ """
620
+ # Initialize Trust Manager if present (ENTERPRISE FEATURE)
621
+ if self.trust_manager:
622
+ try:
623
+ log.info(
624
+ "%s Initializing Trust Manager with periodic publishing...",
625
+ self.log_identifier,
626
+ )
627
+ # Pass event loop and add_timer method to Trust Manager
628
+ await self.trust_manager.initialize(
629
+ add_timer_callback=self.add_timer,
630
+ event_loop=self.get_async_loop(),
631
+ )
632
+ log.info(
633
+ "%s Trust Manager initialized successfully", self.log_identifier
634
+ )
635
+ except Exception as e:
636
+ log.error(
637
+ "%s Failed to initialize Trust Manager: %s",
638
+ self.log_identifier,
639
+ e,
640
+ exc_info=True,
641
+ )
642
+ # Trust Manager failure should not prevent component startup
643
+ # Set to None to disable trust manager for this session
644
+ self.trust_manager = None
645
+
267
646
  @abc.abstractmethod
268
647
  def _pre_async_cleanup(self) -> None:
269
648
  """
@@ -19,6 +19,7 @@ from ...common.a2a import (
19
19
 
20
20
  log = logging.getLogger(__name__)
21
21
 
22
+
22
23
  class BaseGatewayComponent(ComponentBase):
23
24
  pass
24
25
 
@@ -244,6 +245,20 @@ class BaseGatewayApp(App):
244
245
  )
245
246
  },
246
247
  ]
248
+
249
+ # Add trust card subscription if trust manager is enabled
250
+ trust_config = resolved_app_config_block.get("trust_manager")
251
+ if trust_config and trust_config.get("enabled", False):
252
+ from ...common.a2a.protocol import get_trust_card_subscription_topic
253
+
254
+ trust_card_topic = get_trust_card_subscription_topic(self.namespace)
255
+ subscriptions.append({"topic": trust_card_topic})
256
+ log.info(
257
+ "Trust Manager enabled for gateway '%s', added trust card subscription: %s",
258
+ self.gateway_id,
259
+ trust_card_topic,
260
+ )
261
+
247
262
  log.info(
248
263
  "Generated Solace subscriptions for gateway '%s': %s",
249
264
  self.gateway_id,