solace-agent-mesh 1.1.0__py3-none-any.whl → 1.3.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 (168) hide show
  1. solace_agent_mesh/agent/adk/runner.py +18 -12
  2. solace_agent_mesh/agent/adk/services.py +3 -3
  3. solace_agent_mesh/agent/adk/setup.py +141 -34
  4. solace_agent_mesh/agent/protocol/event_handlers.py +27 -21
  5. solace_agent_mesh/agent/sac/app.py +0 -1
  6. solace_agent_mesh/agent/sac/component.py +0 -1
  7. solace_agent_mesh/agent/tools/__init__.py +1 -0
  8. solace_agent_mesh/agent/tools/dynamic_tool.py +362 -0
  9. solace_agent_mesh/assets/docs/404.html +3 -3
  10. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.d97b8e94.js +1 -0
  11. solace_agent_mesh/assets/docs/assets/js/483cef9a.4e972867.js +1 -0
  12. solace_agent_mesh/assets/docs/assets/js/55f47984.cf3781c4.js +1 -0
  13. solace_agent_mesh/assets/docs/assets/js/664b740a.1b744a32.js +1 -0
  14. solace_agent_mesh/assets/docs/assets/js/75384d09.c193a8f0.js +1 -0
  15. solace_agent_mesh/assets/docs/assets/js/9a09e75d.d6607c56.js +1 -0
  16. solace_agent_mesh/assets/docs/assets/js/aba87c2f.071e2d94.js +1 -0
  17. solace_agent_mesh/assets/docs/assets/js/ae0e903d.4d8dda10.js +1 -0
  18. solace_agent_mesh/assets/docs/assets/js/c835a94d.146e3186.js +1 -0
  19. solace_agent_mesh/assets/docs/assets/js/f284c35a.7334119c.js +1 -0
  20. solace_agent_mesh/assets/docs/assets/js/main.1c79039d.js +2 -0
  21. solace_agent_mesh/assets/docs/assets/js/runtime~main.858117b7.js +1 -0
  22. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +29 -0
  23. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +25 -0
  24. solace_agent_mesh/assets/docs/docs/documentation/{migration-guides/a2a-upgrade-to-0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html → Migrations/A2A Upgrade To 0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html } +6 -6
  25. solace_agent_mesh/assets/docs/docs/documentation/{migration-guides/a2a-upgrade-to-0.3.0/a2a-technical-migration-map/index.html → Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html } +6 -6
  26. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +19 -27
  27. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +4 -4
  28. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +4 -4
  29. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
  30. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +4 -4
  31. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +4 -4
  32. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +4 -4
  33. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +4 -4
  34. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +4 -4
  35. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +4 -4
  36. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +4 -4
  37. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +4 -4
  38. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +4 -4
  39. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +4 -4
  40. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
  41. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +4 -4
  42. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +4 -4
  43. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +4 -4
  44. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +4 -4
  45. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +4 -4
  46. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +4 -4
  47. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +4 -4
  48. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
  49. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +4 -4
  50. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +4 -4
  51. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +4 -4
  52. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +4 -4
  53. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +4 -4
  54. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +5 -4
  55. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +4 -4
  56. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +63 -0
  57. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +4 -4
  58. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +4 -4
  59. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +4 -4
  60. solace_agent_mesh/assets/docs/lunr-index-1757531604543.json +1 -0
  61. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  62. solace_agent_mesh/assets/docs/search-doc-1757531604543.json +1 -0
  63. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  64. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  65. solace_agent_mesh/cli/__init__.py +1 -1
  66. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +125 -48
  67. solace_agent_mesh/cli/commands/eval_cmd.py +14 -0
  68. solace_agent_mesh/cli/commands/init_cmd/__init__.py +53 -31
  69. solace_agent_mesh/cli/commands/init_cmd/database_step.py +91 -0
  70. solace_agent_mesh/cli/commands/init_cmd/env_step.py +19 -8
  71. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +80 -25
  72. solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +32 -10
  73. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +74 -15
  74. solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +0 -2
  75. solace_agent_mesh/cli/commands/run_cmd.py +5 -3
  76. solace_agent_mesh/cli/utils.py +68 -12
  77. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-CAX9u8a7.js +1 -0
  78. solace_agent_mesh/client/webui/frontend/static/assets/client-DXU9SPI5.js +25 -0
  79. solace_agent_mesh/client/webui/frontend/static/assets/main-C03yrETa.css +1 -0
  80. solace_agent_mesh/client/webui/frontend/static/assets/main-C1k9E0aC.js +339 -0
  81. solace_agent_mesh/client/webui/frontend/static/assets/vendor-B0BEKoAR.js +390 -0
  82. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -2
  83. solace_agent_mesh/client/webui/frontend/static/index.html +4 -3
  84. solace_agent_mesh/common/utils/embeds/resolver.py +1 -0
  85. solace_agent_mesh/config_portal/backend/common.py +2 -2
  86. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-bFMKlzKf.js +98 -0
  87. solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-d845808d.js → manifest-89db7c30.js} +1 -1
  88. solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
  89. solace_agent_mesh/evaluation/message_organizer.py +35 -56
  90. solace_agent_mesh/evaluation/run.py +26 -5
  91. solace_agent_mesh/evaluation/subscriber.py +35 -10
  92. solace_agent_mesh/evaluation/summary_builder.py +27 -34
  93. solace_agent_mesh/gateway/http_sse/ARCHITECTURE_GUIDE.md +676 -0
  94. solace_agent_mesh/gateway/http_sse/alembic/env.py +85 -0
  95. solace_agent_mesh/gateway/http_sse/alembic/script.py.mako +28 -0
  96. solace_agent_mesh/gateway/http_sse/alembic/versions/b1c2d3e4f5g6_add_database_indexes.py +83 -0
  97. solace_agent_mesh/gateway/http_sse/alembic/versions/d5b3f8f2e9a0_create_initial_database.py +58 -0
  98. solace_agent_mesh/gateway/http_sse/alembic.ini +147 -0
  99. solace_agent_mesh/gateway/http_sse/api/__init__.py +11 -0
  100. solace_agent_mesh/gateway/http_sse/api/controllers/__init__.py +9 -0
  101. solace_agent_mesh/gateway/http_sse/api/controllers/session_controller.py +355 -0
  102. solace_agent_mesh/gateway/http_sse/api/controllers/task_controller.py +279 -0
  103. solace_agent_mesh/gateway/http_sse/api/controllers/user_controller.py +35 -0
  104. solace_agent_mesh/gateway/http_sse/api/dto/__init__.py +10 -0
  105. solace_agent_mesh/gateway/http_sse/api/dto/requests/__init__.py +37 -0
  106. solace_agent_mesh/gateway/http_sse/api/dto/requests/session_requests.py +49 -0
  107. solace_agent_mesh/gateway/http_sse/api/dto/requests/task_requests.py +66 -0
  108. solace_agent_mesh/gateway/http_sse/api/dto/responses/__init__.py +43 -0
  109. solace_agent_mesh/gateway/http_sse/api/dto/responses/session_responses.py +68 -0
  110. solace_agent_mesh/gateway/http_sse/api/dto/responses/task_responses.py +74 -0
  111. solace_agent_mesh/gateway/http_sse/app.py +31 -1
  112. solace_agent_mesh/gateway/http_sse/application/__init__.py +3 -0
  113. solace_agent_mesh/gateway/http_sse/application/services/__init__.py +3 -0
  114. solace_agent_mesh/gateway/http_sse/application/services/session_service.py +135 -0
  115. solace_agent_mesh/gateway/http_sse/component.py +224 -62
  116. solace_agent_mesh/gateway/http_sse/dependencies.py +148 -45
  117. solace_agent_mesh/gateway/http_sse/domain/entities/__init__.py +3 -0
  118. solace_agent_mesh/gateway/http_sse/domain/entities/session.py +90 -0
  119. solace_agent_mesh/gateway/http_sse/domain/repositories/__init__.py +3 -0
  120. solace_agent_mesh/gateway/http_sse/domain/repositories/session_repository.py +54 -0
  121. solace_agent_mesh/gateway/http_sse/infrastructure/__init__.py +4 -0
  122. solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/__init__.py +3 -0
  123. solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/container.py +123 -0
  124. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/__init__.py +4 -0
  125. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_persistence_service.py +16 -0
  126. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_service.py +119 -0
  127. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/models.py +31 -0
  128. solace_agent_mesh/gateway/http_sse/infrastructure/persistence_service.py +12 -0
  129. solace_agent_mesh/gateway/http_sse/infrastructure/repositories/__init__.py +3 -0
  130. solace_agent_mesh/gateway/http_sse/infrastructure/repositories/session_repository.py +174 -0
  131. solace_agent_mesh/gateway/http_sse/main.py +291 -87
  132. solace_agent_mesh/gateway/http_sse/routers/{agents.py → agent_cards.py} +7 -7
  133. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +121 -54
  134. solace_agent_mesh/gateway/http_sse/routers/config.py +3 -1
  135. solace_agent_mesh/gateway/http_sse/routers/tasks.py +83 -2
  136. solace_agent_mesh/gateway/http_sse/routers/visualization.py +7 -7
  137. solace_agent_mesh/gateway/http_sse/services/{agent_service.py → agent_card_service.py} +19 -19
  138. solace_agent_mesh/gateway/http_sse/session_manager.py +64 -30
  139. solace_agent_mesh/gateway/http_sse/shared/__init__.py +9 -0
  140. solace_agent_mesh/gateway/http_sse/shared/auth_utils.py +29 -0
  141. solace_agent_mesh/gateway/http_sse/shared/enums.py +45 -0
  142. solace_agent_mesh/gateway/http_sse/shared/types.py +45 -0
  143. solace_agent_mesh/templates/shared_config.yaml +4 -5
  144. solace_agent_mesh/templates/webui.yaml +8 -10
  145. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/METADATA +5 -3
  146. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/RECORD +150 -104
  147. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.8ccb9901.js +0 -1
  148. solace_agent_mesh/assets/docs/assets/js/55f47984.c484bf96.js +0 -1
  149. solace_agent_mesh/assets/docs/assets/js/6e0db977.39a79ca9.js +0 -1
  150. solace_agent_mesh/assets/docs/assets/js/75384d09.bf78fbdb.js +0 -1
  151. solace_agent_mesh/assets/docs/assets/js/90dd9cf6.88f385ea.js +0 -1
  152. solace_agent_mesh/assets/docs/assets/js/aba87c2f.76376d7c.js +0 -1
  153. solace_agent_mesh/assets/docs/assets/js/f284c35a.fb68323a.js +0 -1
  154. solace_agent_mesh/assets/docs/assets/js/main.a75ecc0d.js +0 -2
  155. solace_agent_mesh/assets/docs/assets/js/runtime~main.458efb1d.js +0 -1
  156. solace_agent_mesh/assets/docs/lunr-index-1756992446316.json +0 -1
  157. solace_agent_mesh/assets/docs/search-doc-1756992446316.json +0 -1
  158. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-BmF2l6vg.js +0 -1
  159. solace_agent_mesh/client/webui/frontend/static/assets/client-D881Dttc.js +0 -49
  160. solace_agent_mesh/client/webui/frontend/static/assets/main-C0jZjYa8.js +0 -699
  161. solace_agent_mesh/client/webui/frontend/static/assets/main-CCeG324-.css +0 -1
  162. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-Bym6YkMd.js +0 -98
  163. solace_agent_mesh/gateway/http_sse/routers/sessions.py +0 -85
  164. solace_agent_mesh/gateway/http_sse/routers/users.py +0 -59
  165. /solace_agent_mesh/assets/docs/assets/js/{main.a75ecc0d.js.LICENSE.txt → main.1c79039d.js.LICENSE.txt} +0 -0
  166. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/WHEEL +0 -0
  167. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/entry_points.txt +0 -0
  168. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -3,34 +3,37 @@ Custom Solace AI Connector Component to host the FastAPI backend for the Web UI.
