solace-agent-mesh 1.3.0__py3-none-any.whl → 1.3.2__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 (145) hide show
  1. solace_agent_mesh/agent/adk/setup.py +141 -34
  2. solace_agent_mesh/agent/protocol/event_handlers.py +91 -0
  3. solace_agent_mesh/agent/sac/app.py +3 -2
  4. solace_agent_mesh/agent/tools/__init__.py +1 -0
  5. solace_agent_mesh/agent/tools/dynamic_tool.py +362 -0
  6. solace_agent_mesh/assets/docs/404.html +3 -3
  7. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.d97b8e94.js +1 -0
  8. solace_agent_mesh/assets/docs/assets/js/483cef9a.03d5dceb.js +1 -0
  9. solace_agent_mesh/assets/docs/assets/js/55f47984.cf3781c4.js +1 -0
  10. solace_agent_mesh/assets/docs/assets/js/664b740a.1b744a32.js +1 -0
  11. solace_agent_mesh/assets/docs/assets/js/75384d09.c193a8f0.js +1 -0
  12. solace_agent_mesh/assets/docs/assets/js/9a09e75d.d6607c56.js +1 -0
  13. solace_agent_mesh/assets/docs/assets/js/aba87c2f.071e2d94.js +1 -0
  14. solace_agent_mesh/assets/docs/assets/js/ae0e903d.4d8dda10.js +1 -0
  15. solace_agent_mesh/assets/docs/assets/js/c835a94d.146e3186.js +1 -0
  16. solace_agent_mesh/assets/docs/assets/js/f284c35a.7334119c.js +1 -0
  17. solace_agent_mesh/assets/docs/assets/js/main.4adc477a.js +2 -0
  18. solace_agent_mesh/assets/docs/assets/js/runtime~main.cf0229ea.js +1 -0
  19. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +29 -0
  20. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +25 -0
  21. solace_agent_mesh/assets/docs/docs/documentation/{migration-guides/a2a-upgrade-to-0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html → Migrations/A2A Upgrade To 0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html } +6 -6
  22. solace_agent_mesh/assets/docs/docs/documentation/{migration-guides/a2a-upgrade-to-0.3.0/a2a-technical-migration-map/index.html → Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html } +6 -6
  23. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +19 -27
  24. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +4 -4
  25. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +4 -4
  26. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
  27. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +4 -4
  28. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +4 -4
  29. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +4 -4
  30. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +4 -4
  31. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +4 -4
  32. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +4 -4
  33. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +4 -4
  34. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +4 -4
  35. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +4 -4
  36. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +4 -4
  37. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
  38. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +4 -4
  39. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +4 -4
  40. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +4 -4
  41. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +4 -4
  42. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +4 -4
  43. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +4 -4
  44. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +4 -4
  45. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
  46. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +4 -4
  47. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +4 -4
  48. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +4 -4
  49. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +4 -4
  50. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +4 -4
  51. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +5 -4
  52. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +4 -4
  53. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +63 -0
  54. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +4 -4
  55. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +4 -4
  56. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +4 -4
  57. solace_agent_mesh/assets/docs/lunr-index-1757704179464.json +1 -0
  58. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  59. solace_agent_mesh/assets/docs/search-doc-1757704179464.json +1 -0
  60. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  61. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  62. solace_agent_mesh/cli/__init__.py +1 -1
  63. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-vY5eu2lI.js → authCallback-CAX9u8a7.js} +1 -1
  64. solace_agent_mesh/client/webui/frontend/static/assets/{client-BeBkzgWW.js → client-DXU9SPI5.js} +1 -1
  65. solace_agent_mesh/client/webui/frontend/static/assets/{main-Bjys1KQs.js → main-DjoMeldu.js} +26 -26
  66. solace_agent_mesh/client/webui/frontend/static/assets/{vendor-CE0AeXyK.js → vendor-B0BEKoAR.js} +69 -74
  67. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  68. solace_agent_mesh/client/webui/frontend/static/index.html +3 -3
  69. solace_agent_mesh/common/a2a/__init__.py +4 -0
  70. solace_agent_mesh/common/a2a/protocol.py +20 -0
  71. solace_agent_mesh/common/sac/sam_component_base.py +29 -9
  72. solace_agent_mesh/common/sam_events/__init__.py +9 -0
  73. solace_agent_mesh/common/sam_events/event_service.py +207 -0
  74. solace_agent_mesh/gateway/http_sse/alembic/env.py +1 -1
  75. solace_agent_mesh/gateway/http_sse/component.py +45 -35
  76. solace_agent_mesh/gateway/http_sse/dependencies.py +129 -66
  77. solace_agent_mesh/gateway/http_sse/main.py +22 -35
  78. solace_agent_mesh/gateway/http_sse/repository/__init__.py +37 -0
  79. solace_agent_mesh/gateway/http_sse/repository/entities/__init__.py +9 -0
  80. solace_agent_mesh/gateway/http_sse/repository/entities/message.py +41 -0
  81. solace_agent_mesh/gateway/http_sse/repository/entities/session.py +45 -0
  82. solace_agent_mesh/gateway/http_sse/repository/entities/session_history.py +16 -0
  83. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +64 -0
  84. solace_agent_mesh/gateway/http_sse/repository/message_repository.py +78 -0
  85. solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +9 -0
  86. solace_agent_mesh/gateway/http_sse/repository/models/base.py +7 -0
  87. solace_agent_mesh/gateway/http_sse/repository/models/message_model.py +27 -0
  88. solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +27 -0
  89. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +139 -0
  90. solace_agent_mesh/gateway/http_sse/routers/{agents.py → agent_cards.py} +7 -7
  91. solace_agent_mesh/gateway/http_sse/routers/config.py +1 -0
  92. solace_agent_mesh/gateway/http_sse/routers/dto/requests/__init__.py +20 -0
  93. solace_agent_mesh/gateway/http_sse/{api → routers}/dto/requests/session_requests.py +1 -8
  94. solace_agent_mesh/gateway/http_sse/routers/dto/responses/__init__.py +16 -0
  95. solace_agent_mesh/gateway/http_sse/{api → routers}/dto/responses/session_responses.py +3 -30
  96. solace_agent_mesh/gateway/http_sse/{api/controllers/session_controller.py → routers/sessions.py} +20 -77
  97. solace_agent_mesh/gateway/http_sse/routers/tasks.py +42 -49
  98. solace_agent_mesh/gateway/http_sse/{api/controllers/user_controller.py → routers/users.py} +1 -1
  99. solace_agent_mesh/gateway/http_sse/services/{agent_service.py → agent_card_service.py} +19 -19
  100. solace_agent_mesh/gateway/http_sse/services/session_service.py +245 -0
  101. solace_agent_mesh/gateway/http_sse/session_manager.py +0 -3
  102. solace_agent_mesh/gateway/http_sse/shared/enums.py +0 -5
  103. {solace_agent_mesh-1.3.0.dist-info → solace_agent_mesh-1.3.2.dist-info}/METADATA +1 -1
  104. {solace_agent_mesh-1.3.0.dist-info → solace_agent_mesh-1.3.2.dist-info}/RECORD +109 -110
  105. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.8ccb9901.js +0 -1
  106. solace_agent_mesh/assets/docs/assets/js/55f47984.c484bf96.js +0 -1
  107. solace_agent_mesh/assets/docs/assets/js/6e0db977.39a79ca9.js +0 -1
  108. solace_agent_mesh/assets/docs/assets/js/75384d09.bf78fbdb.js +0 -1
  109. solace_agent_mesh/assets/docs/assets/js/90dd9cf6.88f385ea.js +0 -1
  110. solace_agent_mesh/assets/docs/assets/js/aba87c2f.76376d7c.js +0 -1
  111. solace_agent_mesh/assets/docs/assets/js/f284c35a.fb68323a.js +0 -1
  112. solace_agent_mesh/assets/docs/assets/js/main.08d30374.js +0 -2
  113. solace_agent_mesh/assets/docs/assets/js/runtime~main.458efb1d.js +0 -1
  114. solace_agent_mesh/assets/docs/lunr-index-1757433031159.json +0 -1
  115. solace_agent_mesh/assets/docs/search-doc-1757433031159.json +0 -1
  116. solace_agent_mesh/gateway/http_sse/ARCHITECTURE_GUIDE.md +0 -676
  117. solace_agent_mesh/gateway/http_sse/api/__init__.py +0 -11
  118. solace_agent_mesh/gateway/http_sse/api/controllers/__init__.py +0 -9
  119. solace_agent_mesh/gateway/http_sse/api/controllers/task_controller.py +0 -279
  120. solace_agent_mesh/gateway/http_sse/api/dto/requests/__init__.py +0 -37
  121. solace_agent_mesh/gateway/http_sse/api/dto/requests/task_requests.py +0 -66
  122. solace_agent_mesh/gateway/http_sse/api/dto/responses/__init__.py +0 -43
  123. solace_agent_mesh/gateway/http_sse/api/dto/responses/task_responses.py +0 -74
  124. solace_agent_mesh/gateway/http_sse/application/__init__.py +0 -3
  125. solace_agent_mesh/gateway/http_sse/application/services/__init__.py +0 -3
  126. solace_agent_mesh/gateway/http_sse/application/services/session_service.py +0 -135
  127. solace_agent_mesh/gateway/http_sse/domain/entities/__init__.py +0 -3
  128. solace_agent_mesh/gateway/http_sse/domain/entities/session.py +0 -90
  129. solace_agent_mesh/gateway/http_sse/domain/repositories/__init__.py +0 -3
  130. solace_agent_mesh/gateway/http_sse/domain/repositories/session_repository.py +0 -54
  131. solace_agent_mesh/gateway/http_sse/infrastructure/__init__.py +0 -4
  132. solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/__init__.py +0 -3
  133. solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/container.py +0 -123
  134. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/__init__.py +0 -4
  135. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_persistence_service.py +0 -16
  136. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_service.py +0 -119
  137. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/models.py +0 -31
  138. solace_agent_mesh/gateway/http_sse/infrastructure/persistence_service.py +0 -12
  139. solace_agent_mesh/gateway/http_sse/infrastructure/repositories/__init__.py +0 -3
  140. solace_agent_mesh/gateway/http_sse/infrastructure/repositories/session_repository.py +0 -174
  141. /solace_agent_mesh/assets/docs/assets/js/{main.08d30374.js.LICENSE.txt → main.4adc477a.js.LICENSE.txt} +0 -0
  142. /solace_agent_mesh/gateway/http_sse/{api → routers}/dto/__init__.py +0 -0
  143. {solace_agent_mesh-1.3.0.dist-info → solace_agent_mesh-1.3.2.dist-info}/WHEEL +0 -0
  144. {solace_agent_mesh-1.3.0.dist-info → solace_agent_mesh-1.3.2.dist-info}/entry_points.txt +0 -0
  145. {solace_agent_mesh-1.3.0.dist-info → solace_agent_mesh-1.3.2.dist-info}/licenses/LICENSE +0 -0
