solace-agent-mesh 1.1.0__py3-none-any.whl → 1.3.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 (139) 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/protocol/event_handlers.py +27 -21
  4. solace_agent_mesh/agent/sac/app.py +1 -1
  5. solace_agent_mesh/agent/sac/component.py +0 -1
  6. solace_agent_mesh/assets/docs/404.html +2 -2
  7. solace_agent_mesh/assets/docs/assets/js/{main.a75ecc0d.js → main.08d30374.js} +2 -2
  8. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +2 -2
  9. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +2 -2
  10. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +2 -2
  11. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +2 -2
  12. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +2 -2
  13. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +2 -2
  14. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +2 -2
  15. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +2 -2
  16. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +2 -2
  17. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +2 -2
  18. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +2 -2
  19. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +2 -2
  20. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +2 -2
  21. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +2 -2
  22. solace_agent_mesh/assets/docs/docs/documentation/migration-guides/a2a-upgrade-to-0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html +2 -2
  23. solace_agent_mesh/assets/docs/docs/documentation/migration-guides/a2a-upgrade-to-0.3.0/a2a-technical-migration-map/index.html +2 -2
  24. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +2 -2
  25. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +2 -2
  26. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +2 -2
  27. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +2 -2
  28. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +2 -2
  29. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +2 -2
  30. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +2 -2
  31. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +2 -2
  32. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +2 -2
  33. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +2 -2
  34. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +2 -2
  35. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +2 -2
  36. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +2 -2
  37. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +2 -2
  38. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +2 -2
  39. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +2 -2
  40. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +2 -2
  41. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +2 -2
  42. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +2 -2
  43. solace_agent_mesh/assets/docs/lunr-index-1757433031159.json +1 -0
  44. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  45. solace_agent_mesh/assets/docs/search-doc-1757433031159.json +1 -0
  46. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  47. solace_agent_mesh/cli/__init__.py +1 -1
  48. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +125 -48
  49. solace_agent_mesh/cli/commands/eval_cmd.py +14 -0
  50. solace_agent_mesh/cli/commands/init_cmd/__init__.py +53 -31
  51. solace_agent_mesh/cli/commands/init_cmd/database_step.py +91 -0
  52. solace_agent_mesh/cli/commands/init_cmd/env_step.py +19 -8
  53. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +80 -25
  54. solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +32 -10
  55. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +74 -15
  56. solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +0 -2
  57. solace_agent_mesh/cli/commands/run_cmd.py +5 -3
  58. solace_agent_mesh/cli/utils.py +68 -12
  59. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-vY5eu2lI.js +1 -0
  60. solace_agent_mesh/client/webui/frontend/static/assets/client-BeBkzgWW.js +25 -0
  61. solace_agent_mesh/client/webui/frontend/static/assets/main-Bjys1KQs.js +339 -0
  62. solace_agent_mesh/client/webui/frontend/static/assets/main-C03yrETa.css +1 -0
  63. solace_agent_mesh/client/webui/frontend/static/assets/vendor-CE0AeXyK.js +395 -0
  64. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -2
  65. solace_agent_mesh/client/webui/frontend/static/index.html +4 -3
  66. solace_agent_mesh/common/utils/embeds/resolver.py +1 -0
  67. solace_agent_mesh/config_portal/backend/common.py +2 -2
  68. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-bFMKlzKf.js +98 -0
  69. solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-d845808d.js → manifest-89db7c30.js} +1 -1
  70. solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
  71. solace_agent_mesh/evaluation/message_organizer.py +35 -56
  72. solace_agent_mesh/evaluation/run.py +26 -5
  73. solace_agent_mesh/evaluation/subscriber.py +35 -10
  74. solace_agent_mesh/evaluation/summary_builder.py +27 -34
  75. solace_agent_mesh/gateway/http_sse/ARCHITECTURE_GUIDE.md +676 -0
  76. solace_agent_mesh/gateway/http_sse/alembic/env.py +85 -0
  77. solace_agent_mesh/gateway/http_sse/alembic/script.py.mako +28 -0
  78. solace_agent_mesh/gateway/http_sse/alembic/versions/b1c2d3e4f5g6_add_database_indexes.py +83 -0
  79. solace_agent_mesh/gateway/http_sse/alembic/versions/d5b3f8f2e9a0_create_initial_database.py +58 -0
  80. solace_agent_mesh/gateway/http_sse/alembic.ini +147 -0
  81. solace_agent_mesh/gateway/http_sse/api/__init__.py +11 -0
  82. solace_agent_mesh/gateway/http_sse/api/controllers/__init__.py +9 -0
  83. solace_agent_mesh/gateway/http_sse/api/controllers/session_controller.py +355 -0
  84. solace_agent_mesh/gateway/http_sse/api/controllers/task_controller.py +279 -0
  85. solace_agent_mesh/gateway/http_sse/api/controllers/user_controller.py +35 -0
  86. solace_agent_mesh/gateway/http_sse/api/dto/__init__.py +10 -0
  87. solace_agent_mesh/gateway/http_sse/api/dto/requests/__init__.py +37 -0
  88. solace_agent_mesh/gateway/http_sse/api/dto/requests/session_requests.py +49 -0
  89. solace_agent_mesh/gateway/http_sse/api/dto/requests/task_requests.py +66 -0
  90. solace_agent_mesh/gateway/http_sse/api/dto/responses/__init__.py +43 -0
  91. solace_agent_mesh/gateway/http_sse/api/dto/responses/session_responses.py +68 -0
  92. solace_agent_mesh/gateway/http_sse/api/dto/responses/task_responses.py +74 -0
  93. solace_agent_mesh/gateway/http_sse/app.py +31 -1
  94. solace_agent_mesh/gateway/http_sse/application/__init__.py +3 -0
  95. solace_agent_mesh/gateway/http_sse/application/services/__init__.py +3 -0
  96. solace_agent_mesh/gateway/http_sse/application/services/session_service.py +135 -0
  97. solace_agent_mesh/gateway/http_sse/component.py +224 -62
  98. solace_agent_mesh/gateway/http_sse/dependencies.py +142 -39
  99. solace_agent_mesh/gateway/http_sse/domain/entities/__init__.py +3 -0
  100. solace_agent_mesh/gateway/http_sse/domain/entities/session.py +90 -0
  101. solace_agent_mesh/gateway/http_sse/domain/repositories/__init__.py +3 -0
  102. solace_agent_mesh/gateway/http_sse/domain/repositories/session_repository.py +54 -0
  103. solace_agent_mesh/gateway/http_sse/infrastructure/__init__.py +4 -0
  104. solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/__init__.py +3 -0
  105. solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/container.py +123 -0
  106. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/__init__.py +4 -0
  107. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_persistence_service.py +16 -0
  108. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_service.py +119 -0
  109. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/models.py +31 -0
  110. solace_agent_mesh/gateway/http_sse/infrastructure/persistence_service.py +12 -0
  111. solace_agent_mesh/gateway/http_sse/infrastructure/repositories/__init__.py +3 -0
  112. solace_agent_mesh/gateway/http_sse/infrastructure/repositories/session_repository.py +174 -0
  113. solace_agent_mesh/gateway/http_sse/main.py +289 -85
  114. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +121 -54
  115. solace_agent_mesh/gateway/http_sse/routers/config.py +3 -1
  116. solace_agent_mesh/gateway/http_sse/routers/tasks.py +83 -2
  117. solace_agent_mesh/gateway/http_sse/routers/visualization.py +7 -7
  118. solace_agent_mesh/gateway/http_sse/session_manager.py +64 -30
  119. solace_agent_mesh/gateway/http_sse/shared/__init__.py +9 -0
  120. solace_agent_mesh/gateway/http_sse/shared/auth_utils.py +29 -0
  121. solace_agent_mesh/gateway/http_sse/shared/enums.py +45 -0
  122. solace_agent_mesh/gateway/http_sse/shared/types.py +45 -0
  123. solace_agent_mesh/templates/shared_config.yaml +4 -5
  124. solace_agent_mesh/templates/webui.yaml +8 -10
  125. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.0.dist-info}/METADATA +5 -3
  126. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.0.dist-info}/RECORD +130 -91
  127. solace_agent_mesh/assets/docs/lunr-index-1756992446316.json +0 -1
  128. solace_agent_mesh/assets/docs/search-doc-1756992446316.json +0 -1
  129. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-BmF2l6vg.js +0 -1
  130. solace_agent_mesh/client/webui/frontend/static/assets/client-D881Dttc.js +0 -49
  131. solace_agent_mesh/client/webui/frontend/static/assets/main-C0jZjYa8.js +0 -699
  132. solace_agent_mesh/client/webui/frontend/static/assets/main-CCeG324-.css +0 -1
  133. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-Bym6YkMd.js +0 -98
  134. solace_agent_mesh/gateway/http_sse/routers/sessions.py +0 -85
  135. solace_agent_mesh/gateway/http_sse/routers/users.py +0 -59
  136. /solace_agent_mesh/assets/docs/assets/js/{main.a75ecc0d.js.LICENSE.txt → main.08d30374.js.LICENSE.txt} +0 -0
  137. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.0.dist-info}/WHEEL +0 -0
  138. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.0.dist-info}/entry_points.txt +0 -0
  139. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -2,77 +2,81 @@