3
3
  """
4
4
 
5
5
  import asyncio
6
- import queue
7
- import uuid
8
6
  import json
7
+ import queue
9
8
  import re
10
9
  import threading
11
- from typing import Any, Dict, Optional, List, Tuple, Union, Set
10
+ import uuid
12
11
  from datetime import datetime, timezone
13
- from fastapi import UploadFile, Request as FastAPIRequest
12
+ from typing import Any
14
13
 
15
14
  import uvicorn
16
- from fastapi import FastAPI
17
-
15
+ from fastapi import FastAPI, UploadFile
16
+ from fastapi import Request as FastAPIRequest
18
17
  from solace_ai_connector.common.log import log
18
+ from solace_ai_connector.components.inputs_outputs.broker_input import BrokerInput
19
19
  from solace_ai_connector.flow.app import App as SACApp
20
- from solace_ai_connector.components.inputs_outputs.broker_input import (
21
- BrokerInput,
22
- )
23
20
 
24
- from ...gateway.http_sse.sse_manager import SSEManager
25
-
26
- from .components import VisualizationForwarderComponent
27
- from ...gateway.http_sse.session_manager import SessionManager
28
- from ...gateway.base.component import BaseGatewayComponent
29
21
  from ...common.agent_registry import AgentRegistry
30
22
  from ...core_a2a.service import CoreA2AService
31
- from google.adk.artifacts import BaseArtifactService
23
+ from ...gateway.base.component import BaseGatewayComponent
24
+ from ...gateway.http_sse.session_manager import SessionManager
25
+ from ...gateway.http_sse.sse_manager import SSEManager
26
+ from .components import VisualizationForwarderComponent
27
+ from .infrastructure.persistence_service import PersistenceService
28
+
29
+ try:
30
+ from google.adk.artifacts import BaseArtifactService
31
+ except ImportError:
32
+
33
+ class BaseArtifactService:
34
+ pass
35
+
32
36
 
33
- from ...common.a2a.types import ContentPart
34
37
  from a2a.types import (
35
38
  A2ARequest,
36
39
  AgentCard,
@@ -40,10 +43,15 @@ from a2a.types import (
40
43
  TaskArtifactUpdateEvent,
41
44
  TaskStatusUpdateEvent,
42
45
  )
46
+
43
47
  from ...common import a2a
44
- from ...agent.utils.artifact_helpers import save_artifact_with_metadata
48
+ from ...common.a2a.types import ContentPart
45
49
  from ...common.middleware.config_resolver import ConfigResolver
46
-
50
+ from ...common.utils.embeds import (
51
+ EARLY_EMBED_TYPES,
52
+ evaluate_embed,
53
+ resolve_embeds_in_string,
54
+ )
47
55
 
48
56
  info = {
49
57
  "class_name": "WebUIBackendComponent",
@@ -114,27 +122,42 @@ class WebUIBackendComponent(BaseGatewayComponent):
114
122
 
115
123
  self.sse_manager = SSEManager(max_queue_size=sse_max_queue_size)
116
124
 
125
+ session_config = self._resolve_session_config()
126
+ if session_config.get("type") == "sql":
127
+ # SQL type explicitly configured - database_url is required
128
+ database_url = session_config.get("database_url")
129
+ if not database_url:
130
+ raise ValueError(
131
+ f"{self.log_identifier} Session service type is 'sql' but no database_url provided. "
132
+ "Please provide a database_url in the session_service configuration or use type 'memory'."
133
+ )
134
+ self.persistence_service = PersistenceService(database_url)
135
+ else:
136
+ # Memory storage or no explicit configuration - no persistence service needed
137
+ self.persistence_service = None
138
+
117
139
  component_config = self.get_config("component_config", {})
118
140
  app_config = component_config.get("app_config", {})
119
141
 
120
142
  self.session_manager = SessionManager(
121
143
  secret_key=self.session_secret_key,
122
144
  app_config=app_config,
145
+ persistence_service=self.persistence_service,
123
146
  )
124
147
 
125
- self.fastapi_app: Optional[FastAPI] = None
126
- self.uvicorn_server: Optional[uvicorn.Server] = None
127
- self.fastapi_thread: Optional[threading.Thread] = None
128
- self.fastapi_event_loop: Optional[asyncio.AbstractEventLoop] = None
148
+ self.fastapi_app: FastAPI | None = None
149
+ self.uvicorn_server: uvicorn.Server | None = None
150
+ self.fastapi_thread: threading.Thread | None = None
151
+ self.fastapi_event_loop: asyncio.AbstractEventLoop | None = None
129
152
 
130
- self._visualization_internal_app: Optional[SACApp] = None
131
- self._visualization_broker_input: Optional[BrokerInput] = None
153
+ self._visualization_internal_app: SACApp | None = None
154
+ self._visualization_broker_input: BrokerInput | None = None
132
155
  self._visualization_message_queue: queue.Queue = queue.Queue(maxsize=200)
133
- self._active_visualization_streams: Dict[str, Dict[str, Any]] = {}
134
- self._visualization_locks: Dict[asyncio.AbstractEventLoop, asyncio.Lock] = {}
156
+ self._active_visualization_streams: dict[str, dict[str, Any]] = {}
157
+ self._visualization_locks: dict[asyncio.AbstractEventLoop, asyncio.Lock] = {}
135
158
  self._visualization_locks_lock = threading.Lock()
136
- self._global_visualization_subscriptions: Dict[str, int] = {}
137
- self._visualization_processor_task: Optional[asyncio.Task] = None
159
+ self._global_visualization_subscriptions: dict[str, int] = {}
160
+ self._visualization_processor_task: asyncio.Task | None = None
138
161
 
139
162
  log.info("%s Web UI Backend Component initialized.", self.log_identifier)
140
163
 
@@ -297,6 +320,37 @@ class WebUIBackendComponent(BaseGatewayComponent):
297
320
  self._visualization_broker_input = None
298
321
  raise
299
322
 
323
+ def _resolve_session_config(self) -> dict:
324
+ """
325
+ Resolve session service configuration with backward compatibility.
326
+
327
+ Priority order:
328
+ 1. Component-specific session_service config (new approach)
329
+ 2. Shared default_session_service config (deprecated, with warning)
330
+ 3. Hardcoded default (SQLite for Web UI)
331
+ """
332
+ # Check component-specific session_service config first
333
+ component_session_config = self.get_config("session_service")
334
+ if component_session_config:
335
+ log.debug("Using component-specific session_service configuration")
336
+ return component_session_config
337
+
338
+ # Backward compatibility: check shared config
339
+ shared_session_config = self.get_config("default_session_service")
340
+ if shared_session_config:
341
+ log.warning(
342
+ "Using session_service from shared config is deprecated. "
343
+ "Move to component-specific configuration in app_config.session_service"
344
+ )
345
+ return shared_session_config
346
+
347
+ # Default configuration for Web UI (backward compatibility)
348
+ default_config = {"type": "memory", "default_behavior": "PERSISTENT"}
349
+ log.info(
350
+ "Using default memory session configuration for Web UI (backward compatibility)"
351
+ )
352
+ return default_config
353
+
300
354
  async def _visualization_message_processor_loop(self) -> None:
301
355
  """
