solace-agent-mesh 1.4.1__py3-none-any.whl → 1.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of solace-agent-mesh might be problematic. Click here for more details.
- solace_agent_mesh/agent/adk/services.py +10 -3
- solace_agent_mesh/agent/sac/app.py +4 -1
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/beecea0d.8bbd852c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{main.9bc1a102.js → main.66eec320.js} +2 -2
- solace_agent_mesh/assets/docs/assets/js/{runtime~main.f2b4ea70.js → runtime~main.355446b2.js} +1 -1
- solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +3 -3
- 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
- solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +6 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +3 -3
- solace_agent_mesh/assets/docs/lunr-index-1758138634356.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1758138634356.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -1
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-8xbvgfVK.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-Cv2k8j3R.js +339 -0
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +13 -13
- solace_agent_mesh/client/webui/frontend/static/index.html +13 -13
- solace_agent_mesh/gateway/base/app.py +121 -70
- solace_agent_mesh/gateway/http_sse/alembic/versions/20250916_f6e7d8c9b0a1_convert_timestamps_to_epoch_and_align_columns.py +342 -0
- solace_agent_mesh/gateway/http_sse/alembic.ini +4 -6
- solace_agent_mesh/gateway/http_sse/app.py +151 -100
- solace_agent_mesh/gateway/http_sse/dependencies.py +30 -24
- solace_agent_mesh/gateway/http_sse/repository/entities/message.py +3 -5
- solace_agent_mesh/gateway/http_sse/repository/entities/session.py +7 -11
- solace_agent_mesh/gateway/http_sse/repository/message_repository.py +11 -7
- solace_agent_mesh/gateway/http_sse/repository/models/message_model.py +7 -7
- solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +10 -8
- solace_agent_mesh/gateway/http_sse/repository/session_repository.py +18 -14
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/base_responses.py +42 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +25 -23
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +10 -19
- solace_agent_mesh/gateway/http_sse/services/session_service.py +5 -6
- solace_agent_mesh/gateway/http_sse/shared/__init__.py +17 -1
- solace_agent_mesh/gateway/http_sse/shared/timestamp_utils.py +97 -0
- solace_agent_mesh/gateway/http_sse/shared/types.py +23 -7
- {solace_agent_mesh-1.4.1.dist-info → solace_agent_mesh-1.4.3.dist-info}/METADATA +1 -1
- {solace_agent_mesh-1.4.1.dist-info → solace_agent_mesh-1.4.3.dist-info}/RECORD +79 -76
- solace_agent_mesh/assets/docs/assets/js/beecea0d.ae31f6a7.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1758036158289.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1758036158289.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-B6BpuH9K.js +0 -339
- solace_agent_mesh/client/webui/frontend/static/assets/main-B9s_V9tJ.css +0 -1
- /solace_agent_mesh/assets/docs/assets/js/{main.9bc1a102.js.LICENSE.txt → main.66eec320.js.LICENSE.txt} +0 -0
- /solace_agent_mesh/gateway/http_sse/alembic/versions/{d5b3f8f2e9a0_create_initial_database.py → 20250910_d5b3f8f2e9a0_create_initial_database.py} +0 -0
- /solace_agent_mesh/gateway/http_sse/alembic/versions/{b1c2d3e4f5g6_add_database_indexes.py → 20250911_b1c2d3e4f5g6_add_database_indexes.py} +0 -0
- {solace_agent_mesh-1.4.1.dist-info → solace_agent_mesh-1.4.3.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.4.1.dist-info → solace_agent_mesh-1.4.3.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.4.1.dist-info → solace_agent_mesh-1.4.3.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
|
|
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
|
|
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
|
-
|
|
115
|
-
|
|
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,18 +186,6 @@ class WebUIBackendApp(BaseGatewayApp):
|
|
|
123
186
|
"%s Initializing WebUIBackendApp...",
|
|
124
187
|
app_info.get("name", "WebUIBackendApp"),
|
|
125
188
|
)
|
|
126
|
-
|
|
127
|
-
app_config_dict = app_info.get("app_config", {})
|
|
128
|
-
try:
|
|
129
|
-
# Validate the raw dict, cleaning None values to allow defaults to apply
|
|
130
|
-
app_config = WebUIBackendAppConfig.model_validate_and_clean(
|
|
131
|
-
app_config_dict
|
|
132
|
-
)
|
|
133
|
-
app_info["app_config"] = app_config
|
|
134
|
-
except ValidationError as e:
|
|
135
|
-
log.error("Web UI Gateway configuration validation failed:\n%s", e)
|
|
136
|
-
raise ValueError(f"Invalid Web UI Gateway configuration: {e}") from e
|
|
137
|
-
|
|
138
189
|
super().__init__(app_info, **kwargs)
|
|
139
190
|
|
|
140
191
|
try:
|
|
@@ -166,4 +217,4 @@ class WebUIBackendApp(BaseGatewayApp):
|
|
|
166
217
|
log.debug("%s WebUIBackendApp initialization complete.", self.name)
|
|
167
218
|
|
|
168
219
|
def _get_gateway_component_class(self) -> type[BaseGatewayComponent]:
|
|
169
|
-
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
441
|
-
|
|
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)
|
|
@@ -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
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
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.
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
22
|
-
|
|
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
|
+
)
|