solace-agent-mesh 1.4.2__py3-none-any.whl → 1.4.4__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 (91) hide show
  1. solace_agent_mesh/agent/adk/services.py +10 -3
  2. solace_agent_mesh/agent/sac/app.py +5 -1
  3. solace_agent_mesh/agent/sac/component.py +7 -3
  4. solace_agent_mesh/agent/tools/builtin_artifact_tools.py +17 -0
  5. solace_agent_mesh/agent/tools/builtin_data_analysis_tools.py +4 -0
  6. solace_agent_mesh/agent/tools/tool_definition.py +8 -0
  7. solace_agent_mesh/agent/tools/web_tools.py +5 -0
  8. solace_agent_mesh/assets/docs/404.html +3 -3
  9. solace_agent_mesh/assets/docs/assets/js/{ae0e903d.abca774a.js → ae0e903d.ac3b9419.js} +1 -1
  10. solace_agent_mesh/assets/docs/assets/js/beecea0d.8bbd852c.js +1 -0
  11. solace_agent_mesh/assets/docs/assets/js/{main.1a0c706b.js → main.03fb0598.js} +2 -2
  12. solace_agent_mesh/assets/docs/assets/js/{runtime~main.f2b4ea70.js → runtime~main.4ee39c6f.js} +1 -1
  13. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +5 -5
  14. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +3 -3
  15. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
  16. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html +3 -3
  17. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +3 -3
  18. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +3 -3
  19. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +3 -3
  20. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +3 -3
  21. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +3 -3
  22. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +3 -3
  23. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +3 -3
  24. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +3 -3
  25. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +3 -3
  26. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +3 -3
  27. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +6 -3
  28. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +3 -3
  29. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
  30. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +3 -3
  31. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +3 -3
  32. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +3 -3
  33. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +3 -3
  34. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +3 -3
  35. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +3 -3
  36. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +3 -3
  37. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +3 -3
  38. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +3 -3
  39. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +3 -3
  40. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +3 -3
  41. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +3 -3
  42. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +3 -3
  43. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +3 -3
  44. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +3 -3
  45. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +3 -3
  46. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +3 -3
  47. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +3 -3
  48. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +3 -3
  49. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +3 -3
  50. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +3 -3
  51. solace_agent_mesh/assets/docs/lunr-index-1758293998763.json +1 -0
  52. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  53. solace_agent_mesh/assets/docs/search-doc-1758293998763.json +1 -0
  54. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  55. solace_agent_mesh/cli/__init__.py +1 -1
  56. solace_agent_mesh/client/webui/frontend/static/assets/main-8xbvgfVK.css +1 -0
  57. solace_agent_mesh/client/webui/frontend/static/assets/main-Cv2k8j3R.js +339 -0
  58. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +13 -13
  59. solace_agent_mesh/client/webui/frontend/static/index.html +13 -13
  60. solace_agent_mesh/gateway/base/app.py +122 -71
  61. solace_agent_mesh/gateway/http_sse/alembic/versions/20250916_f6e7d8c9b0a1_convert_timestamps_to_epoch_and_align_columns.py +342 -0
  62. solace_agent_mesh/gateway/http_sse/alembic.ini +4 -6
  63. solace_agent_mesh/gateway/http_sse/app.py +152 -90
  64. solace_agent_mesh/gateway/http_sse/dependencies.py +30 -24
  65. solace_agent_mesh/gateway/http_sse/main.py +19 -0
  66. solace_agent_mesh/gateway/http_sse/repository/entities/message.py +3 -5
  67. solace_agent_mesh/gateway/http_sse/repository/entities/session.py +7 -11
  68. solace_agent_mesh/gateway/http_sse/repository/message_repository.py +11 -7
  69. solace_agent_mesh/gateway/http_sse/repository/models/message_model.py +7 -7
  70. solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +10 -8
  71. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +18 -14
  72. solace_agent_mesh/gateway/http_sse/routers/dto/responses/base_responses.py +42 -0
  73. solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +25 -23
  74. solace_agent_mesh/gateway/http_sse/routers/sessions.py +10 -19
  75. solace_agent_mesh/gateway/http_sse/services/session_service.py +5 -6
  76. solace_agent_mesh/gateway/http_sse/shared/__init__.py +17 -1
  77. solace_agent_mesh/gateway/http_sse/shared/timestamp_utils.py +97 -0
  78. solace_agent_mesh/gateway/http_sse/shared/types.py +23 -7
  79. {solace_agent_mesh-1.4.2.dist-info → solace_agent_mesh-1.4.4.dist-info}/METADATA +1 -1
  80. {solace_agent_mesh-1.4.2.dist-info → solace_agent_mesh-1.4.4.dist-info}/RECORD +86 -83
  81. solace_agent_mesh/assets/docs/assets/js/beecea0d.ae31f6a7.js +0 -1
  82. solace_agent_mesh/assets/docs/lunr-index-1758046853673.json +0 -1
  83. solace_agent_mesh/assets/docs/search-doc-1758046853673.json +0 -1
  84. solace_agent_mesh/client/webui/frontend/static/assets/main-B6BpuH9K.js +0 -339
  85. solace_agent_mesh/client/webui/frontend/static/assets/main-B9s_V9tJ.css +0 -1
  86. /solace_agent_mesh/assets/docs/assets/js/{main.1a0c706b.js.LICENSE.txt → main.03fb0598.js.LICENSE.txt} +0 -0
  87. /solace_agent_mesh/gateway/http_sse/alembic/versions/{d5b3f8f2e9a0_create_initial_database.py → 20250910_d5b3f8f2e9a0_create_initial_database.py} +0 -0
  88. /solace_agent_mesh/gateway/http_sse/alembic/versions/{b1c2d3e4f5g6_add_database_indexes.py → 20250911_b1c2d3e4f5g6_add_database_indexes.py} +0 -0
  89. {solace_agent_mesh-1.4.2.dist-info → solace_agent_mesh-1.4.4.dist-info}/WHEEL +0 -0
  90. {solace_agent_mesh-1.4.2.dist-info → solace_agent_mesh-1.4.4.dist-info}/entry_points.txt +0 -0
  91. {solace_agent_mesh-1.4.2.dist-info → solace_agent_mesh-1.4.4.dist-info}/licenses/LICENSE +0 -0