302
356
  Asynchronously consumes messages from the _visualization_message_queue,
@@ -564,7 +618,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
564
618
  if not hasattr(
565
619
  self._visualization_broker_input, "add_subscription"
566
620
  ) or not callable(
567
- getattr(self._visualization_broker_input, "add_subscription")
621
+ self._visualization_broker_input.add_subscription
568
622
  ):
569
623
  log.error(
570
624
  "%s Visualization BrokerInput does not support dynamic 'add_subscription'. "
@@ -679,9 +733,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
679
733
  try:
680
734
  if not hasattr(
681
735
  self._visualization_broker_input, "remove_subscription"
682
- ) or not callable(
683
- getattr(self._visualization_broker_input, "remove_subscription")
684
- ):
736
+ ) or not callable(self._visualization_broker_input.remove_subscription):
685
737
  log.error(
686
738
  "%s Visualization BrokerInput does not support dynamic 'remove_subscription'. "
687
739
  "Please upgrade the 'solace-ai-connector' module. Cannot remove subscription '%s'.",
@@ -768,7 +820,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
768
820
 
769
821
  async def _extract_initial_claims(
770
822
  self, external_event_data: Any
771
- ) -> Optional[Dict[str, Any]]:
823
+ ) -> dict[str, Any] | None:
772
824
  """
773
825
  Extracts initial identity claims from the incoming external event.
774
826
  For the WebUI, this means inspecting the FastAPIRequest.
@@ -824,16 +876,12 @@ class WebUIBackendComponent(BaseGatewayComponent):
824
876
  return
825
877
 
826
878
  try:
827
- from ...gateway.http_sse.main import (
828
- app as fastapi_app_instance,
829
- )
830
- from ...gateway.http_sse.main import (
831
- setup_dependencies,
832
- )
879
+ from ...gateway.http_sse.main import app as fastapi_app_instance
880
+ from ...gateway.http_sse.main import setup_dependencies
833
881
 
834
882
  self.fastapi_app = fastapi_app_instance
835
883
 
836
- setup_dependencies(self)
884
+ setup_dependencies(self, self.persistence_service)
837
885
 
838
886
  port = (
839
887
  self.fastapi_https_port
@@ -936,7 +984,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
936
984
  raise
937
985
 
938
986
  def publish_a2a(
939
- self, topic: str, payload: Dict, user_properties: Optional[Dict] = None
987
+ self, topic: str, payload: dict, user_properties: dict | None = None
940
988
  ):
941
989
  """
