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.
Files changed (96) hide show
  1. dtSpark/__init__.py +0 -0
  2. dtSpark/_description.txt +1 -0
  3. dtSpark/_full_name.txt +1 -0
  4. dtSpark/_licence.txt +21 -0
  5. dtSpark/_metadata.yaml +6 -0
  6. dtSpark/_name.txt +1 -0
  7. dtSpark/_version.txt +1 -0
  8. dtSpark/aws/__init__.py +7 -0
  9. dtSpark/aws/authentication.py +296 -0
  10. dtSpark/aws/bedrock.py +578 -0
  11. dtSpark/aws/costs.py +318 -0
  12. dtSpark/aws/pricing.py +580 -0
  13. dtSpark/cli_interface.py +2645 -0
  14. dtSpark/conversation_manager.py +3050 -0
  15. dtSpark/core/__init__.py +12 -0
  16. dtSpark/core/application.py +3355 -0
  17. dtSpark/core/context_compaction.py +735 -0
  18. dtSpark/daemon/__init__.py +104 -0
  19. dtSpark/daemon/__main__.py +10 -0
  20. dtSpark/daemon/action_monitor.py +213 -0
  21. dtSpark/daemon/daemon_app.py +730 -0
  22. dtSpark/daemon/daemon_manager.py +289 -0
  23. dtSpark/daemon/execution_coordinator.py +194 -0
  24. dtSpark/daemon/pid_file.py +169 -0
  25. dtSpark/database/__init__.py +482 -0
  26. dtSpark/database/autonomous_actions.py +1191 -0
  27. dtSpark/database/backends.py +329 -0
  28. dtSpark/database/connection.py +122 -0
  29. dtSpark/database/conversations.py +520 -0
  30. dtSpark/database/credential_prompt.py +218 -0
  31. dtSpark/database/files.py +205 -0
  32. dtSpark/database/mcp_ops.py +355 -0
  33. dtSpark/database/messages.py +161 -0
  34. dtSpark/database/schema.py +673 -0
  35. dtSpark/database/tool_permissions.py +186 -0
  36. dtSpark/database/usage.py +167 -0
  37. dtSpark/files/__init__.py +4 -0
  38. dtSpark/files/manager.py +322 -0
  39. dtSpark/launch.py +39 -0
  40. dtSpark/limits/__init__.py +10 -0
  41. dtSpark/limits/costs.py +296 -0
  42. dtSpark/limits/tokens.py +342 -0
  43. dtSpark/llm/__init__.py +17 -0
  44. dtSpark/llm/anthropic_direct.py +446 -0
  45. dtSpark/llm/base.py +146 -0
  46. dtSpark/llm/context_limits.py +438 -0
  47. dtSpark/llm/manager.py +177 -0
  48. dtSpark/llm/ollama.py +578 -0
  49. dtSpark/mcp_integration/__init__.py +5 -0
  50. dtSpark/mcp_integration/manager.py +653 -0
  51. dtSpark/mcp_integration/tool_selector.py +225 -0
  52. dtSpark/resources/config.yaml.template +631 -0
  53. dtSpark/safety/__init__.py +22 -0
  54. dtSpark/safety/llm_service.py +111 -0
  55. dtSpark/safety/patterns.py +229 -0
  56. dtSpark/safety/prompt_inspector.py +442 -0
  57. dtSpark/safety/violation_logger.py +346 -0
  58. dtSpark/scheduler/__init__.py +20 -0
  59. dtSpark/scheduler/creation_tools.py +599 -0
  60. dtSpark/scheduler/execution_queue.py +159 -0
  61. dtSpark/scheduler/executor.py +1152 -0
  62. dtSpark/scheduler/manager.py +395 -0
  63. dtSpark/tools/__init__.py +4 -0
  64. dtSpark/tools/builtin.py +833 -0
  65. dtSpark/web/__init__.py +20 -0
  66. dtSpark/web/auth.py +152 -0
  67. dtSpark/web/dependencies.py +37 -0
  68. dtSpark/web/endpoints/__init__.py +17 -0
  69. dtSpark/web/endpoints/autonomous_actions.py +1125 -0
  70. dtSpark/web/endpoints/chat.py +621 -0
  71. dtSpark/web/endpoints/conversations.py +353 -0
  72. dtSpark/web/endpoints/main_menu.py +547 -0
  73. dtSpark/web/endpoints/streaming.py +421 -0
  74. dtSpark/web/server.py +578 -0
  75. dtSpark/web/session.py +167 -0
  76. dtSpark/web/ssl_utils.py +195 -0
  77. dtSpark/web/static/css/dark-theme.css +427 -0
  78. dtSpark/web/static/js/actions.js +1101 -0
  79. dtSpark/web/static/js/chat.js +614 -0
  80. dtSpark/web/static/js/main.js +496 -0
  81. dtSpark/web/static/js/sse-client.js +242 -0
  82. dtSpark/web/templates/actions.html +408 -0
  83. dtSpark/web/templates/base.html +93 -0
  84. dtSpark/web/templates/chat.html +814 -0
  85. dtSpark/web/templates/conversations.html +350 -0
  86. dtSpark/web/templates/goodbye.html +81 -0
  87. dtSpark/web/templates/login.html +90 -0
  88. dtSpark/web/templates/main_menu.html +983 -0
  89. dtSpark/web/templates/new_conversation.html +191 -0
  90. dtSpark/web/web_interface.py +137 -0
  91. dtspark-1.0.4.dist-info/METADATA +187 -0
  92. dtspark-1.0.4.dist-info/RECORD +96 -0
  93. dtspark-1.0.4.dist-info/WHEEL +5 -0
  94. dtspark-1.0.4.dist-info/entry_points.txt +3 -0
  95. dtspark-1.0.4.dist-info/licenses/LICENSE +21 -0
  96. dtspark-1.0.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,218 @@