2
2
  FastAPI router for managing session-specific artifacts via REST endpoints.
3
3
  """
4
4
 
5
- from typing import (
6
- List,
7
- Dict,
8
- Optional,
9
- Union,
10
- Any,
11
- TYPE_CHECKING,
12
- )
5
+ from collections.abc import Callable
6
+ from typing import TYPE_CHECKING, Any
13
7
 
14
8
  from fastapi import (
15
9
  APIRouter,
16
10
  Depends,
17
- HTTPException,
18
- UploadFile,
19
11
  File,
12
+ Form,
13
+ HTTPException,
20
14
  Path,
15
+ UploadFile,
21
16
  status,
22
- Form,
23
17
  )
24
- from fastapi.responses import StreamingResponse, Response
25
- from google.adk.artifacts import BaseArtifactService
18
+ from fastapi.responses import Response, StreamingResponse
19
+
20
+ try:
21
+ from google.adk.artifacts import BaseArtifactService
22
+ except ImportError:
23
+
24
+ class BaseArtifactService:
25
+ pass
26
+
27
+
26
28
  import io
27
29
  import json
28
30
  from datetime import datetime, timezone
29
- from urllib.parse import urlparse, parse_qs, quote
30
-
31
- from ..dependencies import (
32
- get_shared_artifact_service,
33
- get_sac_component,
34
- ensure_session_id,
35
- get_user_id,
36
- get_config_resolver,
37
- get_user_config,
38
- )
31
+ from urllib.parse import parse_qs, quote, urlparse
39
32
 
40
33
  from solace_ai_connector.common.log import log
41
34
 
42
- from ....common.middleware import ConfigResolver
43
35
  from ....common.a2a.types import ArtifactInfo
44
- from ....common.utils.mime_helpers import is_text_based_mime_type
36
+ from ....common.middleware import ConfigResolver
45
37
  from ....common.utils.embeds import (
46
- resolve_embeds_recursively_in_string,
47
- evaluate_embed,
48
38
  LATE_EMBED_TYPES,
39
+ evaluate_embed,
40
+ resolve_embeds_recursively_in_string,
41
+ )
42
+ from ....common.utils.mime_helpers import is_text_based_mime_type
43
+ from ..dependencies import (
44
+ get_config_resolver,
45
+ get_sac_component,
46
+ get_session_validator,
47
+ get_shared_artifact_service,
48
+ get_user_config,
49
+ get_user_id,
49
50
  )
50
-
51
51
 
52
52
  if TYPE_CHECKING:
53
53
  from ....gateway.http_sse.component import WebUIBackendComponent
54
+
54
55
  from ....agent.utils.artifact_helpers import (
55
- get_artifact_info_list,
56
- save_artifact_with_metadata,
57
- load_artifact_content_or_metadata,
58
56
  DEFAULT_SCHEMA_MAX_KEYS,
59
57
  format_artifact_uri,
58
+ get_artifact_info_list,
59
+ load_artifact_content_or_metadata,
60
+ save_artifact_with_metadata,
60
61
  )
61
62
 
62
63
  router = APIRouter()
63
64
 
64
65
 
65
66
  @router.get(
66
- "/{filename}/versions",
67
- response_model=List[int],
67
+ "/{session_id}/{filename}/versions",
68
+ response_model=list[int],
68
69
  summary="List Artifact Versions",
69
70
  description="Retrieves a list of available version numbers for a specific artifact.",
70
71
  )
71
72
  async def list_artifact_versions(
73
+ session_id: str = Path(
74
+ ..., title="Session ID", description="The session ID to get artifacts from"
75
+ ),
72
76
  filename: str = Path(..., title="Filename", description="The name of the artifact"),
73
77
  artifact_service: BaseArtifactService = Depends(get_shared_artifact_service),
74
78
  user_id: str = Depends(get_user_id),
75
- session_id: str = Depends(ensure_session_id),
79
+ validate_session: Callable[[str, str], bool] = Depends(get_session_validator),
76
80
  component: "WebUIBackendComponent" = Depends(get_sac_component),
77
81
  config_resolver: ConfigResolver = Depends(get_config_resolver),
78
82
  user_config: dict = Depends(get_user_config),
@@ -91,6 +95,14 @@ async def list_artifact_versions(
91
95
  log_prefix = f"[ArtifactRouter:ListVersions:{filename}] User={user_id}, Session={session_id} -"
92
96
  log.info("%s Request received.", log_prefix)
93
97
 
98
+ # Validate session exists and belongs to user
99
+ if not validate_session(session_id, user_id):
100
+ log.warning("%s Session validation failed or access denied.", log_prefix)
101
+ raise HTTPException(
102
+ status_code=status.HTTP_404_NOT_FOUND,
103
+ detail="Session not found or access denied.",
104
+ )
105
+
94
106
  if artifact_service is None:
95
107
  log.error("%s Artifact service is not configured or available.", log_prefix)
96
108
  raise HTTPException(
@@ -134,23 +146,32 @@ async def list_artifact_versions(
134
146
  )
135
147
 
136
148
 
149
+ @router.get(
150
+ "/{session_id}",
151
+ response_model=list[ArtifactInfo],
152
+ summary="List Artifact Information",
153
+ description="Retrieves detailed information for artifacts available for the specified user session.",
154
+ )
137
155
  @router.get(
138
156
  "/",
139
- response_model=List[ArtifactInfo],
157
+ response_model=list[ArtifactInfo],
140
158
  summary="List Artifact Information",
141
159
  description="Retrieves detailed information for artifacts available for the current user session.",
142
160
  )
143
161
  async def list_artifacts(
162
+ session_id: str = Path(
163
+ ..., title="Session ID", description="The session ID to list artifacts for"
164
+ ),
144
165
  artifact_service: BaseArtifactService = Depends(get_shared_artifact_service),
145
166
  user_id: str = Depends(get_user_id),
146
- session_id: str = Depends(ensure_session_id),
167
+ validate_session: Callable[[str, str], bool] = Depends(get_session_validator),
147
168
  component: "WebUIBackendComponent" = Depends(get_sac_component),
148
169
  config_resolver: ConfigResolver = Depends(get_config_resolver),
149
170
  user_config: dict = Depends(get_user_config),
150
171
  ):
151
172
  """
