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
dtSpark/web/session.py ADDED
@@ -0,0 +1,167 @@
1
+ """
2
+ Session manager for web interface.
3
+
4
+ Implements single-session authentication with configurable timeout.
5
+
6
+ """
7
+
8
+ import logging
9
+ import secrets
10
+ from typing import Optional
11
+ from datetime import datetime, timedelta
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class SessionManager:
17
+ """
18
+ Manages user sessions for the web interface.
19
+
20
+ Features:
21
+ - Single active session at a time (new login invalidates old session)
22
+ - Configurable inactivity timeout
23
+ - Secure session ID generation
24
+ - Automatic session expiration
25
+ """
26
+
27
+ def __init__(self, timeout_minutes: int = 30):
28
+ """
29
+ Initialise the session manager.
30
+
31
+ Args:
32
+ timeout_minutes: Minutes of inactivity before session expires
33
+ """
34
+ self._timeout_minutes = timeout_minutes
35
+ self._session_id: Optional[str] = None
36
+ self._last_activity: Optional[datetime] = None
37
+ self._created_at: Optional[datetime] = None
38
+
39
+ def create_session(self) -> str:
40
+ """
41
+ Create a new session.
42
+
43
+ If a session already exists, it is invalidated and replaced.
44
+
45
+ Returns:
46
+ The new session ID
47
+
48
+ Note:
49
+ Session IDs are cryptographically secure random strings.
50
+ """
51
+ # Generate secure random session ID (32 bytes = 64 hex characters)
52
+ self._session_id = secrets.token_hex(32)
53
+ self._created_at = datetime.now()
54
+ self._last_activity = datetime.now()
55
+
56
+ return self._session_id
57
+
58
+ def validate_session(self, session_id: str) -> bool:
59
+ """
60
+ Validate a session ID and check if it's expired.
61
+
62
+ Args:
63
+ session_id: The session ID to validate
64
+
65
+ Returns:
66
+ True if session is valid and not expired, False otherwise
67
+
68
+ Note:
69
+ If validation succeeds, the last activity timestamp is updated.
70
+ """
71
+ # Check if session exists
72
+ if self._session_id is None:
73
+ logger.debug("Session validation failed: no active session exists")
74
+ return False
75
+
76
+ # Check if session ID matches
77
+ if session_id != self._session_id:
78
+ logger.debug(f"Session validation failed: ID mismatch (provided: {session_id[:8]}..., "
79
+ f"expected: {self._session_id[:8]}...)")
80
+ return False
81
+
82
+ # Check if session has expired
83
+ if self._is_expired():
84
+ logger.info(f"Session expired after {self._timeout_minutes} minutes of inactivity")
85
+ self._invalidate_session()
86
+ return False
87
+
88
+ # Update last activity timestamp
89
+ self._last_activity = datetime.now()
90
+
91
+ return True
92
+
93
+ def invalidate_session(self):
94
+ """
95
+ Explicitly invalidate the current session.
96
+
97
+ Used when user logs out or when session is replaced.
98
+ """
99
+ self._invalidate_session()
100
+
101
+ def get_session_info(self) -> Optional[dict]:
102
+ """
103
+ Get information about the current session.
104
+
105
+ Returns:
106
+ Dictionary with session information, or None if no active session
107
+ """
108
+ if self._session_id is None:
109
+ return None
110
+
111
+ return {
112
+ 'session_id': self._session_id,
113
+ 'created_at': self._created_at,
114
+ 'last_activity': self._last_activity,
115
+ 'timeout_minutes': self._timeout_minutes,
116
+ 'is_expired': self._is_expired(),
117
+ }
118
+
119
+ def update_timeout(self, timeout_minutes: int):
120
+ """
121
+ Update the session timeout duration.
122
+
123
+ Args:
124
+ timeout_minutes: New timeout duration in minutes
125
+ """
126
+ self._timeout_minutes = timeout_minutes
127
+
128
+ def get_remaining_time(self) -> Optional[timedelta]:
129
+ """
130
+ Get the remaining time before session expires.
131
+
132
+ Returns:
133
+ Timedelta representing remaining time, or None if no active session
134
+ """
135
+ if self._session_id is None or self._last_activity is None:
136
+ return None
137
+
138
+ expiry_time = self._last_activity + timedelta(minutes=self._timeout_minutes)
139
+ remaining = expiry_time - datetime.now()
140
+
141
+ return remaining if remaining.total_seconds() > 0 else timedelta(0)
142
+
143
+ def _is_expired(self) -> bool:
144
+ """
145
+ Check if the current session has expired.
146
+
147
+ Returns:
148
+ True if session is expired, False otherwise
149
+
150
+ Note:
151
+ If timeout_minutes is 0 or negative, session never expires.
152
+ """
153
+ if self._last_activity is None:
154
+ return True
155
+
156
+ # Timeout of 0 or less means session never expires
157
+ if self._timeout_minutes <= 0:
158
+ return False
159
+
160
+ expiry_time = self._last_activity + timedelta(minutes=self._timeout_minutes)
161
+ return datetime.now() > expiry_time
162
+
163
+ def _invalidate_session(self):
164
+ """Internal method to clear session data."""
165
+ self._session_id = None
166
+ self._created_at = None
167
+ self._last_activity = None
@@ -0,0 +1,195 @@
1
+ """
2
+ SSL certificate utilities for the web interface.
3
+
4
+ Handles generation of self-signed certificates for HTTPS support.
5
+
6
+ """
7
+
8
+ import logging
9
+ import os.path
10
+ import socket
11
+ import ipaddress
12
+ from pathlib import Path
13
+ from datetime import datetime, timedelta
14
+ from typing import Tuple, Optional
15
+
16
+ from dtPyAppFramework.paths import ApplicationPaths
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ def generate_self_signed_certificate(
22
+ cert_file: str,
23
+ key_file: str,
24
+ hostname: str = "localhost",
25
+ validity_days: int = 365,
26
+ ) -> bool:
27
+ """
28
+ Generate a self-signed SSL certificate and private key.
29
+
30
+ Args:
31
+ cert_file: Path where the certificate will be saved
32
+ key_file: Path where the private key will be saved
33
+ hostname: Hostname for the certificate (default: localhost)
34
+ validity_days: Number of days the certificate is valid (default: 365)
35
+
36
+ Returns:
37
+ True if generation succeeded, False otherwise
38
+ """
39
+ try:
40
+ from cryptography import x509
41
+ from cryptography.x509.oid import NameOID
42
+ from cryptography.hazmat.primitives import hashes
43
+ from cryptography.hazmat.primitives.asymmetric import rsa
44
+ from cryptography.hazmat.primitives import serialization
45
+ except ImportError:
46
+ logger.error("cryptography package is required for SSL certificate generation")
47
+ logger.error("Install it with: pip install cryptography")
48
+ return False
49
+
50
+ try:
51
+ # Generate private key
52
+ logger.info("Generating RSA private key...")
53
+ private_key = rsa.generate_private_key(
54
+ public_exponent=65537,
55
+ key_size=2048,
56
+ )
57
+
58
+ # Create certificate subject and issuer (same for self-signed)
59
+ subject = issuer = x509.Name([
60
+ x509.NameAttribute(NameOID.COUNTRY_NAME, "AU"),
61
+ x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "New South Wales"),
62
+ x509.NameAttribute(NameOID.LOCALITY_NAME, "Sydney"),
63
+ x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Digital-Thought"),
64
+ x509.NameAttribute(NameOID.COMMON_NAME, hostname),
65
+ ])
66
+
67
+ # Build certificate
68
+ logger.info(f"Generating self-signed certificate for {hostname}...")
69
+ cert = (
70
+ x509.CertificateBuilder()
71
+ .subject_name(subject)
72
+ .issuer_name(issuer)
73
+ .public_key(private_key.public_key())
74
+ .serial_number(x509.random_serial_number())
75
+ .not_valid_before(datetime.utcnow())
76
+ .not_valid_after(datetime.utcnow() + timedelta(days=validity_days))
77
+ .add_extension(
78
+ x509.SubjectAlternativeName([
79
+ x509.DNSName(hostname),
80
+ x509.DNSName("localhost"),
81
+ x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")),
82
+ ]),
83
+ critical=False,
84
+ )
85
+ .sign(private_key, hashes.SHA256())
86
+ )
87
+
88
+ # Ensure directory exists
89
+ cert_path = Path(cert_file)
90
+ key_path = Path(key_file)
91
+ cert_path.parent.mkdir(parents=True, exist_ok=True)
92
+ key_path.parent.mkdir(parents=True, exist_ok=True)
93
+
94
+ # Write private key
95
+ logger.info(f"Writing private key to {key_file}")
96
+ with open(key_file, "wb") as f:
97
+ f.write(
98
+ private_key.private_bytes(
99
+ encoding=serialization.Encoding.PEM,
100
+ format=serialization.PrivateFormat.TraditionalOpenSSL,
101
+ encryption_algorithm=serialization.NoEncryption(),
102
+ )
103
+ )
104
+
105
+ # Write certificate
106
+ logger.info(f"Writing certificate to {cert_file}")
107
+ with open(cert_file, "wb") as f:
108
+ f.write(cert.public_bytes(serialization.Encoding.PEM))
109
+
110
+ logger.info(f"Self-signed certificate generated successfully (valid for {validity_days} days)")
111
+ return True
112
+
113
+ except Exception as e:
114
+ logger.error(f"Failed to generate self-signed certificate: {e}")
115
+ return False
116
+
117
+
118
+ def check_certificate_files(cert_file: str, key_file: str) -> bool:
119
+ """
120
+ Check if certificate and key files exist and are readable.
121
+
122
+ Args:
123
+ cert_file: Path to certificate file
124
+ key_file: Path to private key file
125
+
126
+ Returns:
127
+ True if both files exist and are readable, False otherwise
128
+ """
129
+ cert_path = Path(cert_file)
130
+ key_path = Path(key_file)
131
+
132
+ if not cert_path.exists():
133
+ logger.debug(f"Certificate file not found: {cert_file}")
134
+ return False
135
+
136
+ if not key_path.exists():
137
+ logger.debug(f"Key file not found: {key_file}")
138
+ return False
139
+
140
+ # Try to read files
141
+ try:
142
+ with open(cert_file, "r") as f:
143
+ cert_content = f.read()
144
+ with open(key_file, "r") as f:
145
+ key_content = f.read()
146
+
147
+ if not cert_content or not key_content:
148
+ logger.warning("Certificate or key file is empty")
149
+ return False
150
+
151
+ return True
152
+ except Exception as e:
153
+ logger.error(f"Failed to read certificate files: {e}")
154
+ return False
155
+
156
+
157
+ def setup_ssl_certificates(
158
+ cert_file: str,
159
+ key_file: str,
160
+ auto_generate: bool = True,
161
+ hostname: str = "localhost",
162
+ ) -> Tuple[bool, Optional[str], Optional[str]]:
163
+ """
164
+ Setup SSL certificates, generating them if needed.
165
+
166
+ Args:
167
+ cert_file: Path to certificate file
168
+ key_file: Path to private key file
169
+ auto_generate: Whether to automatically generate if files don't exist
170
+ hostname: Hostname for the certificate
171
+
172
+ Returns:
173
+ Tuple of (success, cert_file_path, key_file_path)
174
+ Returns (False, None, None) if setup failed
175
+ """
176
+ # Convert to absolute paths
177
+ cert_file = str(Path(ApplicationPaths().usr_data_root_path, cert_file).resolve())
178
+ key_file = str(Path(ApplicationPaths().usr_data_root_path, key_file).resolve())
179
+
180
+ # Check if files exist
181
+ if check_certificate_files(cert_file, key_file):
182
+ logger.info("SSL certificate files found and verified")
183
+ return True, cert_file, key_file
184
+
185
+ # Generate if auto-generate is enabled
186
+ if auto_generate:
187
+ logger.info("SSL certificate files not found, generating self-signed certificate...")
188
+ if generate_self_signed_certificate(cert_file, key_file, hostname):
189
+ return True, cert_file, key_file
190
+ else:
191
+ logger.error("Failed to generate SSL certificates")
192
+ return False, None, None
193
+ else:
194
+ logger.error("SSL certificate files not found and auto-generation is disabled")
195
+ return False, None, None