942
990
  Publishes an A2A message using the SAC App's send_message method.
@@ -994,8 +1042,8 @@ class WebUIBackendComponent(BaseGatewayComponent):
994
1042
  log.info("%s Visualization resources cleaned up.", self.log_identifier)
995
1043
 
996
1044
  def _infer_visualization_event_details(
997
- self, topic: str, payload: Dict[str, Any]
998
- ) -> Dict[str, Any]:
1045
+ self, topic: str, payload: dict[str, Any]
1046
+ ) -> dict[str, Any]:
999
1047
  """
1000
1048
  Infers details for the visualization SSE payload from the Solace topic and A2A message.
1001
1049
  This version is updated to parse the official A2A SDK message formats.
@@ -1135,19 +1183,19 @@ class WebUIBackendComponent(BaseGatewayComponent):
1135
1183
  (summary_str[:100] + "...") if len(summary_str) > 100 else summary_str
1136
1184
  )
1137
1185
  except Exception:
1138
- details["payload_summary"][
1139
- "params_preview"
1140
- ] = "[Could not serialize payload]"
1186
+ details["payload_summary"]["params_preview"] = (
1187
+ "[Could not serialize payload]"
1188
+ )
1141
1189
 
1142
1190
  return details
1143
1191
 
1144
1192
  def _extract_involved_agents_for_viz(
1145
- self, topic: str, payload_dict: Dict[str, Any]
1146
- ) -> Set[str]:
1193
+ self, topic: str, payload_dict: dict[str, Any]
1194
+ ) -> set[str]:
1147
1195
  """