@@ -3,16 +3,15 @@ Custom Solace AI Connector App class for the Web UI Backend.
3
3
  Defines configuration schema and programmatically creates the WebUIBackendComponent.
4
4
  """
5
5
 
6
- from typing import Any, Dict, List, Optional
6
+ from typing import Any, Dict, List
7
7
  import os
8
8
  from alembic import command
9
9
  from alembic.config import Config
10
- from pydantic import Field, ValidationError
11
10
  from solace_ai_connector.common.log import log
12
11
 
13
12
  from ...gateway.http_sse.component import WebUIBackendComponent
14
13
 
15
- from ...gateway.base.app import BaseGatewayApp, BaseGatewayAppConfig
14
+ from ...gateway.base.app import BaseGatewayApp
16
15
  from ...gateway.base.component import BaseGatewayComponent
17
16
 
18
17
 
@@ -22,88 +21,6 @@ info = {
22
21
  }
23
22
 
24
23
 
25
- class WebUIBackendAppConfig(BaseGatewayAppConfig):
26
- """Pydantic model for the Web UI Backend application configuration."""
27
-
28
- session_secret_key: str = Field(
29
- ..., description="Secret key for signing web user sessions."
30
- )
31
- fastapi_host: str = Field(
32
- default="127.0.0.1", description="Host address for the embedded FastAPI server."
33
- )
34
- fastapi_port: int = Field(
35
- default=8000, description="Port for the embedded FastAPI server."
36
- )
37
- fastapi_https_port: int = Field(
38
- default=8443, description="Port for the embedded FastAPI server when SSL is enabled."
39
- )
40
- cors_allowed_origins: List[str] = Field(
41
- default=["*"], description="List of allowed origins for CORS requests."
42
- )
43
- sse_max_queue_size: int = Field(
44
- default=200,
45
- description="Maximum size of the SSE connection queues. Adjust based on expected load.",
46
- )
47
- sse_buffer_max_age_seconds: int = Field(
48
- default=600, # 10 minutes
49
- description="Maximum age in seconds for a pending event buffer before it is cleaned up.",
50
- )
51
- sse_buffer_cleanup_interval_seconds: int = Field(
52
- default=300, # 5 minutes
53
- description="How often to run the cleanup task for stale pending event buffers.",
54
- )
55
- resolve_artifact_uris_in_gateway: bool = Field(
56
- default=True,
57
- description="If true, the gateway will resolve artifact:// URIs found in A2A messages and embed the content as bytes before sending to the UI. If false, URIs are passed through.",
58
- )
59
- system_purpose: str = Field(
60
- default="",
61
- description="Detailed description of the system's overall purpose, to be optionally used by agents.",
62
- )
63
- response_format: str = Field(
64
- default="",
65
- description="General guidelines on how agent responses should be structured, to be optionally used by agents.",
66
- )
67
- frontend_welcome_message: str = Field(
68
- default="Hi! How can I help?",
69
- description="Initial welcome message displayed in the chat.",
70
- )
71
- frontend_bot_name: str = Field(
72
- default="A2A Agent", description="Name displayed for the bot/agent in the UI."
73
- )
74
- frontend_collect_feedback: bool = Field(
75
- default=False, description="Enable/disable the feedback buttons in the UI."
76
- )
77
- frontend_auth_login_url: str = Field(
78
- default="", description="URL for the external login page (if auth is enabled)."
79
- )
80
- frontend_use_authorization: bool = Field(
81
- default=False, description="Tell frontend whether backend expects authorization."
82
- )
83
- frontend_redirect_url: str = Field(
84
- default="", description="Redirect URL for OAuth flows (if auth is enabled)."
85
- )
86
- external_auth_callback_uri: str = Field(
87
- default="", description="Redirect URI for the OIDC application."
88
- )
89
- external_auth_service_url: str = Field(
90
- default="http://localhost:8080",
91
- description="External authorization service URL for login initiation.",
92
- )
93
- external_auth_provider: str = Field(
94
- default="", description="The external authentication provider."
95
- )
96
- ssl_keyfile: str = Field(
97
- default="", description="The file path to the SSL private key."
98
- )
99
- ssl_certfile: str = Field(
100
- default="", description="The file path to the SSL certificate."
101
- )
102
- ssl_keyfile_password: str = Field(
103
- default="", description="The passphrase for the SSL private key."
104
- )
105
-
106
-
107
24
  class WebUIBackendApp(BaseGatewayApp):
108
25
  """