152
173
  Lists detailed information (filename, size, type, modified date, uri)
153
- for all artifacts associated with the current user and session ID
174
+ for all artifacts associated with the specified user and session ID
154
175
  by calling the artifact helper function.
155
176
  """
156
177
  if not config_resolver.is_feature_enabled(
@@ -161,6 +182,19 @@ async def list_artifacts(
161
182
  log_prefix = f"[ArtifactRouter:ListInfo] User={user_id}, Session={session_id} -"
162
183
  log.info("%s Request received.", log_prefix)
163
184
 
185
+ # Validate session exists and belongs to user
186
+ if not validate_session(session_id, user_id):
187
+ log.warning(
188
+ "%s Session validation failed for session_id=%s, user_id=%s",
189
+ log_prefix,
190
+ session_id,
191
+ user_id,
192
+ )
193
+ raise HTTPException(
194
+ status_code=status.HTTP_404_NOT_FOUND,
195
+ detail="Session not found or access denied.",
196
+ )
197
+
164
198
  if artifact_service is None:
165
199
  log.error("%s Artifact service is not configured or available.", log_prefix)
166
200
  raise HTTPException(
@@ -194,15 +228,18 @@ async def list_artifacts(
194
228
 
195
229
 
196
230
  @router.get(
197
- "/{filename}",
231
+ "/{session_id}/{filename}",
198
232
  summary="Get Latest Artifact Content",
199
233
  description="Retrieves the content of the latest version of a specific artifact.",
200
234
  )
201
235
  async def get_latest_artifact(
236
+ session_id: str = Path(
237
+ ..., title="Session ID", description="The session ID to get artifacts from"
238
+ ),
202
239
  filename: str = Path(..., title="Filename", description="The name of the artifact"),
203
240
  artifact_service: BaseArtifactService = Depends(get_shared_artifact_service),
204
241
  user_id: str = Depends(get_user_id),
205
- session_id: str = Depends(ensure_session_id),
242
+ validate_session: Callable[[str, str], bool] = Depends(get_session_validator),
206
243
  component: "WebUIBackendComponent" = Depends(get_sac_component),
207
244
  config_resolver: ConfigResolver = Depends(get_config_resolver),
208
245
  user_config: dict = Depends(get_user_config),
@@ -331,20 +368,23 @@ async def get_latest_artifact(
331
368
 
332
369
 
333
370
  @router.get(
334
- "/{filename}/versions/{version}",
371
+ "/{session_id}/{filename}/versions/{version}",
335
372
  summary="Get Specific Artifact Version Content",
336
373
  description="Retrieves the content of a specific version of an artifact.",
337
374
  )
338
375
  async def get_specific_artifact_version(
376
+ session_id: str = Path(
377
+ ..., title="Session ID", description="The session ID to get artifacts from"
378
+ ),
339
379
  filename: str = Path(..., title="Filename", description="The name of the artifact"),
340
- version: Union[int, str] = Path(
380
+ version: int | str = Path(
341
381
  ...,
342
382
  title="Version",
343
383
  description="The specific version number to retrieve, or 'latest'",
344
384
  ),
345
385
  artifact_service: BaseArtifactService = Depends(get_shared_artifact_service),
346
386
  user_id: str = Depends(get_user_id),
347
- session_id: str = Depends(ensure_session_id),
387
+ validate_session: Callable[[str, str], bool] = Depends(get_session_validator),
348
388
  component: "WebUIBackendComponent" = Depends(get_sac_component),
349
389
  config_resolver: ConfigResolver = Depends(get_config_resolver),
350
390
  user_config: dict = Depends(get_user_config),
@@ -362,6 +402,14 @@ async def get_specific_artifact_version(
362
402
  log_prefix = f"[ArtifactRouter:GetVersion:{filename} v{version}] User={user_id}, Session={session_id} -"
363
403
  log.info("%s Request received.", log_prefix)
364
404
 
405
+ # Validate session exists and belongs to user
406
+ if not validate_session(session_id, user_id):
407
+ log.warning("%s Session validation failed or access denied.", log_prefix)
408
+ raise HTTPException(
409
+ status_code=status.HTTP_404_NOT_FOUND,
410
+ detail="Session not found or access denied.",
411
+ )
412
+
365
413
  if artifact_service is None:
366
414
  log.error("%s Artifact service is not configured or available.", log_prefix)
367
415
  raise HTTPException(
@@ -381,7 +429,7 @@ async def get_specific_artifact_version(
381
429
  version=version,
382
430
  load_metadata_only=False,
383
431
  return_raw_bytes=True,
384
- log_identifier_prefix=f"[ArtifactRouter:GetVersion]",
432
+ log_identifier_prefix="[ArtifactRouter:GetVersion]",
385
433
  )
386
434
 
387
435
  if load_result.get("status") != "success":
@@ -404,7 +452,6 @@ async def get_specific_artifact_version(
404
452
  mime_type = load_result.get("mime_type", "application/octet-stream")
405
453
  resolved_version_from_helper = load_result.get("version")
406
454
  if data_bytes is None:
407
-
408
455
  log.error(
409
456
  "%s Helper (with return_raw_bytes=True) returned success but no raw_bytes for '%s' v%s (resolved to %s).",
410
457
  log_prefix,
@@ -529,7 +576,7 @@ async def get_artifact_by_uri(
529
576
  This allows fetching artifacts from any context, not just the current user's session,
530
577
  after performing an authorization check.
531
578
  """
