solace-agent-mesh 1.4.7__py3-none-any.whl → 1.4.8__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 (117) hide show
  1. solace_agent_mesh/agent/sac/component.py +2 -3
  2. solace_agent_mesh/assets/docs/404.html +3 -3
  3. solace_agent_mesh/assets/docs/assets/js/{0e682baa.da822665.js → 0e682baa.d054e1d8.js} +1 -1
  4. solace_agent_mesh/assets/docs/assets/js/166ab619.e27886d9.js +1 -0
  5. solace_agent_mesh/assets/docs/assets/js/453a82a6.3c6bb61d.js +1 -0
  6. solace_agent_mesh/assets/docs/assets/js/{75384d09.1e7d7cb7.js → 75384d09.c19e8b51.js} +1 -1
  7. solace_agent_mesh/assets/docs/assets/js/a3a92b25.af35e313.js +1 -0
  8. solace_agent_mesh/assets/docs/assets/js/{bac0be12.bf0181cf.js → bac0be12.17de4316.js} +1 -1
  9. solace_agent_mesh/assets/docs/assets/js/d6a81ee7.829198f1.js +1 -0
  10. solace_agent_mesh/assets/docs/assets/js/f284c35a.ed8dd236.js +1 -0
  11. solace_agent_mesh/assets/docs/assets/js/{main.11f9f9f3.js → main.86924c42.js} +2 -2
  12. solace_agent_mesh/assets/docs/assets/js/runtime~main.0d2ff2b6.js +1 -0
  13. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +4 -4
  14. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/rbac-setup-guilde/index.html +4 -4
  15. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +4 -4
  16. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html +4 -4
  17. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html +4 -4
  18. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +4 -4
  19. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +4 -4
  20. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +4 -4
  21. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
  22. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +4 -4
  23. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +4 -4
  24. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +4 -4
  25. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +4 -4
  26. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +4 -4
  27. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +4 -4
  28. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +11 -12
  29. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/litellm_models/index.html +49 -0
  30. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +4 -4
  31. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +4 -4
  32. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +4 -4
  33. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
  34. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +4 -4
  35. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +5 -5
  36. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +5 -5
  37. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +4 -4
  38. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +6 -6
  39. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +4 -4
  40. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +4 -4
  41. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
  42. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +4 -4
  43. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +4 -4
  44. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +4 -4
  45. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +4 -4
  46. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +4 -4
  47. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +4 -4
  48. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +4 -4
  49. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +4 -4
  50. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +4 -4
  51. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +4 -4
  52. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +4 -4
  53. solace_agent_mesh/assets/docs/lunr-index-1759246102819.json +1 -0
  54. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  55. solace_agent_mesh/assets/docs/search-doc-1759246102819.json +1 -0
  56. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  57. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  58. solace_agent_mesh/cli/__init__.py +1 -1
  59. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +6 -3
  60. solace_agent_mesh/cli/commands/init_cmd/__init__.py +3 -3
  61. solace_agent_mesh/cli/commands/init_cmd/broker_step.py +1 -1
  62. solace_agent_mesh/cli/commands/init_cmd/env_step.py +1 -1
  63. solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +4 -4
  64. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +12 -1
  65. solace_agent_mesh/cli/commands/plugin_cmd/install_cmd.py +5 -5
  66. solace_agent_mesh/client/webui/frontend/static/assets/main-B0PHV3hm.js +339 -0
  67. solace_agent_mesh/client/webui/frontend/static/index.html +1 -1
  68. solace_agent_mesh/config_portal/backend/common.py +1 -1
  69. solace_agent_mesh/config_portal/frontend/static/client/assets/{_index-bFMKlzKf.js → _index-BNuqpWDc.js} +1 -1
  70. solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-89db7c30.js → manifest-44d62be6.js} +1 -1
  71. solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
  72. solace_agent_mesh/gateway/http_sse/component.py +21 -15
  73. solace_agent_mesh/gateway/http_sse/dependencies.py +84 -88
  74. solace_agent_mesh/gateway/http_sse/main.py +8 -2
  75. solace_agent_mesh/gateway/http_sse/repository/entities/message.py +3 -1
  76. solace_agent_mesh/gateway/http_sse/repository/entities/session.py +3 -1
  77. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +5 -0
  78. solace_agent_mesh/gateway/http_sse/repository/message_repository.py +25 -23
  79. solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +12 -4
  80. solace_agent_mesh/gateway/http_sse/repository/models/message_model.py +19 -1
  81. solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +19 -1
  82. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +46 -42
  83. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +199 -59
  84. solace_agent_mesh/gateway/http_sse/routers/dto/requests/__init__.py +1 -6
  85. solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +3 -17
  86. solace_agent_mesh/gateway/http_sse/routers/people.py +4 -37
  87. solace_agent_mesh/gateway/http_sse/routers/sessions.py +33 -68
  88. solace_agent_mesh/gateway/http_sse/routers/tasks.py +54 -28
  89. solace_agent_mesh/gateway/http_sse/services/session_service.py +60 -28
  90. solace_agent_mesh/gateway/http_sse/shared/__init__.py +122 -1
  91. solace_agent_mesh/gateway/http_sse/shared/base_repository.py +278 -0
  92. solace_agent_mesh/gateway/http_sse/shared/database_exceptions.py +274 -0
  93. solace_agent_mesh/gateway/http_sse/shared/database_helpers.py +43 -0
  94. solace_agent_mesh/gateway/http_sse/shared/error_dto.py +107 -0
  95. solace_agent_mesh/gateway/http_sse/shared/exception_handlers.py +192 -0
  96. solace_agent_mesh/gateway/http_sse/shared/exceptions.py +192 -0
  97. solace_agent_mesh/gateway/http_sse/shared/pagination.py +138 -0
  98. solace_agent_mesh/gateway/http_sse/shared/response_utils.py +134 -0
  99. solace_agent_mesh/gateway/http_sse/shared/utils.py +22 -0
  100. solace_agent_mesh/templates/plugin_agent_config_template.yaml +1 -1
  101. solace_agent_mesh/templates/plugin_custom_config_template.yaml +1 -1
  102. solace_agent_mesh/templates/plugin_gateway_config_template.yaml +1 -1
  103. solace_agent_mesh/templates/shared_config.yaml +1 -1
  104. {solace_agent_mesh-1.4.7.dist-info → solace_agent_mesh-1.4.8.dist-info}/METADATA +34 -35
  105. {solace_agent_mesh-1.4.7.dist-info → solace_agent_mesh-1.4.8.dist-info}/RECORD +109 -98
  106. solace_agent_mesh/assets/docs/assets/js/166ab619.bdddc63a.js +0 -1
  107. solace_agent_mesh/assets/docs/assets/js/a3a92b25.6def8980.js +0 -1
  108. solace_agent_mesh/assets/docs/assets/js/beecea0d.ce915979.js +0 -1
  109. solace_agent_mesh/assets/docs/assets/js/f284c35a.525933db.js +0 -1
  110. solace_agent_mesh/assets/docs/assets/js/runtime~main.5922bcf0.js +0 -1
  111. solace_agent_mesh/assets/docs/lunr-index-1759151175744.json +0 -1
  112. solace_agent_mesh/assets/docs/search-doc-1759151175744.json +0 -1
  113. solace_agent_mesh/client/webui/frontend/static/assets/main-BKIoiLSu.js +0 -339
  114. /solace_agent_mesh/assets/docs/assets/js/{main.11f9f9f3.js.LICENSE.txt → main.86924c42.js.LICENSE.txt} +0 -0
  115. {solace_agent_mesh-1.4.7.dist-info → solace_agent_mesh-1.4.8.dist-info}/WHEEL +0 -0
  116. {solace_agent_mesh-1.4.7.dist-info → solace_agent_mesh-1.4.8.dist-info}/entry_points.txt +0 -0
  117. {solace_agent_mesh-1.4.7.dist-info → solace_agent_mesh-1.4.8.dist-info}/licenses/LICENSE +0 -0