1
+ """
2
+ Database credential prompting utility.
3
+
4
+ Provides interactive prompts for missing database credentials.
5
+
6
+
7
+ """
8
+
9
+ import logging
10
+ import getpass
11
+ from typing import Optional
12
+ from rich.console import Console
13
+ from rich.prompt import Prompt, Confirm
14
+ from rich.panel import Panel
15
+
16
+ from .backends import DatabaseCredentials
17
+
18
+
19
+ def prompt_for_credentials(db_type: str, existing_credentials: Optional[DatabaseCredentials] = None) -> DatabaseCredentials:
20
+ """
21
+ Interactively prompt user for missing database credentials.
22
+
23
+ Args:
24
+ db_type: Database type (sqlite, mysql, mariadb, postgresql, mssql)
25
+ existing_credentials: Existing credentials (will only prompt for missing fields)
26
+
27
+ Returns:
28
+ Complete DatabaseCredentials object
29
+ """
30
+ console = Console()
31
+ db_type_lower = db_type.lower()
32
+
33
+ # Start with existing credentials or create new
34
+ if existing_credentials:
35
+ creds = existing_credentials
36
+ else:
37
+ creds = DatabaseCredentials()
38
+
39
+ # SQLite only needs path
40
+ if db_type_lower == 'sqlite':
41
+ if not creds.path:
42
+ creds.path = Prompt.ask(
43
+ "Enter SQLite database file path",
44
+ default="./running/conversations.db"
45
+ )
46
+ return creds
47
+
48
+ # Display information panel
49
+ console.print()
50
+ console.print(Panel(
51
+ f"[bold cyan]Database Connection Setup[/bold cyan]\n\n"
52
+ f"Database type: [yellow]{db_type}[/yellow]\n\n"
53
+ f"Please provide connection credentials for your database server.",
54
+ border_style="cyan",
55
+ padding=(1, 2)
56
+ ))
57
+ console.print()
58
+
59
+ # Prompt for remote database credentials
60
+ if not creds.host:
61
+ creds.host = Prompt.ask(
62
+ "Database host",
63
+ default="localhost"
64
+ )
65
+
66
+ if not creds.port:
67
+ default_ports = {
68
+ 'mysql': 3306,
69
+ 'mariadb': 3306,
70
+ 'postgresql': 5432,
71
+ 'mssql': 1433,
72
+ 'sqlserver': 1433
73
+ }
74
+ default_port = default_ports.get(db_type_lower, 3306)
75
+
76
+ port_input = Prompt.ask(
77
+ "Database port",
78
+ default=str(default_port)
79
+ )
80
+ creds.port = int(port_input)
81
+
82
+ if not creds.database:
83
+ creds.database = Prompt.ask(
84
+ "Database name",
85
+ default="dtawsbedrockcli"
86
+ )
87
+
88
+ if not creds.username:
89
+ creds.username = Prompt.ask("Database username")
90
+
91
+ if not creds.password:
92
+ # Use getpass for secure password input
93
+ console.print("[cyan]Database password:[/cyan] ", end="")
94
+ creds.password = getpass.getpass("")
95
+
96
+ # SSL option
97
+ use_ssl = Confirm.ask("Use SSL/TLS connection?", default=False)
98
+ creds.ssl = use_ssl
99
+
100
+ # MSSQL-specific: driver selection
101
+ if db_type_lower in ('mssql', 'sqlserver', 'mssqlserver'):
102
+ if not creds.driver:
103
+ drivers = [
104
+ "ODBC Driver 17 for SQL Server",
105
+ "ODBC Driver 18 for SQL Server",
106
+ "SQL Server Native Client 11.0",
107
+ "Custom"
108
+ ]
109
+
110
+ console.print()
111
+ console.print("[bold cyan]Select ODBC driver:[/bold cyan]")
112
+ for i, driver in enumerate(drivers, 1):
113
+ console.print(f" [{i}] {driver}")
114
+
115
+ choice = Prompt.ask(
116
+ "Driver selection",
117
+ choices=[str(i) for i in range(1, len(drivers) + 1)],
118
+ default="1"
119
+ )
120
+
121
+ choice_idx = int(choice) - 1
122
+ if choice_idx == len(drivers) - 1: # Custom
123
+ creds.driver = Prompt.ask("Enter custom ODBC driver name")
124
+ else:
125
+ creds.driver = drivers[choice_idx]
126
+
127
+ console.print()
128
+ logging.info(f"Database credentials collected for {db_type}")
129
+
130
+ return creds
131
+
132
+
133
+ def test_credentials(db_type: str, credentials: DatabaseCredentials) -> tuple[bool, Optional[str]]:
134
+ """
135
+ Test database credentials by attempting connection.
136
+
137
+ Args:
138
+ db_type: Database type
139
+ credentials: Database credentials
140
+
141
+ Returns:
142
+ Tuple of (success, error_message)
143
+ """
144
+ from .backends import create_backend
145
+
146
+ console = Console()
147
+
148
+ try:
149
+ console.print()
150
+ console.print("[cyan]Testing database connection...[/cyan]")
151
+
152
+ backend = create_backend(db_type, credentials)
153
+ success = backend.test_connection()
154
+ backend.close()
155
+
156
+ if success:
157
+ console.print("[green]✓ Database connection successful![/green]")
158
+ return True, None
159
+ else:
160
+ error_msg = "Connection test query failed"
161
+ console.print(f"[red]✗ {error_msg}[/red]")
162
+ return False, error_msg
163
+
164
+ except ImportError as e:
165
+ error_msg = str(e)
166
+ console.print(f"[red]✗ {error_msg}[/red]")
167
+ return False, error_msg
168
+
169
+ except Exception as e:
170
+ error_msg = f"Connection failed: {str(e)}"
171
+ console.print(f"[red]✗ {error_msg}[/red]")
172
+ logging.error(f"Database connection test failed: {e}", exc_info=True)
173
+ return False, error_msg
174
+
175
+
176
+ def prompt_and_validate_credentials(db_type: str,
177
+ existing_credentials: Optional[DatabaseCredentials] = None,
178
+ max_retries: int = 3) -> Optional[DatabaseCredentials]:
179
+ """
180
+ Prompt for credentials and validate them, with retry support.
181
+
182
+ Args:
183
+ db_type: Database type
184
+ existing_credentials: Existing credentials (will only prompt for missing fields)
185
+ max_retries: Maximum number of retry attempts
186
+
187
+ Returns:
188
+ Valid DatabaseCredentials or None if max retries exceeded
189
+ """
190
+ console = Console()
191
+ creds = existing_credentials
192
+
193
+ for attempt in range(max_retries):
194
+ # Prompt for credentials
195
+ creds = prompt_for_credentials(db_type, creds)
196
+
197
+ # Test credentials
198
+ success, error_msg = test_credentials(db_type, creds)
199
+
200
+ if success:
201
+ return creds
202
+
203
+ # Failed - offer retry
204
+ if attempt < max_retries - 1:
205
+ console.print()
206
+ retry = Confirm.ask(
207
+ f"[yellow]Connection failed. Retry with different credentials? ({max_retries - attempt - 1} attempts remaining)[/yellow]",
208
+ default=True
209
+ )
210
+
211
+ if not retry:
212
+ return None
213
+ else:
214
+ console.print()
215
+ console.print(f"[red]Maximum connection attempts ({max_retries}) exceeded.[/red]")
216
+ return None
217
+
218
+ return None
@@ -0,0 +1,205 @@
1
+ """
2
+ File attachment operations module.
3
+
4
+ This module handles:
5
+ - Adding files to conversations
6
+ - Retrieving attached files
7
+ - File deletion
8
+ - Tag-based file filtering
9
+ """
10
+
11
+ import sqlite3
12
+ import logging
13
+ from datetime import datetime
14
+ from typing import List, Dict, Optional
15
+
16
+
17
+ def add_file(conn: sqlite3.Connection, conversation_id: int, filename: str,
18
+ file_type: str, file_size: int, content_text: Optional[str] = None,
19
+ content_base64: Optional[str] = None, mime_type: Optional[str] = None,
20
+ token_count: int = 0, tags: Optional[str] = None, user_guid: str = None) -> int:
21
+ """
22
+ Add a file to a conversation.
23
+
24
+ Args:
25
+ conn: Database connection
26
+ conversation_id: ID of the conversation
27
+ filename: Original filename
28
+ file_type: File extension (e.g., .pdf, .docx)
29
+ file_size: Size in bytes
30
+ content_text: Extracted text content
31
+ content_base64: Base64 encoded content (for images)
32
+ mime_type: MIME type (for images)
33
+ token_count: Token count of extracted content
34
+ tags: Comma-separated tags for the file
35
+ user_guid: User GUID for multi-user support
36
+
37
+ Returns:
38
+ ID of the newly added file
39
+ """
40
+ cursor = conn.cursor()
41
+ now = datetime.now()
42
+
43
+ cursor.execute('''
44
+ INSERT INTO conversation_files
45
+ (conversation_id, filename, file_type, file_size, content_text,
46
+ content_base64, mime_type, token_count, added_at, tags, user_guid)
47
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
48
+ ''', (conversation_id, filename, file_type, file_size, content_text,
49
+ content_base64, mime_type, token_count, now, tags, user_guid))
50
+
51
+ conn.commit()
52
+ file_id = cursor.lastrowid
53
+ tags_str = f" with tags '{tags}'" if tags else ""
54
+ logging.info(f"Added file '{filename}' to conversation {conversation_id}{tags_str}")
55
+ return file_id
56
+
57
+
58
+ def get_conversation_files(conn: sqlite3.Connection, conversation_id: int,
59
+ user_guid: str = None) -> List[Dict]:
60
+ """
61
+ Retrieve all files for a conversation.
62
+
63
+ Args:
64
+ conn: Database connection
65
+ conversation_id: ID of the conversation
66
+ user_guid: User GUID for multi-user support
67
+
68
+ Returns:
69
+ List of file dictionaries
70
+ """
71
+ cursor = conn.cursor()
72
+ cursor.execute('''
73
+ SELECT id, filename, file_type, file_size, content_text,
74
+ content_base64, mime_type, token_count, added_at, tags
75
+ FROM conversation_files
76
+ WHERE conversation_id = ? AND user_guid = ?
77
+ ORDER BY added_at ASC
78
+ ''', (conversation_id, user_guid))
79
+
80
+ files = []
81
+ for row in cursor.fetchall():
82
+ files.append({
83
+ 'id': row['id'],
84
+ 'filename': row['filename'],
85
+ 'file_type': row['file_type'],
86
+ 'file_size': row['file_size'],
87
+ 'content_text': row['content_text'],
88
+ 'content_base64': row['content_base64'],
89
+ 'mime_type': row['mime_type'],
90
+ 'token_count': row['token_count'],
91
+ 'added_at': row['added_at'],
92
+ 'tags': row['tags']
93
+ })
94
+
95
+ return files
96
+
97
+
98
+ def get_files_by_tag(conn: sqlite3.Connection, conversation_id: int, tag: str,
99
+ user_guid: str = None) -> List[Dict]:
100
+ """
101
+ Retrieve files for a conversation filtered by tag.
102
+
103
+ Args:
104
+ conn: Database connection
105
+ conversation_id: ID of the conversation
106
+ tag: Tag to filter by (case-insensitive)
107
+ user_guid: User GUID for multi-user support
108
+
109
+ Returns:
110
+ List of file dictionaries with matching tag
111
+ """
112
+ cursor = conn.cursor()
113
+ # Use LIKE to match tag anywhere in the comma-separated tags field
114
+ # We add commas around the search pattern to ensure we match whole tags
115
+ tag_pattern = f"%{tag}%"
116
+
117
+ cursor.execute('''
118
+ SELECT id, filename, file_type, file_size, content_text,
119
+ content_base64, mime_type, token_count, added_at, tags
120
+ FROM conversation_files
121
+ WHERE conversation_id = ?
122
+ AND user_guid = ?
123
+ AND tags LIKE ?
124
+ ORDER BY added_at ASC
125
+ ''', (conversation_id, user_guid, tag_pattern))
126
+
127
+ files = []
128
+ for row in cursor.fetchall():
129
+ # Additional filtering to ensure we match whole tags (not partial matches)
130
+ tags_list = [t.strip().lower() for t in (row['tags'] or '').split(',') if t.strip()]
131
+ if tag.lower() in tags_list:
132
+ files.append({
133
+ 'id': row['id'],
134
+ 'filename': row['filename'],
135
+ 'file_type': row['file_type'],
136
+ 'file_size': row['file_size'],
137
+ 'content_text': row['content_text'],
138
+ 'content_base64': row['content_base64'],
139
+ 'mime_type': row['mime_type'],
140
+ 'token_count': row['token_count'],
141
+ 'added_at': row['added_at'],
142
+ 'tags': row['tags']
143
+ })
144
+
145
+ return files
146
+
147
+
148
+ def delete_conversation_files(conn: sqlite3.Connection, conversation_id: int,
149
+ user_guid: str = None):
150
+ """
151
+ Delete all files for a conversation.
152
+
153
+ Args:
154
+ conn: Database connection
155
+ conversation_id: ID of the conversation
156
+ user_guid: User GUID for multi-user support
157
+ """
158
+ try:
159
+ cursor = conn.cursor()
160
+ cursor.execute('''
161
+ DELETE FROM conversation_files
162
+ WHERE conversation_id = ? AND user_guid = ?
163
+ ''', (conversation_id, user_guid))
164
+ conn.commit()
165
+ logging.info(f"Deleted all files for conversation {conversation_id}")
166
+ except Exception as e:
167
+ logging.error(f"Failed to delete files for conversation {conversation_id}: {e}")
168
+ conn.rollback()
169
+
170
+
171
+ def delete_file(conn: sqlite3.Connection, file_id: int, user_guid: str = None) -> bool:
172
+ """
173
+ Delete a specific file by ID.
174
+
175
+ Args:
176
+ conn: Database connection
177
+ file_id: ID of the file to delete
178
+ user_guid: User GUID for multi-user support (for security filtering)
179
+
180
+ Returns:
181
+ True if successful, False otherwise
182
+ """
183
+ try:
184
+ cursor = conn.cursor()
185
+ # First, get the filename for logging (filtered by user_guid for security)
186
+ cursor.execute('SELECT filename FROM conversation_files WHERE id = ? AND user_guid = ?',
187
+ (file_id, user_guid))
188
+ row = cursor.fetchone()
189
+
190
+ if not row:
191
+ logging.warning(f"File ID {file_id} not found for user {user_guid}")
192
+ return False
193
+
194
+ filename = row['filename']
195
+
196
+ # Delete the file (filtered by user_guid for security)
197
+ cursor.execute('DELETE FROM conversation_files WHERE id = ? AND user_guid = ?',
198
+ (file_id, user_guid))
199
+ conn.commit()
200
+ logging.info(f"Deleted file '{filename}' (ID: {file_id})")
201
+ return True
202
+ except Exception as e:
203
+ logging.error(f"Failed to delete file {file_id}: {e}")
204
+ conn.rollback()
205
+ return False