1148
1196
  Extracts agent names involved in a message from its topic and payload.
1149
1197
  """
1150
- agents: Set[str] = set()
1198
+ agents: set[str] = set()
1151
1199
  log_id_prefix = f"{self.log_identifier}[ExtractAgentsViz]"
1152
1200
 
1153
1201
  topic_agent_match = re.match(
@@ -1274,13 +1322,13 @@ class WebUIBackendComponent(BaseGatewayComponent):
1274
1322
  """Returns the unique identifier for this gateway instance."""
1275
1323
  return self.gateway_id
1276
1324
 
1277
- def get_cors_origins(self) -> List[str]:
1325
+ def get_cors_origins(self) -> list[str]:
1278
1326
  return self.cors_allowed_origins
1279
1327
 
1280
- def get_shared_artifact_service(self) -> Optional[BaseArtifactService]:
1328
+ def get_shared_artifact_service(self) -> BaseArtifactService | None:
1281
1329
  return self.shared_artifact_service
1282
1330
 
1283
- def get_embed_config(self) -> Dict[str, Any]:
1331
+ def get_embed_config(self) -> dict[str, Any]:
1284
1332
  """Returns embed-related configuration needed by dependencies."""
1285
1333
  return {
1286
1334
  "enable_embed_resolution": self.enable_embed_resolution,
@@ -1296,6 +1344,52 @@ class WebUIBackendComponent(BaseGatewayComponent):
1296
1344
  """Returns the instance of the ConfigResolver."""
1297
1345
  return self._config_resolver
1298
1346
 
1347
+ async def _resolve_embeds_for_persistence(
1348
+ self, message_content: str, session_id: str, user_id: str, log_identifier: str
1349
+ ) -> str:
1350
+ """
1351
+ Resolves embeds in a message for database storage.
1352
+ Returns the resolved text.
1353
+
1354
+ Args:
1355
+ message_content: The message text that may contain embeds
1356
+ session_id: The A2A session ID
1357
+ user_id: The user ID
1358
+ log_identifier: Logging identifier
1359
+
1360
+ Returns:
1361
+ The message with embeds resolved (or original if resolution fails)
1362
+ """
1363
+ try:
1364
+ embed_context = {
1365
+ "artifact_service": self.shared_artifact_service,
1366
+ "session_context": {
1367
+ "app_name": self.gateway_id,
1368
+ "user_id": user_id,
1369
+ "session_id": session_id,
1370
+ },
1371
+ "config": self.get_embed_config(),
1372
+ }
1373
+
1374
+ resolved_text, _, _ = await resolve_embeds_in_string(
1375
+ text=message_content,
1376
+ context=embed_context,
1377
+ resolver_func=evaluate_embed,
1378
+ types_to_resolve=EARLY_EMBED_TYPES,
1379
+ log_identifier=log_identifier,
1380
+ config=embed_context["config"],
1381
+ )
1382
+
1383
+ return resolved_text
1384
+
1385
+ except Exception as e:
1386
+ log.warning(
1387
+ "%s Error resolving embeds for storage: %s. Using original message.",
1388
+ log_identifier,
1389
+ e,
1390
+ )
1391
+ return message_content
1392
+
1299
1393
  def _start_listener(self) -> None:
1300
1394
  """