@@ -1,94 +1,51 @@
1
- from fastapi import APIRouter, Body, Depends, HTTPException, status
2
- from fastapi import Request as FastAPIRequest
1
+ from fastapi import APIRouter, Body, Depends, HTTPException, Query, status
3
2
  from solace_ai_connector.common.log import log
3
+ from sqlalchemy.orm import Session
4
4
 
5
- from a2a.types import JSONRPCSuccessResponse
6
- from ..dependencies import get_session_business_service, get_session_manager
5
+ from ..dependencies import get_session_business_service, get_db
7
6
  from ..services.session_service import SessionService
8
- from ..session_manager import SessionManager
9
7
  from ..shared.auth_utils import get_current_user
8
+ from ..shared.pagination import DataResponse, PaginatedResponse, PaginationParams
9
+ from ..shared.response_utils import create_data_response
10
10
  from .dto.requests.session_requests import (
11
11
  GetSessionHistoryRequest,
12
12
  GetSessionRequest,
13
- GetSessionsRequest,
14
13
  UpdateSessionRequest,
15
14
  )
16
- from .dto.responses.session_responses import (
17
- MessageResponse,
18
- SessionListResponse,
19
- SessionResponse,
20
- )
21
- from ....common.a2a import create_generic_success_response
15
+ from .dto.responses.session_responses import MessageResponse, SessionResponse
22
16
 