532
- log_id_prefix = f"[ArtifactRouter:by-uri]"
579
+ log_id_prefix = "[ArtifactRouter:by-uri]"
533
580
  log.info(
534
581
  "%s Received request for URI: %s from user: %s",
535
582
  log_id_prefix,
@@ -632,23 +679,26 @@ async def get_artifact_by_uri(
632
679
 
633
680
 
634
681
  @router.post(
635
- "/{filename}",
682
+ "/{session_id}/{filename}",
636
683
  status_code=status.HTTP_201_CREATED,
637
- response_model=Dict[str, Any],
684
+ response_model=dict[str, Any],
638
685
  summary="Upload Artifact (Create/Update Version with Metadata)",
639
686
  description="Uploads file content and optional metadata to create or update an artifact version.",
640
687
  )
641
688
  async def upload_artifact(
689
+ session_id: str = Path(
690
+ ..., title="Session ID", description="The session ID to upload artifacts to"
691
+ ),
642
692
  filename: str = Path(
643
693
  ..., title="Filename", description="The name of the artifact to create/update"
644
694
  ),
645
695
  upload_file: UploadFile = File(..., description="The file content to upload"),
646
- metadata_json: Optional[str] = Form(
696
+ metadata_json: str | None = Form(
647
697
  None, description="JSON string of artifact metadata (e.g., description, source)"
648
698
  ),
649
699
  artifact_service: BaseArtifactService = Depends(get_shared_artifact_service),
650
700
  user_id: str = Depends(get_user_id),
651
- session_id: str = Depends(ensure_session_id),
701
+ validate_session: Callable[[str, str], bool] = Depends(get_session_validator),
652
702
  component: "WebUIBackendComponent" = Depends(get_sac_component),
653
703
  config_resolver: ConfigResolver = Depends(get_config_resolver),
654
704
  user_config: dict = Depends(get_user_config),
@@ -665,12 +715,20 @@ async def upload_artifact(
665
715
  f"[ArtifactRouter:Post:{filename}] User={user_id}, Session={session_id} -"
666
716
  )
667
717
  log.info(
668
- "%s Request received. Upload filename: '%s', content type: %s",
718
+ "%s Request received. Upload filename: '%s', content type: %s, Metadata provided: %s",
669
719
  log_prefix,
670
720
  upload_file.filename,
671
721
  upload_file.content_type,
672
722
  )
673
723
 
724
+ # Validate session exists and belongs to user
725
+ if not validate_session(session_id, user_id):
726
+ log.warning("%s Session validation failed or access denied.", log_prefix)
727
+ raise HTTPException(
728
+ status_code=status.HTTP_404_NOT_FOUND,
729
+ detail="Session not found or access denied.",
730
+ )
731
+
674
732
  if artifact_service is None:
675
733
  log.error("%s Artifact service is not configured or available.", log_prefix)
676
734
  raise HTTPException(
@@ -777,24 +835,26 @@ async def upload_artifact(
777
835
  detail=f"Failed to save artifact: {str(e)}",
778
836
  )
779
837
  finally:
780
-
781
838
  await upload_file.close()
782
839
  log.debug("%s Upload file closed.", log_prefix)
783
840
 
784
841
 
785
842
  @router.delete(
786
- "/{filename}",
843
+ "/{session_id}/{filename}",
787
844
  status_code=status.HTTP_204_NO_CONTENT,
788
845
  summary="Delete Artifact",
789
846
  description="Deletes an artifact and all its versions.",
790
847
  )
791
848
  async def delete_artifact(
849
+ session_id: str = Path(
850
+ ..., title="Session ID", description="The session ID to delete artifacts from"
851
+ ),
792
852
  filename: str = Path(
793
853
  ..., title="Filename", description="The name of the artifact to delete"
794
854
  ),
795
855
  artifact_service: BaseArtifactService = Depends(get_shared_artifact_service),
796
856
  user_id: str = Depends(get_user_id),
797
- session_id: str = Depends(ensure_session_id),
857
+ validate_session: Callable[[str, str], bool] = Depends(get_session_validator),
798
858
  component: "WebUIBackendComponent" = Depends(get_sac_component),
799
859
  config_resolver: ConfigResolver = Depends(get_config_resolver),
800
860
  user_config: dict = Depends(get_user_config),
@@ -812,6 +872,14 @@ async def delete_artifact(
812
872
  )
813
873
  log.info("%s Request received.", log_prefix)
814
874
 
875
+ # Validate session exists and belongs to user
876
+ if not validate_session(session_id, user_id):
877
+ log.warning("%s Session validation failed or access denied.", log_prefix)
878
+ raise HTTPException(
879
+ status_code=status.HTTP_404_NOT_FOUND,
880
+ detail="Session not found or access denied.",
881
+ )
882
+
815
883
  if artifact_service is None:
816
884
  log.error("%s Artifact service is not configured or available.", log_prefix)
817
885
  raise HTTPException(
@@ -833,7 +901,6 @@ async def delete_artifact(
833
901
  return Response(status_code=status.HTTP_204_NO_CONTENT)
834
902
 
835
903
  except Exception as e:
836
-
837
904
  log.exception("%s Error deleting artifact: %s", log_prefix, e)
838
905
  raise HTTPException(
839
906
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -7,7 +7,7 @@ from typing import Dict, Any
7
7
 
8
8
  from solace_ai_connector.common.log import log
9
9
 
10
- from ....gateway.http_sse.dependencies import get_sac_component
10
+ from ....gateway.http_sse.dependencies import get_sac_component, get_api_config
11
11
  from typing import TYPE_CHECKING
12
12
 
13
13
  if TYPE_CHECKING:
@@ -19,6 +19,7 @@ router = APIRouter()
19
19
  @router.get("/config", response_model=Dict[str, Any])
20
20
  async def get_app_config(
21
21
  component: "WebUIBackendComponent" = Depends(get_sac_component),
22
+ api_config: Dict[str, Any] = Depends(get_api_config),
22
23
  ):
23
24
  """
24
25
  Provides configuration settings needed by the frontend application.
@@ -42,6 +43,7 @@ async def get_app_config(
42
43
  "frontend_collect_feedback", False
43
44
  ),
44
45
  "frontend_bot_name": component.get_config("frontend_bot_name", "A2A Agent"),
46
+ "persistence_enabled": api_config.get("persistence_enabled", False),
45
47
  }
46
48
  log.info("%sReturning frontend configuration.", log_prefix)
47
49
  return config_data
@@ -76,19 +76,99 @@ async def _submit_task(
76
76
  )
77
77
 
78
78
  client_id = session_manager.get_a2a_client_id(request)
79
- session_id = session_manager.ensure_a2a_session(request)
79
+
80
+ # Use session ID from frontend request (contextId) instead of cookie-based session
81
+ # Handle various falsy values: None, empty string, whitespace-only string
82
+ log.info("%s[DEBUG] payload.params.message: %s", log_prefix, payload.params.message)
83
+ log.info("%s[DEBUG] hasattr context_id: %s", log_prefix, hasattr(payload.params.message, 'context_id'))
84
+ if hasattr(payload.params.message, 'context_id'):
85
+ log.info("%s[DEBUG] context_id value: %s", log_prefix, payload.params.message.context_id)
86
+
87
+ frontend_session_id = None
88
+ if hasattr(payload.params.message, 'context_id') and payload.params.message.context_id:
89
+ context_id = payload.params.message.context_id
90
+ if isinstance(context_id, str) and context_id.strip():
91
+ frontend_session_id = context_id.strip()
92
+ log.info("%s[DEBUG] Extracted frontend_session_id: %s", log_prefix, frontend_session_id)
93
+
94
+ if frontend_session_id:
95
+ session_id = frontend_session_id
96
+ log.info("%sUsing session ID from frontend request: %s", log_prefix, session_id)
97
+ else:
98
+ # Create new session when frontend doesn't provide one (None, empty, or whitespace-only)
99
+ session_id = session_manager.create_new_session_id(request)
100
+ log.info("%sNo valid session ID from frontend, created new session: %s", log_prefix, session_id)
80
101
 
81
102
  log.info(
82
103
  "%sUsing ClientID: %s, SessionID: %s", log_prefix, client_id, session_id
83
104
  )
84
105
 
106
+ # Store message in persistence layer if available
107
+ user_id = user_identity.get("id")
108
+ if is_streaming and hasattr(component, "persistence_service") and component.persistence_service:
109
+ try:
110
+ from ....gateway.http_sse.dependencies import get_session_service
111
+ from ....gateway.http_sse.shared.enums import SenderType
112
+
113
+ session_service = get_session_service(component)
114
+
115
+ # First ensure session exists in database - create it with the SessionManager's ID
116
+ # Handle race condition where multiple requests might try to create the same session
117
+ existing_session = session_service.get_session(session_id=session_id, user_id=user_id)
118
+ if not existing_session:
119
+ log.info("%sCreating new session in database: %s", log_prefix, session_id)
120
+ try:
121
+ session_service.create_session(
122
+ user_id=user_id,
123
+ agent_id=agent_name,
124
+ name=None, # Will be auto-generated if needed
125
+ session_id=session_id # Use the SessionManager's session ID
126
+ )
127
+ except Exception as create_error:
128
+ # Another request may have created the session concurrently
129
+ log.warning("%sSession creation failed, checking if session exists: %s", log_prefix, create_error)
130
+ existing_session = session_service.get_session(session_id=session_id, user_id=user_id)
131
+ if not existing_session:
132
+ # If session still doesn't exist, re-raise the original error
133
+ raise create_error
134
+ log.info("%sSession was created by another request: %s", log_prefix, session_id)
135
+
136
+ # Extract text content from the message for storage
137
+ message_text = ""
138
+ if payload.params and payload.params.message:
139
+ parts = a2a.get_parts_from_message(payload.params.message)
140
+ for part in parts:
141
+ if hasattr(part, 'text'):
142
+ message_text = part.text
143
+ break
144
+
145
+ # Now store the message in the existing session
146
+ message_domain = session_service.add_message_to_session(
147
+ session_id=session_id,
148
+ user_id=user_id,
149
+ message=message_text or "Task submitted",
150
+ sender_type=SenderType.USER,
151
+ sender_name=user_id or "user",
152
+ agent_id=agent_name,
153
+ )
154
+
155
+ if message_domain:
156
+ log.info("%sMessage stored in session %s", log_prefix, session_id)
157
+ else:
158
+ log.warning("%sFailed to store message in session %s", log_prefix, session_id)
159
+ except Exception as e:
160
+ log.error("%sFailed to store message in session service: %s", log_prefix, e)
161
+ # Don't fail the request, just log the error
162
+ else:
163
+ log.debug("%sNo persistence available or non-streaming - skipping message storage", log_prefix)
164
+
85
165
  # Use the helper to get the unwrapped parts from the incoming message.
86
166
  a2a_parts = a2a.get_parts_from_message(payload.params.message)
87
167
 
88
168
  external_req_ctx = {
89
169
  "app_name_for_artifacts": component.gateway_id,
90
170
  "user_id_for_artifacts": client_id,
91
- "a2a_session_id": session_id,
171
+ "a2a_session_id": session_id, # This may have been updated by persistence layer
92
172
  "user_id_for_a2a": client_id,
93
173
  "target_agent_name": agent_name,
94
174
  }
@@ -110,6 +190,7 @@ async def _submit_task(
110
190
  )
111
191
 
112
192
  if is_streaming:
193
+ # The task_object already contains the contextId from create_initial_task
113
194
  return a2a.create_send_streaming_message_success_response(
114
195
  result=task_object, request_id=payload.id
115
196
  )
@@ -212,19 +212,19 @@ def _resolve_user_identity_for_authorization(
212
212
  return user_identity
213
213
 
214
214
  if not user_identity:
215
- default_user_identity = component.get_config("default_user_identity")
216
- if default_user_identity:
217
- user_identity = default_user_identity
215
+ use_authorization = component.get_config("frontend_use_authorization", False)
216
+ if not use_authorization:
217
+ user_identity = "sam_dev_user"
218
218
  log.info(
219
- "%s No user_identity provided, using configured default_user_identity: '%s' for visualization",
219
+ "%s No user_identity provided and auth is disabled, using sam_dev_user for visualization",
220
220
  log_id_prefix,
221
- user_identity,
222
221
  )
223
222
  else:
224
- log.warning(
225
- "%s No user_identity and no default_user_identity configured for visualization",
223
+ log.error(
224
+ "%s No user_identity provided but authorization is enabled. This should not happen.",
226
225
  log_id_prefix,
227
226
  )
227
+ raise ValueError("No user identity available when authorization is required")
228
228
 
229
229
  return user_identity
230
230