1301
1395
  GDK Hook: Starts the FastAPI/Uvicorn server.
@@ -1317,8 +1411,8 @@ class WebUIBackendComponent(BaseGatewayComponent):
1317
1411
  pass
1318
1412
 
1319
1413
  async def _translate_external_input(
1320
- self, external_event_data: Dict[str, Any]
1321
- ) -> Tuple[str, List[ContentPart], Dict[str, Any]]:
1414
+ self, external_event_data: dict[str, Any]
1415
+ ) -> tuple[str, list[ContentPart], dict[str, Any]]:
1322
1416
  """
1323
1417
  Translates raw HTTP request data (from FastAPI form) into A2A task parameters.
1324
1418
 
@@ -1342,10 +1436,9 @@ class WebUIBackendComponent(BaseGatewayComponent):
1342
1436
 
1343
1437
  target_agent_name: str = external_event_data.get("agent_name")
1344
1438
  user_message: str = external_event_data.get("message", "")
1345
- files: Optional[List[UploadFile]] = external_event_data.get("files")
1439
+ files: list[UploadFile] | None = external_event_data.get("files")
1346
1440
  client_id: str = external_event_data.get("client_id")
1347
1441
  a2a_session_id: str = external_event_data.get("a2a_session_id")
1348
-
1349
1442
  if not target_agent_name:
1350
1443
  raise ValueError("Target agent name is missing in external_event_data.")
1351
1444
  if not client_id or not a2a_session_id:
@@ -1353,7 +1446,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
1353
1446
  "Client ID or A2A Session ID is missing in external_event_data."
1354
1447
  )
1355
1448
 
1356
- a2a_parts: List[ContentPart] = []
1449
+ a2a_parts: list[ContentPart] = []
1357
1450
 
1358
1451
  if files:
1359
1452
  for upload_file in files:
@@ -1413,18 +1506,24 @@ class WebUIBackendComponent(BaseGatewayComponent):
1413
1506
 
1414
1507
  async def _send_update_to_external(
1415
1508
  self,
1416
- external_request_context: Dict[str, Any],
1417
- event_data: Union[TaskStatusUpdateEvent, TaskArtifactUpdateEvent],
1509
+ external_request_context: dict[str, Any],
1510
+ event_data: TaskStatusUpdateEvent | TaskArtifactUpdateEvent,
1418
1511
  is_final_chunk_of_update: bool,
1419
1512
  ) -> None:
1420
1513
  """