@@ -3,24 +3,31 @@ Defines FastAPI dependency injectors to access shared resources
3
3
  managed by the WebUIBackendComponent.
4
4
  """
5
5
 
6
- from collections.abc import Callable
6
+ from collections.abc import Callable, Generator
7
+ from contextlib import contextmanager
7
8
  from typing import TYPE_CHECKING, Any
8
9
 
9
10
  from fastapi import Depends, HTTPException, Request, status
10
11
  from solace_ai_connector.common.log import log
12
+ from sqlalchemy import create_engine
13
+ from sqlalchemy.orm import Session, sessionmaker
11
14
 
12
15
  from ...common.agent_registry import AgentRegistry
13
16
  from ...common.middleware.config_resolver import ConfigResolver
14
17
  from ...common.services.identity_service import BaseIdentityService
15
18
  from ...core_a2a.service import CoreA2AService
16
19
  from ...gateway.base.task_context import TaskContextManager
17
- from ...gateway.http_sse.services.agent_service import AgentService
20
+ from ...gateway.http_sse.services.agent_card_service import AgentCardService
18
21
  from ...gateway.http_sse.services.people_service import PeopleService
19
22
  from ...gateway.http_sse.services.task_service import TaskService
20
23
  from ...gateway.http_sse.session_manager import SessionManager
21
24
  from ...gateway.http_sse.sse_manager import SSEManager
22
- from .application.services.session_service import SessionService
23
- from .infrastructure.persistence_service import PersistenceService
25
+ from .services.session_service import SessionService
26
+ from .repository import (
27
+ Message,
28
+ MessageRepository,
29
+ SessionRepository,
30
+ )
24
31
 
25
32
  try:
26
33
  from google.adk.artifacts import BaseArtifactService
@@ -34,7 +41,7 @@ if TYPE_CHECKING:
34
41
  from gateway.http_sse.component import WebUIBackendComponent
35
42
 
36
43
  sac_component_instance: "WebUIBackendComponent" = None
37
- persistence_service_instance: "PersistenceService" = None
44
+ SessionLocal: sessionmaker = None
38
45
 
39
46
  api_config: dict[str, Any] | None = None
40
47
 
@@ -49,27 +56,15 @@ def set_component_instance(component: "WebUIBackendComponent"):
49
56
  log.warning("[Dependencies] SAC Component instance already set.")
50
57
 
51
58
 
52
- def set_persistence_service(persistence_service: "PersistenceService"):
53
- """Called by the component during its startup to provide its instance."""
54
- global persistence_service_instance
55
- if persistence_service_instance is None:
56
- persistence_service_instance = persistence_service
57
- log.info("[Dependencies] Persistence Service instance provided.")
59
+ def init_database(database_url: str):
60
+ """Initialize database with direct sessionmaker."""
61
+ global SessionLocal
62
+ if SessionLocal is None:
63
+ engine = create_engine(database_url)
64
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
65
+ log.info("[Dependencies] Database initialized.")
58
66
  else:
59
- log.warning("[Dependencies] Persistence Service instance already set.")
60
-
61
-
62
- def get_persistence_service() -> "PersistenceService":
63
- """FastAPI dependency to get the PersistenceService instance."""
64
- if persistence_service_instance is None:
65
- log.warning(
66
- "[Dependencies] PersistenceService not available - running in compatibility mode"
67
- )
68
- raise HTTPException(
69
- status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
70
- detail="Persistence service not available in compatibility mode.",
71
- )
72
- return persistence_service_instance
67
+ log.warning("[Dependencies] Database already initialized.")
73
68
 
74
69
 
75
70
  def set_api_config(config: dict[str, Any]):
@@ -323,12 +318,12 @@ def get_task_context_manager_from_component(
323
318
  return component.task_context_manager
324
319
 
325
320
 
326
- def get_agent_service(
321
+ def get_agent_card_service(
327
322
  registry: AgentRegistry = Depends(get_agent_registry),
328
- ) -> AgentService:
329
- """FastAPI dependency to get an instance of AgentService."""
330
- log.debug("[Dependencies] get_agent_service called")
331
- return AgentService(agent_registry=registry)
323
+ ) -> AgentCardService:
324
+ """FastAPI dependency to get an instance of AgentCardService."""
325
+ log.debug("[Dependencies] get_agent_card_service called")
326
+ return AgentCardService(agent_registry=registry)
332
327
 
333
328
 
334
329
  def get_task_service(
@@ -357,63 +352,131 @@ def get_task_service(
357
352
  )
358
353
 
359
354
 
360
- def get_session_service(
361
- component: "WebUIBackendComponent" = Depends(get_sac_component),
362
- ) -> SessionService:
363
- log.debug("[Dependencies] get_session_service called")
364
-
365
- if (
366
- hasattr(component, "persistence_service")
367
- and component.persistence_service is not None
368
- ):
369
- log.debug("Using database-backed session service")
370
- container = component.persistence_service.container
371
- return container.get_session_service()
372
- else:
373
- log.debug("No database configured - session persistence not available")
355
+ def get_db() -> Generator[Session, None, None]:
356
+ if SessionLocal is None:
374
357
  raise HTTPException(
375
358
  status_code=status.HTTP_501_NOT_IMPLEMENTED,
376
- detail="Session management requires database configuration. Configure 'database_url' in your gateway configuration.",
359
+ detail="Session management requires database configuration.",
377
360
  )
361
+ db = SessionLocal()
362
+ try:
363
+ yield db
364
+ db.commit()
365
+ except Exception:
366
+ db.rollback()
367
+ raise
368
+ finally:
369
+ db.close()
370
+
371
+
372
+
373
+ def get_session_business_service(
374
+ db: Session = Depends(get_db),
375
+ component: "WebUIBackendComponent" = Depends(get_sac_component),
376
+ ) -> SessionService:
377
+ log.debug("[Dependencies] get_session_business_service called")
378
+
379
+ session_repository = SessionRepository(db)
380
+ message_repository = MessageRepository(db)
381
+ return SessionService(session_repository, message_repository, component)
382
+
383
+
384
+ @contextmanager
385
+ def create_session_service_with_transaction():
386
+ """Create session data access service with its own transaction for non-HTTP contexts."""
387
+ if SessionLocal is None:
388
+ raise RuntimeError("Database not configured")
389
+
390
+ db = SessionLocal()
391
+ try:
392
+ session_repository = SessionRepository(db)
393
+ message_repository = MessageRepository(db)
394
+
395
+ # Create a simple data access object for transaction contexts
396
+ # This provides the basic repository operations without business logic
397
+ class SessionDataAccess:
398
+ def __init__(self, session_repo, message_repo):
399
+ self.session_repository = session_repo
400
+ self.message_repository = message_repo
401
+
402
+ def add_message_to_session(self, session_id, user_id, message, sender_type, sender_name, agent_id=None):
403
+ # Simple data access - just save the message
404
+ from uuid import uuid4
405
+ from datetime import datetime, timezone
406
+ from .shared.enums import MessageType
407
+
408
+ message_entity = Message(
409
+ id=str(uuid4()),
410
+ session_id=session_id,
411
+ message=message,
412
+ sender_type=sender_type,
413
+ sender_name=sender_name,
414
+ message_type=MessageType.TEXT,
415
+ created_at=datetime.now(timezone.utc),
416
+ )
417
+ return self.message_repository.save(message_entity)
418
+
419
+ def get_session(self, session_id, user_id):
420
+ # Use the session repository to find the session
421
+ return self.session_repository.find_user_session(session_id, user_id)
422
+
423
+ def create_session(self, user_id, name=None, agent_id=None, session_id=None):
424
+ # Create a new session using the session repository
425
+ from uuid import uuid4
426
+ from datetime import datetime, timezone
427
+ from .repository.entities import Session
428
+
429
+ if not session_id:
430
+ session_id = str(uuid4())
431
+
432
+ # Leave name as None/empty - frontend will generate display name if needed
433
+
434
+ now = datetime.now(timezone.utc)
435
+ session = Session(
436
+ id=session_id,
437
+ user_id=user_id,
438
+ name=name,
439
+ agent_id=agent_id,
440
+ created_at=now,
441
+ updated_at=now,
442
+ last_activity=now,
443
+ )
444
+
445
+ return self.session_repository.save(session)
446
+
447
+ session_service = SessionDataAccess(session_repository, message_repository)
448
+ yield session_service, db
449
+ db.commit()
450
+ except Exception:
451
+ db.rollback()
452
+ raise
453
+ finally:
454
+ db.close()
378
455
 
379
456
 
380
457
  def get_session_validator(
381
458
  component: "WebUIBackendComponent" = Depends(get_sac_component),
382
459
  ) -> Callable[[str, str], bool]:
383
- """
384
- FastAPI dependency that returns a session validator function.
385
-
386
- With database: Validates sessions against database
387
- Without database: Validates sessions using basic checks (session exists in recent activity)
388
- """
389
460
  log.debug("[Dependencies] get_session_validator called")
390
461
 
391
- # Check if component has a persistence service (database-backed)
392
- if (
393
- hasattr(component, "persistence_service")
394
- and component.persistence_service is not None
395
- ):
462
+ if SessionLocal:
396
463
  log.debug("Using database-backed session validation")
397
464
 
398
465
  def validate_with_database(session_id: str, user_id: str) -> bool:
399
- container = component.persistence_service.container
400
- session_service = container.get_session_service()
401
- session_domain = session_service.get_session(
402
- session_id=session_id, user_id=user_id
403
- )
404
- return session_domain is not None
466
+ try:
467
+ with create_session_service_with_transaction() as (session_service, db):
468
+ session_domain = session_service.get_session(session_id, user_id)
469
+ return session_domain is not None
470
+ except:
471
+ return False
405
472
 
406
473
  return validate_with_database
407
474
  else:
408
475
  log.debug("No database configured - using basic session validation")
409
476
 
410
477
  def validate_without_database(session_id: str, user_id: str) -> bool:
411
- """Basic validation: check session ID format and user authentication"""
412
- # Validate session ID format (should be web-session-* format from SessionManager)
413
478
  if not session_id or not session_id.startswith("web-session-"):
414
479
  return False
415
- # If user_id is provided (authenticated), allow access
416
- # This provides basic security while allowing filesystem-based artifacts
417
480
  return bool(user_id)
418
481
 
419
482
  return validate_without_database
@@ -8,8 +8,9 @@ from a2a.types import InternalError, JSONRPCError
8
8
  from a2a.types import JSONRPCResponse as A2AJSONRPCResponse
9
9
  from alembic import command
10
10
  from alembic.config import Config
11
- from fastapi import FastAPI, HTTPException, status
11
+ from fastapi import FastAPI, HTTPException
12
12
  from fastapi import Request as FastAPIRequest
13
+ from fastapi import status
13
14
  from fastapi.exceptions import RequestValidationError
14
15
  from fastapi.middleware.cors import CORSMiddleware
15
16
  from fastapi.responses import JSONResponse
@@ -20,7 +21,7 @@ from starlette.staticfiles import StaticFiles
20
21
  from ...common import a2a
21
22
  from ...gateway.http_sse import dependencies
22
23
  from ...gateway.http_sse.routers import (
23
- agents,
24
+ agent_cards,
24
25
  artifacts,
25
26
  auth,
26
27
  config,
@@ -31,10 +32,9 @@ from ...gateway.http_sse.routers import (
31
32
  )
32
33
 
33
34
  # Import persistence-aware controllers
34
- from .api.controllers.session_controller import router as session_router
35
- from .api.controllers.task_controller import router as task_router
36
- from .api.controllers.user_controller import router as user_router
37
- from .infrastructure.persistence.database_service import DatabaseService
35
+ from .routers.sessions import router as session_router
36
+ from .routers.tasks import router as task_router
37
+ from .routers.users import router as user_router
38
38
 
39
39
  if TYPE_CHECKING:
40
40
  from gateway.http_sse.component import WebUIBackendComponent
@@ -46,39 +46,25 @@ app = FastAPI(
46
46
  )
47
47
 
48
48
 
49
- def setup_dependencies(component: "WebUIBackendComponent", persistence_service=None):
49
+ def setup_dependencies(component: "WebUIBackendComponent", database_url: str = None):
50
50
  """
