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,39 +3,40 @@ Defines FastAPI dependency injectors to access shared resources
3
3
  managed by the WebUIBackendComponent.
4
4
  """
5
5
 
6
- from fastapi import Depends, HTTPException, status, Request
7
- from typing import (
8
- TYPE_CHECKING,
9
- Callable,
10
- Dict,
11
- Optional,
12
- Any,
13
- )
14
- import os
6
+ from collections.abc import Callable
7
+ from typing import TYPE_CHECKING, Any
15
8
 
9
+ from fastapi import Depends, HTTPException, Request, status
16
10
  from solace_ai_connector.common.log import log
17
- from ...gateway.http_sse.sse_manager import SSEManager
18
- from ...gateway.http_sse.session_manager import SessionManager
19
- from ...common.agent_registry import AgentRegistry
20
-
21
- from ...gateway.http_sse.services.agent_service import AgentService
22
- from ...gateway.http_sse.services.task_service import TaskService
23
- from ...gateway.http_sse.services.people_service import PeopleService
24
- from ...gateway.base.task_context import TaskContextManager
25
11
 
26
- from ...core_a2a.service import CoreA2AService
27
- from ...common.services.identity_service import BaseIdentityService
12
+ from ...common.agent_registry import AgentRegistry
28
13
  from ...common.middleware.config_resolver import ConfigResolver
14
+ from ...common.services.identity_service import BaseIdentityService
15
+ from ...core_a2a.service import CoreA2AService
16
+ from ...gateway.base.task_context import TaskContextManager
17
+ from ...gateway.http_sse.services.agent_card_service import AgentCardService
18
+ from ...gateway.http_sse.services.people_service import PeopleService
19
+ from ...gateway.http_sse.services.task_service import TaskService
20
+ from ...gateway.http_sse.session_manager import SessionManager
21
+ from ...gateway.http_sse.sse_manager import SSEManager
22
+ from .application.services.session_service import SessionService
23
+ from .infrastructure.persistence_service import PersistenceService
29
24
 
30
- from google.adk.artifacts import BaseArtifactService
25
+ try:
26
+ from google.adk.artifacts import BaseArtifactService
27
+ except ImportError:
28
+ # Mock BaseArtifactService for environments without Google ADK
29
+ class BaseArtifactService:
30
+ pass
31
31
 
32
32
 
33
33
  if TYPE_CHECKING:
34
34
  from gateway.http_sse.component import WebUIBackendComponent
35
35
 
36
36
  sac_component_instance: "WebUIBackendComponent" = None
37
+ persistence_service_instance: "PersistenceService" = None
37
38
 
38
- api_config: Optional[Dict[str, Any]] = None
39
+ api_config: dict[str, Any] | None = None
39
40
 
40
41
 
41
42
  def set_component_instance(component: "WebUIBackendComponent"):
@@ -48,7 +49,30 @@ def set_component_instance(component: "WebUIBackendComponent"):
48
49
  log.warning("[Dependencies] SAC Component instance already set.")
49
50
 
50
51
 
51
- def set_api_config(config: Dict[str, Any]):
52
+ def set_persistence_service(persistence_service: "PersistenceService"):
53
+ """Called by the component during its startup to provide its instance."""
54
+ global persistence_service_instance
55
+ if persistence_service_instance is None:
56
+ persistence_service_instance = persistence_service
57
+ log.info("[Dependencies] Persistence Service instance provided.")
58
+ else:
59
+ log.warning("[Dependencies] Persistence Service instance already set.")
60
+
61
+
62
+ def get_persistence_service() -> "PersistenceService":
63
+ """FastAPI dependency to get the PersistenceService instance."""
64
+ if persistence_service_instance is None:
65
+ log.warning(
66
+ "[Dependencies] PersistenceService not available - running in compatibility mode"
67
+ )
68
+ raise HTTPException(
69
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
70
+ detail="Persistence service not available in compatibility mode.",
71
+ )
72
+ return persistence_service_instance
73
+
74
+
75
+ def set_api_config(config: dict[str, Any]):
52
76
  """Called during startup to provide API configuration."""
53
77
  global api_config
54
78
  if api_config is None:
@@ -71,7 +95,7 @@ def get_sac_component() -> "WebUIBackendComponent":
71
95
  return sac_component_instance
72
96
 
73
97
 
74
- def get_api_config() -> Dict[str, Any]:
98
+ def get_api_config() -> dict[str, Any]:
75
99
  """FastAPI dependency to get the API configuration."""
76
100
  if api_config is None:
77
101
  log.critical("[Dependencies] API configuration accessed before it was set!")
@@ -128,26 +152,43 @@ def get_user_id(
128
152
  ) -> str:
129
153
  """
