solace-agent-mesh 1.4.12__py3-none-any.whl → 1.5.0__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 (181) hide show
  1. solace_agent_mesh/agent/adk/adk_llm.txt +3 -4
  2. solace_agent_mesh/agent/adk/adk_llm_detail.txt +566 -0
  3. solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +1 -1
  4. solace_agent_mesh/agent/adk/callbacks.py +51 -2
  5. solace_agent_mesh/agent/adk/models/lite_llm.py +1 -0
  6. solace_agent_mesh/agent/adk/models/models_llm.txt +1 -2
  7. solace_agent_mesh/agent/agent_llm.txt +1 -1
  8. solace_agent_mesh/agent/agent_llm_detail.txt +1702 -0
  9. solace_agent_mesh/agent/protocol/event_handlers.py +2 -13
  10. solace_agent_mesh/agent/protocol/protocol_llm.txt +15 -2
  11. solace_agent_mesh/agent/protocol/protocol_llm_detail.txt +92 -0
  12. solace_agent_mesh/agent/sac/component.py +51 -21
  13. solace_agent_mesh/agent/sac/sac_llm.txt +15 -1
  14. solace_agent_mesh/agent/sac/sac_llm_detail.txt +200 -0
  15. solace_agent_mesh/agent/sac/task_execution_context.py +73 -0
  16. solace_agent_mesh/agent/testing/testing_llm_detail.txt +68 -0
  17. solace_agent_mesh/agent/tools/tools_llm.txt +148 -154
  18. solace_agent_mesh/agent/tools/tools_llm_detail.txt +274 -0
  19. solace_agent_mesh/agent/utils/utils_llm.txt +1 -1
  20. solace_agent_mesh/agent/utils/utils_llm_detail.txt +149 -0
  21. solace_agent_mesh/assets/docs/404.html +3 -3
  22. solace_agent_mesh/assets/docs/assets/js/483cef9a.bf9398af.js +1 -0
  23. solace_agent_mesh/assets/docs/assets/js/{main.f67fc9f4.js → main.0c149855.js} +2 -2
  24. solace_agent_mesh/assets/docs/assets/js/{runtime~main.40527046.js → runtime~main.c66557e4.js} +1 -1
  25. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +3 -3
  26. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/rbac-setup-guilde/index.html +3 -3
  27. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +8 -4
  28. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
  29. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html +3 -3
  30. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +3 -3
  31. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +3 -3
  32. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +3 -3
  33. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +3 -3
  34. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +3 -3
  35. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +3 -3
  36. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +3 -3
  37. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +3 -3
  38. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +3 -3
  39. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +3 -3
  40. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +3 -3
  41. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/litellm_models/index.html +3 -3
  42. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +3 -3
  43. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
  44. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +3 -3
  45. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +3 -3
  46. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +3 -3
  47. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +3 -3
  48. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +3 -3
  49. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +3 -3
  50. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +3 -3
  51. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +3 -3
  52. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +3 -3
  53. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +3 -3
  54. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +3 -3
  55. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +3 -3
  56. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +3 -3
  57. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +3 -3
  58. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +3 -3
  59. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +3 -3
  60. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +3 -3
  61. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +3 -3
  62. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +3 -3
  63. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +3 -3
  64. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +3 -3
  65. solace_agent_mesh/assets/docs/lunr-index-1760032255022.json +1 -0
  66. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  67. solace_agent_mesh/assets/docs/search-doc-1760032255022.json +1 -0
  68. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  69. solace_agent_mesh/cli/__init__.py +1 -1
  70. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-j1LW-wlq.js → authCallback-DwrxZE0E.js} +1 -1
  71. solace_agent_mesh/client/webui/frontend/static/assets/{client-B9p_nFNA.js → client-DarGQzyw.js} +1 -1
  72. solace_agent_mesh/client/webui/frontend/static/assets/main-CZbpmwfA.css +1 -0
  73. solace_agent_mesh/client/webui/frontend/static/assets/main-C__uuUkB.js +339 -0
  74. solace_agent_mesh/client/webui/frontend/static/assets/{vendor-CS5YMf8a.js → vendor-BKIeiHj_.js} +80 -70
  75. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  76. solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
  77. solace_agent_mesh/common/a2a/a2a_llm.txt +1 -1
  78. solace_agent_mesh/common/a2a/a2a_llm_detail.txt +193 -0
  79. solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +1 -1
  80. solace_agent_mesh/common/a2a_spec/a2a_spec_llm_detail.txt +736 -0
  81. solace_agent_mesh/common/a2a_spec/schemas/llm_invocation.json +23 -0
  82. solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +93 -15
  83. solace_agent_mesh/common/a2a_spec/schemas/tool_result.json +23 -0
  84. solace_agent_mesh/common/common_llm.txt +24 -39
  85. solace_agent_mesh/common/common_llm_detail.txt +2562 -0
  86. solace_agent_mesh/common/data_parts.py +9 -1
  87. solace_agent_mesh/common/middleware/middleware_llm_detail.txt +185 -0
  88. solace_agent_mesh/common/sac/sac_llm.txt +1 -1
  89. solace_agent_mesh/common/sac/sac_llm_detail.txt +82 -0
  90. solace_agent_mesh/common/sam_events/sam_events_llm.txt +104 -0
  91. solace_agent_mesh/common/sam_events/sam_events_llm_detail.txt +115 -0
  92. solace_agent_mesh/common/services/services_llm.txt +57 -6
  93. solace_agent_mesh/common/services/services_llm_detail.txt +459 -0
  94. solace_agent_mesh/common/utils/embeds/embeds_llm.txt +1 -1
  95. solace_agent_mesh/common/utils/utils_llm.txt +75 -87
  96. solace_agent_mesh/common/utils/utils_llm_detail.txt +572 -0
  97. solace_agent_mesh/core_a2a/core_a2a_llm_detail.txt +101 -0
  98. solace_agent_mesh/gateway/base/app.py +1 -1
  99. solace_agent_mesh/gateway/base/base_llm.txt +1 -1
  100. solace_agent_mesh/gateway/base/base_llm_detail.txt +235 -0
  101. solace_agent_mesh/gateway/gateway_llm.txt +242 -235
  102. solace_agent_mesh/gateway/gateway_llm_detail.txt +3885 -0
  103. solace_agent_mesh/gateway/http_sse/alembic/alembic_llm.txt +295 -0
  104. solace_agent_mesh/gateway/http_sse/alembic/env.py +10 -1
  105. solace_agent_mesh/gateway/http_sse/alembic/versions/20251006_98882922fa59_add_tasks_events_feedback_chat_tasks.py +190 -0
  106. solace_agent_mesh/gateway/http_sse/alembic/versions/versions_llm.txt +155 -0
  107. solace_agent_mesh/gateway/http_sse/alembic.ini +1 -1
  108. solace_agent_mesh/gateway/http_sse/app.py +148 -2
  109. solace_agent_mesh/gateway/http_sse/component.py +368 -60
  110. solace_agent_mesh/gateway/http_sse/components/components_llm.txt +46 -6
  111. solace_agent_mesh/gateway/http_sse/components/task_logger_forwarder.py +108 -0
  112. solace_agent_mesh/gateway/http_sse/components/visualization_forwarder_component.py +1 -1
  113. solace_agent_mesh/gateway/http_sse/dependencies.py +116 -26
  114. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +172 -172
  115. solace_agent_mesh/gateway/http_sse/http_sse_llm_detail.txt +3278 -0
  116. solace_agent_mesh/gateway/http_sse/main.py +146 -41
  117. solace_agent_mesh/gateway/http_sse/repository/__init__.py +3 -12
  118. solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +103 -0
  119. solace_agent_mesh/gateway/http_sse/repository/entities/__init__.py +5 -3
  120. solace_agent_mesh/gateway/http_sse/repository/entities/chat_task.py +75 -0
  121. solace_agent_mesh/gateway/http_sse/repository/entities/entities_llm.txt +263 -0
  122. solace_agent_mesh/gateway/http_sse/repository/entities/feedback.py +20 -0
  123. solace_agent_mesh/gateway/http_sse/repository/entities/session_history.py +0 -16
  124. solace_agent_mesh/gateway/http_sse/repository/entities/task.py +25 -0
  125. solace_agent_mesh/gateway/http_sse/repository/entities/task_event.py +21 -0
  126. solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +81 -0
  127. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +73 -18
  128. solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +9 -5
  129. solace_agent_mesh/gateway/http_sse/repository/models/chat_task_model.py +31 -0
  130. solace_agent_mesh/gateway/http_sse/repository/models/feedback_model.py +21 -0
  131. solace_agent_mesh/gateway/http_sse/repository/models/models_llm.txt +266 -0
  132. solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +3 -3
  133. solace_agent_mesh/gateway/http_sse/repository/models/task_event_model.py +25 -0
  134. solace_agent_mesh/gateway/http_sse/repository/models/task_model.py +32 -0
  135. solace_agent_mesh/gateway/http_sse/repository/repository_llm.txt +340 -0
  136. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +4 -53
  137. solace_agent_mesh/gateway/http_sse/repository/task_repository.py +173 -0
  138. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +1 -1
  139. solace_agent_mesh/gateway/http_sse/routers/config.py +26 -4
  140. solace_agent_mesh/gateway/http_sse/routers/dto/dto_llm.txt +346 -0
  141. solace_agent_mesh/gateway/http_sse/routers/dto/requests/__init__.py +3 -3
  142. solace_agent_mesh/gateway/http_sse/routers/dto/requests/requests_llm.txt +83 -0
  143. solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +2 -10
  144. solace_agent_mesh/gateway/http_sse/routers/dto/requests/task_requests.py +58 -0
  145. solace_agent_mesh/gateway/http_sse/routers/dto/responses/__init__.py +5 -3
  146. solace_agent_mesh/gateway/http_sse/routers/dto/responses/responses_llm.txt +107 -0
  147. solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +1 -15
  148. solace_agent_mesh/gateway/http_sse/routers/dto/responses/task_responses.py +30 -0
  149. solace_agent_mesh/gateway/http_sse/routers/feedback.py +37 -0
  150. solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +255 -204
  151. solace_agent_mesh/gateway/http_sse/routers/sessions.py +220 -40
  152. solace_agent_mesh/gateway/http_sse/routers/tasks.py +168 -42
  153. solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +272 -0
  154. solace_agent_mesh/gateway/http_sse/services/feedback_service.py +241 -0
  155. solace_agent_mesh/gateway/http_sse/services/people_service.py +0 -80
  156. solace_agent_mesh/gateway/http_sse/services/services_llm.txt +177 -13
  157. solace_agent_mesh/gateway/http_sse/services/session_service.py +151 -84
  158. solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +317 -0
  159. solace_agent_mesh/gateway/http_sse/shared/exception_handlers.py +25 -14
  160. solace_agent_mesh/gateway/http_sse/shared/shared_llm.txt +285 -0
  161. solace_agent_mesh/gateway/http_sse/shared/types.py +7 -0
  162. solace_agent_mesh/gateway/http_sse/utils/__init__.py +1 -0
  163. solace_agent_mesh/gateway/http_sse/utils/stim_utils.py +32 -0
  164. solace_agent_mesh/gateway/http_sse/utils/utils_llm.txt +47 -0
  165. solace_agent_mesh/solace_agent_mesh_llm.txt +1 -1
  166. solace_agent_mesh/solace_agent_mesh_llm_detail.txt +8599 -0
  167. {solace_agent_mesh-1.4.12.dist-info → solace_agent_mesh-1.5.0.dist-info}/METADATA +1 -1
  168. {solace_agent_mesh-1.4.12.dist-info → solace_agent_mesh-1.5.0.dist-info}/RECORD +172 -124
  169. solace_agent_mesh/agent/adk/invocation_monitor.py +0 -295
  170. solace_agent_mesh/assets/docs/assets/js/483cef9a.4736f2d8.js +0 -1
  171. solace_agent_mesh/assets/docs/lunr-index-1759936913198.json +0 -1
  172. solace_agent_mesh/assets/docs/search-doc-1759936913198.json +0 -1
  173. solace_agent_mesh/client/webui/frontend/static/assets/main-ChRwcV89.css +0 -1
  174. solace_agent_mesh/client/webui/frontend/static/assets/main-DnnE01OM.js +0 -339
  175. solace_agent_mesh/gateway/http_sse/repository/entities/message.py +0 -41
  176. solace_agent_mesh/gateway/http_sse/repository/message_repository.py +0 -84
  177. solace_agent_mesh/gateway/http_sse/repository/models/message_model.py +0 -45
  178. /solace_agent_mesh/assets/docs/assets/js/{main.f67fc9f4.js.LICENSE.txt → main.0c149855.js.LICENSE.txt} +0 -0
  179. {solace_agent_mesh-1.4.12.dist-info → solace_agent_mesh-1.5.0.dist-info}/WHEEL +0 -0
  180. {solace_agent_mesh-1.4.12.dist-info → solace_agent_mesh-1.5.0.dist-info}/entry_points.txt +0 -0
  181. {solace_agent_mesh-1.4.12.dist-info → solace_agent_mesh-1.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -29,6 +29,7 @@ from ...gateway.http_sse.routers import (
29
29
  sse,
30
30
  tasks,
31
31
  visualization,
32
+ feedback,
32
33
  )
33
34
  from .routers.sessions import router as session_router
34
35
  from .routers.tasks import router as task_router
@@ -43,6 +44,9 @@ app = FastAPI(
43
44
  description="Backend API and SSE server for the A2A Web UI, hosted by Solace AI Connector.",
44
45
  )
45
46
 
47
+ # Global flag to track if dependencies have been initialized
48
+ _dependencies_initialized = False
49
+
46
50
 
47
51
  def _extract_access_token(request: FastAPIRequest) -> str:
48
52
  auth_header = request.headers.get("Authorization")
@@ -62,7 +66,9 @@ def _extract_access_token(request: FastAPIRequest) -> str:
62
66
  return None
63
67
 
64
68
 
65
- async def _validate_token(auth_service_url: str, auth_provider: str, access_token: str) -> bool:
69
+ async def _validate_token(
70
+ auth_service_url: str, auth_provider: str, access_token: str
71
+ ) -> bool:
66
72
  async with httpx.AsyncClient() as client:
67
73
  validation_response = await client.post(
68
74
  f"{auth_service_url}/is_token_valid",
@@ -72,7 +78,9 @@ async def _validate_token(auth_service_url: str, auth_provider: str, access_toke
72
78
  return validation_response.status_code == 200
73
79
 
74
80
 
75
- async def _get_user_info(auth_service_url: str, auth_provider: str, access_token: str) -> dict:
81
+ async def _get_user_info(
82
+ auth_service_url: str, auth_provider: str, access_token: str
83
+ ) -> dict:
76
84
  async with httpx.AsyncClient() as client:
77
85
  userinfo_response = await client.get(
78
86
  f"{auth_service_url}/user_info?provider={auth_provider}",
@@ -100,7 +108,9 @@ def _extract_user_identifier(user_info: dict) -> str:
100
108
  )
101
109
 
102
110
  if user_identifier and user_identifier.lower() == "unknown":
103
- log.warning("AuthMiddleware: IDP returned 'Unknown' as user identifier. Using fallback.")
111
+ log.warning(
112
+ "AuthMiddleware: IDP returned 'Unknown' as user identifier. Using fallback."
113
+ )
104
114
  return "sam_dev_user"
105
115
 
106
116
  return user_identifier
@@ -124,7 +134,9 @@ def _extract_user_details(user_info: dict, user_identifier: str) -> tuple:
124
134
  return email_from_auth, display_name
125
135
 
126
136
 
127
- async def _create_user_state_without_identity_service(user_identifier: str, email_from_auth: str, display_name: str) -> dict:
137
+ async def _create_user_state_without_identity_service(
138
+ user_identifier: str, email_from_auth: str, display_name: str
139
+ ) -> dict:
128
140
  final_user_id = user_identifier or email_from_auth or "sam_dev_user"
129
141
  if not final_user_id or final_user_id.lower() in ["unknown", "null", "none", ""]:
130
142
  final_user_id = "sam_dev_user"
@@ -146,7 +158,13 @@ async def _create_user_state_without_identity_service(user_identifier: str, emai
146
158
  }
147
159
 
148
160
 
149
- async def _create_user_state_with_identity_service(identity_service, user_identifier: str, email_from_auth: str, display_name: str, user_info: dict) -> dict:
161
+ async def _create_user_state_with_identity_service(
162
+ identity_service,
163
+ user_identifier: str,
164
+ email_from_auth: str,
165
+ display_name: str,
166
+ user_info: dict,
167
+ ) -> dict:
150
168
  lookup_value = email_from_auth if "@" in email_from_auth else user_identifier
151
169
  user_profile = await identity_service.get_user_profile(
152
170
  {identity_service.lookup_key: lookup_value, "user_info": user_info}
@@ -186,24 +204,35 @@ def _create_auth_middleware(component):
186
204
  return
187
205
 
188
206
  skip_paths = [
189
- "/api/v1/config", "/api/v1/auth/callback", "/api/v1/auth/login",
190
- "/api/v1/auth/refresh", "/api/v1/csrf-token", "/health",
207
+ "/api/v1/config",
208
+ "/api/v1/auth/callback",
209
+ "/api/v1/auth/login",
210
+ "/api/v1/auth/refresh",
211
+ "/api/v1/csrf-token",
212
+ "/health",
191
213
  ]
192
214
 
193
215
  if any(request.url.path.startswith(path) for path in skip_paths):
194
216
  await self.app(scope, receive, send)
195
217
  return
196
218
 
197
- use_auth = dependencies.api_config and dependencies.api_config.get("frontend_use_authorization")
219
+ use_auth = dependencies.api_config and dependencies.api_config.get(
220
+ "frontend_use_authorization"
221
+ )
198
222
 
199
223
  if use_auth:
200
224
  await self._handle_authenticated_request(request, scope, receive, send)
201
225
  else:
202
226
  request.state.user = {
203
- "id": "sam_dev_user", "name": "Sam Dev User", "email": "sam@dev.local",
204
- "authenticated": True, "auth_method": "development",
227
+ "id": "sam_dev_user",
228
+ "name": "Sam Dev User",
229
+ "email": "sam@dev.local",
230
+ "authenticated": True,
231
+ "auth_method": "development",
205
232
  }
206
- log.debug("AuthMiddleware: Set development user state with id: sam_dev_user")
233
+ log.debug(
234
+ "AuthMiddleware: Set development user state with id: sam_dev_user"
235
+ )
207
236
 
208
237
  await self.app(scope, receive, send)
209
238
 
@@ -214,13 +243,18 @@ def _create_auth_middleware(component):
214
243
  log.warning("AuthMiddleware: No access token found. Returning 401.")
215
244
  response = JSONResponse(
216
245
  status_code=status.HTTP_401_UNAUTHORIZED,
217
- content={"detail": "Not authenticated", "error_type": "authentication_required"},
246
+ content={
247
+ "detail": "Not authenticated",
248
+ "error_type": "authentication_required",
249
+ },
218
250
  )
219
251
  await response(scope, receive, send)
220
252
  return
221
253
 
222
254
  try:
223
- auth_service_url = dependencies.api_config.get("external_auth_service_url")
255
+ auth_service_url = dependencies.api_config.get(
256
+ "external_auth_service_url"
257
+ )
224
258
  auth_provider = dependencies.api_config.get("external_auth_provider")
225
259
 
226
260
  if not auth_service_url:
@@ -232,47 +266,85 @@ def _create_auth_middleware(component):
232
266
  await response(scope, receive, send)
233
267
  return
234
268
 
235
- if not await _validate_token(auth_service_url, auth_provider, access_token):
269
+ if not await _validate_token(
270
+ auth_service_url, auth_provider, access_token
271
+ ):
236
272
  log.warning("AuthMiddleware: Token validation failed")
237
273
  response = JSONResponse(
238
274
  status_code=status.HTTP_401_UNAUTHORIZED,
239
- content={"detail": "Invalid token", "error_type": "invalid_token"},
275
+ content={
276
+ "detail": "Invalid token",
277
+ "error_type": "invalid_token",
278
+ },
240
279
  )
241
280
  await response(scope, receive, send)
242
281
  return
243
282
 
244
- user_info = await _get_user_info(auth_service_url, auth_provider, access_token)
283
+ user_info = await _get_user_info(
284
+ auth_service_url, auth_provider, access_token
285
+ )
245
286
  if not user_info:
246
- log.warning("AuthMiddleware: Failed to get user info from external auth service")
287
+ log.warning(
288
+ "AuthMiddleware: Failed to get user info from external auth service"
289
+ )
247
290
  response = JSONResponse(
248
291
  status_code=status.HTTP_401_UNAUTHORIZED,
249
- content={"detail": "Could not retrieve user info from auth provider", "error_type": "user_info_failed"},
292
+ content={
293
+ "detail": "Could not retrieve user info from auth provider",
294
+ "error_type": "user_info_failed",
295
+ },
250
296
  )
251
297
  await response(scope, receive, send)
252
298
  return
253
299
 
254
300
  user_identifier = _extract_user_identifier(user_info)
255
- if not user_identifier or user_identifier.lower() in ["null", "none", ""]:
256
- log.error("AuthMiddleware: No valid user identifier from OAuth provider")
301
+ if not user_identifier or user_identifier.lower() in [
302
+ "null",
303
+ "none",
304
+ "",
305
+ ]:
306
+ log.error(
307
+ "AuthMiddleware: No valid user identifier from OAuth provider"
308
+ )
257
309
  response = JSONResponse(
258
310
  status_code=status.HTTP_401_UNAUTHORIZED,
259
- content={"detail": "OAuth provider returned no valid user identifier", "error_type": "invalid_user_identifier_from_provider"},
311
+ content={
312
+ "detail": "OAuth provider returned no valid user identifier",
313
+ "error_type": "invalid_user_identifier_from_provider",
314
+ },
260
315
  )
261
316
  await response(scope, receive, send)
262
317
  return
263
318
 
264
- email_from_auth, display_name = _extract_user_details(user_info, user_identifier)
319
+ email_from_auth, display_name = _extract_user_details(
320
+ user_info, user_identifier
321
+ )
265
322
 
266
323
  identity_service = self.component.identity_service
267
324
  if not identity_service:
268
- request.state.user = await _create_user_state_without_identity_service(user_identifier, email_from_auth, display_name)
325
+ request.state.user = (
326
+ await _create_user_state_without_identity_service(
327
+ user_identifier, email_from_auth, display_name
328
+ )
329
+ )
269
330
  else:
270
- user_state = await _create_user_state_with_identity_service(identity_service, user_identifier, email_from_auth, display_name, user_info)
331
+ user_state = await _create_user_state_with_identity_service(
332
+ identity_service,
333
+ user_identifier,
334
+ email_from_auth,
335
+ display_name,
336
+ user_info,
337
+ )
271
338
  if not user_state:
272
- log.error("AuthMiddleware: User authenticated but not found in internal IdentityService")
339
+ log.error(
340
+ "AuthMiddleware: User authenticated but not found in internal IdentityService"
341
+ )
273
342
  response = JSONResponse(
274
343
  status_code=status.HTTP_403_FORBIDDEN,
275
- content={"detail": "User not authorized for this application", "error_type": "not_authorized"},
344
+ content={
345
+ "detail": "User not authorized for this application",
346
+ "error_type": "not_authorized",
347
+ },
276
348
  )
277
349
  await response(scope, receive, send)
278
350
  return
@@ -287,10 +359,14 @@ def _create_auth_middleware(component):
287
359
  await response(scope, receive, send)
288
360
  return
289
361
  except Exception as exc:
290
- log.error("An unexpected error occurred during token validation: %s", exc)
362
+ log.error(
363
+ "An unexpected error occurred during token validation: %s", exc
364
+ )
291
365
  response = JSONResponse(
292
366
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
293
- content={"detail": "An internal error occurred during authentication"},
367
+ content={
368
+ "detail": "An internal error occurred during authentication"
369
+ },
294
370
  )
295
371
  await response(scope, receive, send)
296
372
  return
@@ -343,10 +419,14 @@ def _run_community_migrations(database_url: str) -> None:
343
419
  except Exception as migration_error:
344
420
  log.error("Community migration failed: %s", migration_error)
345
421
  log.error("Check database connectivity and permissions")
346
- raise RuntimeError(f"Community database migration failed: {migration_error}") from migration_error
422
+ raise RuntimeError(
423
+ f"Community database migration failed: {migration_error}"
424
+ ) from migration_error
347
425
 
348
426
 
349
- def _run_enterprise_migrations(component: "WebUIBackendComponent", database_url: str) -> None:
427
+ def _run_enterprise_migrations(
428
+ component: "WebUIBackendComponent", database_url: str
429
+ ) -> None:
350
430
  """
351
431
  Run migrations for enterprise features like advanced analytics, audit logs, etc.
352
432
  This is optional and only runs if the enterprise package is available.
@@ -420,7 +500,15 @@ def setup_dependencies(component: "WebUIBackendComponent", database_url: str = N
420
500
  backward compatibility with existing API contracts.
421
501
 
422
502
  If database_url is None, runs in compatibility mode with in-memory sessions.
503
+
504
+ This function is idempotent and safe to call multiple times.
423
505
  """
506
+ global _dependencies_initialized
507
+
508
+ if _dependencies_initialized:
509
+ log.debug("[setup_dependencies] Dependencies already initialized, skipping")
510
+ return
511
+
424
512
  dependencies.set_component_instance(component)
425
513
 
426
514
  if database_url:
@@ -441,6 +529,9 @@ def setup_dependencies(component: "WebUIBackendComponent", database_url: str = N
441
529
  _setup_routers()
442
530
  _setup_static_files()
443
531
 
532
+ _dependencies_initialized = True
533
+ log.info("[setup_dependencies] Dependencies initialization complete")
534
+
444
535
 
445
536
  def _setup_middleware(component: "WebUIBackendComponent") -> None:
446
537
  allowed_origins = component.get_cors_origins()
@@ -467,39 +558,50 @@ def _setup_routers() -> None:
467
558
 
468
559
  app.include_router(session_router, prefix=api_prefix, tags=["Sessions"])
469
560
  app.include_router(user_router, prefix=f"{api_prefix}/users", tags=["Users"])
470
- app.include_router(task_router, prefix=f"{api_prefix}/tasks", tags=["Tasks"])
471
561
  app.include_router(config.router, prefix=api_prefix, tags=["Config"])
472
562
  app.include_router(agent_cards.router, prefix=api_prefix, tags=["Agent Cards"])
473
- app.include_router(tasks.router, prefix=api_prefix, tags=["A2A Messages"])
563
+ app.include_router(tasks.router, prefix=api_prefix, tags=["Tasks"])
474
564
  app.include_router(sse.router, prefix=f"{api_prefix}/sse", tags=["SSE"])
475
- app.include_router(artifacts.router, prefix=f"{api_prefix}/artifacts", tags=["Artifacts"])
476
- app.include_router(visualization.router, prefix=f"{api_prefix}/visualization", tags=["Visualization"])
565
+ app.include_router(
566
+ artifacts.router, prefix=f"{api_prefix}/artifacts", tags=["Artifacts"]
567
+ )
568
+ app.include_router(
569
+ visualization.router,
570
+ prefix=f"{api_prefix}/visualization",
571
+ tags=["Visualization"],
572
+ )
477
573
  app.include_router(people.router, prefix=api_prefix, tags=["People"])
478
574
  app.include_router(auth.router, prefix=api_prefix, tags=["Auth"])
575
+ app.include_router(feedback.router, prefix=api_prefix, tags=["Feedback"])
479
576
  log.info("Legacy routers mounted for endpoints not yet migrated")
480
577
 
481
578
  # Register shared exception handlers from community repo
482
579
  from .shared.exception_handlers import register_exception_handlers
580
+
483
581
  register_exception_handlers(app)
484
582
  log.info("Registered shared exception handlers from community repo")
485
583
 
486
584
  # Mount enterprise routers if available
487
585
  try:
488
- from solace_agent_mesh_enterprise.webui_backend.routers import get_enterprise_routers
586
+ from solace_agent_mesh_enterprise.webui_backend.routers import (
587
+ get_enterprise_routers,
588
+ )
489
589
 
490
590
  enterprise_routers = get_enterprise_routers()
491
591
  for router_config in enterprise_routers:
492
592
  app.include_router(
493
593
  router_config["router"],
494
594
  prefix=router_config["prefix"],
495
- tags=router_config["tags"]
595
+ tags=router_config["tags"],
496
596
  )
497
597
  log.info("Mounted %d enterprise routers", len(enterprise_routers))
498
598
 
499
599
  except ImportError:
500
600
  log.debug("No enterprise package detected - skipping enterprise routers")
501
601
  except ModuleNotFoundError:
502
- log.debug("Enterprise module not found - skipping enterprise routers and exception handlers")
602
+ log.debug(
603
+ "Enterprise module not found - skipping enterprise routers and exception handlers"
604
+ )
503
605
  except Exception as e:
504
606
  log.warning("Failed to load enterprise routers and exception handlers: %s", e)
505
607
 
@@ -535,7 +637,7 @@ async def http_exception_handler(request: FastAPIRequest, exc: HTTPException):
535
637
  Returns JSON-RPC format for tasks/SSE endpoints, REST format for others.
536
638
  """
537
639
  log.warning(
538
- "HTTP Exception: Status=%s, Detail=%s, Request: %s %s",
640
+ "HTTP Exception Handler triggered: Status=%s, Detail=%s, Request: %s %s",
539
641
  exc.status_code,
540
642
  exc.detail,
541
643
  request.method,
@@ -594,7 +696,7 @@ async def validation_exception_handler(
594
696
  Handles Pydantic validation errors with format detection.
595
697
  """
596
698
  log.warning(
597
- "Request Validation Error: %s, Request: %s %s",
699
+ "Validation Exception Handler triggered: %s, Request: %s %s",
598
700
  exc.errors(),
599
701
  request.method,
600
702
  request.url,
@@ -603,7 +705,7 @@ async def validation_exception_handler(
603
705
  message="Invalid request parameters", data=exc.errors(), request_id=None
604
706
  )
605
707
  return JSONResponse(
606
- status_code=status.HTTP_400_BAD_REQUEST,
708
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
607
709
  content=response.model_dump(exclude_none=True),
608
710
  )
609
711
 
@@ -614,7 +716,10 @@ async def generic_exception_handler(request: FastAPIRequest, exc: Exception):
614
716
  Handles any other unexpected exceptions with format detection.
615
717
  """
616
718
  log.exception(
617
- "Unhandled Exception: %s, Request: %s %s", exc, request.method, request.url
719
+ "Generic Exception Handler triggered: %s, Request: %s %s",
720
+ exc,
721
+ request.method,
722
+ request.url,
618
723
  )
619
724
  error_obj = a2a.create_internal_error(
620
725
  message="An unexpected server error occurred: %s" % type(exc).__name__
@@ -3,35 +3,26 @@ Repository layer containing all data access logic organized by entity type.
3
3
  """
4
4
 
5
5
  # Interfaces
6
- from .interfaces import IMessageRepository, ISessionRepository
6
+ from .interfaces import ISessionRepository
7
7
 
8
8
  # Implementations
9
- from .message_repository import MessageRepository
10
9
  from .session_repository import SessionRepository
11
10
 
12
11
  # Entities (re-exported for convenience)
13
12
  from .entities.session import Session
14
- from .entities.message import Message
15
- from .entities.session_history import SessionHistory
16
13
 
17
14
  # Models (re-exported for convenience)
18
15
  from .models.base import Base
19
16
  from .models.session_model import SessionModel
20
- from .models.message_model import MessageModel
21
17
 
22
18
  __all__ = [
23
19
  # Interfaces
24
- "IMessageRepository",
25
20
  "ISessionRepository",
26
21
  # Implementations
27
- "MessageRepository",
28
22
  "SessionRepository",
29
23
  # Entities
30
- "Message",
31
- "Session",
32
- "SessionHistory",
24
+ "Session",
33
25
  # Models
34
26
  "Base",
35
- "MessageModel",
36
27
  "SessionModel",
37
- ]
28
+ ]
@@ -0,0 +1,103 @@
1
+ """
2
+ ChatTask repository implementation using SQLAlchemy.
3
+ """
4
+
5
+ from typing import List, Optional
6
+
7
+ from sqlalchemy.orm import Session as DBSession
8
+
9
+ from ..shared import now_epoch_ms
10
+ from ..shared.types import SessionId, UserId
11
+ from .entities import ChatTask
12
+ from .interfaces import IChatTaskRepository
13
+ from .models import ChatTaskModel
14
+
15
+
16
+ class ChatTaskRepository(IChatTaskRepository):
17
+ """SQLAlchemy implementation of chat task repository."""
18
+
19
+ def __init__(self, db: DBSession):
20
+ self.db = db
21
+
22
+ def save(self, task: ChatTask) -> ChatTask:
23
+ """Save or update a chat task (upsert)."""
24
+ existing = self.db.query(ChatTaskModel).filter(
25
+ ChatTaskModel.id == task.id
26
+ ).first()
27
+
28
+ if existing:
29
+ # Update existing task - store strings directly
30
+ existing.user_message = task.user_message
31
+ existing.message_bubbles = task.message_bubbles # Already a string
32
+ existing.task_metadata = task.task_metadata # Already a string
33
+ existing.updated_time = now_epoch_ms()
34
+ else:
35
+ # Create new task - store strings directly
36
+ model = ChatTaskModel(
37
+ id=task.id,
38
+ session_id=task.session_id,
39
+ user_id=task.user_id,
40
+ user_message=task.user_message,
41
+ message_bubbles=task.message_bubbles, # Already a string
42
+ task_metadata=task.task_metadata, # Already a string
43
+ created_time=task.created_time,
44
+ updated_time=task.updated_time
45
+ )
46
+ self.db.add(model)
47
+
48
+ self.db.commit()
49
+
50
+ # Reload to get updated values
51
+ model = self.db.query(ChatTaskModel).filter(
52
+ ChatTaskModel.id == task.id
53
+ ).first()
54
+
55
+ return self._model_to_entity(model)
56
+
57
+ def find_by_session(
58
+ self,
59
+ session_id: SessionId,
60
+ user_id: UserId
61
+ ) -> List[ChatTask]:
62
+ """Find all tasks for a session."""
63
+ models = self.db.query(ChatTaskModel).filter(
64
+ ChatTaskModel.session_id == session_id,
65
+ ChatTaskModel.user_id == user_id
66
+ ).order_by(ChatTaskModel.created_time.asc()).all()
67
+
68
+ return [self._model_to_entity(m) for m in models]
69
+
70
+ def find_by_id(
71
+ self,
72
+ task_id: str,
73
+ user_id: UserId
74
+ ) -> Optional[ChatTask]:
75
+ """Find a specific task."""
76
+ model = self.db.query(ChatTaskModel).filter(
77
+ ChatTaskModel.id == task_id,
78
+ ChatTaskModel.user_id == user_id
79
+ ).first()
80
+
81
+ return self._model_to_entity(model) if model else None
82
+
83
+ def delete_by_session(self, session_id: SessionId) -> bool:
84
+ """Delete all tasks for a session."""
85
+ result = self.db.query(ChatTaskModel).filter(
86
+ ChatTaskModel.session_id == session_id
87
+ ).delete()
88
+ self.db.commit()
89
+ return result > 0
90
+
91
+ def _model_to_entity(self, model: ChatTaskModel) -> ChatTask:
92
+ """Convert SQLAlchemy model to domain entity."""
93
+ # No deserialization - just pass strings through
94
+ return ChatTask(
95
+ id=model.id,
96
+ session_id=model.session_id,
97
+ user_id=model.user_id,
98
+ user_message=model.user_message,
99
+ message_bubbles=model.message_bubbles, # String (opaque)
100
+ task_metadata=model.task_metadata, # String (opaque)
101
+ created_time=model.created_time,
102
+ updated_time=model.updated_time
103
+ )
@@ -2,8 +2,10 @@
2
2
  Domain entities for the repository layer.
3
3
  """
4
4
 
5
- from .message import Message
5
+ from .chat_task import ChatTask
6
+ from .feedback import Feedback
6
7
  from .session import Session
7
- from .session_history import SessionHistory
8
+ from .task import Task
9
+ from .task_event import TaskEvent
8
10
 
9
- __all__ = ["Message", "Session", "SessionHistory"]
11
+ __all__ = ["ChatTask", "Feedback", "Session", "Task", "TaskEvent"]
@@ -0,0 +1,75 @@
1
+ """
2
+ ChatTask domain entity.
3
+ """
4
+
5
+ import json
6
+ from typing import Any, Dict, Optional
7
+
8
+ from pydantic import BaseModel, ConfigDict, field_validator
9
+
10
+
11
+ class ChatTask(BaseModel):
12
+ """ChatTask domain entity with business logic."""
13
+
14
+ model_config = ConfigDict(from_attributes=True)
15
+
16
+ id: str
17
+ session_id: str
18
+ user_id: str
19
+ user_message: Optional[str] = None
20
+ message_bubbles: str # JSON string (opaque to backend)
21
+ task_metadata: Optional[str] = None # JSON string (opaque to backend)
22
+ created_time: int
23
+ updated_time: Optional[int] = None
24
+
25
+ @field_validator("message_bubbles")
26
+ @classmethod
27
+ def validate_message_bubbles(cls, v: str) -> str:
28
+ """Validate that message_bubbles is a non-empty JSON string."""
29
+ if not v or not v.strip():
30
+ raise ValueError("message_bubbles cannot be empty")
31
+
32
+ # Validate it's valid JSON (but don't validate structure)
33
+ try:
34
+ parsed = json.loads(v)
35
+ if not isinstance(parsed, list) or len(parsed) == 0:
36
+ raise ValueError("message_bubbles must be a non-empty JSON array")
37
+ except json.JSONDecodeError as e:
38
+ raise ValueError(f"message_bubbles must be valid JSON: {e}")
39
+
40
+ return v
41
+
42
+ @field_validator("task_metadata")
43
+ @classmethod
44
+ def validate_task_metadata(cls, v: Optional[str]) -> Optional[str]:
45
+ """Validate that task_metadata is valid JSON if provided."""
46
+ if v is None or not v.strip():
47
+ return None
48
+
49
+ # Validate it's valid JSON (but don't validate structure)
50
+ try:
51
+ json.loads(v)
52
+ except json.JSONDecodeError as e:
53
+ raise ValueError(f"task_metadata must be valid JSON: {e}")
54
+
55
+ return v
56
+
57
+ def add_feedback(self, feedback_type: str, feedback_text: Optional[str] = None) -> None:
58
+ """Add or update feedback for this task."""
59
+ # Parse metadata, update, re-serialize
60
+ metadata = json.loads(self.task_metadata) if self.task_metadata else {}
61
+
62
+ metadata["feedback"] = {
63
+ "type": feedback_type,
64
+ "text": feedback_text,
65
+ "submitted": True
66
+ }
67
+
68
+ self.task_metadata = json.dumps(metadata)
69
+
70
+ def get_feedback(self) -> Optional[Dict[str, Any]]:
71
+ """Get feedback for this task."""
72
+ if self.task_metadata:
73
+ metadata = json.loads(self.task_metadata)
74
+ return metadata.get("feedback")
75
+ return None