51
- This function initializes the modern architecture while maintaining full
51
+ This function initializes the simplified architecture while maintaining full
52
52
  backward compatibility with existing API contracts.
53
53
 
54
- If persistence_service is None, runs in compatibility mode with in-memory sessions.
54
+ If database_url is None, runs in compatibility mode with in-memory sessions.
55
55
  """
56
56
 
57
- if persistence_service:
58
- database_url = persistence_service.engine.url.__str__()
59
- global database_service
60
- database_service = DatabaseService(database_url)
61
- log.info("Database service initialized")
62
-
63
- from .infrastructure.dependency_injection.container import initialize_container
64
-
65
- initialize_container(database_url)
66
- log.info("Persistence enabled - sessions will be stored in database")
67
- else:
68
- from .infrastructure.dependency_injection.container import initialize_container
69
-
70
- initialize_container()
71
- log.warning(
72
- "No persistence service provided - using in-memory session storage (data not persisted across restarts)"
73
- )
74
- log.info("This maintains backward compatibility for existing SAM installations")
75
-
76
57
  dependencies.set_component_instance(component)
77
58
 
78
- if persistence_service:
59
+ if database_url:
60
+ dependencies.init_database(database_url)
61
+ log.info("Persistence enabled - sessions will be stored in database")
62
+
79
63
  log.info("Checking database migrations...")
80
64
  try:
81
- inspector = sa.inspect(persistence_service.engine)
65
+ from sqlalchemy import create_engine
66
+ engine = create_engine(database_url)
67
+ inspector = sa.inspect(engine)
82
68
  existing_tables = inspector.get_table_names()
83
69
 
84
70
  if not existing_tables or "sessions" not in existing_tables:
@@ -108,10 +94,11 @@ def setup_dependencies(component: "WebUIBackendComponent", persistence_service=N
108
94
  log.info("Database migrations complete.")
109
95
  except Exception as migration_error:
110
96
  log.warning(f"Migration failed but continuing: {migration_error}")
111
-
112
- dependencies.set_persistence_service(persistence_service)
113
97
  else:
114
- log.info("Skipping database migrations - no persistence service configured")
98
+ log.warning(
99
+ "No database URL provided - using in-memory session storage (data not persisted across restarts)"
100
+ )
101
+ log.info("This maintains backward compatibility for existing SAM installations")
115
102
 
116
103
  webui_app = component.get_app()
117
104
  app_config = {}
@@ -137,7 +124,7 @@ def setup_dependencies(component: "WebUIBackendComponent", persistence_service=N
137
124
  "frontend_redirect_url": app_config.get(
138
125
  "frontend_redirect_url", "http://localhost:3000"
139
126
  ),
140
- "persistence_enabled": persistence_service is not None,
127
+ "persistence_enabled": database_url is not None,
141
128
  }
142
129
 
143
130
  dependencies.set_api_config(api_config_dict)
@@ -492,7 +479,7 @@ def setup_dependencies(component: "WebUIBackendComponent", persistence_service=N
492
479
 
493
480
  # Mount new A2A SDK routers with different paths to avoid conflicts
494
481
  app.include_router(config.router, prefix=api_prefix, tags=["Config"])
495
- app.include_router(agents.router, prefix=api_prefix, tags=["Agents"])
482
+ app.include_router(agent_cards.router, prefix=api_prefix, tags=["Agent Cards"])
496
483
  # New A2A message endpoints (non-conflicting paths)
497
484
  app.include_router(
498
485
  tasks.router, prefix=api_prefix, tags=["A2A Messages"]
@@ -0,0 +1,37 @@
1
+ """
2
+ Repository layer containing all data access logic organized by entity type.
3
+ """
4
+
5
+ # Interfaces
6
+ from .interfaces import IMessageRepository, ISessionRepository
7
+
8
+ # Implementations
9
+ from .message_repository import MessageRepository
10
+ from .session_repository import SessionRepository
11
+
12
+ # Entities (re-exported for convenience)
13
+ from .entities.session import Session
14
+ from .entities.message import Message
15
+ from .entities.session_history import SessionHistory
16
+
17
+ # Models (re-exported for convenience)
18
+ from .models.base import Base
19
+ from .models.session_model import SessionModel
20
+ from .models.message_model import MessageModel
21
+
22
+ __all__ = [
23
+ # Interfaces
24
+ "IMessageRepository",
25
+ "ISessionRepository",
26
+ # Implementations
27
+ "MessageRepository",
28
+ "SessionRepository",
29
+ # Entities
30
+ "Message",
31
+ "Session",
32
+ "SessionHistory",
33
+ # Models
34
+ "Base",
35
+ "MessageModel",
36
+ "SessionModel",
37
+ ]
@@ -0,0 +1,9 @@
1
+ """
2
+ Domain entities for the repository layer.
3
+ """
4
+
5
+ from .message import Message
6
+ from .session import Session
7
+ from .session_history import SessionHistory
8
+
9
+ __all__ = ["Message", "Session", "SessionHistory"]
@@ -0,0 +1,41 @@
1
+ """
2
+ Message domain entity.
3
+ """
4
+
5
+ from datetime import datetime
6
+
7
+ from pydantic import BaseModel
8
+
9
+ from ...shared.enums import MessageType, SenderType
10
+ from ...shared.types import MessageId, SessionId
11
+
12
+
13
+ class Message(BaseModel):
14
+ """Message domain entity with business logic."""
15
+
16
+ id: MessageId
17
+ session_id: SessionId
18
+ message: str
19
+ sender_type: SenderType
20
+ sender_name: str
21
+ message_type: MessageType = MessageType.TEXT
22
+ created_at: datetime
23
+
24
+ def validate_message_content(self) -> None:
25
+ """Validate message content."""
26
+ if not self.message or len(self.message.strip()) == 0:
27
+ raise ValueError("Message content cannot be empty")
28
+ if len(self.message) > 10_000_000:
29
+ raise ValueError("Message content exceeds maximum length (10MB)")
30
+
31
+ def is_from_user(self) -> bool:
32
+ """Check if message is from a user."""
33
+ return self.sender_type == SenderType.USER
34
+
35
+ def is_from_agent(self) -> bool:
36
+ """Check if message is from an agent."""
37
+ return self.sender_type == SenderType.AGENT
38
+
39
+ def is_system_message(self) -> bool:
40
+ """Check if message is a system message."""
41
+ return self.sender_type == SenderType.SYSTEM
@@ -0,0 +1,45 @@
1
+ """
2
+ Session domain entity.
3
+ """
4
+
5
+ from datetime import datetime, timezone
6
+
7
+ from pydantic import BaseModel
8
+
9
+ from ...shared.types import AgentId, SessionId, UserId
10
+
11
+
12
+ class Session(BaseModel):
13
+ """Session domain entity with business logic."""
14
+
15
+ id: SessionId
16
+ user_id: UserId
17
+ name: str | None = None
18
+ agent_id: AgentId | None = None
19
+ created_at: datetime
20
+ updated_at: datetime | None = None
21
+ last_activity: datetime | None = None
22
+
23
+ def update_name(self, new_name: str) -> None:
24
+ """Update session name with validation."""
25
+ if not new_name or len(new_name.strip()) == 0:
26
+ raise ValueError("Session name cannot be empty")
27
+ if len(new_name) > 255:
28
+ raise ValueError("Session name cannot exceed 255 characters")
29
+
30
+ self.name = new_name.strip()
31
+ self.updated_at = datetime.now(timezone.utc)
32
+
33
+ def mark_activity(self) -> None:
34
+ """Mark session as having recent activity."""
35
+ self.last_activity = datetime.now(timezone.utc)
36
+ self.updated_at = datetime.now(timezone.utc)
37
+
38
+
39
+ def can_be_deleted_by_user(self, user_id: UserId) -> bool:
40
+ """Check if user can delete this session."""
41
+ return self.user_id == user_id
42
+
43
+ def can_be_accessed_by_user(self, user_id: UserId) -> bool:
44
+ """Check if user can access this session."""
45
+ return self.user_id == user_id
@@ -0,0 +1,16 @@
1
+ """
2
+ Session history composite entity.
3
+ """
4
+
5
+ from pydantic import BaseModel
6
+
7
+ from .message import Message
8
+ from .session import Session
9
+
10
+
11
+ class SessionHistory(BaseModel):
12
+ """Composite entity representing a session with its messages."""
13
+
14
+ session: Session
15
+ messages: list[Message] = []
16
+ total_message_count: int = 0
@@ -0,0 +1,64 @@
1
+ """
2
+ Repository interfaces defining contracts for data access.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+
7
+ from ..shared.types import PaginationInfo, SessionId, UserId
8
+ from .entities import Message, Session
9
+
10
+
11
+ class ISessionRepository(ABC):
12
+ """Interface for session data access operations."""
13
+
14
+ @abstractmethod
15
+ def find_by_user(
16
+ self, user_id: UserId, pagination: PaginationInfo | None = None
17
+ ) -> list[Session]:
18
+ """Find all sessions for a specific user."""
19
+ pass
20
+
21
+ @abstractmethod
22
+ def find_user_session(
23
+ self, session_id: SessionId, user_id: UserId
24
+ ) -> Session | None:
25
+ """Find a specific session belonging to a user."""
26
+ pass
27
+
28
+ @abstractmethod
29
+ def save(self, session: Session) -> Session:
30
+ """Save or update a session."""
31
+ pass
32
+
33
+ @abstractmethod
34
+ def delete(self, session_id: SessionId, user_id: UserId) -> bool:
35
+ """Delete a session belonging to a user."""
36
+ pass
37
+
38
+ @abstractmethod
39
+ def find_user_session_with_messages(
40
+ self, session_id: SessionId, user_id: UserId, pagination: PaginationInfo | None = None
41
+ ) -> tuple[Session, list[Message]] | None:
42
+ """Find a session with its messages."""
43
+ pass
44
+
45
+
46
+ class IMessageRepository(ABC):
47
+ """Interface for message data access operations."""
48
+
49
+ @abstractmethod
50
+ def find_by_session(
51
+ self, session_id: SessionId, pagination: PaginationInfo | None = None
52
+ ) -> list[Message]:
53
+ """Find all messages in a session."""
54
+ pass
55
+
56
+ @abstractmethod
57
+ def save(self, message: Message) -> Message:
58
+ """Save or update a message."""
59
+ pass
60
+
61
+ @abstractmethod
62
+ def delete_by_session(self, session_id: SessionId) -> bool:
63
+ """Delete all messages in a session."""
64
+ pass
@@ -0,0 +1,78 @@
1
+ """
2
+ Message repository implementation using SQLAlchemy.
3
+ """
4
+
5
+ from sqlalchemy.orm import Session as DBSession
6
+
7
+ from ..shared.enums import MessageType, SenderType
8
+ from ..shared.types import PaginationInfo, SessionId
9
+ from .entities import Message
10
+ from .interfaces import IMessageRepository
11
+ from .models import MessageModel
12
+
13
+
14
+ class MessageRepository(IMessageRepository):
15
+ """SQLAlchemy implementation of message repository."""
16
+
17
+ def __init__(self, db: DBSession):
18
+ self.db = db
19
+
20
+ def find_by_session(
21
+ self, session_id: SessionId, pagination: PaginationInfo | None = None
22
+ ) -> list[Message]:
23
+ """Find all messages in a session."""
24
+ query = self.db.query(MessageModel).filter(MessageModel.session_id == session_id)
25
+
26
+ if pagination:
27
+ offset = (pagination.page - 1) * pagination.page_size
28
+ query = query.offset(offset).limit(pagination.page_size)
29
+
30
+ models = query.order_by(MessageModel.created_at.asc()).all()
31
+ return [self._model_to_entity(model) for model in models]
32
+
33
+ def save(self, message: Message) -> Message:
34
+ """Save or update a message."""
35
+ model = self.db.query(MessageModel).filter(MessageModel.id == message.id).first()
36
+
37
+ if model:
38
+ # Update existing
39
+ model.message = message.message
40
+ model.sender_type = message.sender_type.value
41
+ model.sender_name = message.sender_name
42
+ else:
43
+ # Create new
44
+ model = MessageModel(
45
+ id=message.id,
46
+ session_id=message.session_id,
47
+ message=message.message,
48
+ sender_type=message.sender_type.value,
49
+ sender_name=message.sender_name,
50
+ created_at=message.created_at,
51
+ )
52
+ self.db.add(model)
53
+
54
+ self.db.commit()
55
+ self.db.refresh(model)
56
+ return self._model_to_entity(model)
57
+
58
+ def delete_by_session(self, session_id: SessionId) -> bool:
59
+ """Delete all messages in a session."""
60
+ result = (
61
+ self.db.query(MessageModel)
62
+ .filter(MessageModel.session_id == session_id)
63
+ .delete()
64
+ )
65
+ self.db.commit()
66
+ return result > 0
67
+
68
+ def _model_to_entity(self, model: MessageModel) -> Message:
69
+ """Convert SQLAlchemy model to domain entity."""
70
+ return Message(
71
+ id=model.id,
72
+ session_id=model.session_id,
73
+ message=model.message,
74
+ sender_type=SenderType(model.sender_type),
75
+ sender_name=model.sender_name,
76
+ message_type=MessageType.TEXT, # Default for now
77
+ created_at=model.created_at,
78
+ )