130
154
  FastAPI dependency that returns the user's identity.
131
- It prioritizes the authenticated user from `request.state.user` (set by AuthMiddleware)
132
- and falls back to the SessionManager for non-authenticated or legacy scenarios.
155
+ When FRONTEND_USE_AUTHORIZATION is true: Fully relies on OAuth - user must be authenticated by AuthMiddleware.
156
+ When FRONTEND_USE_AUTHORIZATION is false: Uses development fallback user.
133
157
  """
134
158
  log.debug("[Dependencies] Resolving user_id string")
135
159
 
160
+ # AuthMiddleware should always set user state for both auth enabled/disabled cases
136
161
  if hasattr(request.state, "user") and request.state.user:
137
162
  user_id = request.state.user.get("id")
138
163
  if user_id:
139
164
  log.debug(f"[Dependencies] Using user ID from AuthMiddleware: {user_id}")
140
165
  return user_id
141
166
  else:
142
- log.warning(
143
- "[Dependencies] request.state.user exists but has no 'id' field. Falling back."
167
+ log.error(
168
+ "[Dependencies] request.state.user exists but has no 'id' field: %s. This indicates a bug in AuthMiddleware.",
169
+ request.state.user,
144
170
  )
145
171
 
146
- log.debug("[Dependencies] Falling back to SessionManager for user_id")
147
- try:
148
- return session_manager.get_a2a_client_id(request)
149
- except AssertionError:
150
- return f"anonymous_user:{os.urandom(8).hex()}"
172
+ # If we reach here, AuthMiddleware didn't set user state properly
173
+ use_authorization = session_manager.use_authorization
174
+
175
+ if use_authorization:
176
+ # When OAuth is enabled, we should never reach here - AuthMiddleware should have handled authentication
177
+ log.error(
178
+ "[Dependencies] OAuth is enabled but no authenticated user found. This indicates an authentication failure or middleware bug."
179
+ )
180
+ raise HTTPException(
181
+ status_code=status.HTTP_401_UNAUTHORIZED,
182
+ detail="Authentication required but user not found",
183
+ )
184
+ else:
185
+ # When auth is disabled, use development fallback user
186
+ fallback_id = "sam_dev_user"
187
+ log.info(
188
+ "[Dependencies] Authorization disabled and no user in request state, using fallback user: %s",
189
+ fallback_id,
190
+ )
191
+ return fallback_id
151
192
 
152
193
 
153
194
  def ensure_session_id(
@@ -161,21 +202,21 @@ def ensure_session_id(
161
202
 
162
203
  def get_identity_service(
163
204
  component: "WebUIBackendComponent" = Depends(get_sac_component),
164
- ) -> Optional[BaseIdentityService]:
205
+ ) -> BaseIdentityService | None:
165
206
  """FastAPI dependency to get the configured IdentityService instance."""
166
207
  log.debug("[Dependencies] get_identity_service called")
167
208
  return component.identity_service
168
209
 
169
210
 
170
211
  def get_people_service(
171
- identity_service: Optional[BaseIdentityService] = Depends(get_identity_service),
212
+ identity_service: BaseIdentityService | None = Depends(get_identity_service),
172
213
  ) -> PeopleService:
173
214
  """FastAPI dependency to get an instance of PeopleService."""
174
215
  log.debug("[Dependencies] get_people_service called")
175
216
  return PeopleService(identity_service=identity_service)
176
217
 
177
218
 
178
- PublishFunc = Callable[[str, Dict, Optional[Dict]], None]
219
+ PublishFunc = Callable[[str, dict, dict | None], None]
179
220
 
180
221
 
181
222
  def get_publish_a2a_func(
@@ -212,7 +253,7 @@ def get_config_resolver(
212
253
 
213
254
  def get_app_config(
214
255
  component: "WebUIBackendComponent" = Depends(get_sac_component),
215
- ) -> Dict[str, Any]:
256
+ ) -> dict[str, Any]:
216
257
  """
