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
@@ -8,14 +8,16 @@ from ..shared.auth_utils import get_current_user
8
8
  from ..shared.pagination import DataResponse, PaginatedResponse, PaginationParams
9
9
  from ..shared.response_utils import create_data_response
10
10
  from .dto.requests.session_requests import (
11
- GetSessionHistoryRequest,
12
11
  GetSessionRequest,
13
12
  UpdateSessionRequest,
14
13
  )
15
- from .dto.responses.session_responses import MessageResponse, SessionResponse
14
+ from .dto.requests.task_requests import SaveTaskRequest
15
+ from .dto.responses.session_responses import SessionResponse
16
+ from .dto.responses.task_responses import TaskResponse, TaskListResponse
16
17
 
17
18
  router = APIRouter()
18
19
 
20
+ SESSION_NOT_FOUND_MSG = "Session not found."
19
21
 
20
22
 
21
23
  @router.get("/sessions", response_model=PaginatedResponse[SessionResponse])
@@ -27,7 +29,6 @@ async def get_all_sessions(
27
29
  session_service: SessionService = Depends(get_session_business_service),
28
30
  ):
29
31
  user_id = user.get("id")
30
- log.info(f"User '{user_id}' is listing sessions with pagination (page={page_number}, size={page_size})")
31
32
 
32
33
  try:
33
34
  pagination = PaginationParams(page_number=page_number, page_size=page_size)
@@ -52,7 +53,7 @@ async def get_all_sessions(
52
53
  raise HTTPException(
53
54
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
54
55
  detail="Failed to retrieve sessions",
55
- )
56
+ ) from e
56
57
 
57
58
 
58
59
  @router.get("/sessions/{session_id}", response_model=DataResponse[SessionResponse])
@@ -63,7 +64,6 @@ async def get_session(
63
64
  session_service: SessionService = Depends(get_session_business_service),
64
65
  ):
65
66
  user_id = user.get("id")
66
- log.info("User %s attempting to fetch session_id: %s", user_id, session_id)
67
67
 
68
68
  try:
69
69
  if (
@@ -72,7 +72,7 @@ async def get_session(
72
72
  or session_id in ["null", "undefined"]
73
73
  ):
74
74
  raise HTTPException(
75
- status_code=status.HTTP_404_NOT_FOUND, detail="Session not found."
75
+ status_code=status.HTTP_404_NOT_FOUND, detail=SESSION_NOT_FOUND_MSG
76
76
  )
77
77
 
78
78
  request_dto = GetSessionRequest(session_id=session_id, user_id=user_id)
@@ -83,7 +83,7 @@ async def get_session(
83
83
 
84
84
  if not session_domain:
85
85
  raise HTTPException(
86
- status_code=status.HTTP_404_NOT_FOUND, detail="Session not found."
86
+ status_code=status.HTTP_404_NOT_FOUND, detail=SESSION_NOT_FOUND_MSG
87
87
  )
88
88
 
89
89
  log.info("User %s authorized. Fetching session_id: %s", user_id, session_id)
@@ -111,19 +111,27 @@ async def get_session(
111
111
  raise HTTPException(
112
112
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
113
113
  detail="Failed to retrieve session",
114
- )
114
+ ) from e
115
115
 
116
116
 
117
- @router.get("/sessions/{session_id}/messages")
118
- async def get_session_history(
117
+ @router.post("/sessions/{session_id}/chat-tasks", response_model=TaskResponse)
118
+ async def save_task(
119
119
  session_id: str,
120
+ request: SaveTaskRequest,
120
121
  db: Session = Depends(get_db),
121
122
  user: dict = Depends(get_current_user),
122
123
  session_service: SessionService = Depends(get_session_business_service),
123
124
  ):
125
+ """
126
+ Save a complete task interaction (upsert).
127
+ Creates a new task or updates an existing one.
128
+ """
124
129
  user_id = user.get("id")
125
130
  log.info(
126
- "User %s attempting to fetch history for session_id: %s", user_id, session_id
131
+ "User %s attempting to save task %s for session %s",
132
+ user_id,
133
+ request.task_id,
134
+ session_id,
127
135
  )
128
136
 
129
137
  try:
@@ -133,44 +141,203 @@ async def get_session_history(
133
141
  or session_id in ["null", "undefined"]
134
142
  ):
135
143
  raise HTTPException(
136
- status_code=status.HTTP_404_NOT_FOUND, detail="Session not found."
144
+ status_code=status.HTTP_404_NOT_FOUND, detail=SESSION_NOT_FOUND_MSG
137
145
  )
138
146
 
139
- request_dto = GetSessionHistoryRequest(session_id=session_id, user_id=user_id)
147
+ # Check if task already exists to determine status code
148
+ from ..repository.chat_task_repository import ChatTaskRepository
149
+
150
+ task_repo = ChatTaskRepository(db)
151
+ existing_task = task_repo.find_by_id(request.task_id, user_id)
152
+ is_update = existing_task is not None
140
153
 
141
- history_domain = session_service.get_session_history(
154
+ # Save the task - pass strings directly
155
+ saved_task = session_service.save_task(
142
156
  db=db,
143
- session_id=request_dto.session_id,
144
- user_id=request_dto.user_id,
145
- pagination=request_dto.pagination,
157
+ task_id=request.task_id,
158
+ session_id=session_id,
159
+ user_id=user_id,
160
+ user_message=request.user_message,
161
+ message_bubbles=request.message_bubbles, # Already a string
162
+ task_metadata=request.task_metadata, # Already a string
163
+ )
164
+
165
+ log.info(
166
+ "Task %s %s successfully for session %s",
167
+ request.task_id,
168
+ "updated" if is_update else "created",
169
+ session_id,
170
+ )
171
+
172
+ # Convert to response DTO
173
+ response = TaskResponse(
174
+ task_id=saved_task.id,
175
+ session_id=saved_task.session_id,
176
+ user_message=saved_task.user_message,
177
+ message_bubbles=saved_task.message_bubbles,
178
+ task_metadata=saved_task.task_metadata,
179
+ created_time=saved_task.created_time,
180
+ updated_time=saved_task.updated_time,
146
181
  )
147
182
 
148
- if not history_domain:
183
+ return response
184
+
185
+ except ValueError as e:
186
+ log.warning("Validation error saving task %s: %s", request.task_id, e)
187
+ raise HTTPException(
188
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(e)
189
+ ) from e
190
+ except HTTPException:
191
+ raise
192
+ except Exception as e:
193
+ log.error(
194
+ "Error saving task %s for session %s for user %s: %s",
195
+ request.task_id,
196
+ session_id,
197
+ user_id,
198
+ e,
199
+ )
200
+ raise HTTPException(
201
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
202
+ detail="Failed to save task",
203
+ ) from e
204
+
205
+
206
+ @router.get("/sessions/{session_id}/chat-tasks", response_model=TaskListResponse)
207
+ async def get_session_tasks(
208
+ session_id: str,
209
+ db: Session = Depends(get_db),
210
+ user: dict = Depends(get_current_user),
211
+ session_service: SessionService = Depends(get_session_business_service),
212
+ ):
213
+ """
214
+ Get all tasks for a session.
215
+ Returns tasks in chronological order.
216
+ """
217
+ user_id = user.get("id")
218
+ log.info(
219
+ "User %s attempting to fetch tasks for session_id: %s", user_id, session_id
220
+ )
221
+
222
+ try:
223
+ if (
224
+ not session_id
225
+ or session_id.strip() == ""
226
+ or session_id in ["null", "undefined"]
227
+ ):
149
228
  raise HTTPException(
150
- status_code=status.HTTP_404_NOT_FOUND, detail="Session not found."
229
+ status_code=status.HTTP_404_NOT_FOUND, detail=SESSION_NOT_FOUND_MSG
151
230
  )
152
231
 
232
+ # Get tasks from service
233
+ tasks = session_service.get_session_tasks(
234
+ db=db, session_id=session_id, user_id=user_id
235
+ )
236
+
153
237
  log.info(
154
- "User %s authorized. Fetching history for session_id: %s",
238
+ "User %s authorized. Fetched %d tasks for session_id: %s",
155
239
  user_id,
240
+ len(tasks),
241
+ session_id,
242
+ )
243
+
244
+ # Convert to response DTOs
245
+ task_responses = []
246
+ for task in tasks:
247
+ task_response = TaskResponse(
248
+ task_id=task.id,
249
+ session_id=task.session_id,
250
+ user_message=task.user_message,
251
+ message_bubbles=task.message_bubbles,
252
+ task_metadata=task.task_metadata,
253
+ created_time=task.created_time,
254
+ updated_time=task.updated_time,
255
+ )
256
+ task_responses.append(task_response)
257
+
258
+ return TaskListResponse(tasks=task_responses)
259
+
260
+ except ValueError as e:
261
+ log.warning("Validation error fetching tasks for session %s: %s", session_id, e)
262
+ raise HTTPException(
263
+ status_code=status.HTTP_404_NOT_FOUND, detail=SESSION_NOT_FOUND_MSG
264
+ ) from e
265
+ except HTTPException:
266
+ raise
267
+ except Exception as e:
268
+ log.error(
269
+ "Error fetching tasks for session %s for user %s: %s",
156
270
  session_id,
271
+ user_id,
272
+ e,
157
273
  )
274
+ raise HTTPException(
275
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
276
+ detail="Failed to retrieve session tasks",
277
+ ) from e
278
+
279
+
280
+ @router.get("/sessions/{session_id}/messages")
281
+ async def get_session_history(
282
+ session_id: str,
283
+ db: Session = Depends(get_db),
284
+ user: dict = Depends(get_current_user),
285
+ session_service: SessionService = Depends(get_session_business_service),
286
+ ):
287
+ """
288
+ Get session message history.
289
+ Loads from chat_tasks and flattens message_bubbles for backward compatibility.
290
+ """
291
+ user_id = user.get("id")
292
+ log.info(
293
+ "User %s attempting to fetch history for session_id: %s", user_id, session_id
294
+ )
158
295
 
159
- message_responses = []
160
- for message_domain in history_domain.messages:
161
- message_response = MessageResponse(
162
- id=message_domain.id,
163
- session_id=message_domain.session_id,
164
- message=message_domain.message,
165
- sender_type=message_domain.sender_type,
166
- sender_name=message_domain.sender_name,
167
- message_type=message_domain.message_type,
168
- created_time=message_domain.created_time,
296
+ try:
297
+ if (
298
+ not session_id
299
+ or session_id.strip() == ""
300
+ or session_id in ["null", "undefined"]
301
+ ):
302
+ raise HTTPException(
303
+ status_code=status.HTTP_404_NOT_FOUND, detail=SESSION_NOT_FOUND_MSG
169
304
  )
170
- message_responses.append(message_response)
171
305
 
172
- return message_responses
306
+ # Use task-based message retrieval (returns list of dicts)
307
+ messages = session_service.get_session_messages_from_tasks(
308
+ db=db, session_id=session_id, user_id=user_id
309
+ )
310
+
311
+ log.info(
312
+ "User %s authorized. Fetched %d messages for session_id: %s",
313
+ user_id,
314
+ len(messages),
315
+ session_id,
316
+ )
317
+
318
+ # Convert snake_case to camelCase for backwards compatibility
319
+ camel_case_messages = []
320
+ for msg in messages:
321
+ camel_msg = {
322
+ "id": msg["id"],
323
+ "sessionId": msg["session_id"],
324
+ "message": msg["message"],
325
+ "senderType": msg["sender_type"],
326
+ "senderName": msg["sender_name"],
327
+ "messageType": msg["message_type"],
328
+ "createdTime": msg["created_time"],
329
+ }
330
+ camel_case_messages.append(camel_msg)
331
+
332
+ return camel_case_messages
173
333
 
334
+ except ValueError as e:
335
+ log.warning(
336
+ "Validation error fetching history for session %s: %s", session_id, e
337
+ )
338
+ raise HTTPException(
339
+ status_code=status.HTTP_404_NOT_FOUND, detail=SESSION_NOT_FOUND_MSG
340
+ ) from e
174
341
  except HTTPException:
175
342
  raise
176
343
  except Exception as e:
@@ -183,7 +350,7 @@ async def get_session_history(
183
350
  raise HTTPException(
184
351
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
185
352
  detail="Failed to retrieve session history",
186
- )
353
+ ) from e
187
354
 
188
355
 
189
356
  @router.patch("/sessions/{session_id}", response_model=SessionResponse)
@@ -204,7 +371,7 @@ async def update_session_name(
204
371
  or session_id in ["null", "undefined"]
205
372
  ):
206
373
  raise HTTPException(
207
- status_code=status.HTTP_404_NOT_FOUND, detail="Session not found."
374
+ status_code=status.HTTP_404_NOT_FOUND, detail=SESSION_NOT_FOUND_MSG
208
375
  )
209
376
 
210
377
  request_dto = UpdateSessionRequest(
@@ -220,7 +387,7 @@ async def update_session_name(
220
387
 
221
388
  if not updated_domain:
222
389
  raise HTTPException(
223
- status_code=status.HTTP_404_NOT_FOUND, detail="Session not found."
390
+ status_code=status.HTTP_404_NOT_FOUND, detail=SESSION_NOT_FOUND_MSG
224
391
  )
225
392
 
226
393
  log.info("Session %s updated successfully", session_id)
@@ -240,7 +407,7 @@ async def update_session_name(
240
407
  log.warning("Validation error updating session %s: %s", session_id, e)
241
408
  raise HTTPException(
242
409
  status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(e)
243
- )
410
+ ) from e
244
411
  except Exception as e:
245
412
  log.error(
246
413
  "Error updating session %s for user %s: %s",
@@ -251,7 +418,7 @@ async def update_session_name(
251
418
  raise HTTPException(
252
419
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
253
420
  detail="Failed to update session",
254
- )
421
+ ) from e
255
422
 
256
423
 
257
424
  @router.delete("/sessions/{session_id}", status_code=status.HTTP_204_NO_CONTENT)
@@ -265,20 +432,33 @@ async def delete_session(
265
432
  log.info("User %s attempting to delete session %s", user_id, session_id)
266
433
 
267
434
  try:
435
+ if (
436
+ not session_id
437
+ or session_id.strip() == ""
438
+ or session_id in ["null", "undefined"]
439
+ ):
440
+ raise HTTPException(
441
+ status_code=status.HTTP_404_NOT_FOUND, detail=SESSION_NOT_FOUND_MSG
442
+ )
443
+
268
444
  deleted = session_service.delete_session_with_notifications(
269
445
  db=db, session_id=session_id, user_id=user_id
270
446
  )
271
447
 
272
448
  if not deleted:
273
449
  raise HTTPException(
274
- status_code=status.HTTP_404_NOT_FOUND, detail="Session not found."
450
+ status_code=status.HTTP_404_NOT_FOUND, detail=SESSION_NOT_FOUND_MSG
275
451
  )
276
452
 
277
453
  log.info("Session %s deleted successfully", session_id)
278
454
 
455
+ except HTTPException:
456
+ raise
279
457
  except ValueError as e:
280
458
  log.warning("Validation error deleting session %s: %s", session_id, e)
281
- raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
459
+ raise HTTPException(
460
+ status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)
461
+ ) from e
282
462
  except Exception as e:
283
463
  log.error(
284
464
  "Error deleting session %s for user %s: %s",
@@ -289,4 +469,4 @@ async def delete_session(
289
469
  raise HTTPException(
290
470
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
291
471
  detail="Failed to delete session",
292
- )
472
+ ) from e
@@ -2,20 +2,28 @@
2
2
  API Router for submitting and managing tasks to agents.
3
3
  """
4
4
 
5
+ import yaml
6
+ from datetime import datetime
5
7
  from fastapi import (
6
8
  APIRouter,
7
9
  Depends,
8
10
  HTTPException,
9
11
  Request as FastAPIRequest,
12
+ Response,
10
13
  status,
11
14
  )
12
- from typing import Union
15
+ from fastapi.exceptions import RequestValidationError
16
+ from typing import List, Optional, Union
13
17
 
14
18
  from solace_ai_connector.common.log import log
15
19
 
16
20
  from ....gateway.http_sse.session_manager import SessionManager
17
21
  from ....gateway.http_sse.services.task_service import TaskService
18
22
  from ....gateway.http_sse.services.session_service import SessionService
23
+ from ....gateway.http_sse.repository.interfaces import ITaskRepository
24
+ from ....gateway.http_sse.repository.entities import Task
25
+ from ....gateway.http_sse.shared.types import PaginationParams, UserId
26
+ from ..utils.stim_utils import create_stim_from_task_data
19
27
 
20
28
  from a2a.types import (
21
29
  CancelTaskRequest,
@@ -31,7 +39,12 @@ from ....gateway.http_sse.dependencies import (
31
39
  get_sac_component,
32
40
  get_task_service,
33
41
  get_session_business_service,
42
+ get_task_repository,
43
+ get_user_id,
44
+ get_user_config,
45
+ get_session_business_service,
34
46
  )
47
+ from ....gateway.http_sse.services.session_service import SessionService
35
48
 
36
49
  from typing import TYPE_CHECKING
37
50
 
@@ -58,7 +71,7 @@ async def _submit_task(
58
71
 
59
72
  if not agent_name:
60
73
  raise HTTPException(
61
- status_code=status.HTTP_400_BAD_REQUEST,
74
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
62
75
  detail="Missing 'agent_name' in request payload message metadata.",
63
76
  )
64
77
 
@@ -120,10 +133,14 @@ async def _submit_task(
120
133
  session_id=session_id,
121
134
  )
122
135
  db.commit()
123
- log.info("%sCreated session in database: %s", log_prefix, session_id)
136
+ log.info(
137
+ "%sCreated session in database: %s", log_prefix, session_id
138
+ )
124
139
  except Exception as e:
125
140
  db.rollback()
126
- log.warning("%sFailed to create session in database: %s", log_prefix, e)
141
+ log.warning(
142
+ "%sFailed to create session in database: %s", log_prefix, e
143
+ )
127
144
  finally:
128
145
  db.close()
129
146
 
@@ -131,43 +148,6 @@ async def _submit_task(
131
148
  "%sUsing ClientID: %s, SessionID: %s", log_prefix, client_id, session_id
132
149
  )
133
150
 
134
- # Store message in persistence layer if available
135
- if is_streaming and SessionLocal is not None and session_service is not None:
136
- db = SessionLocal()
137
- try:
138
- from ....gateway.http_sse.shared.enums import SenderType
139
-
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()
158
- except Exception as e:
159
- db.rollback()
160
- log.error(
161
- "%sFailed to store message in session service: %s", log_prefix, e
162
- )
163
- finally:
164
- db.close()
165
- else:
166
- log.debug(
167
- "%sNo persistence available or non-streaming - skipping message storage",
168
- log_prefix,
169
- )
170
-
171
151
  # Use the helper to get the unwrapped parts from the incoming message.
172
152
  a2a_parts = a2a.get_parts_from_message(payload.params.message)
173
153
 
@@ -222,6 +202,146 @@ async def _submit_task(
222
202
  )
223
203
 
224
204
 
205
+ @router.get("/tasks", response_model=List[Task], tags=["Tasks"])
206
+ async def search_tasks(
207
+ request: FastAPIRequest,
208
+ start_date: Optional[str] = None,
209
+ end_date: Optional[str] = None,
210
+ search: Optional[str] = None,
211
+ page: int = 1,
212
+ page_size: int = 20,
213
+ query_user_id: Optional[str] = None,
214
+ user_id: UserId = Depends(get_user_id),
215
+ user_config: dict = Depends(get_user_config),
216
+ repo: ITaskRepository = Depends(get_task_repository),
217
+ ):
218
+ """
219
+ Lists and searches for historical tasks.
220
+ - Regular users can only search their own tasks.
221
+ - Users with the 'tasks:read:all' scope can search for any user's tasks by providing `query_user_id`.
222
+ """
223
+ log_prefix = f"[GET /api/v1/tasks] "
224
+ log.info("%sRequest from user %s", log_prefix, user_id)
225
+
226
+ target_user_id = user_id
227
+ can_query_all = user_config.get("scopes", {}).get("tasks:read:all", False)
228
+
229
+ if query_user_id:
230
+ if can_query_all:
231
+ target_user_id = query_user_id
232
+ log.info(
233
+ "%sAdmin user %s is querying for user %s",
234
+ log_prefix,
235
+ user_id,
236
+ target_user_id,
237
+ )
238
+ else:
239
+ raise HTTPException(
240
+ status_code=status.HTTP_403_FORBIDDEN,
241
+ detail="You do not have permission to query for other users' tasks.",
242
+ )
243
+ elif can_query_all:
244
+ target_user_id = "*"
245
+ log.info("%sAdmin user %s is querying for all users.", log_prefix, user_id)
246
+
247
+ start_time_ms = None
248
+ if start_date:
249
+ try:
250
+ start_time_ms = int(datetime.fromisoformat(start_date).timestamp() * 1000)
251
+ except ValueError:
252
+ raise HTTPException(
253
+ status_code=status.HTTP_400_BAD_REQUEST,
254
+ detail="Invalid start_date format. Use ISO 8601 format.",
255
+ )
256
+
257
+ end_time_ms = None
258
+ if end_date:
259
+ try:
260
+ end_time_ms = int(datetime.fromisoformat(end_date).timestamp() * 1000)
261
+ except ValueError:
262
+ raise HTTPException(
263
+ status_code=status.HTTP_400_BAD_REQUEST,
264
+ detail="Invalid end_date format. Use ISO 8601 format.",
265
+ )
266
+
267
+ pagination = PaginationParams(page=page, page_size=page_size)
268
+
269
+ try:
270
+ tasks = repo.search(
271
+ user_id=target_user_id,
272
+ start_date=start_time_ms,
273
+ end_date=end_time_ms,
274
+ search_query=search,
275
+ pagination=pagination,
276
+ )
277
+ return tasks
278
+ except Exception as e:
279
+ log.exception("%sError searching for tasks: %s", log_prefix, e)
280
+ raise HTTPException(
281
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
282
+ detail="An error occurred while searching for tasks.",
283
+ )
284
+
285
+
286
+ @router.get("/tasks/{task_id}", tags=["Tasks"])
287
+ async def get_task_as_stim_file(
288
+ task_id: str,
289
+ request: FastAPIRequest,
290
+ user_id: UserId = Depends(get_user_id),
291
+ user_config: dict = Depends(get_user_config),
292
+ repo: ITaskRepository = Depends(get_task_repository),
293
+ ):
294
+ """
295
+ Retrieves the complete event history for a single task and returns it as a `.stim` file.
296
+ """
297
+ log_prefix = f"[GET /api/v1/tasks/{task_id}] "
298
+ log.info("%sRequest from user %s", log_prefix, user_id)
299
+
300
+ try:
301
+ result = repo.find_by_id_with_events(task_id)
302
+ if not result:
303
+ raise HTTPException(
304
+ status_code=status.HTTP_404_NOT_FOUND,
305
+ detail=f"Task with ID '{task_id}' not found.",
306
+ )
307
+
308
+ task, events = result
309
+
310
+ can_read_all = user_config.get("scopes", {}).get("tasks:read:all", False)
311
+ if task.user_id != user_id and not can_read_all:
312
+ raise HTTPException(
313
+ status_code=status.HTTP_403_FORBIDDEN,
314
+ detail="You do not have permission to view this task.",
315
+ )
316
+
317
+ # Format into .stim structure
318
+ stim_data = create_stim_from_task_data(task, events)
319
+
320
+ yaml_content = yaml.dump(
321
+ stim_data,
322
+ sort_keys=False,
323
+ allow_unicode=True,
324
+ indent=2,
325
+ default_flow_style=False,
326
+ )
327
+
328
+ return Response(
329
+ content=yaml_content,
330
+ media_type="application/x-yaml",
331
+ headers={"Content-Disposition": f'attachment; filename="{task_id}.stim"'},
332
+ )
333
+
334
+ except HTTPException:
335
+ # Re-raise HTTPExceptions (404, 403, etc.) without modification
336
+ raise
337
+ except Exception as e:
338
+ log.exception("%sError retrieving task: %s", log_prefix, e)
339
+ raise HTTPException(
340
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
341
+ detail="An error occurred while retrieving the task.",
342
+ )
343
+
344
+
225
345
  @router.post("/message:send", response_model=SendMessageSuccessResponse)
226
346
  async def send_task_to_agent(
227
347
  request: FastAPIRequest,
@@ -278,6 +398,7 @@ async def cancel_agent_task(
278
398
  """
279
399
  Sends a cancellation request for a specific task to the specified agent.
280
400
  Returns 202 Accepted, as cancellation is asynchronous.
401
+ Returns 404 if the task context is not found.
281
402
  """
282
403
  log_prefix = f"[POST /api/v1/tasks/{taskId}:cancel] "
283
404
  log.info("%sReceived cancellation request.", log_prefix)
@@ -290,6 +411,11 @@ async def cancel_agent_task(
290
411
 
291
412
  context = component.task_context_manager.get_context(taskId)
292
413
  if not context:
414
+ log.warning(
415
+ "%sNo active task context found for task ID: %s",
416
+ log_prefix,
417
+ taskId,
418
+ )
293
419
  raise HTTPException(
294
420
  status_code=status.HTTP_404_NOT_FOUND,
295
421
  detail=f"No active task context found for task ID: {taskId}",
@@ -328,4 +454,4 @@ async def cancel_agent_task(
328
454
  raise HTTPException(
329
455
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
330
456
  detail=error_resp.model_dump(exclude_none=True),
331
- )
457
+ )