1421
1514
  Sends an intermediate update (TaskStatusUpdateEvent or TaskArtifactUpdateEvent)
1422
- to the external platform (Web UI via SSE).
1515
+ to the external platform (Web UI via SSE) and stores agent messages in the database.
1423
1516
  """
1424
1517
  log_id_prefix = f"{self.log_identifier}[SendUpdate]"
1425
1518
  sse_task_id = external_request_context.get("a2a_task_id_for_event")
1426
1519
  a2a_task_id = event_data.task_id
1427
1520
 
1521
+ log.debug(
1522
+ "%s _send_update_to_external called with event_type: %s",
1523
+ log_id_prefix,
1524
+ type(event_data).__name__,
1525
+ )
1526
+
1428
1527
  if not sse_task_id:
1429
1528
  log.error(
1430
1529
  "%s Cannot send update: 'a2a_task_id_for_event' missing from external_request_context.",
@@ -1459,6 +1558,10 @@ class WebUIBackendComponent(BaseGatewayComponent):
1459
1558
  sse_event_type,
1460
1559
  a2a_task_id,
1461
1560
  )
1561
+
1562
+ # Note: Agent message storage is handled in _send_final_response_to_external
1563
+ # to avoid duplicate storage of intermediate status updates
1564
+
1462
1565
  except Exception as e:
1463
1566
  log.exception(
1464
1567
  "%s Failed to send %s via SSE for A2A Task ID %s: %s",
@@ -1469,7 +1572,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
1469
1572
  )
1470
1573
 
1471
1574
  async def _send_final_response_to_external(
1472
- self, external_request_context: Dict[str, Any], task_data: Task
1575
+ self, external_request_context: dict[str, Any], task_data: Task
1473
1576
  ) -> None:
1474
1577
  """