109
26
  Custom App class for the A2A Web UI Backend.
@@ -111,8 +28,154 @@ class WebUIBackendApp(BaseGatewayApp):
111
28
  - Defines WebUI-specific configuration parameters.
112
29
  """
113
30
 
114
- # This is now a placeholder. Validation is handled by the Pydantic model.
115
- SPECIFIC_APP_SCHEMA_PARAMS: List[Dict[str, Any]] = []
31
+ SPECIFIC_APP_SCHEMA_PARAMS: List[Dict[str, Any]] = [
32
+ {
33
+ "name": "session_secret_key",
34
+ "required": True,
35
+ "type": "string",
36
+ "description": "Secret key for signing web user sessions.",
37
+ },
38
+ {
39
+ "name": "fastapi_host",
40
+ "required": False,
41
+ "type": "string",
42
+ "default": "127.0.0.1",
43
+ "description": "Host address for the embedded FastAPI server.",
44
+ },
45
+ {
46
+ "name": "fastapi_port",
47
+ "required": False,
48
+ "type": "integer",
49
+ "default": 8000,
50
+ "description": "Port for the embedded FastAPI server.",
51
+ },
52
+ {
53
+ "name": "fastapi_https_port",
54
+ "required": False,
55
+ "type": "integer",
56
+ "default": 8443,
57
+ "description": "Port for the embedded FastAPI server when SSL is enabled.",
58
+ },
59
+ {
60
+ "name": "cors_allowed_origins",
61
+ "required": False,
62
+ "type": "list",
63
+ "default": ["*"],
64
+ "description": "List of allowed origins for CORS requests.",
65
+ },
66
+ {
67
+ "name": "sse_max_queue_size",
68
+ "required": False,
69
+ "type": "integer",
70
+ "default": 200,
71
+ "description": "Maximum size of the SSE connection queues. Adjust based on expected load.",
72
+ },
73
+ {
74
+ "name": "resolve_artifact_uris_in_gateway",
75
+ "required": False,
76
+ "type": "boolean",
77
+ "default": True,
78
+ "description": "If true, the gateway will resolve artifact:// URIs found in A2A messages and embed the content as bytes before sending to the UI. If false, URIs are passed through.",
79
+ },
80
+ {
81
+ "name": "system_purpose",
82
+ "required": False,
83
+ "type": "string",
84
+ "default": "",
85
+ "description": "Detailed description of the system's overall purpose, to be optionally used by agents.",
86
+ },
87
+ {
88
+ "name": "response_format",
89
+ "required": False,
90
+ "type": "string",
91
+ "default": "",
92
+ "description": "General guidelines on how agent responses should be structured, to be optionally used by agents.",
93
+ },
94
+ {
95
+ "name": "frontend_welcome_message",
96
+ "required": False,
97
+ "type": "string",
98
+ "default": "Hi! How can I help?",
99
+ "description": "Initial welcome message displayed in the chat.",
100
+ },
101
+ {
102
+ "name": "frontend_bot_name",
103
+ "required": False,
104
+ "type": "string",
105
+ "default": "A2A Agent",
106
+ "description": "Name displayed for the bot/agent in the UI.",
107
+ },
108
+ {
109
+ "name": "frontend_collect_feedback",
110
+ "required": False,
111
+ "type": "boolean",
112
+ "default": False,
113
+ "description": "Enable/disable the feedback buttons in the UI.",
114
+ },
115
+ {
116
+ "name": "frontend_auth_login_url",
117
+ "required": False,
118
+ "type": "string",
119
+ "default": "",
120
+ "description": "URL for the external login page (if auth is enabled).",
121
+ },
122
+ {
123
+ "name": "frontend_use_authorization",
124
+ "required": False,
125
+ "type": "boolean",
126
+ "default": False,
127
+ "description": "Tell frontend whether backend expects authorization.",
128
+ },
129
+ {
130
+ "name": "frontend_redirect_url",
131
+ "required": False,
132
+ "type": "string",
133
+ "default": "",
134
+ "description": "Redirect URL for OAuth flows (if auth is enabled).",
135
+ },
136
+ {
137
+ "name": "external_auth_callback_uri",
138
+ "required": False,
139
+ "type": "string",
140
+ "default": "",
141
+ "description": "Redirect URI for the OIDC application.",
142
+ },
143
+ {
144
+ "name": "external_auth_service_url",
145
+ "required": False,
146
+ "type": "string",
147
+ "default": "http://localhost:8080",
148
+ "description": "External authorization service URL for login initiation.",
149
+ },
150
+ {
151
+ "name": "external_auth_provider",
152
+ "required": False,
153
+ "type": "string",
154
+ "default": "",
155
+ "description": "The external authentication provider.",
156
+ },
157
+ {
158
+ "name": "ssl_keyfile",
159
+ "required": False,
160
+ "type": "string",
161
+ "default": "",
162
+ "description": "The file path to the SSL private key.",
163
+ },
164
+ {
165
+ "name": "ssl_certfile",
166
+ "required": False,
167
+ "type": "string",
168
+ "default": "",
169
+ "description": "The file path to the SSL certificate.",
170
+ },
171
+ {
172
+ "name": "ssl_keyfile_password",
173
+ "required": False,
174
+ "type": "string",
175
+ "default": "",
176
+ "description": "The passphrase for the SSL private key.",
177
+ },
178
+ ]
116
179
 
117
180
  def __init__(self, app_info: Dict[str, Any], **kwargs):
118
181
  """
