solace-agent-mesh 1.5.1__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 (184) hide show
  1. solace_agent_mesh/agent/adk/callbacks.py +0 -5
  2. solace_agent_mesh/agent/adk/models/lite_llm.py +123 -8
  3. solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +245 -0
  4. solace_agent_mesh/agent/protocol/event_handlers.py +213 -31
  5. solace_agent_mesh/agent/proxies/__init__.py +0 -0
  6. solace_agent_mesh/agent/proxies/a2a/__init__.py +3 -0
  7. solace_agent_mesh/agent/proxies/a2a/app.py +55 -0
  8. solace_agent_mesh/agent/proxies/a2a/component.py +1115 -0
  9. solace_agent_mesh/agent/proxies/a2a/config.py +140 -0
  10. solace_agent_mesh/agent/proxies/a2a/oauth_token_cache.py +104 -0
  11. solace_agent_mesh/agent/proxies/base/__init__.py +3 -0
  12. solace_agent_mesh/agent/proxies/base/app.py +99 -0
  13. solace_agent_mesh/agent/proxies/base/component.py +650 -0
  14. solace_agent_mesh/agent/proxies/base/config.py +85 -0
  15. solace_agent_mesh/agent/proxies/base/proxy_task_context.py +17 -0
  16. solace_agent_mesh/agent/sac/app.py +58 -5
  17. solace_agent_mesh/agent/sac/component.py +238 -75
  18. solace_agent_mesh/agent/sac/task_execution_context.py +46 -0
  19. solace_agent_mesh/agent/tools/audio_tools.py +125 -8
  20. solace_agent_mesh/agent/tools/web_tools.py +10 -5
  21. solace_agent_mesh/agent/utils/artifact_helpers.py +141 -3
  22. solace_agent_mesh/assets/docs/404.html +3 -3
  23. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.eda4bcb2.js +1 -0
  24. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.f4b15f3b.js +1 -0
  25. solace_agent_mesh/assets/docs/assets/js/71da7b71.38583438.js +1 -0
  26. solace_agent_mesh/assets/docs/assets/js/77cf947d.48cb18a2.js +1 -0
  27. solace_agent_mesh/assets/docs/assets/js/924ffdeb.8095e148.js +1 -0
  28. solace_agent_mesh/assets/docs/assets/js/9e9d0a82.570c057b.js +1 -0
  29. solace_agent_mesh/assets/docs/assets/js/{ad71b5ed.60668e9e.js → ad71b5ed.af3ecfd1.js} +1 -1
  30. solace_agent_mesh/assets/docs/assets/js/ceb2a7a6.5d92d7d0.js +1 -0
  31. solace_agent_mesh/assets/docs/assets/js/{da0b5bad.9d369087.js → da0b5bad.d08a9466.js} +1 -1
  32. solace_agent_mesh/assets/docs/assets/js/db924877.e98d12a1.js +1 -0
  33. solace_agent_mesh/assets/docs/assets/js/de915948.27d6b065.js +1 -0
  34. solace_agent_mesh/assets/docs/assets/js/{e3d9abda.2b916f9e.js → e3d9abda.6b9493d0.js} +1 -1
  35. solace_agent_mesh/assets/docs/assets/js/e6f9706b.e74a984d.js +1 -0
  36. solace_agent_mesh/assets/docs/assets/js/f284c35a.42f59cdd.js +1 -0
  37. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.15b02f97.js +1 -0
  38. solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js → main.b12eac43.js} +2 -2
  39. solace_agent_mesh/assets/docs/assets/js/runtime~main.e268214e.js +1 -0
  40. solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +15 -4
  41. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +4 -4
  42. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +4 -4
  43. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +4 -4
  44. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +4 -4
  45. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +4 -4
  46. solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +4 -4
  47. solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +4 -4
  48. solace_agent_mesh/assets/docs/docs/documentation/components/index.html +4 -4
  49. solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +4 -4
  50. solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +4 -4
  51. solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +262 -0
  52. solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +3 -3
  53. solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +31 -3
  54. solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +3 -3
  55. solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +3 -3
  56. solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +4 -4
  57. solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +5 -5
  58. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +4 -4
  59. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +4 -4
  60. solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +135 -0
  61. solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +6 -4
  62. solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +4 -4
  63. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +4 -4
  64. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +4 -4
  65. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +5 -5
  66. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +4 -4
  67. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +4 -4
  68. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +4 -4
  69. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +4 -4
  70. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +4 -4
  71. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +4 -4
  72. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +3 -3
  73. solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +3 -3
  74. solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +3 -3
  75. solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +3 -3
  76. solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +3 -3
  77. solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +3 -3
  78. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
  79. solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +3 -3
  80. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +6 -5
  81. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +3 -3
  82. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +3 -3
  83. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +100 -3
  84. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +3 -3
  85. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
  86. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +3 -3
  87. solace_agent_mesh/assets/docs/lunr-index-1761248203150.json +1 -0
  88. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  89. solace_agent_mesh/assets/docs/search-doc-1761248203150.json +1 -0
  90. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  91. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  92. solace_agent_mesh/cli/__init__.py +1 -1
  93. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +2 -69
  94. solace_agent_mesh/cli/commands/eval_cmd.py +11 -49
  95. solace_agent_mesh/cli/commands/init_cmd/__init__.py +0 -5
  96. solace_agent_mesh/cli/commands/init_cmd/env_step.py +10 -12
  97. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +9 -61
  98. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +9 -49
  99. solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +1 -2
  100. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-DwrxZE0E.js → authCallback-BTf6dqwp.js} +1 -1
  101. solace_agent_mesh/client/webui/frontend/static/assets/{client-DarGQzyw.js → client-CaY59VuC.js} +1 -1
  102. solace_agent_mesh/client/webui/frontend/static/assets/main-B32noGmR.js +342 -0
  103. solace_agent_mesh/client/webui/frontend/static/assets/main-DHJKSW1S.css +1 -0
  104. solace_agent_mesh/client/webui/frontend/static/assets/{vendor-BKIeiHj_.js → vendor-BEmvJSYz.js} +1 -1
  105. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  106. solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
  107. solace_agent_mesh/common/a2a/__init__.py +24 -0
  108. solace_agent_mesh/common/a2a/artifact.py +39 -0
  109. solace_agent_mesh/common/a2a/events.py +29 -0
  110. solace_agent_mesh/common/a2a/message.py +68 -0
  111. solace_agent_mesh/common/a2a/protocol.py +151 -1
  112. solace_agent_mesh/common/agent_registry.py +83 -3
  113. solace_agent_mesh/common/constants.py +3 -1
  114. solace_agent_mesh/common/sac/sam_component_base.py +383 -4
  115. solace_agent_mesh/common/utils/pydantic_utils.py +12 -0
  116. solace_agent_mesh/config_portal/backend/common.py +1 -1
  117. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-ByU1X1HD.js +98 -0
  118. solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-44d62be6.js → manifest-61038fc6.js} +1 -1
  119. solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
  120. solace_agent_mesh/evaluation/evaluator.py +128 -104
  121. solace_agent_mesh/evaluation/message_organizer.py +116 -110
  122. solace_agent_mesh/evaluation/report_data_processor.py +84 -86
  123. solace_agent_mesh/evaluation/report_generator.py +73 -79
  124. solace_agent_mesh/evaluation/run.py +421 -235
  125. solace_agent_mesh/evaluation/shared/__init__.py +92 -0
  126. solace_agent_mesh/evaluation/shared/constants.py +47 -0
  127. solace_agent_mesh/evaluation/shared/exceptions.py +50 -0
  128. solace_agent_mesh/evaluation/shared/helpers.py +35 -0
  129. solace_agent_mesh/evaluation/shared/test_case_loader.py +167 -0
  130. solace_agent_mesh/evaluation/shared/test_suite_loader.py +280 -0
  131. solace_agent_mesh/evaluation/subscriber.py +111 -232
  132. solace_agent_mesh/evaluation/summary_builder.py +227 -117
  133. solace_agent_mesh/gateway/base/app.py +16 -1
  134. solace_agent_mesh/gateway/base/component.py +112 -39
  135. solace_agent_mesh/gateway/http_sse/alembic/versions/20251015_add_session_performance_indexes.py +70 -0
  136. solace_agent_mesh/gateway/http_sse/component.py +99 -3
  137. solace_agent_mesh/gateway/http_sse/dependencies.py +4 -4
  138. solace_agent_mesh/gateway/http_sse/main.py +1 -0
  139. solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +12 -13
  140. solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +15 -18
  141. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +25 -18
  142. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +30 -26
  143. solace_agent_mesh/gateway/http_sse/repository/task_repository.py +35 -44
  144. solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +4 -3
  145. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +95 -203
  146. solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +4 -3
  147. solace_agent_mesh/gateway/http_sse/routers/sessions.py +2 -2
  148. solace_agent_mesh/gateway/http_sse/routers/tasks.py +33 -41
  149. solace_agent_mesh/gateway/http_sse/routers/users.py +47 -1
  150. solace_agent_mesh/gateway/http_sse/routers/visualization.py +17 -11
  151. solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +4 -4
  152. solace_agent_mesh/gateway/http_sse/services/feedback_service.py +51 -43
  153. solace_agent_mesh/gateway/http_sse/services/session_service.py +20 -20
  154. solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +8 -8
  155. solace_agent_mesh/gateway/http_sse/shared/base_repository.py +45 -71
  156. solace_agent_mesh/gateway/http_sse/shared/types.py +0 -18
  157. solace_agent_mesh/templates/gateway_config_template.yaml +0 -5
  158. solace_agent_mesh/templates/logging_config_template.ini +10 -6
  159. solace_agent_mesh/templates/plugin_gateway_config_template.yaml +0 -3
  160. solace_agent_mesh/templates/shared_config.yaml +40 -0
  161. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.1.dist-info}/METADATA +47 -21
  162. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.1.dist-info}/RECORD +166 -145
  163. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.e49689dd.js +0 -1
  164. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.39d5851d.js +0 -1
  165. solace_agent_mesh/assets/docs/assets/js/71da7b71.804d6567.js +0 -1
  166. solace_agent_mesh/assets/docs/assets/js/77cf947d.64c9bd6c.js +0 -1
  167. solace_agent_mesh/assets/docs/assets/js/9e9d0a82.dd810042.js +0 -1
  168. solace_agent_mesh/assets/docs/assets/js/db924877.cbc66f02.js +0 -1
  169. solace_agent_mesh/assets/docs/assets/js/de915948.139b4b9c.js +0 -1
  170. solace_agent_mesh/assets/docs/assets/js/e6f9706b.582a78ca.js +0 -1
  171. solace_agent_mesh/assets/docs/assets/js/f284c35a.5766a13d.js +0 -1
  172. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.9c0297a6.js +0 -1
  173. solace_agent_mesh/assets/docs/assets/js/runtime~main.18dc45dd.js +0 -1
  174. solace_agent_mesh/assets/docs/lunr-index-1760121512891.json +0 -1
  175. solace_agent_mesh/assets/docs/search-doc-1760121512891.json +0 -1
  176. solace_agent_mesh/client/webui/frontend/static/assets/main-2nd1gbaH.js +0 -339
  177. solace_agent_mesh/client/webui/frontend/static/assets/main-DoKXctCM.css +0 -1
  178. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-BNuqpWDc.js +0 -98
  179. solace_agent_mesh/evaluation/config_loader.py +0 -657
  180. solace_agent_mesh/evaluation/test_case_loader.py +0 -714
  181. /solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js.LICENSE.txt → main.b12eac43.js.LICENSE.txt} +0 -0
  182. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.1.dist-info}/WHEEL +0 -0
  183. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.1.dist-info}/entry_points.txt +0 -0
  184. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -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,