23
17
  router = APIRouter()
24
18
 
25
19
 
26
- @router.post("/sessions/new", response_model=JSONRPCSuccessResponse)
27
- async def create_new_session(
28
- request: FastAPIRequest,
29
- user: dict = Depends(get_current_user),
30
- session_manager: SessionManager = Depends(get_session_manager),
31
- session_service: SessionService = Depends(get_session_business_service),
32
- ):
33
- """Creates a new session on-demand and returns its ID."""
34
- user_id = user.get("id")
35
- log.info("User %s requesting new session", user_id)
36
- try:
37
- new_session_id = session_manager.create_new_session_id(request)
38
- log.info("Created new session ID: %s for user %s", new_session_id, user_id)
39
-
40
- # Attempt to create the session record in the DB.
41
- # The service will handle the check for whether persistence is enabled.
42
- session_service.create_session(
43
- user_id=user_id,
44
- agent_id=None, # Agent is not known at this point
45
- name=None,
46
- session_id=new_session_id,
47
- )
48
-
49
- return create_generic_success_response(
50
- result={"id": new_session_id}, request_id=None
51
- )
52
- except Exception as e:
53
- log.error("Error creating new session for user %s: %s", user_id, e)
54
- raise HTTPException(
55
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
56
- detail="Failed to create a new session",
57
- )
58
-
59
20
 
60
- @router.get("/sessions", response_model=SessionListResponse)
21
+ @router.get("/sessions", response_model=PaginatedResponse[SessionResponse])
61
22
  async def get_all_sessions(
23
+ page_number: int = Query(default=1, ge=1, alias="pageNumber"),
24
+ page_size: int = Query(default=20, ge=1, le=100, alias="pageSize"),
25
+ db: Session = Depends(get_db),
62
26
  user: dict = Depends(get_current_user),
63
27
  session_service: SessionService = Depends(get_session_business_service),
64
28
  ):
65
29
  user_id = user.get("id")
66
- log.info("Fetching sessions for user_id: %s", user_id)
30
+ log.info(f"User '{user_id}' is listing sessions with pagination (page={page_number}, size={page_size})")
67
31
 
68
32
  try:
69
- request_dto = GetSessionsRequest(user_id=user_id)
70
-
71
- session_domains = session_service.get_user_sessions(
72
- user_id=request_dto.user_id, pagination=request_dto.pagination
73
- )
33
+ pagination = PaginationParams(page_number=page_number, page_size=page_size)
34
+ paginated_response = session_service.get_user_sessions(db, user_id, pagination)
74
35
 
75
36
  session_responses = []
