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
@@ -0,0 +1,355 @@
1
+ from fastapi import APIRouter, Body, Depends, HTTPException, status
2
+ from solace_ai_connector.common.log import log
3
+
4
+ from ...application.services.session_service import SessionService
5
+ from ...dependencies import (
6
+ PublishFunc,
7
+ get_namespace,
8
+ get_publish_a2a_func,
9
+ get_session_service,
10
+ )
11
+ from ...shared.auth_utils import get_current_user
12
+ from ..dto.requests.session_requests import (
13
+ DeleteSessionRequest,
14
+ GetSessionHistoryRequest,
15
+ GetSessionRequest,
16
+ GetSessionsRequest,
17
+ UpdateSessionRequest,
18
+ )
19
+ from ..dto.responses.session_responses import (
20
+ MessageResponse,
21
+ SessionListResponse,
22
+ SessionResponse,
23
+ )
24
+
25
+ router = APIRouter()
26
+
27
+
28
+ @router.get("/sessions", response_model=SessionListResponse)
29
+ async def get_all_sessions(
30
+ user: dict = Depends(get_current_user),
31
+ session_service: SessionService = Depends(get_session_service),
32
+ ):
33
+ user_id = user.get("id")
34
+ log.info("Fetching sessions for user_id: %s", user_id)
35
+
36
+ try:
37
+ request_dto = GetSessionsRequest(user_id=user_id)
38
+
39
+ session_domains = session_service.get_user_sessions(
40
+ user_id=request_dto.user_id, pagination=request_dto.pagination
41
+ )
42
+
43
+ session_responses = []
44
+ for domain in session_domains:
45
+ session_response = SessionResponse(
46
+ id=domain.id,
47
+ user_id=domain.user_id,
48
+ name=domain.name,
49
+ agent_id=domain.agent_id,
50
+ status=domain.status,
51
+ created_at=domain.created_at,
52
+ updated_at=domain.updated_at,
53
+ last_activity=domain.last_activity,
54
+ )
55
+ session_responses.append(session_response)
56
+
57
+ return SessionListResponse(
58
+ sessions=session_responses,
59
+ total_count=len(session_responses),
60
+ pagination=request_dto.pagination,
61
+ )
62
+
63
+ except Exception as e:
64
+ log.error("Error fetching sessions for user %s: %s", user_id, e)
65
+ raise HTTPException(
66
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
67
+ detail="Failed to retrieve sessions",
68
+ )
69
+
70
+
71
+ @router.get("/sessions/{session_id}", response_model=SessionResponse)
72
+ async def get_session(
73
+ session_id: str,
74
+ user: dict = Depends(get_current_user),
75
+ session_service: SessionService = Depends(get_session_service),
76
+ ):
77
+ user_id = user.get("id")
78
+ log.info("User %s attempting to fetch session_id: %s", user_id, session_id)
79
+
80
+ try:
81
+ if (
82
+ not session_id
83
+ or session_id.strip() == ""
84
+ or session_id in ["null", "undefined"]
85
+ ):
86
+ raise HTTPException(
87
+ status_code=status.HTTP_404_NOT_FOUND, detail="Session not found."
88
+ )
89
+
90
+ request_dto = GetSessionRequest(session_id=session_id, user_id=user_id)
91
+
92
+ session_domain = session_service.get_session(
93
+ session_id=request_dto.session_id, user_id=request_dto.user_id
94
+ )
95
+
96
+ if not session_domain:
97
+ raise HTTPException(
98
+ status_code=status.HTTP_404_NOT_FOUND, detail="Session not found."
99
+ )
100
+
101
+ log.info("User %s authorized. Fetching session_id: %s", user_id, session_id)
102
+
103
+ return SessionResponse(
104
+ id=session_domain.id,
105
+ user_id=session_domain.user_id,
106
+ name=session_domain.name,
107
+ agent_id=session_domain.agent_id,
108
+ status=session_domain.status,
109
+ created_at=session_domain.created_at,
110
+ updated_at=session_domain.updated_at,
111
+ last_activity=session_domain.last_activity,
112
+ )
113
+
114
+ except HTTPException:
115
+ raise
116
+ except Exception as e:
117
+ log.error(
118
+ "Error fetching session %s for user %s: %s",
119
+ session_id,
120
+ user_id,
121
+ e,
122
+ )
123
+ raise HTTPException(
124
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
125
+ detail="Failed to retrieve session",
126
+ )
127
+
128
+
129
+ @router.get("/sessions/{session_id}/messages")
130
+ async def get_session_history(
131
+ session_id: str,
132
+ user: dict = Depends(get_current_user),
133
+ session_service: SessionService = Depends(get_session_service),
134
+ ):
135
+ user_id = user.get("id")
136
+ log.info(
137
+ "User %s attempting to fetch history for session_id: %s", user_id, session_id
138
+ )
139
+
140
+ try:
141
+ if (
142
+ not session_id
143
+ or session_id.strip() == ""
144
+ or session_id in ["null", "undefined"]
145
+ ):
146
+ raise HTTPException(
147
+ status_code=status.HTTP_404_NOT_FOUND, detail="Session not found."
148
+ )
149
+
150
+ request_dto = GetSessionHistoryRequest(session_id=session_id, user_id=user_id)
151
+
152
+ history_domain = session_service.get_session_history(
153
+ session_id=request_dto.session_id,
154
+ user_id=request_dto.user_id,
155
+ pagination=request_dto.pagination,
156
+ )
157
+
158
+ if not history_domain:
159
+ raise HTTPException(
160
+ status_code=status.HTTP_404_NOT_FOUND, detail="Session not found."
161
+ )
162
+
163
+ log.info(
164
+ "User %s authorized. Fetching history for session_id: %s",
165
+ user_id,
166
+ session_id,
167
+ )
168
+
169
+ message_responses = []
170
+ for message_domain in history_domain.messages:
171
+ message_response = MessageResponse(
172
+ id=message_domain.id,
173
+ session_id=message_domain.session_id,
174
+ message=message_domain.message,
175
+ sender_type=message_domain.sender_type,
176
+ sender_name=message_domain.sender_name,
177
+ message_type=message_domain.message_type,
178
+ timestamp=message_domain.created_at,
179
+ created_at=message_domain.created_at,
180
+ )
181
+ message_responses.append(message_response)
182
+
183
+ return message_responses
184
+
185
+ except HTTPException:
186
+ raise
187
+ except Exception as e:
188
+ log.error(
189
+ "Error fetching history for session %s for user %s: %s",
190
+ session_id,
191
+ user_id,
192
+ e,
193
+ )
194
+ raise HTTPException(
195
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
196
+ detail="Failed to retrieve session history",
197
+ )
198
+
199
+
200
+ @router.patch("/sessions/{session_id}", response_model=SessionResponse)
201
+ async def update_session_name(
202
+ session_id: str,
203
+ name: str = Body(..., embed=True),
204
+ user: dict = Depends(get_current_user),
205
+ session_service: SessionService = Depends(get_session_service),
206
+ ):
207
+ user_id = user.get("id")
208
+ log.info("User %s attempting to update session %s", user_id, session_id)
209
+
210
+ try:
211
+ if (
212
+ not session_id
213
+ or session_id.strip() == ""
214
+ or session_id in ["null", "undefined"]
215
+ ):
216
+ raise HTTPException(
217
+ status_code=status.HTTP_404_NOT_FOUND, detail="Session not found."
218
+ )
219
+
220
+ request_dto = UpdateSessionRequest(
221
+ session_id=session_id, user_id=user_id, name=name
222
+ )
223
+
224
+ updated_domain = session_service.update_session_name(
225
+ session_id=request_dto.session_id,
226
+ user_id=request_dto.user_id,
227
+ name=request_dto.name,
228
+ )
229
+
230
+ if not updated_domain:
231
+ raise HTTPException(
232
+ status_code=status.HTTP_404_NOT_FOUND, detail="Session not found."
233
+ )
234
+
235
+ log.info("Session %s updated successfully", session_id)
236
+
237
+ return SessionResponse(
238
+ id=updated_domain.id,
239
+ user_id=updated_domain.user_id,
240
+ name=updated_domain.name,
241
+ agent_id=updated_domain.agent_id,
242
+ status=updated_domain.status,
243
+ created_at=updated_domain.created_at,
244
+ updated_at=updated_domain.updated_at,
245
+ last_activity=updated_domain.last_activity,
246
+ )
247
+
248
+ except HTTPException:
249
+ raise
250
+ except ValueError as e:
251
+ log.warning("Validation error updating session %s: %s", session_id, e)
252
+ raise HTTPException(
253
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(e)
254
+ )
255
+ except Exception as e:
256
+ log.error(
257
+ "Error updating session %s for user %s: %s",
258
+ session_id,
259
+ user_id,
260
+ e,
261
+ )
262
+ raise HTTPException(
263
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
264
+ detail="Failed to update session",
265
+ )
266
+
267
+
268
+ @router.delete("/sessions/{session_id}", status_code=status.HTTP_204_NO_CONTENT)
269
+ async def delete_session(
270
+ session_id: str,
271
+ user: dict = Depends(get_current_user),
272
+ session_service: SessionService = Depends(get_session_service),
273
+ publish_func: PublishFunc = Depends(get_publish_a2a_func),
274
+ namespace: str = Depends(get_namespace),
275
+ ):
276
+ user_id = user.get("id")
277
+ log.info("User %s attempting to delete session %s", user_id, session_id)
278
+
279
+ try:
280
+ if (
281
+ not session_id
282
+ or session_id.strip() == ""
283
+ or session_id in ["null", "undefined"]
284
+ ):
285
+ raise HTTPException(
286
+ status_code=status.HTTP_404_NOT_FOUND, detail="Session not found."
287
+ )
288
+
289
+ request_dto = DeleteSessionRequest(session_id=session_id, user_id=user_id)
290
+
291
+ # Get session details before deletion to find the agent_id
292
+ session = session_service.get_session(session_id=session_id, user_id=user_id)
293
+ if not session:
294
+ raise HTTPException(
295
+ status_code=status.HTTP_404_NOT_FOUND, detail="Session not found."
296
+ )
297
+
298
+ agent_id = session.agent_id
299
+
300
+ deleted = session_service.delete_session(
301
+ session_id=request_dto.session_id, user_id=request_dto.user_id
302
+ )
303
+
304
+ if not deleted:
305
+ raise HTTPException(
306
+ status_code=status.HTTP_404_NOT_FOUND, detail="Session not found."
307
+ )
308
+
309
+ log.info("Session %s deleted successfully", session_id)
310
+
311
+ if agent_id:
312
+ try:
313
+ from solace_agent_mesh.common.a2a.protocol import (
314
+ get_agent_request_topic,
315
+ )
316
+
317
+ control_message = {
318
+ "control": {
319
+ "action": "delete_session",
320
+ "session_id": session_id,
321
+ "user_id": user_id,
322
+ }
323
+ }
324
+
325
+ target_topic = get_agent_request_topic(namespace, agent_id)
326
+
327
+ log.info(
328
+ "Sending session deletion notification to agent %s for session %s",
329
+ agent_id,
330
+ session_id,
331
+ )
332
+
333
+ publish_func(target_topic, control_message, None)
334
+
335
+ except Exception as e:
336
+ log.warning(
337
+ "Failed to notify agent %s about session %s deletion: %s",
338
+ agent_id,
339
+ session_id,
340
+ e,
341
+ )
342
+
343
+ except HTTPException:
344
+ raise
345
+ except Exception as e:
346
+ log.error(
347
+ "Error deleting session %s for user %s: %s",
348
+ session_id,
349
+ user_id,
350
+ e,
351
+ )
352
+ raise HTTPException(
353
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
354
+ detail="Failed to delete session",
355
+ )
@@ -0,0 +1,279 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from fastapi import APIRouter, Depends, File, Form, HTTPException
4
+ from fastapi import Request as FastAPIRequest
5
+ from fastapi import UploadFile, status
6
+ from solace_ai_connector.common.log import log
7
+
8
+ from a2a.types import InternalError, InvalidRequestError, JSONRPCResponse
9
+ from .....common import a2a
10
+ from ...dependencies import (
11
+ get_sac_component,
12
+ get_session_manager,
13
+ get_user_id,
14
+ )
15
+ from ...session_manager import SessionManager
16
+ from ...shared.enums import SenderType
17
+ from ..dto.requests.task_requests import (
18
+ CancelTaskRequest,
19
+ ProcessedTaskRequest,
20
+ TaskFilesInfo,
21
+ )
22
+
23
+ if TYPE_CHECKING:
24
+ from ...component import WebUIBackendComponent
25
+
26
+ router = APIRouter()
27
+
28
+
29
+ @router.post("/send", response_model=JSONRPCResponse)
30
+ async def send_task_to_agent(
31
+ request: FastAPIRequest,
32
+ agent_name: str = Form(...),
33
+ message: str = Form(...),
34
+ files: list[UploadFile] = File([]),
35
+ session_manager: SessionManager = Depends(get_session_manager),
36
+ component: "WebUIBackendComponent" = Depends(get_sac_component),
37
+ user_id: str = Depends(get_user_id),
38
+ ):
39
+ """
40
+ Submits a non-streaming task request to the specified agent.
41
+ This corresponds to the A2A `tasks/send` method.
42
+ """
43
+ log_prefix = "[POST /api/v1/tasks/send] "
44
+ log.info("%sReceived request for agent: %s", log_prefix, agent_name)
45
+
46
+ try:
47
+ task_files = []
48
+ for file in files:
49
+ if file.filename:
50
+ task_files.append(
51
+ TaskFilesInfo(
52
+ filename=file.filename,
53
+ content_type=file.content_type or "application/octet-stream",
54
+ size=0, # We'd need to read the file to get size
55
+ )
56
+ )
57
+
58
+ request_dto = ProcessedTaskRequest(
59
+ agent_name=agent_name, message=message, user_id=user_id, files=task_files
60
+ )
61
+
62
+ # Continue with existing logic
63
+ client_id = session_manager.get_a2a_client_id(request)
64
+ session_id = session_manager.ensure_a2a_session(request)
65
+
66
+ log.info(
67
+ "%sUsing ClientID: %s, SessionID: %s", log_prefix, client_id, session_id
68
+ )
69
+
70
+ external_event_data = {
71
+ "agent_name": agent_name,
72
+ "message": message,
73
+ "files": files,
74
+ "client_id": client_id,
75
+ "a2a_session_id": session_id,
76
+ }
77
+ (
78
+ target_agent,
79
+ a2a_parts,
80
+ external_request_context,
81
+ ) = await component._translate_external_input(external_event_data)
82
+
83
+ user_identity = {"id": user_id}
84
+ log.info(
85
+ "%sAuthenticated user identity: %s",
86
+ log_prefix,
87
+ user_identity.get("id", "unknown"),
88
+ )
89
+ task_id = await component.submit_a2a_task(
90
+ target_agent_name=target_agent,
91
+ a2a_parts=a2a_parts,
92
+ user_identity=user_identity,
93
+ external_request_context=external_request_context,
94
+ is_streaming=False,
95
+ )
96
+
97
+ log.info(
98
+ "%sNon-streaming task submitted successfully. TaskID: %s",
99
+ log_prefix,
100
+ task_id,
101
+ )
102
+
103
+ return JSONRPCResponse(result={"taskId": task_id})
104
+
105
+ except InvalidRequestError as e:
106
+ log.warning("%sInvalid request: %s", log_prefix, e.message, exc_info=True)
107
+ raise HTTPException(
108
+ status_code=status.HTTP_400_BAD_REQUEST,
109
+ detail=e.model_dump(exclude_none=True),
110
+ )
111
+ except Exception as e:
112
+ log.exception("%sUnexpected error processing task: %s", log_prefix, e)
113
+ error_resp = a2a.create_internal_error(message=f"Failed to process task: {e}")
114
+ raise HTTPException(
115
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
116
+ detail=error_resp.model_dump(exclude_none=True),
117
+ )
118
+
119
+
120
+ @router.post("/subscribe", response_model=JSONRPCResponse)
121
+ async def subscribe_task_from_agent(
122
+ request: FastAPIRequest,
123
+ agent_name: str = Form(...),
124
+ message: str = Form(...),
125
+ files: list[UploadFile] = File([]),
126
+ session_id: str | None = Form(None),
127
+ session_manager: SessionManager = Depends(get_session_manager),
128
+ component: "WebUIBackendComponent" = Depends(get_sac_component),
129
+ user_id: str = Depends(get_user_id),
130
+ ):
131
+ """
132
+ Submits a streaming task request (`tasks/sendSubscribe`) to the specified agent.
133
+ """
134
+ log_prefix = "[POST /api/v1/tasks/subscribe] "
135
+ log.info("%sReceived streaming request for agent: %s", log_prefix, agent_name)
136
+
137
+ try:
138
+ task_files = []
139
+ for file in files:
140
+ if file.filename:
141
+ task_files.append(
142
+ TaskFilesInfo(
143
+ filename=file.filename,
144
+ content_type=file.content_type or "application/octet-stream",
145
+ size=0,
146
+ )
147
+ )
148
+
149
+ request_dto = ProcessedTaskRequest(
150
+ agent_name=agent_name,
151
+ message=message,
152
+ user_id=user_id,
153
+ session_id=session_id,
154
+ files=task_files,
155
+ )
156
+
157
+ client_id = session_manager.get_a2a_client_id(request)
158
+
159
+ # If session_id is not provided by the client, create a new one.
160
+ if not session_id:
161
+ log.info("%sNo session_id provided, creating a new one.", log_prefix)
162
+ session_id = session_manager.start_new_a2a_session(request)
163
+
164
+ # Store message only if persistence is available
165
+ if hasattr(component, "persistence_service") and component.persistence_service:
166
+ try:
167
+ from ...dependencies import get_session_service
168
+ session_service = get_session_service(component)
169
+ message_domain = session_service.add_message_to_session(
170
+ session_id=session_id,
171
+ user_id=user_id,
172
+ message=message,
173
+ sender_type=SenderType.USER,
174
+ sender_name=user_id,
175
+ agent_id=agent_name,
176
+ )
177
+ # Use the actual session ID from the message (may be different if session was recreated)
178
+ if message_domain:
179
+ session_id = message_domain.session_id
180
+ except ValueError as e:
181
+ # Handle business domain validation errors
182
+ log.warning("Validation error in session service: %s", e)
183
+ raise HTTPException(
184
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(e)
185
+ )
186
+ except Exception as e:
187
+ log.error("Failed to store message in session service: %s", e)
188
+ raise HTTPException(
189
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
190
+ detail="Failed to store message",
191
+ )
192
+ else:
193
+ log.debug("%sNo persistence available - skipping message storage", log_prefix)
194
+
195
+ log.info(
196
+ "%sUsing ClientID: %s, SessionID: %s", log_prefix, client_id, session_id
197
+ )
198
+
199
+ external_event_data = {
200
+ "agent_name": agent_name,
201
+ "message": message,
202
+ "files": files,
203
+ "client_id": client_id,
204
+ "a2a_session_id": session_id,
205
+ }
206
+ (
207
+ target_agent,
208
+ a2a_parts,
209
+ external_request_context,
210
+ ) = await component._translate_external_input(external_event_data)
211
+
212
+ user_identity = {"id": user_id}
213
+ log.info(
214
+ "%sAuthenticated user identity: %s",
215
+ log_prefix,
216
+ user_identity.get("id", "unknown"),
217
+ )
218
+ task_id = await component.submit_a2a_task(
219
+ target_agent_name=target_agent,
220
+ a2a_parts=a2a_parts,
221
+ user_identity=user_identity,
222
+ external_request_context=external_request_context,
223
+ is_streaming=True,
224
+ )
225
+
226
+ log.info(
227
+ "%sStreaming task submitted successfully. TaskID: %s", log_prefix, task_id
228
+ )
229
+
230
+ return JSONRPCResponse(result={"taskId": task_id, "sessionId": session_id})
231
+
232
+ except InvalidRequestError as e:
233
+ log.warning("%sInvalid request: %s", log_prefix, e.message, exc_info=True)
234
+ raise HTTPException(
235
+ status_code=status.HTTP_400_BAD_REQUEST,
236
+ detail=e.model_dump(exclude_none=True),
237
+ )
238
+ except HTTPException:
239
+ # Re-raise HTTPExceptions (like 422 validation errors) without modification
240
+ raise
241
+ except Exception as e:
242
+ log.exception("%sUnexpected error processing task: %s", log_prefix, e)
243
+ error_resp = a2a.create_internal_error(message=f"Failed to process task: {e}")
244
+ raise HTTPException(
245
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
246
+ detail=error_resp.model_dump(exclude_none=True),
247
+ )
248
+
249
+
250
+ @router.post("/cancel", response_model=JSONRPCResponse)
251
+ async def cancel_agent_task(
252
+ request: FastAPIRequest,
253
+ task_id: str = Form(...),
254
+ session_manager: SessionManager = Depends(get_session_manager),
255
+ component: "WebUIBackendComponent" = Depends(get_sac_component),
256
+ user_id: str = Depends(get_user_id),
257
+ ):
258
+ """
259
+ Sends a cancellation request for a specific task.
260
+ """
261
+ log_prefix = f"[POST /api/v1/tasks/cancel] TaskID: {task_id} "
262
+ log.info("%sReceived cancellation request.", log_prefix)
263
+
264
+ try:
265
+ request_dto = CancelTaskRequest(task_id=task_id, user_id=user_id)
266
+
267
+ client_id = session_manager.get_a2a_client_id(request)
268
+ await component.cancel_a2a_task(task_id, client_id)
269
+ log.info("%sCancellation request sent successfully.", log_prefix)
270
+ return JSONRPCResponse(
271
+ result={"message": f"Cancellation request sent for task {task_id}"}
272
+ )
273
+ except Exception as e:
274
+ log.exception("%sUnexpected error sending cancellation: %s", log_prefix, e)
275
+ error_resp = a2a.create_internal_error(message="Unexpected server error: %s" % e)
276
+ raise HTTPException(
277
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
278
+ detail=error_resp.model_dump(exclude_none=True),
279
+ )
@@ -0,0 +1,35 @@
1
+ """
2
+ Router for user-related endpoints.
3
+ Maintains backward compatibility with original API format.
4
+ """
5
+
6
+ from typing import Any
7
+
8
+ from fastapi import APIRouter, Depends
9
+ from solace_ai_connector.common.log import log
10
+
11
+ from ...shared.auth_utils import get_current_user
12
+
13
+ router = APIRouter()
14
+
15
+
16
+ @router.get("/me", response_model=dict[str, Any])
17
+ async def get_current_user_endpoint(
18
+ user: dict = Depends(get_current_user),
19
+ ):
20
+ log.info("[GET /api/v1/users/me] Request received.")
21
+
22
+ # Get the user ID with proper priority
23
+ username = (
24
+ user.get("id") # Primary ID from AuthMiddleware
25
+ or user.get("user_id")
26
+ or user.get("username")
27
+ or user.get("email")
28
+ or "anonymous"
29
+ )
30
+
31
+ return {
32
+ "username": username,
33
+ "authenticated": user.get("authenticated", False),
34
+ "auth_method": user.get("auth_method", "none"),
35
+ }
@@ -0,0 +1,10 @@
1
+ """
2
+ Data Transfer Objects (DTOs)
3
+
4
+ Contains request and response DTOs for API contract definition and validation.
5
+ """
6
+
7
+ from . import requests
8
+ from . import responses
9
+
10
+ __all__ = ["requests", "responses"]