solace-agent-mesh 1.4.6__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.
- solace_agent_mesh/agent/adk/runner.py +24 -8
- solace_agent_mesh/agent/sac/component.py +13 -1
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/{0e682baa.da822665.js → 0e682baa.d054e1d8.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/166ab619.e27886d9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{1c6e87d2.43771adc.js → 1c6e87d2.e056b7e0.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/453a82a6.3c6bb61d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/483cef9a.4736f2d8.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{4c2787c2.fc6804f2.js → 4c2787c2.c1290a40.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/{5b4258a4.dff11eca.js → 5b4258a4.fdfd2325.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/{75384d09.abdf9cf9.js → 75384d09.c19e8b51.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/85387663.be2bc838.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/945fb41e.16e00776.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a12a4955.25fbed32.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a3a92b25.af35e313.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ae0e903d.5fe5203f.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/bac0be12.17de4316.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cee5d587.47904f5e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/d6a81ee7.829198f1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.ed8dd236.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f897a61a.126663fe.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{fbfa3e75.aca209c9.js → fbfa3e75.e144b16c.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/main.86924c42.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/runtime~main.0d2ff2b6.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/Enterprise/rbac-setup-guilde/index.html +201 -0
- solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +4 -4
- 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
- solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +7 -7
- solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +6 -6
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +7 -7
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +12 -13
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/litellm_models/index.html +49 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +6 -6
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +6 -6
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +8 -8
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +7 -7
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +6 -6
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +7 -7
- solace_agent_mesh/assets/docs/lunr-index-1759246102819.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1759246102819.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -1
- solace_agent_mesh/assets/docs/sitemap.xml +1 -1
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +6 -3
- solace_agent_mesh/cli/commands/init_cmd/__init__.py +3 -3
- solace_agent_mesh/cli/commands/init_cmd/broker_step.py +1 -1
- solace_agent_mesh/cli/commands/init_cmd/env_step.py +1 -1
- solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +4 -4
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +12 -1
- solace_agent_mesh/cli/commands/plugin_cmd/install_cmd.py +5 -5
- solace_agent_mesh/client/webui/frontend/static/assets/main-B0PHV3hm.js +339 -0
- solace_agent_mesh/client/webui/frontend/static/index.html +1 -1
- solace_agent_mesh/config_portal/backend/common.py +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/{_index-bFMKlzKf.js → _index-BNuqpWDc.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-89db7c30.js → manifest-44d62be6.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
- solace_agent_mesh/gateway/http_sse/alembic/versions/20250916_f6e7d8c9b0a1_convert_timestamps_to_epoch_and_align_columns.py +112 -42
- solace_agent_mesh/gateway/http_sse/app.py +0 -28
- solace_agent_mesh/gateway/http_sse/component.py +21 -15
- solace_agent_mesh/gateway/http_sse/dependencies.py +84 -88
- solace_agent_mesh/gateway/http_sse/main.py +37 -15
- solace_agent_mesh/gateway/http_sse/repository/entities/message.py +3 -1
- solace_agent_mesh/gateway/http_sse/repository/entities/session.py +3 -1
- solace_agent_mesh/gateway/http_sse/repository/interfaces.py +5 -0
- solace_agent_mesh/gateway/http_sse/repository/message_repository.py +25 -23
- solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +12 -4
- solace_agent_mesh/gateway/http_sse/repository/models/message_model.py +19 -1
- solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +19 -1
- solace_agent_mesh/gateway/http_sse/repository/session_repository.py +46 -42
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +199 -59
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/__init__.py +1 -6
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +3 -17
- solace_agent_mesh/gateway/http_sse/routers/people.py +4 -37
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +33 -68
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +54 -28
- solace_agent_mesh/gateway/http_sse/services/session_service.py +60 -28
- solace_agent_mesh/gateway/http_sse/shared/__init__.py +122 -1
- solace_agent_mesh/gateway/http_sse/shared/base_repository.py +278 -0
- solace_agent_mesh/gateway/http_sse/shared/database_exceptions.py +274 -0
- solace_agent_mesh/gateway/http_sse/shared/database_helpers.py +43 -0
- solace_agent_mesh/gateway/http_sse/shared/error_dto.py +107 -0
- solace_agent_mesh/gateway/http_sse/shared/exception_handlers.py +192 -0
- solace_agent_mesh/gateway/http_sse/shared/exceptions.py +192 -0
- solace_agent_mesh/gateway/http_sse/shared/pagination.py +138 -0
- solace_agent_mesh/gateway/http_sse/shared/response_utils.py +134 -0
- solace_agent_mesh/gateway/http_sse/shared/utils.py +22 -0
- solace_agent_mesh/templates/plugin_agent_config_template.yaml +1 -1
- solace_agent_mesh/templates/plugin_custom_config_template.yaml +1 -1
- solace_agent_mesh/templates/plugin_gateway_config_template.yaml +1 -1
- solace_agent_mesh/templates/shared_config.yaml +1 -1
- {solace_agent_mesh-1.4.6.dist-info → solace_agent_mesh-1.4.8.dist-info}/METADATA +34 -35
- {solace_agent_mesh-1.4.6.dist-info → solace_agent_mesh-1.4.8.dist-info}/RECORD +123 -110
- solace_agent_mesh/assets/docs/assets/js/166ab619.e8f3a7c7.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/483cef9a.8d318c2f.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/85387663.6bf41934.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/945fb41e.abf2be91.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/a3a92b25.1d029b81.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/ae0e903d.c786e887.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/bac0be12.27ee2c26.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/beecea0d.8bbd852c.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/cee5d587.f1e1ca86.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/f284c35a.2b2f5048.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/f897a61a.bc634a3e.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/main.72d74e33.js +0 -2
- solace_agent_mesh/assets/docs/assets/js/runtime~main.3dcfaf51.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1758893005563.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1758893005563.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-BKIoiLSu.js +0 -339
- /solace_agent_mesh/assets/docs/assets/js/{main.72d74e33.js.LICENSE.txt → main.86924c42.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.4.6.dist-info → solace_agent_mesh-1.4.8.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.4.6.dist-info → solace_agent_mesh-1.4.8.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.4.6.dist-info → solace_agent_mesh-1.4.8.dist-info}/licenses/LICENSE +0 -0
|
@@ -104,22 +104,38 @@ def _upgrade_sqlite(current_time_ms: int) -> None:
|
|
|
104
104
|
def _upgrade_standard_sql(current_time_ms: int) -> None:
|
|
105
105
|
"""Handle PostgreSQL/MySQL upgrade using ALTER COLUMN (standard SQL approach)."""
|
|
106
106
|
|
|
107
|
+
bind = op.get_bind()
|
|
108
|
+
|
|
107
109
|
# For sessions table
|
|
108
110
|
op.add_column("sessions", sa.Column("created_time", sa.BigInteger(), nullable=True))
|
|
109
111
|
op.add_column("sessions", sa.Column("updated_time", sa.BigInteger(), nullable=True))
|
|
110
112
|
|
|
111
|
-
# Convert timestamps using
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
113
|
+
# Convert timestamps using database-appropriate functions
|
|
114
|
+
if bind.dialect.name == 'postgresql':
|
|
115
|
+
op.execute("""
|
|
116
|
+
UPDATE sessions
|
|
117
|
+
SET created_time = CAST(EXTRACT(EPOCH FROM created_at) * 1000 AS BIGINT)
|
|
118
|
+
WHERE created_at IS NOT NULL
|
|
119
|
+
""")
|
|
120
|
+
|
|
121
|
+
op.execute("""
|
|
122
|
+
UPDATE sessions
|
|
123
|
+
SET updated_time = CAST(EXTRACT(EPOCH FROM updated_at) * 1000 AS BIGINT)
|
|
124
|
+
WHERE updated_at IS NOT NULL
|
|
125
|
+
""")
|
|
126
|
+
else:
|
|
127
|
+
# MySQL and other databases use UNIX_TIMESTAMP
|
|
128
|
+
op.execute("""
|
|
129
|
+
UPDATE sessions
|
|
130
|
+
SET created_time = CAST(UNIX_TIMESTAMP(created_at) * 1000 AS UNSIGNED)
|
|
131
|
+
WHERE created_at IS NOT NULL
|
|
132
|
+
""")
|
|
133
|
+
|
|
134
|
+
op.execute("""
|
|
135
|
+
UPDATE sessions
|
|
136
|
+
SET updated_time = CAST(UNIX_TIMESTAMP(updated_at) * 1000 AS UNSIGNED)
|
|
137
|
+
WHERE updated_at IS NOT NULL
|
|
138
|
+
""")
|
|
123
139
|
|
|
124
140
|
# Set current epoch ms for null values
|
|
125
141
|
op.execute(f"""
|
|
@@ -145,11 +161,19 @@ def _upgrade_standard_sql(current_time_ms: int) -> None:
|
|
|
145
161
|
# For chat_messages table
|
|
146
162
|
op.add_column("chat_messages", sa.Column("created_time", sa.BigInteger(), nullable=True))
|
|
147
163
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
164
|
+
if bind.dialect.name == 'postgresql':
|
|
165
|
+
op.execute("""
|
|
166
|
+
UPDATE chat_messages
|
|
167
|
+
SET created_time = CAST(EXTRACT(EPOCH FROM created_at) * 1000 AS BIGINT)
|
|
168
|
+
WHERE created_at IS NOT NULL
|
|
169
|
+
""")
|
|
170
|
+
else:
|
|
171
|
+
# MySQL and other databases use UNIX_TIMESTAMP
|
|
172
|
+
op.execute("""
|
|
173
|
+
UPDATE chat_messages
|
|
174
|
+
SET created_time = CAST(UNIX_TIMESTAMP(created_at) * 1000 AS UNSIGNED)
|
|
175
|
+
WHERE created_at IS NOT NULL
|
|
176
|
+
""")
|
|
153
177
|
|
|
154
178
|
op.execute(f"""
|
|
155
179
|
UPDATE chat_messages
|
|
@@ -199,18 +223,41 @@ def _create_updated_indexes() -> None:
|
|
|
199
223
|
|
|
200
224
|
def _create_indexes_safe(index_name: str, table_name: str, columns: list) -> None:
|
|
201
225
|
"""Create index only if it doesn't exist."""
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
226
|
+
bind = op.get_bind()
|
|
227
|
+
inspector = sa.inspect(bind)
|
|
228
|
+
|
|
229
|
+
if bind.dialect.name == 'postgresql':
|
|
230
|
+
columns_str = ', '.join(columns)
|
|
231
|
+
bind.execute(sa.text(f"CREATE INDEX IF NOT EXISTS {index_name} ON {table_name} ({columns_str})"))
|
|
232
|
+
elif bind.dialect.name == 'sqlite':
|
|
233
|
+
try:
|
|
234
|
+
op.create_index(index_name, table_name, columns)
|
|
235
|
+
except Exception:
|
|
236
|
+
pass
|
|
237
|
+
else:
|
|
238
|
+
existing_indexes = inspector.get_indexes(table_name)
|
|
239
|
+
index_exists = any(idx['name'] == index_name for idx in existing_indexes)
|
|
240
|
+
if not index_exists:
|
|
241
|
+
op.create_index(index_name, table_name, columns)
|
|
206
242
|
|
|
207
243
|
|
|
208
244
|
def _drop_index_safe(index_name: str, table_name: str) -> None:
|
|
209
245
|
"""Drop index only if it exists."""
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
246
|
+
bind = op.get_bind()
|
|
247
|
+
inspector = sa.inspect(bind)
|
|
248
|
+
|
|
249
|
+
if bind.dialect.name == 'postgresql':
|
|
250
|
+
bind.execute(sa.text(f"DROP INDEX IF EXISTS {index_name}"))
|
|
251
|
+
elif bind.dialect.name == 'sqlite':
|
|
252
|
+
try:
|
|
253
|
+
op.drop_index(index_name, table_name=table_name)
|
|
254
|
+
except Exception:
|
|
255
|
+
pass
|
|
256
|
+
else:
|
|
257
|
+
existing_indexes = inspector.get_indexes(table_name)
|
|
258
|
+
index_exists = any(idx['name'] == index_name for idx in existing_indexes)
|
|
259
|
+
if index_exists:
|
|
260
|
+
op.drop_index(index_name, table_name=table_name)
|
|
214
261
|
|
|
215
262
|
|
|
216
263
|
def downgrade() -> None:
|
|
@@ -297,6 +344,7 @@ def _downgrade_sqlite() -> None:
|
|
|
297
344
|
|
|
298
345
|
def _downgrade_standard_sql() -> None:
|
|
299
346
|
"""Handle PostgreSQL/MySQL downgrade using ALTER COLUMN."""
|
|
347
|
+
bind = op.get_bind()
|
|
300
348
|
|
|
301
349
|
# Drop indexes on new columns
|
|
302
350
|
_drop_index_safe("ix_chat_messages_session_id_created_time", "chat_messages")
|
|
@@ -308,18 +356,32 @@ def _downgrade_standard_sql() -> None:
|
|
|
308
356
|
op.add_column("sessions", sa.Column("created_at", sa.DateTime(), nullable=True))
|
|
309
357
|
op.add_column("sessions", sa.Column("updated_at", sa.DateTime(), nullable=True))
|
|
310
358
|
|
|
311
|
-
# Convert epoch milliseconds back to datetime
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
359
|
+
# Convert epoch milliseconds back to datetime based on database type
|
|
360
|
+
if bind.dialect.name == 'postgresql':
|
|
361
|
+
op.execute("""
|
|
362
|
+
UPDATE sessions
|
|
363
|
+
SET created_at = to_timestamp(created_time / 1000.0)
|
|
364
|
+
WHERE created_time IS NOT NULL
|
|
365
|
+
""")
|
|
366
|
+
|
|
367
|
+
op.execute("""
|
|
368
|
+
UPDATE sessions
|
|
369
|
+
SET updated_at = to_timestamp(updated_time / 1000.0)
|
|
370
|
+
WHERE updated_time IS NOT NULL
|
|
371
|
+
""")
|
|
372
|
+
else:
|
|
373
|
+
# MySQL and other databases
|
|
374
|
+
op.execute("""
|
|
375
|
+
UPDATE sessions
|
|
376
|
+
SET created_at = FROM_UNIXTIME(created_time / 1000.0)
|
|
377
|
+
WHERE created_time IS NOT NULL
|
|
378
|
+
""")
|
|
379
|
+
|
|
380
|
+
op.execute("""
|
|
381
|
+
UPDATE sessions
|
|
382
|
+
SET updated_at = FROM_UNIXTIME(updated_time / 1000.0)
|
|
383
|
+
WHERE updated_time IS NOT NULL
|
|
384
|
+
""")
|
|
323
385
|
|
|
324
386
|
op.drop_column("sessions", "created_time")
|
|
325
387
|
op.drop_column("sessions", "updated_time")
|
|
@@ -327,11 +389,19 @@ def _downgrade_standard_sql() -> None:
|
|
|
327
389
|
# For chat_messages table: convert back to datetime column
|
|
328
390
|
op.add_column("chat_messages", sa.Column("created_at", sa.DateTime(), nullable=True))
|
|
329
391
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
392
|
+
if bind.dialect.name == 'postgresql':
|
|
393
|
+
op.execute("""
|
|
394
|
+
UPDATE chat_messages
|
|
395
|
+
SET created_at = to_timestamp(created_time / 1000.0)
|
|
396
|
+
WHERE created_time IS NOT NULL
|
|
397
|
+
""")
|
|
398
|
+
else:
|
|
399
|
+
# MySQL and other databases
|
|
400
|
+
op.execute("""
|
|
401
|
+
UPDATE chat_messages
|
|
402
|
+
SET created_at = FROM_UNIXTIME(created_time / 1000.0)
|
|
403
|
+
WHERE created_time IS NOT NULL
|
|
404
|
+
""")
|
|
335
405
|
|
|
336
406
|
op.drop_column("chat_messages", "created_time")
|
|
337
407
|
|
|
@@ -4,9 +4,6 @@ Defines configuration schema and programmatically creates the WebUIBackendCompon
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from typing import Any, Dict, List
|
|
7
|
-
import os
|
|
8
|
-
from alembic import command
|
|
9
|
-
from alembic.config import Config
|
|
10
7
|
from solace_ai_connector.common.log import log
|
|
11
8
|
|
|
12
9
|
from ...gateway.http_sse.component import WebUIBackendComponent
|
|
@@ -188,31 +185,6 @@ class WebUIBackendApp(BaseGatewayApp):
|
|
|
188
185
|
)
|
|
189
186
|
super().__init__(app_info, **kwargs)
|
|
190
187
|
|
|
191
|
-
try:
|
|
192
|
-
|
|
193
|
-
alembic_ini_path = os.path.join(os.path.dirname(__file__), "alembic.ini")
|
|
194
|
-
if os.path.exists(alembic_ini_path):
|
|
195
|
-
log.debug("Loading Alembic configuration from alembic.ini.")
|
|
196
|
-
alembic_cfg = Config(alembic_ini_path)
|
|
197
|
-
else:
|
|
198
|
-
log.warning(
|
|
199
|
-
"alembic.ini not found. Falling back to programmatic configuration."
|
|
200
|
-
)
|
|
201
|
-
alembic_cfg = Config()
|
|
202
|
-
alembic_cfg.set_main_option(
|
|
203
|
-
"script_location",
|
|
204
|
-
os.path.join(os.path.dirname(__file__), "alembic"),
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
session_service_config = self.get_config("session_service", {})
|
|
208
|
-
db_url = session_service_config.get("database_url")
|
|
209
|
-
if db_url:
|
|
210
|
-
alembic_cfg.set_main_option("sqlalchemy.url", db_url)
|
|
211
|
-
command.upgrade(alembic_cfg, "head")
|
|
212
|
-
else:
|
|
213
|
-
log.warning("Database URL not configured. Skipping migrations.")
|
|
214
|
-
except Exception as e:
|
|
215
|
-
log.warning(f"Alembic migration failed: {e}")
|
|
216
188
|
|
|
217
189
|
log.debug("%s WebUIBackendApp initialization complete.", self.name)
|
|
218
190
|
|
|
@@ -1690,23 +1690,29 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1690
1690
|
message_text += part.text
|
|
1691
1691
|
|
|
1692
1692
|
if message_text and session_id and user_id:
|
|
1693
|
-
from .dependencies import
|
|
1694
|
-
create_session_service_with_transaction,
|
|
1695
|
-
)
|
|
1693
|
+
from .dependencies import SessionLocal, get_session_business_service
|
|
1696
1694
|
from ...gateway.http_sse.shared.enums import SenderType
|
|
1697
1695
|
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
db
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1696
|
+
# For background processing, create simple session wrapper
|
|
1697
|
+
if SessionLocal:
|
|
1698
|
+
db = SessionLocal()
|
|
1699
|
+
try:
|
|
1700
|
+
session_service = get_session_business_service()
|
|
1701
|
+
session_service.add_message_to_session(
|
|
1702
|
+
db=db,
|
|
1703
|
+
session_id=session_id,
|
|
1704
|
+
user_id=user_id,
|
|
1705
|
+
message=message_text,
|
|
1706
|
+
sender_type=SenderType.AGENT,
|
|
1707
|
+
sender_name=agent_name,
|
|
1708
|
+
agent_id=agent_name,
|
|
1709
|
+
)
|
|
1710
|
+
db.commit()
|
|
1711
|
+
except Exception:
|
|
1712
|
+
db.rollback()
|
|
1713
|
+
raise
|
|
1714
|
+
finally:
|
|
1715
|
+
db.close()
|
|
1710
1716
|
log.info(
|
|
1711
1717
|
"%s Final agent response stored in session %s",
|
|
1712
1718
|
log_id_prefix,
|
|
@@ -273,6 +273,54 @@ async def get_user_config(
|
|
|
273
273
|
)
|
|
274
274
|
|
|
275
275
|
|
|
276
|
+
class ValidatedUserConfig:
|
|
277
|
+
"""
|
|
278
|
+
FastAPI dependency class for validating user scopes and returning user config.
|
|
279
|
+
|
|
280
|
+
This class creates a callable dependency that validates a user has the required
|
|
281
|
+
scopes before allowing access to protected endpoints.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
required_scopes: List of scope strings required for authorization
|
|
285
|
+
|
|
286
|
+
Raises:
|
|
287
|
+
HTTPException: 403 if user lacks required scopes
|
|
288
|
+
|
|
289
|
+
Example:
|
|
290
|
+
@router.get("/artifacts")
|
|
291
|
+
async def list_artifacts(
|
|
292
|
+
user_config: dict = Depends(ValidatedUserConfig(["tool:artifact:list"])),
|
|
293
|
+
):
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
def __init__(self, required_scopes: list[str]):
|
|
297
|
+
self.required_scopes = required_scopes
|
|
298
|
+
|
|
299
|
+
async def __call__(
|
|
300
|
+
self,
|
|
301
|
+
request: Request,
|
|
302
|
+
config_resolver: ConfigResolver = Depends(get_config_resolver),
|
|
303
|
+
user_config: dict[str, Any] = Depends(get_user_config),
|
|
304
|
+
) -> dict[str, Any]:
|
|
305
|
+
user_id = user_config.get("user_profile", {}).get("id")
|
|
306
|
+
|
|
307
|
+
log.debug(f"[Dependencies] ValidatedUserConfig called for user_id: {user_id} with required scopes: {self.required_scopes}")
|
|
308
|
+
|
|
309
|
+
# Validate scopes
|
|
310
|
+
if not config_resolver.is_feature_enabled(
|
|
311
|
+
user_config, {"tool_metadata": {"required_scopes": self.required_scopes}}, {}
|
|
312
|
+
):
|
|
313
|
+
log.warning(
|
|
314
|
+
f"[Dependencies] Authorization denied for user '{user_id}'. Required scopes: {self.required_scopes}"
|
|
315
|
+
)
|
|
316
|
+
raise HTTPException(
|
|
317
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
318
|
+
detail=f"Not authorized. Required scopes: {self.required_scopes}"
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
return user_config
|
|
322
|
+
|
|
323
|
+
|
|
276
324
|
def get_shared_artifact_service(
|
|
277
325
|
component: "WebUIBackendComponent" = Depends(get_sac_component),
|
|
278
326
|
) -> BaseArtifactService | None:
|
|
@@ -366,98 +414,15 @@ def get_db() -> Generator[Session, None, None]:
|
|
|
366
414
|
|
|
367
415
|
|
|
368
416
|
def get_session_business_service(
|
|
369
|
-
db: Session = Depends(get_db),
|
|
370
417
|
component: "WebUIBackendComponent" = Depends(get_sac_component),
|
|
371
418
|
) -> SessionService:
|
|
372
419
|
log.debug("[Dependencies] get_session_business_service called")
|
|
373
420
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
return SessionService(
|
|
377
|
-
|
|
421
|
+
# Note: Session and message repositories will be created per request
|
|
422
|
+
# when the SessionService methods receive the db parameter
|
|
423
|
+
return SessionService(component=component)
|
|
378
424
|
|
|
379
|
-
@contextmanager
|
|
380
|
-
def create_session_service_with_transaction():
|
|
381
|
-
"""Create session data access service with its own transaction for non-HTTP contexts."""
|
|
382
|
-
if SessionLocal is None:
|
|
383
|
-
raise RuntimeError("Database not configured")
|
|
384
425
|
|
|
385
|
-
db = SessionLocal()
|
|
386
|
-
try:
|
|
387
|
-
session_repository = SessionRepository(db)
|
|
388
|
-
message_repository = MessageRepository(db)
|
|
389
|
-
|
|
390
|
-
# Create a simple data access object for transaction contexts
|
|
391
|
-
# This provides the basic repository operations without business logic
|
|
392
|
-
class SessionDataAccess:
|
|
393
|
-
def __init__(self, session_repo, message_repo):
|
|
394
|
-
self.session_repository = session_repo
|
|
395
|
-
self.message_repository = message_repo
|
|
396
|
-
|
|
397
|
-
def add_message_to_session(
|
|
398
|
-
self,
|
|
399
|
-
session_id,
|
|
400
|
-
user_id,
|
|
401
|
-
message,
|
|
402
|
-
sender_type,
|
|
403
|
-
sender_name,
|
|
404
|
-
agent_id=None,
|
|
405
|
-
):
|
|
406
|
-
# Simple data access - just save the message
|
|
407
|
-
from uuid import uuid4
|
|
408
|
-
|
|
409
|
-
from .shared.enums import MessageType
|
|
410
|
-
from .shared import now_epoch_ms
|
|
411
|
-
|
|
412
|
-
message_entity = Message(
|
|
413
|
-
id=str(uuid4()),
|
|
414
|
-
session_id=session_id,
|
|
415
|
-
message=message,
|
|
416
|
-
sender_type=sender_type,
|
|
417
|
-
sender_name=sender_name,
|
|
418
|
-
message_type=MessageType.TEXT,
|
|
419
|
-
created_time=now_epoch_ms(),
|
|
420
|
-
)
|
|
421
|
-
return self.message_repository.save(message_entity)
|
|
422
|
-
|
|
423
|
-
def get_session(self, session_id, user_id):
|
|
424
|
-
# Use the session repository to find the session
|
|
425
|
-
return self.session_repository.find_user_session(session_id, user_id)
|
|
426
|
-
|
|
427
|
-
def create_session(
|
|
428
|
-
self, user_id, name=None, agent_id=None, session_id=None
|
|
429
|
-
):
|
|
430
|
-
# Create a new session using the session repository
|
|
431
|
-
from uuid import uuid4
|
|
432
|
-
|
|
433
|
-
from .repository.entities import Session
|
|
434
|
-
from .shared import now_epoch_ms
|
|
435
|
-
|
|
436
|
-
if not session_id:
|
|
437
|
-
session_id = str(uuid4())
|
|
438
|
-
|
|
439
|
-
# Leave name as None/empty - frontend will generate display name if needed
|
|
440
|
-
|
|
441
|
-
now_ms = now_epoch_ms()
|
|
442
|
-
session = Session(
|
|
443
|
-
id=session_id,
|
|
444
|
-
user_id=user_id,
|
|
445
|
-
name=name,
|
|
446
|
-
agent_id=agent_id,
|
|
447
|
-
created_time=now_ms,
|
|
448
|
-
updated_time=now_ms,
|
|
449
|
-
)
|
|
450
|
-
|
|
451
|
-
return self.session_repository.save(session)
|
|
452
|
-
|
|
453
|
-
session_service = SessionDataAccess(session_repository, message_repository)
|
|
454
|
-
yield session_service, db
|
|
455
|
-
db.commit()
|
|
456
|
-
except Exception:
|
|
457
|
-
db.rollback()
|
|
458
|
-
raise
|
|
459
|
-
finally:
|
|
460
|
-
db.close()
|
|
461
426
|
|
|
462
427
|
|
|
463
428
|
def get_session_validator(
|
|
@@ -470,9 +435,13 @@ def get_session_validator(
|
|
|
470
435
|
|
|
471
436
|
def validate_with_database(session_id: str, user_id: str) -> bool:
|
|
472
437
|
try:
|
|
473
|
-
|
|
474
|
-
|
|
438
|
+
db = SessionLocal()
|
|
439
|
+
try:
|
|
440
|
+
session_repository = SessionRepository(db)
|
|
441
|
+
session_domain = session_repository.find_user_session(session_id, user_id)
|
|
475
442
|
return session_domain is not None
|
|
443
|
+
finally:
|
|
444
|
+
db.close()
|
|
476
445
|
except:
|
|
477
446
|
return False
|
|
478
447
|
|
|
@@ -486,3 +455,30 @@ def get_session_validator(
|
|
|
486
455
|
return bool(user_id)
|
|
487
456
|
|
|
488
457
|
return validate_without_database
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def get_db_optional() -> Generator[Session | None, None, None]:
|
|
461
|
+
"""Optional database dependency that returns None if database is not configured."""
|
|
462
|
+
if SessionLocal is None:
|
|
463
|
+
log.debug("[Dependencies] Database not configured, returning None")
|
|
464
|
+
yield None
|
|
465
|
+
else:
|
|
466
|
+
db = SessionLocal()
|
|
467
|
+
try:
|
|
468
|
+
yield db
|
|
469
|
+
db.commit()
|
|
470
|
+
except Exception:
|
|
471
|
+
db.rollback()
|
|
472
|
+
raise
|
|
473
|
+
finally:
|
|
474
|
+
db.close()
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def get_session_business_service_optional(
|
|
478
|
+
component: "WebUIBackendComponent" = Depends(get_sac_component),
|
|
479
|
+
) -> SessionService | None:
|
|
480
|
+
"""Optional session service dependency that returns None if database is not configured."""
|
|
481
|
+
if SessionLocal is None:
|
|
482
|
+
log.debug("[Dependencies] Database not configured, returning None for session service")
|
|
483
|
+
return None
|
|
484
|
+
return SessionService(component=component)
|
|
@@ -310,38 +310,48 @@ def _setup_alembic_config(database_url: str) -> Config:
|
|
|
310
310
|
|
|
311
311
|
|
|
312
312
|
def _run_community_migrations(database_url: str) -> None:
|
|
313
|
+
"""
|
|
314
|
+
Run Alembic migrations for the community database schema.
|
|
315
|
+
This includes sessions, chat_messages tables and their indexes.
|
|
316
|
+
"""
|
|
313
317
|
try:
|
|
314
318
|
from sqlalchemy import create_engine
|
|
315
319
|
|
|
320
|
+
log.info("Starting community migrations...")
|
|
316
321
|
engine = create_engine(database_url)
|
|
317
322
|
inspector = sa.inspect(engine)
|
|
318
323
|
existing_tables = inspector.get_table_names()
|
|
319
324
|
|
|
320
325
|
if not existing_tables or "sessions" not in existing_tables:
|
|
321
|
-
log.info("Running community database
|
|
326
|
+
log.info("Running initial community database setup")
|
|
322
327
|
alembic_cfg = _setup_alembic_config(database_url)
|
|
323
328
|
command.upgrade(alembic_cfg, "head")
|
|
324
|
-
log.info("Community database migrations
|
|
329
|
+
log.info("Community database migrations completed")
|
|
325
330
|
else:
|
|
326
|
-
log.info(
|
|
327
|
-
|
|
328
|
-
)
|
|
331
|
+
log.info("Checking for community schema updates")
|
|
332
|
+
alembic_cfg = _setup_alembic_config(database_url)
|
|
333
|
+
command.upgrade(alembic_cfg, "head")
|
|
334
|
+
log.info("Community database schema is current")
|
|
329
335
|
except Exception as e:
|
|
330
336
|
log.warning(
|
|
331
|
-
"Community migration check failed
|
|
337
|
+
"Community migration check failed: %s - attempting to run migrations",
|
|
332
338
|
e,
|
|
333
339
|
)
|
|
334
340
|
try:
|
|
335
341
|
alembic_cfg = _setup_alembic_config(database_url)
|
|
336
342
|
command.upgrade(alembic_cfg, "head")
|
|
337
|
-
log.info("Community database migrations
|
|
343
|
+
log.info("Community database migrations completed")
|
|
338
344
|
except Exception as migration_error:
|
|
339
|
-
log.
|
|
340
|
-
|
|
341
|
-
)
|
|
345
|
+
log.error("Community migration failed: %s", migration_error)
|
|
346
|
+
log.error("Check database connectivity and permissions")
|
|
347
|
+
raise RuntimeError(f"Community database migration failed: {migration_error}") from migration_error
|
|
342
348
|
|
|
343
349
|
|
|
344
350
|
def _run_enterprise_migrations(component: "WebUIBackendComponent", database_url: str) -> None:
|
|
351
|
+
"""
|
|
352
|
+
Run migrations for enterprise features like advanced analytics, audit logs, etc.
|
|
353
|
+
This is optional and only runs if the enterprise package is available.
|
|
354
|
+
"""
|
|
345
355
|
try:
|
|
346
356
|
from solace_agent_mesh_enterprise.webui_backend.migration_runner import (
|
|
347
357
|
run_migrations,
|
|
@@ -349,19 +359,25 @@ def _run_enterprise_migrations(component: "WebUIBackendComponent", database_url:
|
|
|
349
359
|
|
|
350
360
|
webui_app = component.get_app()
|
|
351
361
|
app_config = getattr(webui_app, "app_config", {}) if webui_app else {}
|
|
352
|
-
log.info("
|
|
362
|
+
log.info("Starting enterprise migrations...")
|
|
353
363
|
run_migrations(database_url, app_config)
|
|
354
364
|
log.info("Enterprise migrations completed")
|
|
355
365
|
except (ImportError, ModuleNotFoundError):
|
|
356
366
|
log.debug("Enterprise module not found - skipping enterprise migrations")
|
|
357
367
|
except Exception as e:
|
|
358
|
-
log.
|
|
368
|
+
log.error("Enterprise migration failed: %s", e)
|
|
369
|
+
log.error("Advanced features may be unavailable")
|
|
370
|
+
raise RuntimeError(f"Enterprise database migration failed: {e}") from e
|
|
359
371
|
|
|
360
372
|
|
|
361
373
|
def _setup_database(component: "WebUIBackendComponent", database_url: str) -> None:
|
|
374
|
+
"""
|
|
375
|
+
Initialize database connection and run all required migrations.
|
|
376
|
+
This sets up both community and enterprise database schemas.
|
|
377
|
+
"""
|
|
362
378
|
dependencies.init_database(database_url)
|
|
363
379
|
log.info("Persistence enabled - sessions will be stored in database")
|
|
364
|
-
log.info("
|
|
380
|
+
log.info("Running database migrations...")
|
|
365
381
|
|
|
366
382
|
_run_community_migrations(database_url)
|
|
367
383
|
_run_enterprise_migrations(component, database_url)
|
|
@@ -463,6 +479,12 @@ def _setup_routers() -> None:
|
|
|
463
479
|
app.include_router(auth.router, prefix=api_prefix, tags=["Auth"])
|
|
464
480
|
log.info("Legacy routers mounted for endpoints not yet migrated")
|
|
465
481
|
|
|
482
|
+
# Register shared exception handlers from community repo
|
|
483
|
+
from .shared.exception_handlers import register_exception_handlers
|
|
484
|
+
register_exception_handlers(app)
|
|
485
|
+
log.info("Registered shared exception handlers from community repo")
|
|
486
|
+
|
|
487
|
+
# Mount enterprise routers if available
|
|
466
488
|
try:
|
|
467
489
|
from solace_agent_mesh_enterprise.webui_backend.routers import get_enterprise_routers
|
|
468
490
|
|
|
@@ -478,9 +500,9 @@ def _setup_routers() -> None:
|
|
|
478
500
|
except ImportError:
|
|
479
501
|
log.debug("No enterprise package detected - skipping enterprise routers")
|
|
480
502
|
except ModuleNotFoundError:
|
|
481
|
-
log.debug("Enterprise
|
|
503
|
+
log.debug("Enterprise module not found - skipping enterprise routers and exception handlers")
|
|
482
504
|
except Exception as e:
|
|
483
|
-
log.warning("Failed to load enterprise routers: %s", e)
|
|
505
|
+
log.warning("Failed to load enterprise routers and exception handlers: %s", e)
|
|
484
506
|
|
|
485
507
|
|
|
486
508
|
def _setup_static_files() -> None:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Message domain entity.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from pydantic import BaseModel
|
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
|
6
6
|
|
|
7
7
|
from ...shared.enums import MessageType, SenderType
|
|
8
8
|
from ...shared.types import MessageId, SessionId
|
|
@@ -11,6 +11,8 @@ from ...shared.types import MessageId, SessionId
|
|
|
11
11
|
class Message(BaseModel):
|
|
12
12
|
"""Message domain entity with business logic."""
|
|
13
13
|
|
|
14
|
+
model_config = ConfigDict(from_attributes=True)
|
|
15
|
+
|
|
14
16
|
id: MessageId
|
|
15
17
|
session_id: SessionId
|
|
16
18
|
message: str
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Session domain entity.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from pydantic import BaseModel
|
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
|
6
6
|
|
|
7
7
|
from ...shared import now_epoch_ms
|
|
8
8
|
from ...shared.types import AgentId, SessionId, UserId
|
|
@@ -11,6 +11,8 @@ from ...shared.types import AgentId, SessionId, UserId
|
|
|
11
11
|
class Session(BaseModel):
|
|
12
12
|
"""Session domain entity with business logic."""
|
|
13
13
|
|
|
14
|
+
model_config = ConfigDict(from_attributes=True)
|
|
15
|
+
|
|
14
16
|
id: SessionId
|
|
15
17
|
user_id: UserId
|
|
16
18
|
name: str | None = None
|
|
@@ -18,6 +18,11 @@ class ISessionRepository(ABC):
|
|
|
18
18
|
"""Find all sessions for a specific user."""
|
|
19
19
|
pass
|
|
20
20
|
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def count_by_user(self, user_id: UserId) -> int:
|
|
23
|
+
"""Count total sessions for a specific user."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
21
26
|
@abstractmethod
|
|
22
27
|
def find_user_session(
|
|
23
28
|
self, session_id: SessionId, user_id: UserId
|