@@ -272,7 +287,7 @@ class BaseGatewayApp(App):
272
287
  broker_config["queue_name"] = (
273
288
  f"{self.namespace.strip('/')}/q/gdk/gateway/{self.gateway_id}"
274
289
  )
275
- broker_config["temporary_queue"] = True
290
+ broker_config["temporary_queue"] = modified_app_info.get("broker", {}).get("temporary_queue", True)
276
291
  log.debug(
277
292
  "Injected broker settings for gateway '%s': %s",
278
293
  self.gateway_id,
@@ -282,6 +282,9 @@ class BaseGatewayComponent(SamComponentBase):
282
282
  "user_id_for_a2a", user_identity.get("id")
283
283
  )
284
284
 
285
+ system_purpose = self.get_config("system_purpose", "")
286
+ response_format = self.get_config("response_format", "")
287
+
285
288
  if not a2a_session_id:
286
289
  a2a_session_id = f"gdk-session-{uuid.uuid4().hex}"
287
290
  log.warning(
@@ -291,7 +294,11 @@ class BaseGatewayComponent(SamComponentBase):
291
294
  )
292
295
  external_request_context["a2a_session_id"] = a2a_session_id
293
296
 
294
- a2a_metadata = {"agent_name": target_agent_name}
297
+ a2a_metadata = {
298
+ "agent_name": target_agent_name,
299
+ "system_purpose": system_purpose,
300
+ "response_format": response_format,
301
+ }
295
302
  invoked_artifacts = external_request_context.get("invoked_with_artifacts")