1475
1578
  Sends the final A2A Task result to the external platform (Web UI via SSE).
@@ -1478,6 +1581,8 @@ class WebUIBackendComponent(BaseGatewayComponent):
1478
1581
  sse_task_id = external_request_context.get("a2a_task_id_for_event")
1479
1582
  a2a_task_id = task_data.id
1480
1583
 
1584
+ log.debug("%s _send_final_response_to_external called", log_id_prefix)
1585
+
1481
1586
  if not sse_task_id:
1482
1587
  log.error(
1483
1588
  "%s Cannot send final response: 'a2a_task_id_for_event' missing from external_request_context.",
@@ -1506,6 +1611,63 @@ class WebUIBackendComponent(BaseGatewayComponent):
1506
1611
  log_id_prefix,
1507
1612
  a2a_task_id,
1508
1613
  )
1614
+
1615
+ # Store final agent response in persistence layer if available
1616
+ if hasattr(self, "persistence_service") and self.persistence_service:
1617
+ try:
1618
+ session_id = external_request_context.get("a2a_session_id")
1619
+ user_id = external_request_context.get("user_id_for_a2a")
1620
+ agent_name = external_request_context.get(
1621
+ "target_agent_name", "agent"
1622
+ )
1623
+
1624
+ # Extract message content from the task status
1625
+ message_text = ""
1626
+ if task_data.status and task_data.status.message:
1627
+ parts = a2a.get_parts_from_message(task_data.status.message)
1628
+ for part in parts:
1629
+ if hasattr(part, "text") and part.text:
1630
+ if message_text:
1631
+ message_text += "\n"
1632
+ message_text += part.text
1633
+
1634
+ log.info(
1635
+ "%s Final agent response storage debug - session_id: %s, user_id: %s, message_text: '%s', parts_count: %s",
1636
+ log_id_prefix,
1637
+ session_id,
1638
+ user_id,
1639
+ message_text[:100] if message_text else None,
1640
+ len(a2a.get_parts_from_message(task_data.status.message))
1641
+ if task_data.status and task_data.status.message
1642
+ else 0,
1643
+ )
1644
+
1645
+ if message_text and session_id and user_id:
1646
+ from .dependencies import get_session_service
1647
+ from .shared.enums import SenderType
1648
+
1649
+ session_service = get_session_service(self)
1650
+ session_service.add_message_to_session(
1651
+ session_id=session_id,
1652
+ user_id=user_id,
1653
+ message=message_text,
1654
+ sender_type=SenderType.AGENT,
1655
+ sender_name=agent_name,
1656
+ agent_id=agent_name,
1657
+ )
1658
+ log.info(
1659
+ "%s Final agent response stored in session %s",
1660
+ log_id_prefix,
1661
+ session_id,
1662
+ )
1663
+ except Exception as storage_error:
1664
+ log.warning(
1665
+ "%s Failed to store final agent response: %s",
1666
+ log_id_prefix,
1667
+ storage_error,
1668
+ )
1669
+ # Don't fail the SSE send if storage fails
1670
+
1509
1671
  except Exception as e:
1510
1672
  log.exception(
1511
1673
  "%s Failed to send final_response via SSE for A2A Task ID %s: %s",
@@ -1522,7 +1684,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
1522
1684
  )
1523
1685
 
1524
1686
  async def _send_error_to_external(
1525
- self, external_request_context: Dict[str, Any], error_data: JSONRPCError
1687
+ self, external_request_context: dict[str, Any], error_data: JSONRPCError
1526
1688
  ) -> None:
1527
1689
  """
1528
1690
  Sends an error notification to the external platform (Web UI via SSE).