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.
- solace_agent_mesh/agent/adk/services.py +10 -3
- solace_agent_mesh/agent/sac/app.py +5 -1
- solace_agent_mesh/agent/sac/component.py +7 -3
- solace_agent_mesh/agent/tools/builtin_artifact_tools.py +17 -0
- solace_agent_mesh/agent/tools/builtin_data_analysis_tools.py +4 -0
- solace_agent_mesh/agent/tools/tool_definition.py +8 -0
- solace_agent_mesh/agent/tools/web_tools.py +5 -0
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/{ae0e903d.abca774a.js → ae0e903d.ac3b9419.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/beecea0d.8bbd852c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{main.1a0c706b.js → main.03fb0598.js} +2 -2
- solace_agent_mesh/assets/docs/assets/js/{runtime~main.f2b4ea70.js → runtime~main.4ee39c6f.js} +1 -1
- solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +5 -5
- 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-1758293998763.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1758293998763.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 +122 -71
- 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 +152 -90
- solace_agent_mesh/gateway/http_sse/dependencies.py +30 -24
- solace_agent_mesh/gateway/http_sse/main.py +19 -0
- 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.2.dist-info → solace_agent_mesh-1.4.4.dist-info}/METADATA +1 -1
- {solace_agent_mesh-1.4.2.dist-info → solace_agent_mesh-1.4.4.dist-info}/RECORD +86 -83
- solace_agent_mesh/assets/docs/assets/js/beecea0d.ae31f6a7.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1758046853673.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1758046853673.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.1a0c706b.js.LICENSE.txt → main.03fb0598.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.2.dist-info → solace_agent_mesh-1.4.4.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.4.2.dist-info → solace_agent_mesh-1.4.4.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.4.2.dist-info → solace_agent_mesh-1.4.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/assets/favicon-BLgzUch9.ico" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Solace Agent Mesh</title>
|
|
8
|
+
<script type="module" crossorigin src="/assets/authCallback-j1LW-wlq.js"></script>
|
|
9
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-CS5YMf8a.js">
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/client-B9p_nFNA.js">
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="root"></div>
|
|
14
|
+
</body>
|
|
15
15
|
</html>
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/assets/favicon-BLgzUch9.ico" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Solace Agent Mesh</title>
|
|
8
|
+
<script type="module" crossorigin src="/assets/main-Cv2k8j3R.js"></script>
|
|
9
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-CS5YMf8a.js">
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/client-B9p_nFNA.js">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/main-8xbvgfVK.css">
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div id="root"></div>
|
|
15
|
+
</body>
|
|
16
16
|
</html>
|
|
@@ -4,15 +4,13 @@ Base App class for Gateway implementations in the Solace AI Connector.
|
|
|
4
4
|
|
|
5
5
|
import uuid
|
|
6
6
|
from abc import abstractmethod
|
|
7
|
-
from typing import Any, Dict, List, Type
|
|
7
|
+
from typing import Any, Dict, List, Type
|
|
8
8
|
|
|
9
|
-
from pydantic import Field, ValidationError
|
|
10
9
|
from solace_ai_connector.common.log import log
|
|
11
10
|
from solace_ai_connector.common.utils import deep_merge
|
|
12
11
|
from solace_ai_connector.flow.app import App
|
|
13
12
|
from solace_ai_connector.components.component_base import ComponentBase
|
|
14
13
|
|
|
15
|
-
from ...common.utils.pydantic_utils import SamConfigBase
|
|
16
14
|
from ...common.a2a import (
|
|
17
15
|
get_discovery_topic,
|
|
18
16
|
get_gateway_response_subscription_topic,
|
|
@@ -24,58 +22,91 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
24
22
|
pass
|
|
25
23
|
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"'
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
25
|
+
BASE_GATEWAY_APP_SCHEMA: Dict[str, List[Dict[str, Any]]] = {
|
|
26
|
+
"config_parameters": [
|
|
27
|
+
{
|
|
28
|
+
"name": "namespace",
|
|
29
|
+
"required": True,
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "Absolute topic prefix for A2A communication (e.g., 'myorg/dev').",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "gateway_id",
|
|
35
|
+
"required": False,
|
|
36
|
+
"type": "string",
|
|
37
|
+
"default": None,
|
|
38
|
+
"description": "Unique ID for this gateway instance. Auto-generated if omitted.",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "artifact_service",
|
|
42
|
+
"required": True,
|
|
43
|
+
"type": "object",
|
|
44
|
+
"description": "Configuration for the SHARED ADK Artifact Service.",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "enable_embed_resolution",
|
|
48
|
+
"required": False,
|
|
49
|
+
"type": "boolean",
|
|
50
|
+
"default": True,
|
|
51
|
+
"description": "Enable late-stage 'artifact_content' embed resolution in the gateway.",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "gateway_max_artifact_resolve_size_bytes",
|
|
55
|
+
"required": False,
|
|
56
|
+
"type": "integer",
|
|
57
|
+
"default": 104857600, # 100MB
|
|
58
|
+
"description": "Maximum size of an individual artifact's raw content for 'artifact_content' embeds and max total accumulated size for a parent artifact after internal recursive resolution.",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "gateway_recursive_embed_depth",
|
|
62
|
+
"required": False,
|
|
63
|
+
"type": "integer",
|
|
64
|
+
"default": 12,
|
|
65
|
+
"description": "Maximum depth for recursively resolving 'artifact_content' embeds within files.",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "artifact_handling_mode",
|
|
69
|
+
"required": False,
|
|
70
|
+
"type": "string",
|
|
71
|
+
"default": "reference",
|
|
72
|
+
"description": (
|
|
73
|
+
"How the gateway handles file parts from clients. "
|
|
74
|
+
"'reference': Save inline file bytes to the artifact store and replace with a URI. "
|
|
75
|
+
"'embed': Resolve file URIs and embed content as bytes. "
|
|
76
|
+
"'passthrough': Send file parts to the agent as-is."
|
|
77
|
+
),
|
|
78
|
+
"enum": ["reference", "embed", "passthrough"],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "gateway_max_message_size_bytes",
|
|
82
|
+
"required": False,
|
|
83
|
+
"type": "integer",
|
|
84
|
+
"default": 10_000_000, # 10MB
|
|
85
|
+
"description": "Maximum allowed message size in bytes for messages published by the gateway.",
|
|
86
|
+
},
|
|
87
|
+
# --- Default User Identity Configuration ---
|
|
88
|
+
{
|
|
89
|
+
"name": "default_user_identity",
|
|
90
|
+
"required": False,
|
|
91
|
+
"type": "string",
|
|
92
|
+
"description": "Default user identity to use when no user authentication is provided. WARNING: Only use in development environments with trusted access!",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"name": "force_user_identity",
|
|
96
|
+
"required": False,
|
|
97
|
+
"type": "string",
|
|
98
|
+
"description": "Override any provided user identity with this value. WARNING: Development only! This completely replaces authentication.",
|
|
99
|
+
},
|
|
100
|
+
# --- Identity Service Configuration ---
|
|
101
|
+
{
|
|
102
|
+
"name": "identity_service",
|
|
103
|
+
"required": False,
|
|
104
|
+
"type": "object",
|
|
105
|
+
"default": None,
|
|
106
|
+
"description": "Configuration for the pluggable Identity Service provider.",
|
|
107
|
+
},
|
|
108
|
+
]
|
|
109
|
+
}
|
|
79
110
|
|
|
80
111
|
|
|
81
112
|
class BaseGatewayApp(App):
|
|
@@ -83,14 +114,45 @@ class BaseGatewayApp(App):
|
|
|
83
114
|
Base class for Gateway applications.
|
|
84
115
|
|
|
85
116
|
Handles common configuration, Solace broker setup, and instantiation
|
|
86
|
-
of the gateway-specific component.
|
|
117
|
+
of the gateway-specific component. It also automatically merges its
|
|
118
|
+
base schema with specific schema parameters defined by subclasses.
|
|
87
119
|
"""
|
|
88
120
|
|
|
89
|
-
|
|
90
|
-
app_schema: Dict[str, List[Dict[str, Any]]] = {"config_parameters": []}
|
|
121
|
+
app_schema: Dict[str, List[Dict[str, Any]]] = BASE_GATEWAY_APP_SCHEMA
|
|
91
122
|
SPECIFIC_APP_SCHEMA_PARAMS_ATTRIBUTE_NAME = "SPECIFIC_APP_SCHEMA_PARAMS"
|
|
92
123
|
|
|
93
|
-
def
|
|
124
|
+
def __init_subclass__(cls, **kwargs):
|
|
125
|
+
"""
|
|
126
|
+
Automatically merges the base gateway schema with specific schema
|
|
127
|
+
parameters defined in any subclass.
|
|
128
|
+
"""
|
|
129
|
+
super().__init_subclass__(**kwargs)
|
|
130
|
+
|
|
131
|
+
specific_params = getattr(
|
|
132
|
+
cls, cls.SPECIFIC_APP_SCHEMA_PARAMS_ATTRIBUTE_NAME, []
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if not isinstance(specific_params, list):
|
|
136
|
+
log.warning(
|
|
137
|
+
"Class attribute '%s' in %s is not a list. Schema merging might be incorrect.",
|
|
138
|
+
cls.SPECIFIC_APP_SCHEMA_PARAMS_ATTRIBUTE_NAME,
|
|
139
|
+
cls.__name__,
|
|
140
|
+
)
|
|
141
|
+
specific_params = []
|
|
142
|
+
|
|
143
|
+
base_params = BaseGatewayApp.app_schema.get("config_parameters", [])
|
|
144
|
+
|
|
145
|
+
merged_config_parameters = list(base_params)
|
|
146
|
+
merged_config_parameters.extend(specific_params)
|
|
147
|
+
|
|
148
|
+
cls.app_schema = {"config_parameters": merged_config_parameters}
|
|
149
|
+
log.debug(
|
|
150
|
+
"BaseGatewayApp.__init_subclass__ created merged app_schema for %s with %d params.",
|
|
151
|
+
cls.__name__,
|
|
152
|
+
len(merged_config_parameters),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def __init__(self, app_info: Dict[str, Any], **kwargs):
|
|
94
156
|
"""
|
|
95
157
|
Initializes the BaseGatewayApp.
|
|
96
158
|
|
|
@@ -111,17 +173,6 @@ class BaseGatewayApp(App):
|
|
|
111
173
|
code_config_app_block, yaml_app_config_block
|
|
112
174
|
)
|
|
113
175
|
|
|
114
|
-
try:
|
|
115
|
-
# Validate the configuration against the base model
|
|
116
|
-
validated_config = gateway_app_config.model_validate_and_clean(
|
|
117
|
-
resolved_app_config_block
|
|
118
|
-
)
|
|
119
|
-
# Use the validated model's dict representation
|
|
120
|
-
resolved_app_config_block = validated_config
|
|
121
|
-
except ValidationError as e:
|
|
122
|
-
log.error("Base Gateway configuration validation failed:\n%s", e)
|
|
123
|
-
raise ValueError(f"Invalid Base Gateway configuration: {e}") from e
|
|
124
|
-
|
|
125
176
|
self.namespace: str = resolved_app_config_block.get("namespace")
|
|
126
177
|
if not self.namespace:
|
|
127
178
|
raise ValueError(
|
|
@@ -238,4 +289,4 @@ class BaseGatewayApp(App):
|
|
|
238
289
|
Returns:
|
|
239
290
|
The specific gateway component class (e.g., WebUIBackendComponent).
|
|
240
291
|
"""
|
|
241
|
-
pass
|
|
292
|
+
pass
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""Convert timestamps to epoch milliseconds and align column names
|
|
2
|
+
|
|
3
|
+
Revision ID: f6e7d8c9b0a1
|
|
4
|
+
Revises: b1c2d3e4f5g6
|
|
5
|
+
Create Date: 2025-09-16 16:30:00.000000
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
|
|
11
|
+
import sqlalchemy as sa
|
|
12
|
+
from alembic import op
|
|
13
|
+
|
|
14
|
+
# revision identifiers, used by Alembic.
|
|
15
|
+
revision: str = "f6e7d8c9b0a1"
|
|
16
|
+
down_revision: str | None = "b1c2d3e4f5g6"
|
|
17
|
+
branch_labels: str | Sequence[str] | None = None
|
|
18
|
+
depends_on: str | Sequence[str] | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def upgrade() -> None:
|
|
22
|
+
"""Convert datetime columns to epoch milliseconds and rename columns."""
|
|
23
|
+
from sqlalchemy import inspect
|
|
24
|
+
import time
|
|
25
|
+
|
|
26
|
+
bind = op.get_bind()
|
|
27
|
+
inspector = inspect(bind)
|
|
28
|
+
current_time_ms = int(time.time() * 1000)
|
|
29
|
+
|
|
30
|
+
if bind.dialect.name == 'sqlite':
|
|
31
|
+
# SQLite doesn't support ALTER COLUMN, so we need to recreate tables
|
|
32
|
+
_upgrade_sqlite(current_time_ms)
|
|
33
|
+
else:
|
|
34
|
+
# PostgreSQL, MySQL, and other databases support ALTER COLUMN
|
|
35
|
+
_upgrade_standard_sql(current_time_ms)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _upgrade_sqlite(current_time_ms: int) -> None:
|
|
39
|
+
"""Handle SQLite upgrade by recreating tables (SQLite doesn't support dropping columns)."""
|
|
40
|
+
|
|
41
|
+
# 1. Create new sessions table with epoch timestamp columns
|
|
42
|
+
op.create_table(
|
|
43
|
+
'sessions_new',
|
|
44
|
+
sa.Column('id', sa.String(), nullable=False),
|
|
45
|
+
sa.Column('name', sa.String(), nullable=True),
|
|
46
|
+
sa.Column('user_id', sa.String(), nullable=False),
|
|
47
|
+
sa.Column('agent_id', sa.String(), nullable=True),
|
|
48
|
+
sa.Column('created_time', sa.BigInteger(), nullable=False),
|
|
49
|
+
sa.Column('updated_time', sa.BigInteger(), nullable=False),
|
|
50
|
+
sa.PrimaryKeyConstraint('id')
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# 2. Copy data from old table with timestamp conversion
|
|
54
|
+
op.execute(f"""
|
|
55
|
+
INSERT INTO sessions_new (id, name, user_id, agent_id, created_time, updated_time)
|
|
56
|
+
SELECT
|
|
57
|
+
id,
|
|
58
|
+
name,
|
|
59
|
+
user_id,
|
|
60
|
+
agent_id,
|
|
61
|
+
COALESCE(CAST(strftime('%s', created_at) * 1000 AS INTEGER), {current_time_ms}) as created_time,
|
|
62
|
+
COALESCE(CAST(strftime('%s', updated_at) * 1000 AS INTEGER), {current_time_ms}) as updated_time
|
|
63
|
+
FROM sessions
|
|
64
|
+
""")
|
|
65
|
+
|
|
66
|
+
# 3. Drop old table and rename new table
|
|
67
|
+
op.drop_table('sessions')
|
|
68
|
+
op.rename_table('sessions_new', 'sessions')
|
|
69
|
+
|
|
70
|
+
# 4. Create new chat_messages table with epoch timestamp column
|
|
71
|
+
op.create_table(
|
|
72
|
+
'chat_messages_new',
|
|
73
|
+
sa.Column('id', sa.String(), nullable=False),
|
|
74
|
+
sa.Column('session_id', sa.String(), nullable=False),
|
|
75
|
+
sa.Column('message', sa.Text(), nullable=False),
|
|
76
|
+
sa.Column('sender_type', sa.String(), nullable=True),
|
|
77
|
+
sa.Column('sender_name', sa.String(), nullable=True),
|
|
78
|
+
sa.Column('created_time', sa.BigInteger(), nullable=False),
|
|
79
|
+
sa.PrimaryKeyConstraint('id'),
|
|
80
|
+
sa.ForeignKeyConstraint(['session_id'], ['sessions.id'], ondelete='CASCADE')
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# 5. Copy data from old chat_messages table with timestamp conversion
|
|
84
|
+
op.execute(f"""
|
|
85
|
+
INSERT INTO chat_messages_new (id, session_id, message, sender_type, sender_name, created_time)
|
|
86
|
+
SELECT
|
|
87
|
+
id,
|
|
88
|
+
session_id,
|
|
89
|
+
message,
|
|
90
|
+
sender_type,
|
|
91
|
+
sender_name,
|
|
92
|
+
COALESCE(CAST(strftime('%s', created_at) * 1000 AS INTEGER), {current_time_ms}) as created_time
|
|
93
|
+
FROM chat_messages
|
|
94
|
+
""")
|
|
95
|
+
|
|
96
|
+
# 6. Drop old table and rename new table
|
|
97
|
+
op.drop_table('chat_messages')
|
|
98
|
+
op.rename_table('chat_messages_new', 'chat_messages')
|
|
99
|
+
|
|
100
|
+
# 7. Create indexes
|
|
101
|
+
_create_updated_indexes()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _upgrade_standard_sql(current_time_ms: int) -> None:
|
|
105
|
+
"""Handle PostgreSQL/MySQL upgrade using ALTER COLUMN (standard SQL approach)."""
|
|
106
|
+
|
|
107
|
+
# For sessions table
|
|
108
|
+
op.add_column("sessions", sa.Column("created_time", sa.BigInteger(), nullable=True))
|
|
109
|
+
op.add_column("sessions", sa.Column("updated_time", sa.BigInteger(), nullable=True))
|
|
110
|
+
|
|
111
|
+
# Convert timestamps using EXTRACT function
|
|
112
|
+
op.execute("""
|
|
113
|
+
UPDATE sessions
|
|
114
|
+
SET created_time = CAST(EXTRACT(EPOCH FROM created_at) * 1000 AS BIGINT)
|
|
115
|
+
WHERE created_at IS NOT NULL
|
|
116
|
+
""")
|
|
117
|
+
|
|
118
|
+
op.execute("""
|
|
119
|
+
UPDATE sessions
|
|
120
|
+
SET updated_time = CAST(EXTRACT(EPOCH FROM updated_at) * 1000 AS BIGINT)
|
|
121
|
+
WHERE updated_at IS NOT NULL
|
|
122
|
+
""")
|
|
123
|
+
|
|
124
|
+
# Set current epoch ms for null values
|
|
125
|
+
op.execute(f"""
|
|
126
|
+
UPDATE sessions
|
|
127
|
+
SET created_time = {current_time_ms}
|
|
128
|
+
WHERE created_time IS NULL
|
|
129
|
+
""")
|
|
130
|
+
|
|
131
|
+
op.execute(f"""
|
|
132
|
+
UPDATE sessions
|
|
133
|
+
SET updated_time = {current_time_ms}
|
|
134
|
+
WHERE updated_time IS NULL
|
|
135
|
+
""")
|
|
136
|
+
|
|
137
|
+
# Make new columns NOT NULL
|
|
138
|
+
op.alter_column("sessions", "created_time", nullable=False)
|
|
139
|
+
op.alter_column("sessions", "updated_time", nullable=False)
|
|
140
|
+
|
|
141
|
+
# Drop old columns
|
|
142
|
+
op.drop_column("sessions", "created_at")
|
|
143
|
+
op.drop_column("sessions", "updated_at")
|
|
144
|
+
|
|
145
|
+
# For chat_messages table
|
|
146
|
+
op.add_column("chat_messages", sa.Column("created_time", sa.BigInteger(), nullable=True))
|
|
147
|
+
|
|
148
|
+
op.execute("""
|
|
149
|
+
UPDATE chat_messages
|
|
150
|
+
SET created_time = CAST(EXTRACT(EPOCH FROM created_at) * 1000 AS BIGINT)
|
|
151
|
+
WHERE created_at IS NOT NULL
|
|
152
|
+
""")
|
|
153
|
+
|
|
154
|
+
op.execute(f"""
|
|
155
|
+
UPDATE chat_messages
|
|
156
|
+
SET created_time = {current_time_ms}
|
|
157
|
+
WHERE created_time IS NULL
|
|
158
|
+
""")
|
|
159
|
+
|
|
160
|
+
op.alter_column("chat_messages", "created_time", nullable=False)
|
|
161
|
+
op.drop_column("chat_messages", "created_at")
|
|
162
|
+
|
|
163
|
+
# Add indexes - this will be called after either upgrade path
|
|
164
|
+
_create_updated_indexes()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _create_updated_indexes() -> None:
|
|
168
|
+
"""Create indexes on new timestamp columns."""
|
|
169
|
+
|
|
170
|
+
# For SQLite, indexes are recreated when tables are recreated
|
|
171
|
+
# For other databases, we need to manage index transitions
|
|
172
|
+
|
|
173
|
+
bind = op.get_bind()
|
|
174
|
+
|
|
175
|
+
if bind.dialect.name == 'sqlite':
|
|
176
|
+
# SQLite: Create all indexes fresh (old ones were dropped with table recreation)
|
|
177
|
+
_create_indexes_safe("ix_sessions_user_id", "sessions", ["user_id"])
|
|
178
|
+
_create_indexes_safe("ix_sessions_agent_id", "sessions", ["agent_id"])
|
|
179
|
+
_create_indexes_safe("ix_sessions_updated_time", "sessions", ["updated_time"])
|
|
180
|
+
_create_indexes_safe("ix_sessions_user_id_updated_time", "sessions", ["user_id", "updated_time"])
|
|
181
|
+
|
|
182
|
+
_create_indexes_safe("ix_chat_messages_session_id", "chat_messages", ["session_id"])
|
|
183
|
+
_create_indexes_safe("ix_chat_messages_created_time", "chat_messages", ["created_time"])
|
|
184
|
+
_create_indexes_safe("ix_chat_messages_session_id_created_time", "chat_messages", ["session_id", "created_time"])
|
|
185
|
+
|
|
186
|
+
else:
|
|
187
|
+
# PostgreSQL/MySQL: Drop old indexes and create new ones
|
|
188
|
+
_drop_index_safe("ix_sessions_updated_at", "sessions")
|
|
189
|
+
_drop_index_safe("ix_sessions_user_id_updated_at", "sessions")
|
|
190
|
+
_drop_index_safe("ix_chat_messages_created_at", "chat_messages")
|
|
191
|
+
_drop_index_safe("ix_chat_messages_session_id_created_at", "chat_messages")
|
|
192
|
+
|
|
193
|
+
# Create new indexes (user_id and agent_id indexes already exist)
|
|
194
|
+
_create_indexes_safe("ix_sessions_updated_time", "sessions", ["updated_time"])
|
|
195
|
+
_create_indexes_safe("ix_sessions_user_id_updated_time", "sessions", ["user_id", "updated_time"])
|
|
196
|
+
_create_indexes_safe("ix_chat_messages_created_time", "chat_messages", ["created_time"])
|
|
197
|
+
_create_indexes_safe("ix_chat_messages_session_id_created_time", "chat_messages", ["session_id", "created_time"])
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _create_indexes_safe(index_name: str, table_name: str, columns: list) -> None:
|
|
201
|
+
"""Create index only if it doesn't exist."""
|
|
202
|
+
try:
|
|
203
|
+
op.create_index(index_name, table_name, columns)
|
|
204
|
+
except Exception:
|
|
205
|
+
pass # Index might already exist
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _drop_index_safe(index_name: str, table_name: str) -> None:
|
|
209
|
+
"""Drop index only if it exists."""
|
|
210
|
+
try:
|
|
211
|
+
op.drop_index(index_name, table_name=table_name)
|
|
212
|
+
except Exception:
|
|
213
|
+
pass # Index might not exist
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def downgrade() -> None:
|
|
217
|
+
"""Convert back to datetime columns with original names."""
|
|
218
|
+
bind = op.get_bind()
|
|
219
|
+
|
|
220
|
+
if bind.dialect.name == 'sqlite':
|
|
221
|
+
_downgrade_sqlite()
|
|
222
|
+
else:
|
|
223
|
+
_downgrade_standard_sql()
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _downgrade_sqlite() -> None:
|
|
227
|
+
"""Handle SQLite downgrade by recreating tables with original datetime columns."""
|
|
228
|
+
|
|
229
|
+
# 1. Create sessions table with original datetime columns
|
|
230
|
+
op.create_table(
|
|
231
|
+
'sessions_old',
|
|
232
|
+
sa.Column('id', sa.String(), nullable=False),
|
|
233
|
+
sa.Column('name', sa.String(), nullable=True),
|
|
234
|
+
sa.Column('user_id', sa.String(), nullable=False),
|
|
235
|
+
sa.Column('agent_id', sa.String(), nullable=True),
|
|
236
|
+
sa.Column('created_at', sa.DateTime(), nullable=True),
|
|
237
|
+
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
|
238
|
+
sa.PrimaryKeyConstraint('id')
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# 2. Copy data back with timestamp conversion
|
|
242
|
+
op.execute("""
|
|
243
|
+
INSERT INTO sessions_old (id, name, user_id, agent_id, created_at, updated_at)
|
|
244
|
+
SELECT
|
|
245
|
+
id,
|
|
246
|
+
name,
|
|
247
|
+
user_id,
|
|
248
|
+
agent_id,
|
|
249
|
+
datetime(created_time / 1000.0, 'unixepoch') as created_at,
|
|
250
|
+
datetime(updated_time / 1000.0, 'unixepoch') as updated_at
|
|
251
|
+
FROM sessions
|
|
252
|
+
""")
|
|
253
|
+
|
|
254
|
+
# 3. Drop new table and rename old table
|
|
255
|
+
op.drop_table('sessions')
|
|
256
|
+
op.rename_table('sessions_old', 'sessions')
|
|
257
|
+
|
|
258
|
+
# 4. Create chat_messages table with original datetime column
|
|
259
|
+
op.create_table(
|
|
260
|
+
'chat_messages_old',
|
|
261
|
+
sa.Column('id', sa.String(), nullable=False),
|
|
262
|
+
sa.Column('session_id', sa.String(), nullable=False),
|
|
263
|
+
sa.Column('message', sa.Text(), nullable=False),
|
|
264
|
+
sa.Column('sender_type', sa.String(), nullable=True),
|
|
265
|
+
sa.Column('sender_name', sa.String(), nullable=True),
|
|
266
|
+
sa.Column('created_at', sa.DateTime(), nullable=True),
|
|
267
|
+
sa.PrimaryKeyConstraint('id'),
|
|
268
|
+
sa.ForeignKeyConstraint(['session_id'], ['sessions.id'], ondelete='CASCADE')
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# 5. Copy data back with timestamp conversion
|
|
272
|
+
op.execute("""
|
|
273
|
+
INSERT INTO chat_messages_old (id, session_id, message, sender_type, sender_name, created_at)
|
|
274
|
+
SELECT
|
|
275
|
+
id,
|
|
276
|
+
session_id,
|
|
277
|
+
message,
|
|
278
|
+
sender_type,
|
|
279
|
+
sender_name,
|
|
280
|
+
datetime(created_time / 1000.0, 'unixepoch') as created_at
|
|
281
|
+
FROM chat_messages
|
|
282
|
+
""")
|
|
283
|
+
|
|
284
|
+
# 6. Drop new table and rename old table
|
|
285
|
+
op.drop_table('chat_messages')
|
|
286
|
+
op.rename_table('chat_messages_old', 'chat_messages')
|
|
287
|
+
|
|
288
|
+
# 7. Recreate original indexes
|
|
289
|
+
_create_indexes_safe("ix_sessions_user_id", "sessions", ["user_id"])
|
|
290
|
+
_create_indexes_safe("ix_sessions_agent_id", "sessions", ["agent_id"])
|
|
291
|
+
_create_indexes_safe("ix_sessions_updated_at", "sessions", ["updated_at"])
|
|
292
|
+
_create_indexes_safe("ix_sessions_user_id_updated_at", "sessions", ["user_id", "updated_at"])
|
|
293
|
+
_create_indexes_safe("ix_chat_messages_session_id", "chat_messages", ["session_id"])
|
|
294
|
+
_create_indexes_safe("ix_chat_messages_created_at", "chat_messages", ["created_at"])
|
|
295
|
+
_create_indexes_safe("ix_chat_messages_session_id_created_at", "chat_messages", ["session_id", "created_at"])
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _downgrade_standard_sql() -> None:
|
|
299
|
+
"""Handle PostgreSQL/MySQL downgrade using ALTER COLUMN."""
|
|
300
|
+
|
|
301
|
+
# Drop indexes on new columns
|
|
302
|
+
_drop_index_safe("ix_chat_messages_session_id_created_time", "chat_messages")
|
|
303
|
+
_drop_index_safe("ix_chat_messages_created_time", "chat_messages")
|
|
304
|
+
_drop_index_safe("ix_sessions_user_id_updated_time", "sessions")
|
|
305
|
+
_drop_index_safe("ix_sessions_updated_time", "sessions")
|
|
306
|
+
|
|
307
|
+
# For sessions table: convert back to datetime columns
|
|
308
|
+
op.add_column("sessions", sa.Column("created_at", sa.DateTime(), nullable=True))
|
|
309
|
+
op.add_column("sessions", sa.Column("updated_at", sa.DateTime(), nullable=True))
|
|
310
|
+
|
|
311
|
+
# Convert epoch milliseconds back to datetime
|
|
312
|
+
op.execute("""
|
|
313
|
+
UPDATE sessions
|
|
314
|
+
SET created_at = to_timestamp(created_time / 1000.0)
|
|
315
|
+
WHERE created_time IS NOT NULL
|
|
316
|
+
""")
|
|
317
|
+
|
|
318
|
+
op.execute("""
|
|
319
|
+
UPDATE sessions
|
|
320
|
+
SET updated_at = to_timestamp(updated_time / 1000.0)
|
|
321
|
+
WHERE updated_time IS NOT NULL
|
|
322
|
+
""")
|
|
323
|
+
|
|
324
|
+
op.drop_column("sessions", "created_time")
|
|
325
|
+
op.drop_column("sessions", "updated_time")
|
|
326
|
+
|
|
327
|
+
# For chat_messages table: convert back to datetime column
|
|
328
|
+
op.add_column("chat_messages", sa.Column("created_at", sa.DateTime(), nullable=True))
|
|
329
|
+
|
|
330
|
+
op.execute("""
|
|
331
|
+
UPDATE chat_messages
|
|
332
|
+
SET created_at = to_timestamp(created_time / 1000.0)
|
|
333
|
+
WHERE created_time IS NOT NULL
|
|
334
|
+
""")
|
|
335
|
+
|
|
336
|
+
op.drop_column("chat_messages", "created_time")
|
|
337
|
+
|
|
338
|
+
# Recreate the old indexes
|
|
339
|
+
_create_indexes_safe("ix_sessions_updated_at", "sessions", ["updated_at"])
|
|
340
|
+
_create_indexes_safe("ix_sessions_user_id_updated_at", "sessions", ["user_id", "updated_at"])
|
|
341
|
+
_create_indexes_safe("ix_chat_messages_created_at", "chat_messages", ["created_at"])
|
|
342
|
+
_create_indexes_safe("ix_chat_messages_session_id_created_at", "chat_messages", ["session_id", "created_at"])
|
|
@@ -5,13 +5,11 @@
|
|
|
5
5
|
# this is typically a path given in POSIX (e.g. forward slashes)
|
|
6
6
|
# format, relative to the token %(here)s which refers to the location of this
|
|
7
7
|
# ini file
|
|
8
|
-
script_location = alembic
|
|
8
|
+
script_location = %(here)s/alembic
|
|
9
9
|
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
# for all available tokens
|
|
14
|
-
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
|
10
|
+
# Template used to generate migration file names
|
|
11
|
+
# Format: YYYYMMDD_revision_description
|
|
12
|
+
file_template = %%(year)d%%(month).2d%%(day).2d_%%(rev)s_%%(slug)s
|
|
15
13
|
|
|
16
14
|
# sys.path path, will be prepended to sys.path if present.
|
|
17
15
|
# defaults to the current working directory. for multiple paths, the path separator
|