@@ -123,8 +186,7 @@ class WebUIBackendApp(BaseGatewayApp):
123
186
  "%s Initializing WebUIBackendApp...",
124
187
  app_info.get("name", "WebUIBackendApp"),
125
188
  )
126
-
127
- super().__init__(app_info, gateway_app_config=WebUIBackendAppConfig, **kwargs)
189
+ super().__init__(app_info, **kwargs)
128
190
 
129
191
  try:
130
192
 
@@ -155,4 +217,4 @@ class WebUIBackendApp(BaseGatewayApp):
155
217
  log.debug("%s WebUIBackendApp initialization complete.", self.name)
156
218
 
157
219
  def _get_gateway_component_class(self) -> type[BaseGatewayComponent]:
158
- return WebUIBackendComponent
220
+ return WebUIBackendComponent
@@ -22,12 +22,8 @@ from ...gateway.http_sse.services.people_service import PeopleService
22
22
  from ...gateway.http_sse.services.task_service import TaskService
23
23
  from ...gateway.http_sse.session_manager import SessionManager
24
24
  from ...gateway.http_sse.sse_manager import SSEManager
25
+ from .repository import Message, MessageRepository, SessionRepository
25
26
  from .services.session_service import SessionService
26
- from .repository import (
27
- Message,
28
- MessageRepository,
29
- SessionRepository,
30
- )
31
27
 
