dtSpark 1.0.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.
- dtSpark/__init__.py +0 -0
- dtSpark/_description.txt +1 -0
- dtSpark/_full_name.txt +1 -0
- dtSpark/_licence.txt +21 -0
- dtSpark/_metadata.yaml +6 -0
- dtSpark/_name.txt +1 -0
- dtSpark/_version.txt +1 -0
- dtSpark/aws/__init__.py +7 -0
- dtSpark/aws/authentication.py +296 -0
- dtSpark/aws/bedrock.py +578 -0
- dtSpark/aws/costs.py +318 -0
- dtSpark/aws/pricing.py +580 -0
- dtSpark/cli_interface.py +2645 -0
- dtSpark/conversation_manager.py +3050 -0
- dtSpark/core/__init__.py +12 -0
- dtSpark/core/application.py +3355 -0
- dtSpark/core/context_compaction.py +735 -0
- dtSpark/daemon/__init__.py +104 -0
- dtSpark/daemon/__main__.py +10 -0
- dtSpark/daemon/action_monitor.py +213 -0
- dtSpark/daemon/daemon_app.py +730 -0
- dtSpark/daemon/daemon_manager.py +289 -0
- dtSpark/daemon/execution_coordinator.py +194 -0
- dtSpark/daemon/pid_file.py +169 -0
- dtSpark/database/__init__.py +482 -0
- dtSpark/database/autonomous_actions.py +1191 -0
- dtSpark/database/backends.py +329 -0
- dtSpark/database/connection.py +122 -0
- dtSpark/database/conversations.py +520 -0
- dtSpark/database/credential_prompt.py +218 -0
- dtSpark/database/files.py +205 -0
- dtSpark/database/mcp_ops.py +355 -0
- dtSpark/database/messages.py +161 -0
- dtSpark/database/schema.py +673 -0
- dtSpark/database/tool_permissions.py +186 -0
- dtSpark/database/usage.py +167 -0
- dtSpark/files/__init__.py +4 -0
- dtSpark/files/manager.py +322 -0
- dtSpark/launch.py +39 -0
- dtSpark/limits/__init__.py +10 -0
- dtSpark/limits/costs.py +296 -0
- dtSpark/limits/tokens.py +342 -0
- dtSpark/llm/__init__.py +17 -0
- dtSpark/llm/anthropic_direct.py +446 -0
- dtSpark/llm/base.py +146 -0
- dtSpark/llm/context_limits.py +438 -0
- dtSpark/llm/manager.py +177 -0
- dtSpark/llm/ollama.py +578 -0
- dtSpark/mcp_integration/__init__.py +5 -0
- dtSpark/mcp_integration/manager.py +653 -0
- dtSpark/mcp_integration/tool_selector.py +225 -0
- dtSpark/resources/config.yaml.template +631 -0
- dtSpark/safety/__init__.py +22 -0
- dtSpark/safety/llm_service.py +111 -0
- dtSpark/safety/patterns.py +229 -0
- dtSpark/safety/prompt_inspector.py +442 -0
- dtSpark/safety/violation_logger.py +346 -0
- dtSpark/scheduler/__init__.py +20 -0
- dtSpark/scheduler/creation_tools.py +599 -0
- dtSpark/scheduler/execution_queue.py +159 -0
- dtSpark/scheduler/executor.py +1152 -0
- dtSpark/scheduler/manager.py +395 -0
- dtSpark/tools/__init__.py +4 -0
- dtSpark/tools/builtin.py +833 -0
- dtSpark/web/__init__.py +20 -0
- dtSpark/web/auth.py +152 -0
- dtSpark/web/dependencies.py +37 -0
- dtSpark/web/endpoints/__init__.py +17 -0
- dtSpark/web/endpoints/autonomous_actions.py +1125 -0
- dtSpark/web/endpoints/chat.py +621 -0
- dtSpark/web/endpoints/conversations.py +353 -0
- dtSpark/web/endpoints/main_menu.py +547 -0
- dtSpark/web/endpoints/streaming.py +421 -0
- dtSpark/web/server.py +578 -0
- dtSpark/web/session.py +167 -0
- dtSpark/web/ssl_utils.py +195 -0
- dtSpark/web/static/css/dark-theme.css +427 -0
- dtSpark/web/static/js/actions.js +1101 -0
- dtSpark/web/static/js/chat.js +614 -0
- dtSpark/web/static/js/main.js +496 -0
- dtSpark/web/static/js/sse-client.js +242 -0
- dtSpark/web/templates/actions.html +408 -0
- dtSpark/web/templates/base.html +93 -0
- dtSpark/web/templates/chat.html +814 -0
- dtSpark/web/templates/conversations.html +350 -0
- dtSpark/web/templates/goodbye.html +81 -0
- dtSpark/web/templates/login.html +90 -0
- dtSpark/web/templates/main_menu.html +983 -0
- dtSpark/web/templates/new_conversation.html +191 -0
- dtSpark/web/web_interface.py +137 -0
- dtspark-1.0.4.dist-info/METADATA +187 -0
- dtspark-1.0.4.dist-info/RECORD +96 -0
- dtspark-1.0.4.dist-info/WHEEL +5 -0
- dtspark-1.0.4.dist-info/entry_points.txt +3 -0
- dtspark-1.0.4.dist-info/licenses/LICENSE +21 -0
- dtspark-1.0.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP (Model Context Protocol) operations module.
|
|
3
|
+
|
|
4
|
+
This module handles:
|
|
5
|
+
- Recording MCP tool transactions for Cyber Security monitoring
|
|
6
|
+
- Retrieving transaction audit trails
|
|
7
|
+
- Managing MCP server enabled/disabled states per conversation
|
|
8
|
+
- Exporting transaction data for security audits
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sqlite3
|
|
12
|
+
import logging
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from typing import List, Dict, Optional
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def record_mcp_transaction(conn: sqlite3.Connection, conversation_id: int,
|
|
18
|
+
user_prompt: str, tool_name: str, tool_server: str,
|
|
19
|
+
tool_input: str, tool_response: str, is_error: bool = False,
|
|
20
|
+
execution_time_ms: Optional[int] = None,
|
|
21
|
+
message_id: Optional[int] = None, user_guid: str = None) -> int:
|
|
22
|
+
"""
|
|
23
|
+
Record an MCP tool transaction for Cyber Security monitoring and audit trails.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
conn: Database connection
|
|
27
|
+
conversation_id: ID of the conversation
|
|
28
|
+
user_prompt: The user's original prompt that triggered the tool call
|
|
29
|
+
tool_name: Name of the MCP tool called
|
|
30
|
+
tool_server: Name of the MCP server
|
|
31
|
+
tool_input: JSON string of tool input parameters
|
|
32
|
+
tool_response: Response from the tool
|
|
33
|
+
is_error: Whether the transaction resulted in an error
|
|
34
|
+
execution_time_ms: Execution time in milliseconds
|
|
35
|
+
message_id: Optional ID of the related message
|
|
36
|
+
user_guid: User GUID for multi-user support
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
ID of the newly created transaction record
|
|
40
|
+
"""
|
|
41
|
+
cursor = conn.cursor()
|
|
42
|
+
now = datetime.now()
|
|
43
|
+
|
|
44
|
+
cursor.execute('''
|
|
45
|
+
INSERT INTO mcp_transactions
|
|
46
|
+
(conversation_id, message_id, user_prompt, tool_name, tool_server,
|
|
47
|
+
tool_input, tool_response, is_error, execution_time_ms, transaction_timestamp, user_guid)
|
|
48
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
49
|
+
''', (conversation_id, message_id, user_prompt, tool_name, tool_server,
|
|
50
|
+
tool_input, tool_response, 1 if is_error else 0, execution_time_ms, now, user_guid))
|
|
51
|
+
|
|
52
|
+
conn.commit()
|
|
53
|
+
transaction_id = cursor.lastrowid
|
|
54
|
+
logging.info(f"Recorded MCP transaction: {tool_server}.{tool_name} (ID: {transaction_id})")
|
|
55
|
+
return transaction_id
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_mcp_transactions(conn: sqlite3.Connection, conversation_id: Optional[int] = None,
|
|
59
|
+
tool_name: Optional[str] = None, tool_server: Optional[str] = None,
|
|
60
|
+
limit: Optional[int] = None, user_guid: str = None) -> List[Dict]:
|
|
61
|
+
"""
|
|
62
|
+
Retrieve MCP transactions with optional filtering.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
conn: Database connection
|
|
66
|
+
conversation_id: Optional filter by conversation ID
|
|
67
|
+
tool_name: Optional filter by tool name
|
|
68
|
+
tool_server: Optional filter by server name
|
|
69
|
+
limit: Optional limit on number of results
|
|
70
|
+
user_guid: User GUID for multi-user support
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
List of transaction dictionaries
|
|
74
|
+
"""
|
|
75
|
+
cursor = conn.cursor()
|
|
76
|
+
|
|
77
|
+
query = '''
|
|
78
|
+
SELECT id, conversation_id, message_id, user_prompt, tool_name, tool_server,
|
|
79
|
+
tool_input, tool_response, is_error, execution_time_ms, transaction_timestamp
|
|
80
|
+
FROM mcp_transactions
|
|
81
|
+
WHERE user_guid = ?
|
|
82
|
+
'''
|
|
83
|
+
params = [user_guid]
|
|
84
|
+
|
|
85
|
+
if conversation_id is not None:
|
|
86
|
+
query += ' AND conversation_id = ?'
|
|
87
|
+
params.append(conversation_id)
|
|
88
|
+
|
|
89
|
+
if tool_name is not None:
|
|
90
|
+
query += ' AND tool_name = ?'
|
|
91
|
+
params.append(tool_name)
|
|
92
|
+
|
|
93
|
+
if tool_server is not None:
|
|
94
|
+
query += ' AND tool_server = ?'
|
|
95
|
+
params.append(tool_server)
|
|
96
|
+
|
|
97
|
+
query += ' ORDER BY transaction_timestamp DESC'
|
|
98
|
+
|
|
99
|
+
if limit is not None:
|
|
100
|
+
query += ' LIMIT ?'
|
|
101
|
+
params.append(limit)
|
|
102
|
+
|
|
103
|
+
cursor.execute(query, params)
|
|
104
|
+
|
|
105
|
+
transactions = []
|
|
106
|
+
for row in cursor.fetchall():
|
|
107
|
+
transactions.append({
|
|
108
|
+
'id': row['id'],
|
|
109
|
+
'conversation_id': row['conversation_id'],
|
|
110
|
+
'message_id': row['message_id'],
|
|
111
|
+
'user_prompt': row['user_prompt'],
|
|
112
|
+
'tool_name': row['tool_name'],
|
|
113
|
+
'tool_server': row['tool_server'],
|
|
114
|
+
'tool_input': row['tool_input'],
|
|
115
|
+
'tool_response': row['tool_response'],
|
|
116
|
+
'is_error': bool(row['is_error']),
|
|
117
|
+
'execution_time_ms': row['execution_time_ms'],
|
|
118
|
+
'transaction_timestamp': row['transaction_timestamp']
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
return transactions
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_mcp_transaction_stats(conn: sqlite3.Connection, user_guid: str = None) -> Dict:
|
|
125
|
+
"""
|
|
126
|
+
Get statistics about MCP transactions for Cyber Security monitoring.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
conn: Database connection
|
|
130
|
+
user_guid: User GUID for multi-user support
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Dictionary with transaction statistics
|
|
134
|
+
"""
|
|
135
|
+
cursor = conn.cursor()
|
|
136
|
+
|
|
137
|
+
# Total transactions
|
|
138
|
+
cursor.execute('SELECT COUNT(*) as total FROM mcp_transactions WHERE user_guid = ?',
|
|
139
|
+
(user_guid,))
|
|
140
|
+
total = cursor.fetchone()['total']
|
|
141
|
+
|
|
142
|
+
# Error count
|
|
143
|
+
cursor.execute('SELECT COUNT(*) as errors FROM mcp_transactions WHERE is_error = 1 AND user_guid = ?',
|
|
144
|
+
(user_guid,))
|
|
145
|
+
errors = cursor.fetchone()['errors']
|
|
146
|
+
|
|
147
|
+
# Most used tools
|
|
148
|
+
cursor.execute('''
|
|
149
|
+
SELECT tool_server || '.' || tool_name as tool, COUNT(*) as count
|
|
150
|
+
FROM mcp_transactions
|
|
151
|
+
WHERE user_guid = ?
|
|
152
|
+
GROUP BY tool_server, tool_name
|
|
153
|
+
ORDER BY count DESC
|
|
154
|
+
LIMIT 10
|
|
155
|
+
''', (user_guid,))
|
|
156
|
+
top_tools = [{'tool': row['tool'], 'count': row['count']} for row in cursor.fetchall()]
|
|
157
|
+
|
|
158
|
+
# Recent transactions by conversation
|
|
159
|
+
cursor.execute('''
|
|
160
|
+
SELECT c.name, COUNT(t.id) as count
|
|
161
|
+
FROM mcp_transactions t
|
|
162
|
+
JOIN conversations c ON t.conversation_id = c.id
|
|
163
|
+
WHERE t.user_guid = ? AND c.user_guid = ?
|
|
164
|
+
GROUP BY c.id
|
|
165
|
+
ORDER BY count DESC
|
|
166
|
+
LIMIT 10
|
|
167
|
+
''', (user_guid, user_guid))
|
|
168
|
+
top_conversations = [{'conversation': row['name'], 'count': row['count']} for row in cursor.fetchall()]
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
'total_transactions': total,
|
|
172
|
+
'error_count': errors,
|
|
173
|
+
'error_rate': (errors / total * 100) if total > 0 else 0,
|
|
174
|
+
'top_tools': top_tools,
|
|
175
|
+
'top_conversations': top_conversations
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def export_mcp_transactions_to_csv(conn: sqlite3.Connection, file_path: str,
|
|
180
|
+
conversation_id: Optional[int] = None,
|
|
181
|
+
user_guid: str = None) -> bool:
|
|
182
|
+
"""
|
|
183
|
+
Export MCP transactions to CSV for Cyber Security audit.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
conn: Database connection
|
|
187
|
+
file_path: Path to save the CSV file
|
|
188
|
+
conversation_id: Optional filter by conversation ID
|
|
189
|
+
user_guid: User GUID for multi-user support
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
True if successful, False otherwise
|
|
193
|
+
"""
|
|
194
|
+
try:
|
|
195
|
+
import csv
|
|
196
|
+
|
|
197
|
+
transactions = get_mcp_transactions(conn, conversation_id=conversation_id,
|
|
198
|
+
user_guid=user_guid)
|
|
199
|
+
|
|
200
|
+
with open(file_path, 'w', newline='', encoding='utf-8') as csvfile:
|
|
201
|
+
fieldnames = [
|
|
202
|
+
'id', 'transaction_timestamp', 'conversation_id', 'tool_server',
|
|
203
|
+
'tool_name', 'user_prompt', 'tool_input', 'tool_response',
|
|
204
|
+
'is_error', 'execution_time_ms'
|
|
205
|
+
]
|
|
206
|
+
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
|
207
|
+
|
|
208
|
+
writer.writeheader()
|
|
209
|
+
for txn in transactions:
|
|
210
|
+
writer.writerow({
|
|
211
|
+
'id': txn['id'],
|
|
212
|
+
'transaction_timestamp': txn['transaction_timestamp'],
|
|
213
|
+
'conversation_id': txn['conversation_id'],
|
|
214
|
+
'tool_server': txn['tool_server'],
|
|
215
|
+
'tool_name': txn['tool_name'],
|
|
216
|
+
'user_prompt': txn['user_prompt'][:100] + '...' if len(txn['user_prompt']) > 100 else txn['user_prompt'],
|
|
217
|
+
'tool_input': txn['tool_input'],
|
|
218
|
+
'tool_response': txn['tool_response'][:200] + '...' if len(txn['tool_response']) > 200 else txn['tool_response'],
|
|
219
|
+
'is_error': txn['is_error'],
|
|
220
|
+
'execution_time_ms': txn['execution_time_ms']
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
logging.info(f"Exported {len(transactions)} MCP transactions to {file_path}")
|
|
224
|
+
return True
|
|
225
|
+
|
|
226
|
+
except Exception as e:
|
|
227
|
+
logging.error(f"Failed to export MCP transactions: {e}")
|
|
228
|
+
return False
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def get_enabled_mcp_servers(conn: sqlite3.Connection, conversation_id: int,
|
|
232
|
+
user_guid: str = None) -> List[str]:
|
|
233
|
+
"""
|
|
234
|
+
Get list of enabled MCP servers for a conversation.
|
|
235
|
+
If no records exist, all servers are considered enabled by default.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
conn: Database connection
|
|
239
|
+
conversation_id: Conversation ID
|
|
240
|
+
user_guid: User GUID for multi-user support
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
List of enabled server names (empty list if all disabled)
|
|
244
|
+
"""
|
|
245
|
+
cursor = conn.cursor()
|
|
246
|
+
cursor.execute('''
|
|
247
|
+
SELECT server_name
|
|
248
|
+
FROM conversation_mcp_servers
|
|
249
|
+
WHERE conversation_id = ? AND enabled = 1 AND user_guid = ?
|
|
250
|
+
''', (conversation_id, user_guid))
|
|
251
|
+
|
|
252
|
+
return [row['server_name'] for row in cursor.fetchall()]
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def is_mcp_server_enabled(conn: sqlite3.Connection, conversation_id: int,
|
|
256
|
+
server_name: str, user_guid: str = None) -> bool:
|
|
257
|
+
"""
|
|
258
|
+
Check if an MCP server is enabled for a conversation.
|
|
259
|
+
Returns True by default if no record exists (all servers enabled by default).
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
conn: Database connection
|
|
263
|
+
conversation_id: Conversation ID
|
|
264
|
+
server_name: Name of the MCP server
|
|
265
|
+
user_guid: User GUID for multi-user support
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
True if enabled, False if disabled
|
|
269
|
+
"""
|
|
270
|
+
cursor = conn.cursor()
|
|
271
|
+
cursor.execute('''
|
|
272
|
+
SELECT enabled
|
|
273
|
+
FROM conversation_mcp_servers
|
|
274
|
+
WHERE conversation_id = ? AND server_name = ? AND user_guid = ?
|
|
275
|
+
''', (conversation_id, server_name, user_guid))
|
|
276
|
+
|
|
277
|
+
row = cursor.fetchone()
|
|
278
|
+
if row is None:
|
|
279
|
+
# No record exists, default to enabled
|
|
280
|
+
return True
|
|
281
|
+
return bool(row['enabled'])
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def set_mcp_server_enabled(conn: sqlite3.Connection, conversation_id: int,
|
|
285
|
+
server_name: str, enabled: bool, user_guid: str = None) -> bool:
|
|
286
|
+
"""
|
|
287
|
+
Enable or disable an MCP server for a conversation.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
conn: Database connection
|
|
291
|
+
conversation_id: Conversation ID
|
|
292
|
+
server_name: Name of the MCP server
|
|
293
|
+
enabled: True to enable, False to disable
|
|
294
|
+
user_guid: User GUID for multi-user support
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
True if successful, False otherwise
|
|
298
|
+
"""
|
|
299
|
+
try:
|
|
300
|
+
cursor = conn.cursor()
|
|
301
|
+
now = datetime.now().isoformat()
|
|
302
|
+
|
|
303
|
+
cursor.execute('''
|
|
304
|
+
INSERT INTO conversation_mcp_servers
|
|
305
|
+
(conversation_id, server_name, enabled, updated_at, user_guid)
|
|
306
|
+
VALUES (?, ?, ?, ?, ?)
|
|
307
|
+
ON CONFLICT(conversation_id, server_name) DO UPDATE SET
|
|
308
|
+
enabled = excluded.enabled,
|
|
309
|
+
updated_at = excluded.updated_at
|
|
310
|
+
''', (conversation_id, server_name, int(enabled), now, user_guid))
|
|
311
|
+
|
|
312
|
+
conn.commit()
|
|
313
|
+
logging.info(f"MCP server '{server_name}' {'enabled' if enabled else 'disabled'} for conversation {conversation_id}")
|
|
314
|
+
return True
|
|
315
|
+
|
|
316
|
+
except Exception as e:
|
|
317
|
+
logging.error(f"Failed to update MCP server state: {e}")
|
|
318
|
+
conn.rollback()
|
|
319
|
+
return False
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def get_all_mcp_server_states(conn: sqlite3.Connection, conversation_id: int,
|
|
323
|
+
all_server_names: List[str], user_guid: str = None) -> List[Dict]:
|
|
324
|
+
"""
|
|
325
|
+
Get enabled/disabled state for all MCP servers.
|
|
326
|
+
Servers with no record are considered enabled by default.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
conn: Database connection
|
|
330
|
+
conversation_id: Conversation ID
|
|
331
|
+
all_server_names: List of all available MCP server names
|
|
332
|
+
user_guid: User GUID for multi-user support
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
List of dicts with 'server_name' and 'enabled' keys
|
|
336
|
+
"""
|
|
337
|
+
cursor = conn.cursor()
|
|
338
|
+
cursor.execute('''
|
|
339
|
+
SELECT server_name, enabled
|
|
340
|
+
FROM conversation_mcp_servers
|
|
341
|
+
WHERE conversation_id = ? AND user_guid = ?
|
|
342
|
+
''', (conversation_id, user_guid))
|
|
343
|
+
|
|
344
|
+
# Create a dict of server states
|
|
345
|
+
server_states = {row['server_name']: bool(row['enabled']) for row in cursor.fetchall()}
|
|
346
|
+
|
|
347
|
+
# Build result list with all servers, defaulting to enabled
|
|
348
|
+
result = []
|
|
349
|
+
for server_name in all_server_names:
|
|
350
|
+
result.append({
|
|
351
|
+
'server_name': server_name,
|
|
352
|
+
'enabled': server_states.get(server_name, True) # Default to enabled
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
return result
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Message operations module.
|
|
3
|
+
|
|
4
|
+
This module handles:
|
|
5
|
+
- Adding messages to conversations
|
|
6
|
+
- Retrieving conversation messages
|
|
7
|
+
- Message rollup management
|
|
8
|
+
- Message token tracking
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sqlite3
|
|
12
|
+
import logging
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from typing import List, Dict
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def add_message(conn: sqlite3.Connection, conversation_id: int, role: str,
|
|
18
|
+
content: str, token_count: int, user_guid: str = None) -> int:
|
|
19
|
+
"""
|
|
20
|
+
Add a message to a conversation.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
conn: Database connection
|
|
24
|
+
conversation_id: ID of the conversation
|
|
25
|
+
role: Message role (user, assistant, system)
|
|
26
|
+
content: Message content
|
|
27
|
+
token_count: Number of tokens in the message
|
|
28
|
+
user_guid: User GUID for multi-user support
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
ID of the newly created message
|
|
32
|
+
"""
|
|
33
|
+
cursor = conn.cursor()
|
|
34
|
+
now = datetime.now()
|
|
35
|
+
|
|
36
|
+
cursor.execute('''
|
|
37
|
+
INSERT INTO messages (conversation_id, role, content, token_count, timestamp, user_guid)
|
|
38
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
39
|
+
''', (conversation_id, role, content, token_count, now, user_guid))
|
|
40
|
+
|
|
41
|
+
# Update conversation total tokens and last_updated
|
|
42
|
+
cursor.execute('''
|
|
43
|
+
UPDATE conversations
|
|
44
|
+
SET total_tokens = total_tokens + ?,
|
|
45
|
+
last_updated = ?
|
|
46
|
+
WHERE id = ? AND user_guid = ?
|
|
47
|
+
''', (token_count, now, conversation_id, user_guid))
|
|
48
|
+
|
|
49
|
+
conn.commit()
|
|
50
|
+
message_id = cursor.lastrowid
|
|
51
|
+
logging.debug(f"Added message {message_id} to conversation {conversation_id}")
|
|
52
|
+
return message_id
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_conversation_messages(conn: sqlite3.Connection, conversation_id: int,
|
|
56
|
+
include_rolled_up: bool = False, user_guid: str = None) -> List[Dict]:
|
|
57
|
+
"""
|
|
58
|
+
Retrieve messages for a conversation.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
conn: Database connection
|
|
62
|
+
conversation_id: ID of the conversation
|
|
63
|
+
include_rolled_up: Whether to include rolled-up messages
|
|
64
|
+
user_guid: User GUID for multi-user support
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
List of message dictionaries
|
|
68
|
+
"""
|
|
69
|
+
cursor = conn.cursor()
|
|
70
|
+
|
|
71
|
+
if include_rolled_up:
|
|
72
|
+
query = '''
|
|
73
|
+
SELECT id, role, content, token_count, timestamp, is_rolled_up
|
|
74
|
+
FROM messages
|
|
75
|
+
WHERE conversation_id = ? AND user_guid = ?
|
|
76
|
+
ORDER BY timestamp ASC
|
|
77
|
+
'''
|
|
78
|
+
else:
|
|
79
|
+
query = '''
|
|
80
|
+
SELECT id, role, content, token_count, timestamp, is_rolled_up
|
|
81
|
+
FROM messages
|
|
82
|
+
WHERE conversation_id = ? AND user_guid = ? AND is_rolled_up = 0
|
|
83
|
+
ORDER BY timestamp ASC
|
|
84
|
+
'''
|
|
85
|
+
|
|
86
|
+
cursor.execute(query, (conversation_id, user_guid))
|
|
87
|
+
|
|
88
|
+
messages = []
|
|
89
|
+
for row in cursor.fetchall():
|
|
90
|
+
messages.append({
|
|
91
|
+
'id': row['id'],
|
|
92
|
+
'role': row['role'],
|
|
93
|
+
'content': row['content'],
|
|
94
|
+
'token_count': row['token_count'],
|
|
95
|
+
'timestamp': row['timestamp'],
|
|
96
|
+
'is_rolled_up': bool(row['is_rolled_up'])
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
return messages
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def mark_messages_as_rolled_up(conn: sqlite3.Connection, message_ids: List[int],
|
|
103
|
+
user_guid: str = None):
|
|
104
|
+
"""
|
|
105
|
+
Mark messages as rolled up.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
conn: Database connection
|
|
109
|
+
message_ids: List of message IDs to mark
|
|
110
|
+
user_guid: User GUID for multi-user support (for safety filtering)
|
|
111
|
+
"""
|
|
112
|
+
cursor = conn.cursor()
|
|
113
|
+
placeholders = ','.join('?' * len(message_ids))
|
|
114
|
+
# Add user_guid filtering for security
|
|
115
|
+
cursor.execute(f'''
|
|
116
|
+
UPDATE messages
|
|
117
|
+
SET is_rolled_up = 1
|
|
118
|
+
WHERE id IN ({placeholders}) AND user_guid = ?
|
|
119
|
+
''', message_ids + [user_guid])
|
|
120
|
+
conn.commit()
|
|
121
|
+
logging.info(f"Marked {len(message_ids)} messages as rolled up")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def record_rollup(conn: sqlite3.Connection, conversation_id: int,
|
|
125
|
+
original_message_count: int, summarised_content: str,
|
|
126
|
+
original_token_count: int, summarised_token_count: int,
|
|
127
|
+
user_guid: str = None):
|
|
128
|
+
"""
|
|
129
|
+
Record a rollup operation in history.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
conn: Database connection
|
|
133
|
+
conversation_id: ID of the conversation
|
|
134
|
+
original_message_count: Number of messages that were summarised
|
|
135
|
+
summarised_content: The summary content
|
|
136
|
+
original_token_count: Original token count
|
|
137
|
+
summarised_token_count: Token count after summarisation
|
|
138
|
+
user_guid: User GUID for multi-user support
|
|
139
|
+
"""
|
|
140
|
+
cursor = conn.cursor()
|
|
141
|
+
now = datetime.now()
|
|
142
|
+
|
|
143
|
+
cursor.execute('''
|
|
144
|
+
INSERT INTO rollup_history
|
|
145
|
+
(conversation_id, original_message_count, summarised_content,
|
|
146
|
+
original_token_count, summarised_token_count, rollup_timestamp, user_guid)
|
|
147
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
148
|
+
''', (conversation_id, original_message_count, summarised_content,
|
|
149
|
+
original_token_count, summarised_token_count, now, user_guid))
|
|
150
|
+
|
|
151
|
+
# Update conversation total tokens
|
|
152
|
+
token_reduction = original_token_count - summarised_token_count
|
|
153
|
+
cursor.execute('''
|
|
154
|
+
UPDATE conversations
|
|
155
|
+
SET total_tokens = total_tokens - ?
|
|
156
|
+
WHERE id = ? AND user_guid = ?
|
|
157
|
+
''', (token_reduction, conversation_id, user_guid))
|
|
158
|
+
|
|
159
|
+
conn.commit()
|
|
160
|
+
logging.info(f"Recorded rollup for conversation {conversation_id}, "
|
|
161
|
+
f"reduced tokens by {token_reduction}")
|