76
- for domain in session_domains:
37
+ for session_domain in paginated_response.data:
77
38
  session_response = SessionResponse(
78
- id=domain.id,
79
- user_id=domain.user_id,
80
- name=domain.name,
81
- agent_id=domain.agent_id,
82
- created_time=domain.created_time,
83
- updated_time=domain.updated_time,
39
+ id=session_domain.id,
40
+ user_id=session_domain.user_id,
41
+ name=session_domain.name,
42
+ agent_id=session_domain.agent_id,
43
+ created_time=session_domain.created_time,
44
+ updated_time=session_domain.updated_time,
84
45
  )
85
46
  session_responses.append(session_response)
86
47
 
87
- return SessionListResponse(
88
- sessions=session_responses,
89
- total_count=len(session_responses),
90
- pagination=request_dto.pagination,
91
- )
48
+ return PaginatedResponse(data=session_responses, meta=paginated_response.meta)
92
49
 
93
50
  except Exception as e:
94
51
  log.error("Error fetching sessions for user %s: %s", user_id, e)
@@ -98,9 +55,10 @@ async def get_all_sessions(
98
55
  )
99
56
 
100
57
 
101
- @router.get("/sessions/{session_id}", response_model=SessionResponse)
58
+ @router.get("/sessions/{session_id}", response_model=DataResponse[SessionResponse])
102
59
  async def get_session(
103
60
  session_id: str,
61
+ db: Session = Depends(get_db),
104
62
  user: dict = Depends(get_current_user),
105
63
  session_service: SessionService = Depends(get_session_business_service),
106
64
  ):