32
28
  try:
33
29
  from google.adk.artifacts import BaseArtifactService
@@ -369,7 +365,6 @@ def get_db() -> Generator[Session, None, None]:
369
365
  db.close()
370
366
 
371
367
 
372
-
373
368
  def get_session_business_service(
374
369
  db: Session = Depends(get_db),
375
370
  component: "WebUIBackendComponent" = Depends(get_sac_component),
@@ -391,20 +386,29 @@ def create_session_service_with_transaction():
391
386
  try:
392
387
  session_repository = SessionRepository(db)
393
388
  message_repository = MessageRepository(db)
394
-
389
+
395
390
  # Create a simple data access object for transaction contexts
396
391
  # This provides the basic repository operations without business logic
397
392
  class SessionDataAccess:
398
393
  def __init__(self, session_repo, message_repo):
399
394
  self.session_repository = session_repo
400
395
  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):
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
+ ):
403
406
  # Simple data access - just save the message
404
407
  from uuid import uuid4
405
- from datetime import datetime, timezone
408
+
406
409
  from .shared.enums import MessageType
407
-
410
+ from .shared import now_epoch_ms
411
+
408
412
  message_entity = Message(
409
413
  id=str(uuid4()),
410
414
  session_id=session_id,
@@ -412,36 +416,38 @@ def create_session_service_with_transaction():
412
416
  sender_type=sender_type,
413
417
  sender_name=sender_name,
414
418
  message_type=MessageType.TEXT,
415
- created_at=datetime.now(timezone.utc),
419
+ created_time=now_epoch_ms(),
416
420
  )
417
421
  return self.message_repository.save(message_entity)
418
-
422
+
419
423
  def get_session(self, session_id, user_id):
420
424
  # Use the session repository to find the session
421
425
  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):
426
+
427
+ def create_session(
428
+ self, user_id, name=None, agent_id=None, session_id=None
429
+ ):
424
430
  # Create a new session using the session repository
425
431
  from uuid import uuid4
426
- from datetime import datetime, timezone
432
+
427
433
  from .repository.entities import Session
428
-
434
+ from .shared import now_epoch_ms
435
+
429
436
  if not session_id:
430
437
  session_id = str(uuid4())
431
-
438
+
432
439
  # Leave name as None/empty - frontend will generate display name if needed
433
-
434
- now = datetime.now(timezone.utc)
440
+
441
+ now_ms = now_epoch_ms()
435
442
  session = Session(
436
443
  id=session_id,
437
444
  user_id=user_id,
438
445
  name=name,
439
446
  agent_id=agent_id,
440
- created_at=now,
441
- updated_at=now,
442
- last_activity=now,
447
+ created_time=now_ms,
448
+ updated_time=now_ms,
443
449
  )
444
-
450
+
445
451
  return self.session_repository.save(session)
446
452
 
447
453
  session_service = SessionDataAccess(session_repository, message_repository)
@@ -463,6 +463,25 @@ def _setup_routers() -> None:
463
463
  app.include_router(auth.router, prefix=api_prefix, tags=["Auth"])
464
464
  log.info("Legacy routers mounted for endpoints not yet migrated")
465
465
 
466
+ try:
467
+ from solace_agent_mesh_enterprise.webui_backend.routers import get_enterprise_routers
468
+
469
+ enterprise_routers = get_enterprise_routers()
470
+ for router_config in enterprise_routers:
471
+ app.include_router(
472
+ router_config["router"],
473
+ prefix=router_config["prefix"],
474
+ tags=router_config["tags"]
475
+ )
476
+ log.info("Mounted %d enterprise routers", len(enterprise_routers))
477
+
478
+ except ImportError:
479
+ log.debug("No enterprise package detected - skipping enterprise routers")
480
+ except ModuleNotFoundError:
481
+ log.debug("Enterprise router module not found - skipping enterprise routers")
482
+ except Exception as e:
483
+ log.warning("Failed to load enterprise routers: %s", e)
484
+
466
485
 
