solace-agent-mesh 1.4.7__py3-none-any.whl → 1.4.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of solace-agent-mesh might be problematic. Click here for more details.
- solace_agent_mesh/agent/sac/component.py +2 -3
- 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/453a82a6.3c6bb61d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{75384d09.1e7d7cb7.js → 75384d09.c19e8b51.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/a3a92b25.af35e313.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{bac0be12.bf0181cf.js → bac0be12.17de4316.js} +1 -1
- 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/{main.11f9f9f3.js → main.86924c42.js} +2 -2
- 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 +4 -4
- 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 +4 -4
- 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 +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +11 -12
- 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 +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +4 -4
- 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 +5 -5
- 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 +6 -6
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +4 -4
- 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 +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +4 -4
- 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/component.py +21 -15
- solace_agent_mesh/gateway/http_sse/dependencies.py +84 -88
- solace_agent_mesh/gateway/http_sse/main.py +8 -2
- 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.7.dist-info → solace_agent_mesh-1.4.8.dist-info}/METADATA +34 -35
- {solace_agent_mesh-1.4.7.dist-info → solace_agent_mesh-1.4.8.dist-info}/RECORD +109 -98
- solace_agent_mesh/assets/docs/assets/js/166ab619.bdddc63a.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/a3a92b25.6def8980.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/beecea0d.ce915979.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/f284c35a.525933db.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/runtime~main.5922bcf0.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1759151175744.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1759151175744.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-BKIoiLSu.js +0 -339
- /solace_agent_mesh/assets/docs/assets/js/{main.11f9f9f3.js.LICENSE.txt → main.86924c42.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.4.7.dist-info → solace_agent_mesh-1.4.8.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.4.7.dist-info → solace_agent_mesh-1.4.8.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.4.7.dist-info → solace_agent_mesh-1.4.8.dist-info}/licenses/LICENSE +0 -0
|
@@ -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)
|
|
@@ -479,6 +479,12 @@ def _setup_routers() -> None:
|
|
|
479
479
|
app.include_router(auth.router, prefix=api_prefix, tags=["Auth"])
|
|
480
480
|
log.info("Legacy routers mounted for endpoints not yet migrated")
|
|
481
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
|
|
482
488
|
try:
|
|
483
489
|
from solace_agent_mesh_enterprise.webui_backend.routers import get_enterprise_routers
|
|
484
490
|
|
|
@@ -494,9 +500,9 @@ def _setup_routers() -> None:
|
|
|
494
500
|
except ImportError:
|
|
495
501
|
log.debug("No enterprise package detected - skipping enterprise routers")
|
|
496
502
|
except ModuleNotFoundError:
|
|
497
|
-
log.debug("Enterprise
|
|
503
|
+
log.debug("Enterprise module not found - skipping enterprise routers and exception handlers")
|
|
498
504
|
except Exception as e:
|
|
499
|
-
log.warning("Failed to load enterprise routers: %s", e)
|
|
505
|
+
log.warning("Failed to load enterprise routers and exception handlers: %s", e)
|
|
500
506
|
|
|
501
507
|
|
|
502
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
|
|
@@ -4,19 +4,26 @@ Message repository implementation using SQLAlchemy.
|
|
|
4
4
|
|
|
5
5
|
from sqlalchemy.orm import Session as DBSession
|
|
6
6
|
|
|
7
|
+
from ..shared.base_repository import PaginatedRepository
|
|
7
8
|
from ..shared.enums import MessageType, SenderType
|
|
8
9
|
from ..shared.types import PaginationInfo, SessionId
|
|
9
10
|
from .entities import Message
|
|
10
11
|
from .interfaces import IMessageRepository
|
|
11
|
-
from .models import MessageModel
|
|
12
|
+
from .models import MessageModel, CreateMessageModel, UpdateMessageModel
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
class MessageRepository(IMessageRepository):
|
|
15
|
-
"""SQLAlchemy implementation of message repository."""
|
|
15
|
+
class MessageRepository(PaginatedRepository[MessageModel, Message], IMessageRepository):
|
|
16
|
+
"""SQLAlchemy implementation of message repository using BaseRepository."""
|
|
16
17
|
|
|
17
18
|
def __init__(self, db: DBSession):
|
|
19
|
+
super().__init__(MessageModel, Message)
|
|
18
20
|
self.db = db
|
|
19
21
|
|
|
22
|
+
@property
|
|
23
|
+
def entity_name(self) -> str:
|
|
24
|
+
"""Return the entity name for error messages."""
|
|
25
|
+
return "message"
|
|
26
|
+
|
|
20
27
|
def find_by_session(
|
|
21
28
|
self, session_id: SessionId, pagination: PaginationInfo | None = None
|
|
22
29
|
) -> list[Message]:
|
|
@@ -24,28 +31,28 @@ class MessageRepository(IMessageRepository):
|
|
|
24
31
|
query = self.db.query(MessageModel).filter(
|
|
25
32
|
MessageModel.session_id == session_id
|
|
26
33
|
)
|
|
34
|
+
query = query.order_by(MessageModel.created_time.asc())
|
|
27
35
|
|
|
28
36
|
if pagination:
|
|
29
37
|
offset = (pagination.page - 1) * pagination.page_size
|
|
30
38
|
query = query.offset(offset).limit(pagination.page_size)
|
|
31
39
|
|
|
32
|
-
models = query.
|
|
33
|
-
return [self.
|
|
40
|
+
models = query.all()
|
|
41
|
+
return [self._convert_model_to_entity(model) for model in models]
|
|
34
42
|
|
|
35
43
|
def save(self, message: Message) -> Message:
|
|
36
44
|
"""Save or update a message."""
|
|
37
|
-
|
|
38
|
-
self.db.query(MessageModel).filter(MessageModel.id == message.id).first()
|
|
39
|
-
)
|
|
45
|
+
existing_model = self.db.query(MessageModel).filter(MessageModel.id == message.id).first()
|
|
40
46
|
|
|
41
|
-
if
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
if existing_model:
|
|
48
|
+
update_model = UpdateMessageModel(
|
|
49
|
+
message=message.message,
|
|
50
|
+
sender_type=message.sender_type.value,
|
|
51
|
+
sender_name=message.sender_name,
|
|
52
|
+
)
|
|
53
|
+
return self.update(self.db, message.id, update_model.model_dump())
|
|
46
54
|
else:
|
|
47
|
-
|
|
48
|
-
model = MessageModel(
|
|
55
|
+
create_model = CreateMessageModel(
|
|
49
56
|
id=message.id,
|
|
50
57
|
session_id=message.session_id,
|
|
51
58
|
message=message.message,
|
|
@@ -53,11 +60,7 @@ class MessageRepository(IMessageRepository):
|
|
|
53
60
|
sender_name=message.sender_name,
|
|
54
61
|
created_time=message.created_time,
|
|
55
62
|
)
|
|
56
|
-
self.db.
|
|
57
|
-
|
|
58
|
-
self.db.commit()
|
|
59
|
-
self.db.refresh(model)
|
|
60
|
-
return self._model_to_entity(model)
|
|
63
|
+
return self.create(self.db, create_model.model_dump())
|
|
61
64
|
|
|
62
65
|
def delete_by_session(self, session_id: SessionId) -> bool:
|
|
63
66
|
"""Delete all messages in a session."""
|
|
@@ -66,11 +69,10 @@ class MessageRepository(IMessageRepository):
|
|
|
66
69
|
.filter(MessageModel.session_id == session_id)
|
|
67
70
|
.delete()
|
|
68
71
|
)
|
|
69
|
-
self.db.commit()
|
|
70
72
|
return result > 0
|
|
71
73
|
|
|
72
|
-
def
|
|
73
|
-
"""Convert SQLAlchemy model to domain entity."""
|
|
74
|
+
def _convert_model_to_entity(self, model: MessageModel) -> Message:
|
|
75
|
+
"""Convert SQLAlchemy model to domain entity with enum handling."""
|
|
74
76
|
return Message(
|
|
75
77
|
id=model.id,
|
|
76
78
|
session_id=model.session_id,
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
"""
|
|
2
|
-
SQLAlchemy models for database persistence.
|
|
2
|
+
SQLAlchemy models and Pydantic models for database persistence.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from .base import Base
|
|
6
|
-
from .message_model import MessageModel
|
|
7
|
-
from .session_model import SessionModel
|
|
6
|
+
from .message_model import MessageModel, CreateMessageModel, UpdateMessageModel
|
|
7
|
+
from .session_model import SessionModel, CreateSessionModel, UpdateSessionModel
|
|
8
8
|
|
|
9
|
-
__all__ = [
|
|
9
|
+
__all__ = [
|
|
10
|
+
"Base",
|
|
11
|
+
"MessageModel",
|
|
12
|
+
"SessionModel",
|
|
13
|
+
"CreateMessageModel",
|
|
14
|
+
"UpdateMessageModel",
|
|
15
|
+
"CreateSessionModel",
|
|
16
|
+
"UpdateSessionModel",
|
|
17
|
+
]
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Message SQLAlchemy model.
|
|
2
|
+
Message SQLAlchemy model and Pydantic models for strongly-typed operations.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from pydantic import BaseModel
|
|
5
6
|
from sqlalchemy import BigInteger, Column, ForeignKey, String, Text
|
|
6
7
|
from sqlalchemy.orm import relationship
|
|
7
8
|
|
|
@@ -25,3 +26,20 @@ class MessageModel(Base):
|
|
|
25
26
|
|
|
26
27
|
# Relationship to session
|
|
27
28
|
session = relationship("SessionModel", back_populates="messages")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CreateMessageModel(BaseModel):
|
|
32
|
+
"""Pydantic model for creating a message."""
|
|
33
|
+
id: str
|
|
34
|
+
session_id: str
|
|
35
|
+
message: str
|
|
36
|
+
sender_type: str
|
|
37
|
+
sender_name: str
|
|
38
|
+
created_time: int
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class UpdateMessageModel(BaseModel):
|
|
42
|
+
"""Pydantic model for updating a message."""
|
|
43
|
+
message: str
|
|
44
|
+
sender_type: str
|
|
45
|
+
sender_name: str
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Session SQLAlchemy model.
|
|
2
|
+
Session SQLAlchemy model and Pydantic models for strongly-typed operations.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from pydantic import BaseModel
|
|
5
6
|
from sqlalchemy import BigInteger, Column, String
|
|
6
7
|
from sqlalchemy.orm import relationship
|
|
7
8
|
|
|
@@ -27,3 +28,20 @@ class SessionModel(Base):
|
|
|
27
28
|
messages = relationship(
|
|
28
29
|
"MessageModel", back_populates="session", cascade="all, delete-orphan"
|
|
29
30
|
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CreateSessionModel(BaseModel):
|
|
34
|
+
"""Pydantic model for creating a session."""
|
|
35
|
+
id: str
|
|
36
|
+
name: str | None
|
|
37
|
+
user_id: str
|
|
38
|
+
agent_id: str | None
|
|
39
|
+
created_time: int
|
|
40
|
+
updated_time: int
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class UpdateSessionModel(BaseModel):
|
|
44
|
+
"""Pydantic model for updating a session."""
|
|
45
|
+
name: str | None = None
|
|
46
|
+
agent_id: str | None = None
|
|
47
|
+
updated_time: int
|
|
@@ -4,30 +4,47 @@ Session repository implementation using SQLAlchemy.
|
|
|
4
4
|
|
|
5
5
|
from sqlalchemy.orm import Session as DBSession
|
|
6
6
|
|
|
7
|
+
from ..shared.base_repository import PaginatedRepository
|
|
7
8
|
from ..shared.types import PaginationInfo, SessionId, UserId
|
|
8
9
|
from .entities import Message, Session
|
|
9
10
|
from .interfaces import ISessionRepository
|
|
10
|
-
from .models import
|
|
11
|
+
from .models import (
|
|
12
|
+
MessageModel,
|
|
13
|
+
SessionModel,
|
|
14
|
+
CreateSessionModel,
|
|
15
|
+
UpdateSessionModel,
|
|
16
|
+
)
|
|
11
17
|
|
|
12
18
|
|
|
13
|
-
class SessionRepository(ISessionRepository):
|
|
14
|
-
"""SQLAlchemy implementation of session repository."""
|
|
19
|
+
class SessionRepository(PaginatedRepository[SessionModel, Session], ISessionRepository):
|
|
20
|
+
"""SQLAlchemy implementation of session repository using BaseRepository."""
|
|
15
21
|
|
|
16
22
|
def __init__(self, db: DBSession):
|
|
23
|
+
super().__init__(SessionModel, Session)
|
|
17
24
|
self.db = db
|
|
18
25
|
|
|
26
|
+
@property
|
|
27
|
+
def entity_name(self) -> str:
|
|
28
|
+
"""Return the entity name for error messages."""
|
|
29
|
+
return "session"
|
|
30
|
+
|
|
19
31
|
def find_by_user(
|
|
20
32
|
self, user_id: UserId, pagination: PaginationInfo | None = None
|
|
21
33
|
) -> list[Session]:
|
|
22
34
|
"""Find all sessions for a specific user."""
|
|
23
35
|
query = self.db.query(SessionModel).filter(SessionModel.user_id == user_id)
|
|
36
|
+
query = query.order_by(SessionModel.updated_time.desc())
|
|
24
37
|
|
|
25
38
|
if pagination:
|
|
26
39
|
offset = (pagination.page - 1) * pagination.page_size
|
|
27
40
|
query = query.offset(offset).limit(pagination.page_size)
|
|
28
41
|
|
|
29
|
-
models = query.
|
|
30
|
-
return [
|
|
42
|
+
models = query.all()
|
|
43
|
+
return [Session.model_validate(model) for model in models]
|
|
44
|
+
|
|
45
|
+
def count_by_user(self, user_id: UserId) -> int:
|
|
46
|
+
"""Count total sessions for a specific user."""
|
|
47
|
+
return self.db.query(SessionModel).filter(SessionModel.user_id == user_id).count()
|
|
31
48
|
|
|
32
49
|
def find_user_session(
|
|
33
50
|
self, session_id: SessionId, user_id: UserId
|
|
@@ -41,22 +58,21 @@ class SessionRepository(ISessionRepository):
|
|
|
41
58
|
)
|
|
42
59
|
.first()
|
|
43
60
|
)
|
|
44
|
-
return
|
|
61
|
+
return Session.model_validate(model) if model else None
|
|
45
62
|
|
|
46
63
|
def save(self, session: Session) -> Session:
|
|
47
64
|
"""Save or update a session."""
|
|
48
|
-
|
|
49
|
-
self.db.query(SessionModel).filter(SessionModel.id == session.id).first()
|
|
50
|
-
)
|
|
65
|
+
existing_model = self.db.query(SessionModel).filter(SessionModel.id == session.id).first()
|
|
51
66
|
|
|
52
|
-
if
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
67
|
+
if existing_model:
|
|
68
|
+
update_model = UpdateSessionModel(
|
|
69
|
+
name=session.name,
|
|
70
|
+
agent_id=session.agent_id,
|
|
71
|
+
updated_time=session.updated_time,
|
|
72
|
+
)
|
|
73
|
+
return self.update(self.db, session.id, update_model.model_dump(exclude_none=True))
|
|
57
74
|
else:
|
|
58
|
-
|
|
59
|
-
model = SessionModel(
|
|
75
|
+
create_model = CreateSessionModel(
|
|
60
76
|
id=session.id,
|
|
61
77
|
name=session.name,
|
|
62
78
|
user_id=session.user_id,
|
|
@@ -64,24 +80,22 @@ class SessionRepository(ISessionRepository):
|
|
|
64
80
|
created_time=session.created_time,
|
|
65
81
|
updated_time=session.updated_time,
|
|
66
82
|
)
|
|
67
|
-
self.db.
|
|
68
|
-
|
|
69
|
-
self.db.commit()
|
|
70
|
-
self.db.refresh(model)
|
|
71
|
-
return self._model_to_entity(model)
|
|
83
|
+
return self.create(self.db, create_model.model_dump())
|
|
72
84
|
|
|
73
85
|
def delete(self, session_id: SessionId, user_id: UserId) -> bool:
|
|
74
86
|
"""Delete a session belonging to a user."""
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
# Check if session belongs to user first
|
|
88
|
+
session_model = self.db.query(SessionModel).filter(
|
|
89
|
+
SessionModel.id == session_id,
|
|
90
|
+
SessionModel.user_id == user_id,
|
|
91
|
+
).first()
|
|
92
|
+
|
|
93
|
+
if not session_model:
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
# Use BaseRepository delete method
|
|
97
|
+
super().delete(self.db, session_id)
|
|
98
|
+
return True
|
|
85
99
|
|
|
86
100
|
def find_user_session_with_messages(
|
|
87
101
|
self,
|
|
@@ -112,21 +126,11 @@ class SessionRepository(ISessionRepository):
|
|
|
112
126
|
|
|
113
127
|
message_models = message_query.order_by(MessageModel.created_time.asc()).all()
|
|
114
128
|
|
|
115
|
-
session =
|
|
129
|
+
session = Session.model_validate(session_model)
|
|
116
130
|
messages = [self._message_model_to_entity(model) for model in message_models]
|
|
117
131
|
|
|
118
132
|
return session, messages
|
|
119
133
|
|
|
120
|
-
def _model_to_entity(self, model: SessionModel) -> Session:
|
|
121
|
-
"""Convert SQLAlchemy model to domain entity."""
|
|
122
|
-
return Session(
|
|
123
|
-
id=model.id,
|
|
124
|
-
user_id=model.user_id,
|
|
125
|
-
name=model.name,
|
|
126
|
-
agent_id=model.agent_id,
|
|
127
|
-
created_time=model.created_time,
|
|
128
|
-
updated_time=model.updated_time,
|
|
129
|
-
)
|
|
130
134
|
|
|
131
135
|
def _message_model_to_entity(self, model: MessageModel) -> Message:
|
|
132
136
|
"""Convert SQLAlchemy message model to domain entity."""
|