296
303
  if invoked_artifacts:
297
304
  a2a_metadata["invoked_with_artifacts"] = invoked_artifacts
@@ -336,6 +343,37 @@ class BaseGatewayComponent(SamComponentBase):
336
343
  if user_config:
337
344
  user_properties["a2aUserConfig"] = user_config
338
345
 
346
+ # Enterprise feature: Add signed user claims if trust manager available
347
+ if hasattr(self, "trust_manager") and self.trust_manager:
348
+ log.debug(
349
+ "%s Attempting to sign user claims for task %s",
350
+ log_id_prefix,
351
+ task_id,
352
+ )
353
+ try:
354
+ auth_token = self.trust_manager.sign_user_claims(
355
+ user_info=user_identity, task_id=task_id
356
+ )
357
+ user_properties["authToken"] = auth_token
358
+ log.debug(
359
+ "%s Successfully signed user claims for task %s",
360
+ log_id_prefix,
361
+ task_id,
362
+ )
363
+ except Exception as e:
364
+ log.error(
365
+ "%s Failed to sign user claims for task %s: %s",
366
+ log_id_prefix,
367
+ task_id,
368
+ e,
369
+ )
370
+ # Continue without token - enterprise feature is optional
371
+ else:
372
+ log.debug(
373
+ "%s Trust Manager not available, proceeding without authentication token",
374
+ log_id_prefix,
375
+ )
376
+
339
377
  user_properties["replyTo"] = a2a.get_gateway_response_topic(
340
378
  self.namespace, self.gateway_id, task_id
341
379
  )