467
486
  def _setup_static_files() -> None:
468
487
  current_dir = os.path.dirname(os.path.abspath(__file__))
@@ -2,8 +2,6 @@
2
2
  Message domain entity.
3
3
  """
4
4
 
5
- from datetime import datetime
6
-
7
5
  from pydantic import BaseModel
8
6
 
9
7
  from ...shared.enums import MessageType, SenderType
@@ -12,14 +10,14 @@ from ...shared.types import MessageId, SessionId
12
10
 
13
11
  class Message(BaseModel):
14
12
  """Message domain entity with business logic."""
15
-
13
+
16
14
  id: MessageId
17
15
  session_id: SessionId
18
16
  message: str
19
17
  sender_type: SenderType
20
18
  sender_name: str
21
19
  message_type: MessageType = MessageType.TEXT
22
- created_at: datetime
20
+ created_time: int
23
21
 
24
22
  def validate_message_content(self) -> None:
25
23
  """Validate message content."""
@@ -38,4 +36,4 @@ class Message(BaseModel):
38
36
 
39
37
  def is_system_message(self) -> bool:
40
38
  """Check if message is a system message."""
41
- return self.sender_type == SenderType.SYSTEM
39
+ return self.sender_type == SenderType.SYSTEM
@@ -2,23 +2,21 @@
2
2
  Session domain entity.
3
3
  """
4
4
 
5
- from datetime import datetime, timezone
6
-
7
5
  from pydantic import BaseModel
8
6
 
7
+ from ...shared import now_epoch_ms
9
8
  from ...shared.types import AgentId, SessionId, UserId
10
9
 
11
10
 
12
11
  class Session(BaseModel):
13
12
  """Session domain entity with business logic."""
14
-
13
+
15
14
  id: SessionId
16
15
  user_id: UserId
17
16
  name: str | None = None
18
17
  agent_id: AgentId | None = None
19
- created_at: datetime
20
- updated_at: datetime | None = None
21
- last_activity: datetime | None = None
18
+ created_time: int
19
+ updated_time: int | None = None
22
20
 
23
21
  def update_name(self, new_name: str) -> None:
24
22
  """Update session name with validation."""
@@ -28,13 +26,11 @@ class Session(BaseModel):
28
26
  raise ValueError("Session name cannot exceed 255 characters")
29
27
 
30
28
  self.name = new_name.strip()
31
- self.updated_at = datetime.now(timezone.utc)
29
+ self.updated_time = now_epoch_ms()
32
30
 
33
31
  def mark_activity(self) -> None:
34
32
  """Mark session as having recent activity."""
35
- self.last_activity = datetime.now(timezone.utc)
36
- self.updated_at = datetime.now(timezone.utc)
37
-
33
+ self.updated_time = now_epoch_ms()
38
34
 
39
35
  def can_be_deleted_by_user(self, user_id: UserId) -> bool:
40
36
  """Check if user can delete this session."""
@@ -42,4 +38,4 @@ class Session(BaseModel):
42
38
 
43
39
  def can_be_accessed_by_user(self, user_id: UserId) -> bool:
44
40
  """Check if user can access this session."""
45
- return self.user_id == user_id
41
+ return self.user_id == user_id
@@ -13,7 +13,7 @@ from .models import MessageModel
13
13
 
14
14
  class MessageRepository(IMessageRepository):
15
15
  """SQLAlchemy implementation of message repository."""
16
-
16
+
17
17
  def __init__(self, db: DBSession):
18
18
  self.db = db
19
19
 
@@ -21,18 +21,22 @@ class MessageRepository(IMessageRepository):
21
21
  self, session_id: SessionId, pagination: PaginationInfo | None = None
22
22
  ) -> list[Message]:
23
23
  """Find all messages in a session."""
24
- query = self.db.query(MessageModel).filter(MessageModel.session_id == session_id)
24
+ query = self.db.query(MessageModel).filter(
25
+ MessageModel.session_id == session_id
26
+ )
25
27
 