217
258
  FastAPI dependency to safely get the application configuration dictionary.
218
259
  """
@@ -225,8 +266,8 @@ async def get_user_config(
225
266
  user_id: str = Depends(get_user_id),
226
267
  config_resolver: ConfigResolver = Depends(get_config_resolver),
227
268
  component: "WebUIBackendComponent" = Depends(get_sac_component),
228
- app_config: Dict[str, Any] = Depends(get_app_config),
229
- ) -> Dict[str, Any]:
269
+ app_config: dict[str, Any] = Depends(get_app_config),
270
+ ) -> dict[str, Any]:
230
271
  """
231
272
  FastAPI dependency to get the user-specific configuration.
232
273
  """
@@ -243,7 +284,7 @@ async def get_user_config(
243
284
 
244
285
  def get_shared_artifact_service(
245
286
  component: "WebUIBackendComponent" = Depends(get_sac_component),
246
- ) -> Optional[BaseArtifactService]:
287
+ ) -> BaseArtifactService | None:
247
288
  """FastAPI dependency to get the shared ArtifactService."""
248
289
  log.debug("[Dependencies] get_shared_artifact_service called")
249
290
  return component.get_shared_artifact_service()
@@ -251,7 +292,7 @@ def get_shared_artifact_service(
251
292
 
252
293
  def get_embed_config(
253
294
  component: "WebUIBackendComponent" = Depends(get_sac_component),
254
- ) -> Dict[str, Any]:
295
+ ) -> dict[str, Any]:
255
296
  """FastAPI dependency to get embed-related configuration."""
256
297
  log.debug("[Dependencies] get_embed_config called")
257
298
  return component.get_embed_config()
@@ -282,12 +323,12 @@ def get_task_context_manager_from_component(
282
323
  return component.task_context_manager
283
324
 
284
325
 
285
- def get_agent_service(
326
+ def get_agent_card_service(
286
327
  registry: AgentRegistry = Depends(get_agent_registry),
287
- ) -> AgentService:
288
- """FastAPI dependency to get an instance of AgentService."""
289
- log.debug("[Dependencies] get_agent_service called")
290
- return AgentService(agent_registry=registry)
328
+ ) -> AgentCardService:
329
+ """FastAPI dependency to get an instance of AgentCardService."""
330
+ log.debug("[Dependencies] get_agent_card_service called")
331
+ return AgentCardService(agent_registry=registry)
291
332
 
292
333
 
293
334
  def get_task_service(
@@ -314,3 +355,65 @@ def get_task_service(
314
355
  task_context_lock=task_context_manager._lock,
315
356
  app_name=app_name,
316
357
  )
358
+
359
+
360
+ def get_session_service(
361
+ component: "WebUIBackendComponent" = Depends(get_sac_component),
362
+ ) -> SessionService:
363
+ log.debug("[Dependencies] get_session_service called")
364
+
365
+ if (
366
+ hasattr(component, "persistence_service")
367
+ and component.persistence_service is not None
368
+ ):
369
+ log.debug("Using database-backed session service")
370
+ container = component.persistence_service.container
371
+ return container.get_session_service()
372
+ else:
373
+ log.debug("No database configured - session persistence not available")
374
+ raise HTTPException(
375
+ status_code=status.HTTP_501_NOT_IMPLEMENTED,
376
+ detail="Session management requires database configuration. Configure 'database_url' in your gateway configuration.",
377
+ )
378
+
379
+
380
+ def get_session_validator(
381
+ component: "WebUIBackendComponent" = Depends(get_sac_component),
382
+ ) -> Callable[[str, str], bool]:
383
+ """
384
+ FastAPI dependency that returns a session validator function.
385
+
386
+ With database: Validates sessions against database
387
+ Without database: Validates sessions using basic checks (session exists in recent activity)
388
+ """
389
+ log.debug("[Dependencies] get_session_validator called")
390
+
391
+ # Check if component has a persistence service (database-backed)
392
+ if (
393
+ hasattr(component, "persistence_service")
394
+ and component.persistence_service is not None
395
+ ):
396
+ log.debug("Using database-backed session validation")
397
+
398
+ def validate_with_database(session_id: str, user_id: str) -> bool:
399
+ container = component.persistence_service.container
400
+ session_service = container.get_session_service()
401
+ session_domain = session_service.get_session(
402
+ session_id=session_id, user_id=user_id
403
+ )
404
+ return session_domain is not None
405
+
406
+ return validate_with_database
407
+ else:
408
+ log.debug("No database configured - using basic session validation")
409
+
410
+ def validate_without_database(session_id: str, user_id: str) -> bool:
411
+ """Basic validation: check session ID format and user authentication"""
412
+ # Validate session ID format (should be web-session-* format from SessionManager)
413
+ if not session_id or not session_id.startswith("web-session-"):
414
+ return False
415
+ # If user_id is provided (authenticated), allow access
416
+ # This provides basic security while allowing filesystem-based artifacts
417
+ return bool(user_id)
418
+
419
+ return validate_without_database
@@ -0,0 +1,3 @@
1
+ from .session import Message, Session, SessionHistory
2
+
3
+ __all__ = ["Session", "Message", "SessionHistory"]
@@ -0,0 +1,90 @@
1
+ from datetime import datetime, timezone
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from ...shared.enums import MessageType, SenderType, SessionStatus
6
+ from ...shared.types import AgentId, MessageId, SessionId, UserId
7
+
8
+
9
+ class Session(BaseModel):
10
+ id: SessionId
11
+ user_id: UserId
12
+ name: str | None = None
13
+ agent_id: AgentId | None = None
14
+ status: SessionStatus = SessionStatus.ACTIVE
15
+ created_at: datetime
16
+ updated_at: datetime | None = None
17
+ last_activity: datetime | None = None
18
+
19
+ def update_name(self, new_name: str) -> None:
20
+ if not new_name or len(new_name.strip()) == 0:
21
+ raise ValueError("Session name cannot be empty")
22
+ if len(new_name) > 255:
23
+ raise ValueError("Session name cannot exceed 255 characters")
24
+
25
+ self.name = new_name.strip()
26
+ self.updated_at = datetime.now(timezone.utc)
27
+
28
+ def mark_activity(self) -> None:
29
+ self.last_activity = datetime.now(timezone.utc)
30
+ self.updated_at = datetime.now(timezone.utc)
31
+
32
+ def archive(self) -> None:
33
+ self.status = SessionStatus.ARCHIVED
34
+ self.updated_at = datetime.now(timezone.utc)
35
+
36
+ def activate(self) -> None:
37
+ self.status = SessionStatus.ACTIVE
38
+ self.updated_at = datetime.now(timezone.utc)
39
+
40
+ def can_be_deleted_by_user(self, user_id: UserId) -> bool:
41
+ return self.user_id == user_id
42
+
43
+ def can_be_accessed_by_user(self, user_id: UserId) -> bool:
44
+ return self.user_id == user_id
45
+
46
+
47
+ class Message(BaseModel):
48
+ id: MessageId
49
+ session_id: SessionId
50
+ message: str
51
+ sender_type: SenderType
52
+ sender_name: str
53
+ message_type: MessageType = MessageType.TEXT
54
+ created_at: datetime
55
+
56
+ def validate_message_content(self) -> None:
57
+ if not self.message or len(self.message.strip()) == 0:
58
+ raise ValueError("Message content cannot be empty")
59
+ if len(self.message) > 10_000_000:
60
+ raise ValueError("Message content exceeds maximum length (10MB)")
61
+
62
+ def is_from_user(self) -> bool:
63
+ return self.sender_type == SenderType.USER
64
+
65
+ def is_from_agent(self) -> bool:
66
+ return self.sender_type == SenderType.AGENT
67
+
68
+ def is_system_message(self) -> bool:
69
+ return self.sender_type == SenderType.SYSTEM
70
+
71
+
72
+ class SessionHistory(BaseModel):
73
+ session: Session
74
+ messages: list[Message] = []
75
+ total_message_count: int = 0
76
+
77
+ def add_message(self, message: Message) -> None:
78
+ if message.session_id != self.session.id:
79
+ raise ValueError("Message does not belong to this session")
80
+
81
+ message.validate_message_content()
82
+ self.messages.append(message)
83
+ self.total_message_count += 1
84
+ self.session.mark_activity()
85
+
86
+ def get_messages_by_sender_type(self, sender_type: SenderType) -> list[Message]:
87
+ return [msg for msg in self.messages if msg.sender_type == sender_type]
88
+
89
+ def get_latest_messages(self, count: int = 10) -> list[Message]:
90
+ return sorted(self.messages, key=lambda x: x.created_at, reverse=True)[:count]
@@ -0,0 +1,3 @@
1
+ from .session_repository import IMessageRepository, ISessionRepository
2
+
3
+ __all__ = ["ISessionRepository", "IMessageRepository"]
@@ -0,0 +1,54 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from ...shared.types import PaginationInfo, SessionId, UserId
4
+ from ..entities.session import Message, Session
5
+
6
+
7
+ class ISessionRepository(ABC):
8
+ @abstractmethod
9
+ def get_by_id(self, session_id: SessionId) -> Session | None:
10
+ pass
11
+
12
+ @abstractmethod
13
+ def get_by_user_id(
14
+ self, user_id: UserId, pagination: PaginationInfo | None = None
15
+ ) -> list[Session]:
16
+ pass
17
+
18
+ @abstractmethod
19
+ def get_user_session(
20
+ self, session_id: SessionId, user_id: UserId
21
+ ) -> Session | None:
22
+ pass
23
+
24
+ @abstractmethod
25
+ def create(self, session: Session) -> Session:
26
+ pass
27
+
28
+ @abstractmethod
29
+ def update(self, session: Session) -> Session | None:
30
+ pass
31
+
32
+ @abstractmethod
33
+ def delete(self, session_id: SessionId, user_id: UserId) -> bool:
34
+ pass
35
+
36
+ @abstractmethod
37
+ def exists(self, session_id: SessionId) -> bool:
38
+ pass
39
+
40
+
41
+ class IMessageRepository(ABC):
42
+ @abstractmethod
43
+ def get_by_session_id(
44
+ self, session_id: SessionId, pagination: PaginationInfo | None = None
45
+ ) -> list[Message]:
46
+ pass
47
+
48
+ @abstractmethod
49
+ def create(self, message: Message) -> Message:
50
+ pass
51
+
52
+ @abstractmethod
53
+ def delete_by_session_id(self, session_id: SessionId) -> bool:
54
+ pass
@@ -0,0 +1,4 @@
1
+ from .persistence.database_service import DatabaseService
2
+ from .repositories.session_repository import SessionRepository, MessageRepository
3
+
4
+ __all__ = ["DatabaseService", "SessionRepository", "MessageRepository"]
@@ -0,0 +1,3 @@
1
+ from .container import ApplicationContainer, get_container, initialize_container
2
+
3
+ __all__ = ["ApplicationContainer", "initialize_container", "get_container"]
@@ -0,0 +1,123 @@
1
+ from collections.abc import Callable
2
+ from typing import Any, TypeVar
3
+
4
+ from ...application.services.session_service import SessionService
5
+ from ...domain.repositories.session_repository import (
6
+ IMessageRepository,
7
+ ISessionRepository,
8
+ )
9
+ from ...infrastructure.persistence import database_service as db_service_module
10
+ from ...infrastructure.persistence.database_service import DatabaseService
11
+ from ...infrastructure.repositories.session_repository import (
12
+ MessageRepository,
13
+ SessionRepository,
14
+ )
15
+
16
+ T = TypeVar("T")
17
+
18
+
19
+ class DIContainer:
20
+ """Dependency injection container."""
21
+
22
+ def __init__(self):
23
+ self._services: dict[str, Any] = {}
24
+ self._factories: dict[str, Callable] = {}
25
+ self._singletons: dict[str, Any] = {}
26
+
27
+ def register_singleton(self, service_type: type, instance: Any) -> None:
28
+ self._singletons[service_type.__name__] = instance
29
+
30
+ def register_factory(self, service_type: type, factory: Callable) -> None:
31
+ self._factories[service_type.__name__] = factory
32
+
33
+ def register_transient(self, service_type: type, implementation: type) -> None:
34
+ """Register a transient service (new instance each time)."""
35
+ self._services[service_type.__name__] = implementation
36
+
37
+ def get(self, service_type: type) -> Any:
38
+ """Get an instance of the requested service."""
39
+ service_name = service_type.__name__
40
+
41
+ # Check singletons first
42
+ if service_name in self._singletons:
43
+ return self._singletons[service_name]
44
+
45
+ # Check factories
46
+ if service_name in self._factories:
47
+ return self._factories[service_name]()
48
+
49
+ # Check transient services
50
+ if service_name in self._services:
51
+ implementation = self._services[service_name]
52
+ return self._create_instance(implementation)
53
+
54
+ raise ValueError(f"Service {service_name} is not registered")
55
+
56
+ def _create_instance(self, implementation: type) -> Any:
57
+ """Create an instance with dependency injection."""
58
+ # This is a simplified implementation
59
+ # In a production system, you'd use a more sophisticated approach
60
+ # like inspecting constructor parameters and resolving them automatically
61
+ return implementation()
62
+
63
+
64
+ class ApplicationContainer:
65
+ """Application-specific dependency injection container."""
66
+
67
+ def __init__(self, database_url: str | None = None):
68
+ self.container = DIContainer()
69
+ self.database_url = database_url
70
+ self.has_database = database_url is not None
71
+ self._setup_dependencies()
72
+
73
+ def _setup_dependencies(self) -> None:
74
+ if self.has_database:
75
+ database_service = DatabaseService(self.database_url)
76
+ self.container.register_singleton(DatabaseService, database_service)
77
+ db_service_module.database_service = database_service
78
+
79
+ session_repository = SessionRepository(database_service)
80
+ message_repository = MessageRepository(database_service)
81
+
82
+ self.container.register_singleton(ISessionRepository, session_repository)
83
+ self.container.register_singleton(IMessageRepository, message_repository)
84
+
85
+ def session_service_factory():
86
+ return SessionService(session_repository, message_repository)
87
+
88
+ self.container.register_factory(SessionService, session_service_factory)
89
+
90
+ def get_database_service(self) -> DatabaseService | None:
91
+ if not self.has_database:
92
+ return None
93
+ return self.container.get(DatabaseService)
94
+
95
+ def get_session_service(self) -> SessionService | None:
96
+ if not self.has_database:
97
+ return None
98
+ return self.container.get(SessionService)
99
+
100
+
101
+ # Global container instance
102
+ _container: ApplicationContainer | None = None
103
+
104
+
105
+ def initialize_container(database_url: str | None = None) -> ApplicationContainer:
106
+ global _container
107
+ _container = ApplicationContainer(database_url)
108
+
109
+ # Only create tables if database is available
110
+ if _container.has_database:
111
+ database_service = _container.get_database_service()
112
+ if database_service:
113
+ database_service.create_tables()
114
+
115
+ return _container
116
+
117
+
118
+ def get_container() -> ApplicationContainer:
119
+ if _container is None:
120
+ raise RuntimeError(
121
+ "Container not initialized. Call initialize_container() first."
122
+ )
123
+ return _container
@@ -0,0 +1,4 @@
1
+ from .database_service import DatabaseService
2
+ from .models import Base, SessionModel, MessageModel
3
+
4
+ __all__ = ["DatabaseService", "Base", "SessionModel", "MessageModel"]
@@ -0,0 +1,16 @@
1
+ from sqlalchemy.orm import sessionmaker
2
+
3
+ from .database_service import DatabaseService
4
+
5
+
6
+ class DatabasePersistenceService:
7
+ def __init__(self, db_url: str):
8
+ self.db_service = DatabaseService(db_url)
9
+ self._session_factory = sessionmaker(bind=self.db_service.engine)
10
+
11
+ def Session(self):
12
+ return self._session_factory()
13
+
14
+ @property
15
+ def engine(self):
16
+ return self.db_service.engine