@@ -359,49 +397,55 @@ class BaseGatewayComponent(SamComponentBase):
359
397
  )
360
398
  return task_id
361
399
 
362
- def process_event(self, event: Event):
363
- if event.event_type == EventType.MESSAGE:
364
- original_broker_message: Optional[SolaceMessage] = event.data
365
- if not original_broker_message:
366
- log.warning(
367
- "%s Received MESSAGE event with no data. Ignoring.",
368
- self.log_identifier,
369
- )
370
- return
400
+ def _handle_message(self, message: SolaceMessage, topic: str) -> None:
401
+ """
402
+ Override to use queue-based pattern instead of direct async.
371
403
 
372
- log.debug(
373
- "%s Received SolaceMessage on topic: %s. Bridging to internal queue.",
404
+ Gateway uses an internal queue for message processing to ensure
405
+ strict ordering and backpressure handling.
406
+
407
+ Args:
408
+ message: The Solace message
409
+ topic: The topic the message was received on
410
+ """
411
+ log.debug(
412
+ "%s Received SolaceMessage on topic: %s. Bridging to internal queue.",
413
+ self.log_identifier,
414
+ topic,
415
+ )
416
+
417
+ try:
418
+ msg_data_for_processor = {
419
+ "topic": topic,
420
+ "payload": message.get_payload(),
421
+ "user_properties": message.get_user_properties(),
422
+ "_original_broker_message": message,
423
+ }
424
+ self.internal_event_queue.put_nowait(msg_data_for_processor)
425
+ except queue.Full:
426
+ log.error(
427
+ "%s Internal event queue full. Cannot bridge message.",
374
428
  self.log_identifier,
375
- original_broker_message.get_topic(),
376
429
  )