@@ -120,7 +78,7 @@ async def get_session(
120
78
  request_dto = GetSessionRequest(session_id=session_id, user_id=user_id)
121
79
 
122
80
  session_domain = session_service.get_session_details(
123
- session_id=request_dto.session_id, user_id=request_dto.user_id
81
+ db=db, session_id=request_dto.session_id, user_id=request_dto.user_id
124
82
  )
125
83
 
126
84
  if not session_domain:
@@ -130,7 +88,7 @@ async def get_session(
130
88
 
131
89
  log.info("User %s authorized. Fetching session_id: %s", user_id, session_id)
132
90
 
133
- return SessionResponse(
91
+ session_response = SessionResponse(
134
92
  id=session_domain.id,
135
93
  user_id=session_domain.user_id,
136
94
  name=session_domain.name,
@@ -139,6 +97,8 @@ async def get_session(
139
97
  updated_time=session_domain.updated_time,
140
98
  )
141
99
 
100
+ return create_data_response(session_response)
101
+
142
102
  except HTTPException:
143
103
  raise
144
104
  except Exception as e:
@@ -157,6 +117,7 @@ async def get_session(
157
117
  @router.get("/sessions/{session_id}/messages")
158
118
  async def get_session_history(
159
119
  session_id: str,
120
+ db: Session = Depends(get_db),
160
121
  user: dict = Depends(get_current_user),
161
122
  session_service: SessionService = Depends(get_session_business_service),
162
123
  ):
@@ -178,6 +139,7 @@ async def get_session_history(
178
139
  request_dto = GetSessionHistoryRequest(session_id=session_id, user_id=user_id)
179
140
 
180
141
  history_domain = session_service.get_session_history(
142
+ db=db,
181
143
  session_id=request_dto.session_id,
182
144
  user_id=request_dto.user_id,
183
145
  pagination=request_dto.pagination,
@@ -228,6 +190,7 @@ async def get_session_history(
228
190
  async def update_session_name(
229
191
  session_id: str,
230
192
  name: str = Body(..., embed=True),
193
+ db: Session = Depends(get_db),
231
194
  user: dict = Depends(get_current_user),
232
195
  session_service: SessionService = Depends(get_session_business_service),
233
196
  ):
@@ -249,6 +212,7 @@ async def update_session_name(
249
212
  )
250
213
 
251
214
  updated_domain = session_service.update_session_name(
215
+ db=db,
252
216
  session_id=request_dto.session_id,
253
217
  user_id=request_dto.user_id,
254
218
  name=request_dto.name,
@@ -293,6 +257,7 @@ async def update_session_name(
293
257
  @router.delete("/sessions/{session_id}", status_code=status.HTTP_204_NO_CONTENT)
294
258
  async def delete_session(
295
259
  session_id: str,
260
+ db: Session = Depends(get_db),
296
261
  user: dict = Depends(get_current_user),
297
262
  session_service: SessionService = Depends(get_session_business_service),
298
263
  ):
@@ -301,7 +266,7 @@ async def delete_session(
301
266
 
302
267
  try:
303
268
  deleted = session_service.delete_session_with_notifications(
304
- session_id=session_id, user_id=user_id
269
+ db=db, session_id=session_id, user_id=user_id
305
270
  )
306
271
 
307
272
  if not deleted:
@@ -15,6 +15,7 @@ from solace_ai_connector.common.log import log
15
15
 
16
16
  from ....gateway.http_sse.session_manager import SessionManager
17
17
  from ....gateway.http_sse.services.task_service import TaskService
18
+ from ....gateway.http_sse.services.session_service import SessionService
18
19
 
19
20
  from a2a.types import (
20
21
  CancelTaskRequest,
@@ -29,6 +30,7 @@ from ....gateway.http_sse.dependencies import (
29
30
  get_session_manager,
30
31
  get_sac_component,
31
32
  get_task_service,
33
+ get_session_business_service,
32
34
  )
33
35
 
34
36
  from typing import TYPE_CHECKING
@@ -45,6 +47,7 @@ async def _submit_task(
45
47
  session_manager: SessionManager,
46
48
  component: "WebUIBackendComponent",
47
49
  is_streaming: bool,
50
+ session_service: SessionService | None = None,
48
51
  ):
49
52
  """Helper to submit a task, handling both streaming and non-streaming cases."""
50
53
  log_prefix = f"[POST /api/v1/message:{'stream' if is_streaming else 'send'}] "
@@ -77,7 +80,7 @@ async def _submit_task(
77
80
 
78
81
  client_id = session_manager.get_a2a_client_id(request)
79
82
 
80
- # Use session ID from frontend request (contextId) instead of cookie-based session
83
+ # Use session ID from frontend request (contextId per A2A spec) instead of cookie-based session
81
84
  # Handle various falsy values: None, empty string, whitespace-only string
82
85
  frontend_session_id = None
83
86
  if (
@@ -88,13 +91,16 @@ async def _submit_task(
88
91
  if isinstance(context_id, str) and context_id.strip():
89
92
  frontend_session_id = context_id.strip()
90
93
 
94
+ user_id = user_identity.get("id")
95
+ from ....gateway.http_sse.dependencies import SessionLocal
96
+
91
97
  if frontend_session_id:
92
98
  session_id = frontend_session_id
93
99
  log.info(
94
100
  "%sUsing session ID from frontend request: %s", log_prefix, session_id
95
101
  )
96
102
  else:
97
- # Create new session when frontend doesn't provide one (None, empty, or whitespace-only)
103
+ # Create new session when frontend doesn't provide one
98
104
  session_id = session_manager.create_new_session_id(request)
99
105
  log.info(
100
106
  "%sNo valid session ID from frontend, created new session: %s",
@@ -102,43 +108,60 @@ async def _submit_task(
102
108
  session_id,
103
109
  )
104
110
 
111
+ # Immediately create session in database if persistence is enabled
112
+ # This ensures the session exists before any other operations (like artifact listing)
113
+ if SessionLocal is not None and session_service is not None:
114
+ db = SessionLocal()
115
+ try:
116
+ session_service.create_session(
117
+ db=db,
118
+ user_id=user_id,
119
+ agent_id=agent_name,
120
+ session_id=session_id,
121
+ )
122
+ db.commit()
123
+ log.info("%sCreated session in database: %s", log_prefix, session_id)
124
+ except Exception as e:
125
+ db.rollback()
126
+ log.warning("%sFailed to create session in database: %s", log_prefix, e)
127
+ finally:
128
+ db.close()
129
+
105
130
  log.info(
106
131
  "%sUsing ClientID: %s, SessionID: %s", log_prefix, client_id, session_id
107
132
  )
108
133
 
109
134
  # Store message in persistence layer if available
110
- user_id = user_identity.get("id")
111
- from ....gateway.http_sse.dependencies import SessionLocal
112
-
113
- if is_streaming and SessionLocal is not None:
135
+ if is_streaming and SessionLocal is not None and session_service is not None:
136
+ db = SessionLocal()
114
137
  try:
115
- from ....gateway.http_sse.dependencies import (
116
- create_session_service_with_transaction,
117
- )
118
138
  from ....gateway.http_sse.shared.enums import SenderType
119
139
 
120
- with create_session_service_with_transaction() as (session_service, db):
121
- message_text = ""
122
- if payload.params and payload.params.message:
123
- parts = a2a.get_parts_from_message(payload.params.message)
124
- for part in parts:
125
- if hasattr(part, "text"):
126
- message_text = part.text
127
- break
128
-
129
- session_service.add_message_to_session(
130
- session_id=session_id,
131
- user_id=user_id,
132
- message=message_text or "Task submitted",
133
- sender_type=SenderType.USER,
134
- sender_name=user_id or "user",
135
- agent_id=agent_name,
136
- )
137
-
140
+ message_text = ""
141
+ if payload.params and payload.params.message:
142
+ parts = a2a.get_parts_from_message(payload.params.message)
143
+ for part in parts:
144
+ if hasattr(part, "text"):
145
+ message_text = part.text
146
+ break
147
+
148
+ session_service.add_message_to_session(
149
+ db=db,
150
+ session_id=session_id,
151
+ user_id=user_id,
152
+ message=message_text or "Task submitted",
153
+ sender_type=SenderType.USER,
154
+ sender_name=user_id or "user",
155
+ agent_id=agent_name,
156
+ )
157
+ db.commit()
138
158
  except Exception as e:
159
+ db.rollback()
139
160
  log.error(
140
161
  "%sFailed to store message in session service: %s", log_prefix, e
141
162
  )
163
+ finally:
164
+ db.close()
142
165
  else:
143
166
  log.debug(
144
167
  "%sNo persistence available or non-streaming - skipping message storage",
@@ -216,6 +239,7 @@ async def send_task_to_agent(
216
239
  session_manager=session_manager,
217
240
  component=component,
218
241
  is_streaming=False,
242
+ session_service=None,
219
243
  )
220
244
 
221
245
 
@@ -225,6 +249,7 @@ async def subscribe_task_from_agent(
225
249
  payload: SendStreamingMessageRequest,
226
250
  session_manager: SessionManager = Depends(get_session_manager),
227
251
  component: "WebUIBackendComponent" = Depends(get_sac_component),
252
+ session_service: SessionService = Depends(get_session_business_service),
228
253
  ):
229
254
  """
230
255
  Submits a streaming task request to the specified agent.
@@ -237,6 +262,7 @@ async def subscribe_task_from_agent(
237
262
  session_manager=session_manager,
238
263
  component=component,
239
264
  is_streaming=True,
265
+ session_service=session_service,
240
266
  )
241
267
 
242
268
 
@@ -302,4 +328,4 @@ async def cancel_agent_task(
302
328
  raise HTTPException(
303
329
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
304
330
  detail=error_resp.model_dump(exclude_none=True),
305
- )
331
+ )
@@ -2,6 +2,7 @@ import uuid
2
2
  from typing import TYPE_CHECKING, Optional
3
3
 
4
4
  from solace_ai_connector.common.log import log
5
+ from sqlalchemy.orm import Session as DbSession
5
6
 
6
7
  from ..repository import (
7
8
  IMessageRepository,
@@ -13,6 +14,7 @@ from ..repository import (
13
14
  from ..shared.enums import MessageType, SenderType
14
15
  from ..shared.types import PaginationInfo, SessionId, UserId
15
16
  from ..shared import now_epoch_ms
17
+ from ..shared.pagination import PaginationParams, PaginatedResponse, get_pagination_or_default
16
18
 
17
19
  if TYPE_CHECKING:
18
20
  from ..component import WebUIBackendComponent
@@ -21,38 +23,65 @@ if TYPE_CHECKING:
21
23
  class SessionService:
22
24
  def __init__(
23
25
  self,
24
- session_repository: ISessionRepository,
25
- message_repository: IMessageRepository,
26
26
  component: "WebUIBackendComponent" = None,
27
27
  ):
28
- self.session_repository = session_repository
29
- self.message_repository = message_repository
30
28
  self.component = component
31
29
 
30
+ def _get_repositories(self, db: DbSession):
31
+ """Create repositories for the given database session."""
32
+ from ..repository import SessionRepository, MessageRepository
33
+ session_repository = SessionRepository(db)
34
+ message_repository = MessageRepository(db)
35
+ return session_repository, message_repository
36
+
32
37
  def is_persistence_enabled(self) -> bool:
33
38
  """Checks if the service is configured with a persistent backend."""
34
- # The presence of a database_url on the component is the source of truth
35
- # for whether SQL persistence is enabled.
36
39
  return self.component and self.component.database_url is not None
37
40
 
38
41
  def get_user_sessions(
39
- self, user_id: UserId, pagination: PaginationInfo | None = None
40
- ) -> list[Session]:
42
+ self,
43
+ db: DbSession,
44
+ user_id: UserId,
45
+ pagination: PaginationParams | None = None
46
+ ) -> PaginatedResponse[Session]:
47
+ """
48
+ Get paginated sessions for a user with full metadata.
49
+
50
+ Uses default pagination if none provided (page 1, size 20).
51
+ Returns paginated response with pageNumber, pageSize, nextPage, totalPages, totalCount.
52
+ """
41
53
  if not user_id or user_id.strip() == "":
42
54
  raise ValueError("User ID cannot be empty")
43
55
 
44
- return self.session_repository.find_by_user(user_id, pagination)
56
+ pagination = get_pagination_or_default(pagination)
57
+ session_repository, _ = self._get_repositories(db)
58
+
59
+ pagination_info = PaginationInfo(
60
+ page=pagination.page_number,
61
+ page_size=pagination.page_size,
62
+ total_items=0,
63
+ total_pages=0,
64
+ has_next=False,
65
+ has_previous=False,
66
+ )
67
+
68
+ sessions = session_repository.find_by_user(user_id, pagination_info)
69
+ total_count = session_repository.count_by_user(user_id)
70
+
71
+ return PaginatedResponse.create(sessions, total_count, pagination)
45
72
 
46
73
  def get_session_details(
47
- self, session_id: SessionId, user_id: UserId
74
+ self, db: DbSession, session_id: SessionId, user_id: UserId
48
75
  ) -> Session | None:
49
76
  if not self._is_valid_session_id(session_id):
50
77
  return None
51
78
 
52
- return self.session_repository.find_user_session(session_id, user_id)
79
+ session_repository, _ = self._get_repositories(db)
80
+ return session_repository.find_user_session(session_id, user_id)
53
81
 
54
82
  def get_session_history(
55
83
  self,
84
+ db: DbSession,
56
85
  session_id: SessionId,
57
86
  user_id: UserId,
58
87
  pagination: PaginationInfo | None = None,
@@ -60,7 +89,8 @@ class SessionService:
60
89
  if not self._is_valid_session_id(session_id):
61
90
  return None
62
91
 
63
- result = self.session_repository.find_user_session_with_messages(
92
+ session_repository, _ = self._get_repositories(db)
93
+ result = session_repository.find_user_session_with_messages(
64
94
  session_id, user_id, pagination
65
95
  )
66
96
  if not result:
@@ -75,6 +105,7 @@ class SessionService:
75
105
 
76
106
  def create_session(
77
107
  self,
108
+ db: DbSession,
78
109
  user_id: UserId,
79
110
  name: str | None = None,
80
111
  agent_id: str | None = None,
@@ -90,8 +121,6 @@ class SessionService:
90
121
  if not session_id:
91
122
  session_id = str(uuid.uuid4())
92
123
 
93
- # Leave name as None/empty - frontend will generate display name if needed
94
-
95
124
  now_ms = now_epoch_ms()
96
125
  session = Session(
97
126
  id=session_id,
@@ -102,10 +131,8 @@ class SessionService:
102
131
  updated_time=now_ms,
103
132
  )
104
133
 
105
- if not session:
106
- raise ValueError(f"Failed to create session for {session_id}")
107
-
108
- created_session = self.session_repository.save(session)
134
+ session_repository, _ = self._get_repositories(db)
135
+ created_session = session_repository.save(session)
109
136
  log.info("Created new session %s for user %s", created_session.id, user_id)
110
137
 
111
138
  if not created_session:
@@ -114,7 +141,7 @@ class SessionService:
114
141
  return created_session
115
142
 
116
143
  def update_session_name(
117
- self, session_id: SessionId, user_id: UserId, name: str
144
+ self, db: DbSession, session_id: SessionId, user_id: UserId, name: str
118
145
  ) -> Session | None:
119
146
  if not self._is_valid_session_id(session_id):
120
147
  raise ValueError("Invalid session ID")
@@ -125,23 +152,25 @@ class SessionService:
125
152
  if len(name.strip()) > 255:
126
153
  raise ValueError("Session name cannot exceed 255 characters")
127
154
 
128
- session = self.session_repository.find_user_session(session_id, user_id)
155
+ session_repository, _ = self._get_repositories(db)
156
+ session = session_repository.find_user_session(session_id, user_id)
129
157
  if not session:
130
158
  return None
131
159
 
132
160
  session.update_name(name)
133
- updated_session = self.session_repository.save(session)
161
+ updated_session = session_repository.save(session)
134
162
 
135
163
  log.info("Updated session %s name to '%s'", session_id, name)
136
164
  return updated_session
137
165
 
138
166
  def delete_session_with_notifications(
139
- self, session_id: SessionId, user_id: UserId
167
+ self, db: DbSession, session_id: SessionId, user_id: UserId
140
168
  ) -> bool:
141
169
  if not self._is_valid_session_id(session_id):
142
170
  raise ValueError("Invalid session ID")
143
171
 
144
- session = self.session_repository.find_user_session(session_id, user_id)
172
+ session_repository, _ = self._get_repositories(db)
173
+ session = session_repository.find_user_session(session_id, user_id)
145
174
  if not session:
146
175
  log.warning(
147
176
  "Attempted to delete non-existent session %s by user %s",
@@ -158,7 +187,7 @@ class SessionService:
158
187
  )
159
188
  return False
160
189
 
161
- deleted = self.session_repository.delete(session_id, user_id)
190
+ deleted = session_repository.delete(session_id, user_id)
162
191
  if not deleted:
163
192
  return False
164
193
 
@@ -171,6 +200,7 @@ class SessionService:
171
200
 
172
201
  def add_message_to_session(
173
202
  self,
203
+ db: DbSession,
174
204
  session_id: SessionId,
175
205
  user_id: UserId,
176
206
  message: str,
@@ -185,9 +215,11 @@ class SessionService:
185
215
  if not message or message.strip() == "":
186
216
  raise ValueError("Message cannot be empty")
187
217
 
188
- session = self.session_repository.find_user_session(session_id, user_id)
218
+ session_repository, message_repository = self._get_repositories(db)
219
+ session = session_repository.find_user_session(session_id, user_id)
189
220
  if not session:
190
221
  session = self.create_session(
222
+ db=db,
191
223
  user_id=user_id,
192
224
  agent_id=agent_id,
193
225
  session_id=session_id,
@@ -203,10 +235,10 @@ class SessionService:
203
235
  created_time=now_epoch_ms(),
204
236
  )
205
237
 
206
- saved_message = self.message_repository.save(message_entity)
238
+ saved_message = message_repository.save(message_entity)
207
239
 
208
240
  session.mark_activity()
209
- self.session_repository.save(session)
241
+ session_repository.save(session)
210
242
 
211
243
  log.info("Added message to session %s from %s", session_id, sender_name)
212
244
  return saved_message
@@ -257,4 +289,4 @@ class SessionService:
257
289
  "Failed to publish session deletion event to agent %s: %s",
258
290
  agent_id,
259
291
  e,
260
- )
292
+ )