26
28
  if pagination:
27
29
  offset = (pagination.page - 1) * pagination.page_size
28
30
  query = query.offset(offset).limit(pagination.page_size)
29
31
 
30
- models = query.order_by(MessageModel.created_at.asc()).all()
32
+ models = query.order_by(MessageModel.created_time.asc()).all()
31
33
  return [self._model_to_entity(model) for model in models]
32
34
 
33
35
  def save(self, message: Message) -> Message:
34
36
  """Save or update a message."""
35
- model = self.db.query(MessageModel).filter(MessageModel.id == message.id).first()
37
+ model = (
38
+ self.db.query(MessageModel).filter(MessageModel.id == message.id).first()
39
+ )
36
40
 
37
41
  if model:
38
42
  # Update existing
@@ -47,7 +51,7 @@ class MessageRepository(IMessageRepository):
47
51
  message=message.message,
48
52
  sender_type=message.sender_type.value,
49
53
  sender_name=message.sender_name,
50
- created_at=message.created_at,
54
+ created_time=message.created_time,
51
55
  )
52
56
  self.db.add(model)
53
57
 
@@ -74,5 +78,5 @@ class MessageRepository(IMessageRepository):
74
78
  sender_type=SenderType(model.sender_type),
75
79
  sender_name=model.sender_name,
76
80
  message_type=MessageType.TEXT, # Default for now
77
- created_at=model.created_at,
78
- )
81
+ created_time=model.created_time,
82
+ )
@@ -2,26 +2,26 @@
2
2
  Message SQLAlchemy model.
3
3
  """
4
4
 
5
- from sqlalchemy import Column, DateTime, ForeignKey, String, Text
5
+ from sqlalchemy import BigInteger, Column, ForeignKey, String, Text
6
6
  from sqlalchemy.orm import relationship
7
- from sqlalchemy.sql import func
8
7
 
8
+ from ...shared import now_epoch_ms
9
9
  from .base import Base
10
10
 
11
11
 
12
12
  class MessageModel(Base):
13
13
  """SQLAlchemy model for messages."""
14
-
14
+
15
15
  __tablename__ = "chat_messages"
16
-
16
+
17
17
  id = Column(String, primary_key=True)
18
18
  session_id = Column(
19
19
  String, ForeignKey("sessions.id", ondelete="CASCADE"), nullable=False
20
20
  )
21
21
  message = Column(Text, nullable=False)
22
- created_at = Column(DateTime, default=func.now())
22
+ created_time = Column(BigInteger, nullable=False, default=now_epoch_ms)
23
23
  sender_type = Column(String(50))
24
24
  sender_name = Column(String(255))
25
-
25
+
26
26
  # Relationship to session
27
- session = relationship("SessionModel", back_populates="messages")
27
+ session = relationship("SessionModel", back_populates="messages")
@@ -2,26 +2,28 @@
2
2
  Session SQLAlchemy model.
3
3
  """
4
4
 
5
- from sqlalchemy import Column, DateTime, String
5
+ from sqlalchemy import BigInteger, Column, String
6
6
  from sqlalchemy.orm import relationship
7
- from sqlalchemy.sql import func
8
7
 
8
+ from ...shared import now_epoch_ms
9
9
  from .base import Base
10
10
 
11
11
 
12
12
  class SessionModel(Base):
13
13
  """SQLAlchemy model for sessions."""
14
-
14
+
15
15
  __tablename__ = "sessions"
16
-
16
+
17
17
  id = Column(String, primary_key=True)
18
18
  name = Column(String, nullable=True)
19
19
  user_id = Column(String, nullable=False)
20
20
  agent_id = Column(String, nullable=True)
21
- created_at = Column(DateTime, default=func.now())
22
- updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
23
-
21
+ created_time = Column(BigInteger, nullable=False, default=now_epoch_ms)
22
+ updated_time = Column(
23
+ BigInteger, nullable=False, default=now_epoch_ms, onupdate=now_epoch_ms
24
+ )
25
+
24
26
  # Relationship to messages
25
27
  messages = relationship(
26
28
  "MessageModel", back_populates="session", cascade="all, delete-orphan"
27
- )
29
+ )