377
- try:
378
- msg_data_for_processor = {
379
- "topic": original_broker_message.get_topic(),
380
- "payload": original_broker_message.get_payload(),
381
- "user_properties": original_broker_message.get_user_properties(),
382
- "_original_broker_message": original_broker_message,
383
- }
384
- self.internal_event_queue.put_nowait(msg_data_for_processor)
385
- except queue.Full:
386
- log.error(
387
- "%s Internal event queue full. Cannot bridge message. NACKing.",
388
- self.log_identifier,
389
- )
390
- original_broker_message.call_negative_acknowledgements()
391
- except Exception as e:
392
- log.exception(
393
- "%s Error bridging message to internal queue: %s. NACKing.",
394
- self.log_identifier,
395
- e,
396
- )
397
- original_broker_message.call_negative_acknowledgements()
398
- else:
399
- log.debug(
400
- "%s Received non-MESSAGE event type: %s. Passing to super.",
430
+ raise
431
+ except Exception as e:
432
+ log.exception(
433
+ "%s Error bridging message to internal queue: %s",
401
434
  self.log_identifier,
402
- event.event_type,
435
+ e,
403
436
  )
404
- super().process_event(event)
437
+ raise
438
+
439
+ async def _handle_message_async(self, message, topic: str) -> None:
440
+ """
441
+ Not used by gateway - we override _handle_message() instead.
442
+
443
+ This is here to satisfy the abstract method requirement, but the
444
+ gateway uses the queue-based pattern via _handle_message() override.
445
+ """
446
+ raise NotImplementedError(
447
+ "Gateway uses queue-based message handling via _handle_message() override"
448
+ )
405
449
 
406
450
  async def _handle_resolved_signals(
407
451
  self,
@@ -1113,6 +1157,9 @@ class BaseGatewayComponent(SamComponentBase):
1113
1157
 
1114
1158
  async def _async_setup_and_run(self) -> None:
1115
1159
  """Main async logic for the gateway component."""
1160
+ # Call base class to initialize Trust Manager
1161
+ await super()._async_setup_and_run()
1162
+
1116
1163
  log.info(
1117
1164
  "%s Starting _start_listener() to initiate external platform connection.",
1118
1165
  self.log_identifier,
@@ -1127,6 +1174,17 @@ class BaseGatewayComponent(SamComponentBase):
1127
1174
 
1128
1175
  def _pre_async_cleanup(self) -> None:
1129
1176
  """Pre-cleanup actions for the gateway component."""
1177
+ # Cleanup Trust Manager if present (ENTERPRISE FEATURE)
1178
+ if self.trust_manager:
1179
+ try:
1180
+ log.info("%s Cleaning up Trust Manager...", self.log_identifier)
1181
+ self.trust_manager.cleanup(self.cancel_timer)
1182
+ log.info("%s Trust Manager cleanup complete", self.log_identifier)
1183
+ except Exception as e:
1184
+ log.error(
1185
+ "%s Error during Trust Manager cleanup: %s", self.log_identifier, e
1186
+ )
1187
+
1130
1188
  log.info("%s Calling _stop_listener()...", self.log_identifier)
1131
1189
  self._stop_listener()
1132
1190
 
@@ -1177,6 +1235,13 @@ class BaseGatewayComponent(SamComponentBase):
1177
1235
  processed_successfully = await self._handle_discovery_message(
1178
1236
  payload
1179
1237
  )
1238
+ elif (
1239
+ hasattr(self, "trust_manager")
1240
+ and self.trust_manager
1241
+ and self.trust_manager.is_trust_card_topic(topic)
1242
+ ):
1243
+ await self.trust_manager.handle_trust_card_message(payload, topic)
1244
+ processed_successfully = True
1180
1245
  elif a2a.topic_matches_subscription(
1181
1246
  topic,
1182
1247
  a2a.get_gateway_response_subscription_topic(
@@ -1321,6 +1386,14 @@ class BaseGatewayComponent(SamComponentBase):
1321
1386
  ) -> None:
1322
1387
  pass
1323
1388
 
1389
+ def _get_component_id(self) -> str:
1390
+ """Returns the gateway ID as the component identifier."""
1391
+ return self.gateway_id
1392
+
1393
+ def _get_component_type(self) -> str:
1394
+ """Returns 'gateway' as the component type."""
1395
+ return "gateway"
1396
+
1324
1397
  def invoke(self, message, data):
1325
1398
  if isinstance(message, SolaceMessage):
1326
1399
  message.call_acknowledgements()
@@ -0,0 +1,70 @@
1
+ """add performance indexes for query optimization
2
+
3
+ Revision ID: 20251015_session_idx
4
+ Revises: 98882922fa59
5
+ Create Date: 2025-10-15
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ revision: str = "20251015_session_idx"
15
+ down_revision: str | Sequence[str] | None = "98882922fa59"
16
+ branch_labels: str | Sequence[str] | None = None
17
+ depends_on: str | Sequence[str] | None = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ """Add composite indexes for optimal query performance."""
22
+
23
+ op.create_index("ix_sessions_user_id", "sessions", ["user_id"], unique=False)
24
+
25
+ op.create_index(
26
+ "ix_sessions_user_updated",
27
+ "sessions",
28
+ ["user_id", sa.text("updated_time DESC")],
29
+ unique=False,
30
+ )
31
+
32
+ op.create_index(
33
+ "ix_chat_tasks_session_user_created",
34
+ "chat_tasks",
35
+ ["session_id", "user_id", "created_time"],
36
+ unique=False,
37
+ )
38
+
39
+ op.create_index(
40
+ "ix_tasks_user_start_time",
41
+ "tasks",
42
+ ["user_id", sa.text("start_time DESC")],
43
+ unique=False,
44
+ )
45
+
46
+ op.create_index(
47
+ "ix_task_events_task_created",
48
+ "task_events",
49
+ ["task_id", "created_time"],
50
+ unique=False,
51
+ )
52
+
53
+ op.drop_index("ix_tasks_initial_request_text", table_name="tasks")
54
+
55
+
56
+ def downgrade() -> None:
57
+ """Remove performance indexes."""
58
+
59
+ op.create_index(
60
+ op.f("ix_tasks_initial_request_text"),
61
+ "tasks",
62
+ ["initial_request_text"],
63
+ unique=False,
64
+ )
65
+
66
+ op.drop_index("ix_task_events_task_created", table_name="task_events")
67
+ op.drop_index("ix_tasks_user_start_time", table_name="tasks")
68
+ op.drop_index("ix_chat_tasks_session_user_created", table_name="chat_tasks")
69
+ op.drop_index("ix_sessions_user_updated", table_name="sessions")
70
+ op.drop_index("ix_sessions_user_id", table_name="sessions")
@@ -146,6 +146,27 @@ class WebUIBackendComponent(BaseGatewayComponent):
146
146
  timer_id=self._sse_cleanup_timer_id,
147
147
  interval_ms=cleanup_interval_sec * 1000,
148
148
  )
149
+
150
+ # Set up health check timer for agent registry
151
+ from ...common.constants import HEALTH_CHECK_INTERVAL_SECONDS
152
+ self.health_check_timer_id = f"agent_health_check_{self.gateway_id}"
153
+ health_check_interval_seconds = self.get_config("agent_health_check_interval_seconds", HEALTH_CHECK_INTERVAL_SECONDS)
154
+ if health_check_interval_seconds > 0:
155
+ log.info(
156
+ "%s Scheduling agent health check every %d seconds.",
157
+ self.log_identifier,
158
+ health_check_interval_seconds,
159
+ )
160
+ self.add_timer(
161
+ delay_ms=health_check_interval_seconds * 1000,
162
+ timer_id=self.health_check_timer_id,
163
+ interval_ms=health_check_interval_seconds * 1000,
164
+ )
165
+ else:
166
+ log.warning(
167
+ "%s Agent health check interval not configured or invalid, health checks will not run periodically.",
168
+ self.log_identifier,
169
+ )
149
170
 
150
171
  session_config = self._resolve_session_config()
151
172
  if session_config.get("type") == "sql":
@@ -265,6 +286,10 @@ class WebUIBackendComponent(BaseGatewayComponent):
265
286
  log.debug("%s SSE buffer cleanup timer triggered.", self.log_identifier)
266
287
  self.sse_event_buffer.cleanup_stale_buffers()
267
288
  return
289
+ elif event.data.get("timer_id") == self.health_check_timer_id:
290
+ log.debug("%s Agent health check timer triggered.", self.log_identifier)
291
+ self._check_agent_health()
292
+ return
268
293
 
269
294
  if timer_id == self._data_retention_timer_id:
270
295
  log.debug("%s Data retention cleanup timer triggered.", self.log_identifier)
@@ -354,7 +379,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
354
379
  ),
355
380
  "retry_interval": main_broker_config.get("retry_interval"),
356
381
  "retry_count": main_broker_config.get("retry_count"),
357
- "temporary_queue": True,
382
+ "temporary_queue": main_broker_config.get("temporary_queue", True),
358
383
  },
359
384
  }
360
385
 
@@ -486,7 +511,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
486
511
  ),
487
512
  "retry_interval": main_broker_config.get("retry_interval"),
488
513
  "retry_count": main_broker_config.get("retry_count"),
489
- "temporary_queue": True,
514
+ "temporary_queue": main_broker_config.get("temporary_queue", True),
490
515
  },
491
516
  }
492
517
 
@@ -1197,7 +1222,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
1197
1222
  app=self.fastapi_app,
1198
1223
  host=self.fastapi_host,
1199
1224
  port=port,
1200
- log_level="info",
1225
+ log_level="warning",
1201
1226
  lifespan="on",
1202
1227
  ssl_keyfile=self.ssl_keyfile,
1203
1228
  ssl_certfile=self.ssl_certfile,
@@ -1404,6 +1429,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
1404
1429
  self.data_retention_service = None
1405
1430
  log.info("%s Data retention service cleaned up.", self.log_identifier)
1406
1431
 
1432
+ self.cancel_timer(self.health_check_timer_id)
1407
1433
  log.info("%s Cleaning up visualization resources...", self.log_identifier)
1408
1434
  if self._visualization_message_queue:
1409
1435
  self._visualization_message_queue.put(None)
@@ -1729,6 +1755,76 @@ class WebUIBackendComponent(BaseGatewayComponent):
1729
1755
 
1730
1756
  def get_agent_registry(self) -> AgentRegistry:
1731
1757
  return self.agent_registry
1758
+
1759
+ def _check_agent_health(self):
1760
+ """
1761
+ Checks the health of peer agents and de-registers unresponsive ones.
1762
+ This is called periodically by the health check timer.
1763
+ Uses TTL-based expiration to determine if an agent is unresponsive.
1764
+ """
1765
+
1766
+ log.debug("%s Performing agent health check...", self.log_identifier)
1767
+
1768
+ # Get TTL from configuration or use default from constants
1769
+ from ...common.constants import HEALTH_CHECK_TTL_SECONDS, HEALTH_CHECK_INTERVAL_SECONDS
1770
+ ttl_seconds = self.get_config("agent_health_check_ttl_seconds", HEALTH_CHECK_TTL_SECONDS)
1771
+ health_check_interval = self.get_config("agent_health_check_interval_seconds", HEALTH_CHECK_INTERVAL_SECONDS)
1772
+
1773
+ log.debug(
1774
+ "%s Health check configuration: interval=%d seconds, TTL=%d seconds",
1775
+ self.log_identifier,
1776
+ health_check_interval,
1777
+ ttl_seconds
1778
+ )
1779
+
1780
+ # Validate configuration values
1781
+ if ttl_seconds <= 0 or health_check_interval <= 0 or ttl_seconds < health_check_interval:
1782
+ log.error(
1783
+ "%s agent_health_check_ttl_seconds (%d) and agent_health_check_interval_seconds (%d) must be positive and TTL must be greater than interval.",
1784
+ self.log_identifier,
1785
+ ttl_seconds,
1786
+ health_check_interval
1787
+ )
1788
+ raise ValueError(f"Invalid health check configuration. agent_health_check_ttl_seconds ({ttl_seconds}) and agent_health_check_interval_seconds ({health_check_interval}) must be positive and TTL must be greater than interval.")
1789
+
1790
+ # Get all agent names from the registry
1791
+ agent_names = self.agent_registry.get_agent_names()
1792
+ total_agents = len(agent_names)
1793
+ agents_to_deregister = []
1794
+
1795
+ log.debug("%s Checking health of %d peer agents", self.log_identifier, total_agents)
1796
+
1797
+ for agent_name in agent_names:
1798
+ # Check if the agent's TTL has expired
1799
+ is_expired, time_since_last_seen = self.agent_registry.check_ttl_expired(agent_name, ttl_seconds)
1800
+
1801
+ if is_expired:
1802
+ log.warning(
1803
+ "%s Agent '%s' TTL has expired. De-registering. Time since last seen: %d seconds (TTL: %d seconds)",
1804
+ self.log_identifier,
1805
+ agent_name,
1806
+ time_since_last_seen,
1807
+ ttl_seconds
1808
+ )
1809
+ agents_to_deregister.append(agent_name)
1810
+
1811
+ # De-register unresponsive agents
1812
+ for agent_name in agents_to_deregister:
1813
+ self._deregister_agent(agent_name)
1814
+
1815
+ log.info(
1816
+ "%s Agent health check completed. Total agents: %d, De-registered: %d",
1817
+ self.log_identifier,
1818
+ total_agents,
1819
+ len(agents_to_deregister)
1820
+ )
1821
+
1822
+ def _deregister_agent(self, agent_name: str):
1823
+ """
1824
+ De-registers an agent from the registry and publishes a de-registration event.
1825
+ """
1826
+ # Remove from registry
1827
+ self.agent_registry.remove_agent(agent_name)
1732
1828
 
1733
1829
  def get_sse_manager(self) -> SSEManager:
1734
1830
  return self.sse_manager
@@ -243,10 +243,10 @@ def get_people_service(
243
243
  return PeopleService(identity_service=identity_service)
244
244
 
245
245
 
246
- def get_task_repository(db: Session = Depends(get_db)) -> ITaskRepository:
246
+ def get_task_repository() -> ITaskRepository:
247
247
  """FastAPI dependency to get an instance of TaskRepository."""
248
248
  log.debug("[Dependencies] get_task_repository called")
249
- return TaskRepository(db)
249
+ return TaskRepository()
250
250
 
251
251
 
252
252
  def get_feedback_service(
@@ -525,9 +525,9 @@ def get_session_validator(
525
525
  try:
526
526
  db = SessionLocal()
527
527
  try:
528
- session_repository = SessionRepository(db)
528
+ session_repository = SessionRepository()
529
529
  session_domain = session_repository.find_user_session(
530
- session_id, user_id
530
+ db, session_id, user_id
531
531
  )
532
532
  return session_domain is not None
533
533
  finally:
@@ -107,6 +107,7 @@ def _extract_user_identifier(user_info: dict) -> str:
107
107
  or user_info.get("email")
108
108
  or user_info.get("name")
109
109
  or user_info.get("azp")
110
+ or user_info.get("user_id") # internal /user_info endpoint format maps identifier to user_id
110
111
  )
111
112
 
112
113
  if user_identifier and user_identifier.lower() == "unknown":
@@ -16,12 +16,9 @@ from .models import ChatTaskModel
16
16
  class ChatTaskRepository(IChatTaskRepository):
17
17
  """SQLAlchemy implementation of chat task repository."""
18
18
 
19
- def __init__(self, db: DBSession):
20
- self.db = db
21
-
22
- def save(self, task: ChatTask) -> ChatTask:
19
+ def save(self, session: DBSession, task: ChatTask) -> ChatTask:
23
20
  """Save or update a chat task (upsert)."""
24
- existing = self.db.query(ChatTaskModel).filter(
21
+ existing = session.query(ChatTaskModel).filter(
25
22
  ChatTaskModel.id == task.id
26
23
  ).first()
27
24
 
@@ -43,12 +40,12 @@ class ChatTaskRepository(IChatTaskRepository):
43
40
  created_time=task.created_time,
44
41
  updated_time=task.updated_time
45
42
  )
46
- self.db.add(model)
43
+ session.add(model)
47
44
 
48
- self.db.commit()
45
+ session.flush()
49
46
 
50
47
  # Reload to get updated values
51
- model = self.db.query(ChatTaskModel).filter(
48
+ model = session.query(ChatTaskModel).filter(
52
49
  ChatTaskModel.id == task.id
53
50
  ).first()
54
51
 
@@ -56,11 +53,12 @@ class ChatTaskRepository(IChatTaskRepository):
56
53
 
57
54
  def find_by_session(
58
55
  self,
56
+ session: DBSession,
59
57
  session_id: SessionId,
60
58
  user_id: UserId
61
59
  ) -> List[ChatTask]:
62
60
  """Find all tasks for a session."""
63
- models = self.db.query(ChatTaskModel).filter(
61
+ models = session.query(ChatTaskModel).filter(
64
62
  ChatTaskModel.session_id == session_id,
65
63
  ChatTaskModel.user_id == user_id
66
64
  ).order_by(ChatTaskModel.created_time.asc()).all()
@@ -69,23 +67,24 @@ class ChatTaskRepository(IChatTaskRepository):
69
67
 
70
68
  def find_by_id(
71
69
  self,
70
+ session: DBSession,
72
71
  task_id: str,
73
72
  user_id: UserId
74
73
  ) -> Optional[ChatTask]:
75
74
  """Find a specific task."""
76
- model = self.db.query(ChatTaskModel).filter(
75
+ model = session.query(ChatTaskModel).filter(
77
76
  ChatTaskModel.id == task_id,
78
77
  ChatTaskModel.user_id == user_id
79
78
  ).first()
80
79
 
81
80
  return self._model_to_entity(model) if model else None
82
81
 
83
- def delete_by_session(self, session_id: SessionId) -> bool:
82
+ def delete_by_session(self, session: DBSession, session_id: SessionId) -> bool:
84
83
  """Delete all tasks for a session."""
85
- result = self.db.query(ChatTaskModel).filter(
84
+ result = session.query(ChatTaskModel).filter(
86
85
  ChatTaskModel.session_id == session_id
87
86
  ).delete()
88
- self.db.commit()
87
+ session.flush()
89
88
  return result > 0
90
89
 
91
90
  def _model_to_entity(self, model: ChatTaskModel) -> ChatTask:
@@ -12,10 +12,7 @@ from .models import FeedbackModel
12
12
  class FeedbackRepository(IFeedbackRepository):
13
13
  """SQLAlchemy implementation of feedback repository."""
14
14
 
15
- def __init__(self, db: DBSession):
16
- self.db = db
17
-
18
- def save(self, feedback: Feedback) -> Feedback:
15
+ def save(self, session: DBSession, feedback: Feedback) -> Feedback:
19
16
  """Save feedback."""
20
17
  model = FeedbackModel(
21
18
  id=feedback.id,
@@ -26,12 +23,12 @@ class FeedbackRepository(IFeedbackRepository):
26
23
  comment=feedback.comment,
27
24
  created_time=feedback.created_time,
28
25
  )
29
- self.db.add(model)
30
- self.db.commit()
31
- self.db.refresh(model)
26
+ session.add(model)
27
+ session.flush()
28
+ session.refresh(model)
32
29
  return self._model_to_entity(model)
33
30
 
34
- def delete_feedback_older_than(self, cutoff_time_ms: int, batch_size: int) -> int:
31
+ def delete_feedback_older_than(self, session: DBSession, cutoff_time_ms: int, batch_size: int) -> int:
35
32
  """
36
33
  Delete feedback records older than the cutoff time.
37
34
  Uses batch deletion to avoid long-running transactions.
@@ -44,36 +41,36 @@ class FeedbackRepository(IFeedbackRepository):
44
41
  Total number of feedback records deleted
45
42
  """
46
43
  total_deleted = 0
47
-
44
+
48
45
  while True:
49
46
  # Find a batch of feedback IDs to delete
50
47
  feedback_ids_to_delete = (
51
- self.db.query(FeedbackModel.id)
48
+ session.query(FeedbackModel.id)
52
49
  .filter(FeedbackModel.created_time < cutoff_time_ms)
53
50
  .limit(batch_size)
54
51
  .all()
55
52
  )
56
-
53
+
57
54
  if not feedback_ids_to_delete:
58
55
  break
59
-
56
+
60
57
  # Extract IDs from the result tuples
61
58
  ids = [feedback_id[0] for feedback_id in feedback_ids_to_delete]
62
-
59
+
63
60
  # Delete this batch
64
61
  deleted_count = (
65
- self.db.query(FeedbackModel)
62
+ session.query(FeedbackModel)
66
63
  .filter(FeedbackModel.id.in_(ids))
67
64
  .delete(synchronize_session=False)
68
65
  )
69
-
70
- self.db.commit()
66
+
67
+ session.commit()
71
68
  total_deleted += deleted_count
72
-
69
+
73
70
  # If we deleted fewer than batch_size, we're done
74
71
  if deleted_count < batch_size:
75
72
  break
76
-
73
+
77
74
  return total_deleted
78
75
 
79
76
  def _model_to_entity(self, model: FeedbackModel) -> Feedback: