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/aws/costs.py ADDED
@@ -0,0 +1,318 @@
1
+ """
2
+ Cost tracking module for AWS Bedrock usage.
3
+
4
+ This module provides functionality for:
5
+ - Retrieving Bedrock usage costs from AWS Cost Explorer
6
+ - Breaking down costs by model/usage type
7
+ - Reporting on monthly and daily costs
8
+ """
9
+
10
+ import logging
11
+ from datetime import datetime, timedelta
12
+ from typing import Dict, Optional, List
13
+ from botocore.exceptions import ClientError
14
+
15
+
16
+ class CostTracker:
17
+ """Tracks AWS Bedrock costs using Cost Explorer API."""
18
+
19
+ def __init__(self, cost_explorer_client):
20
+ """
21
+ Initialise the cost tracker.
22
+
23
+ Args:
24
+ cost_explorer_client: Boto3 Cost Explorer client
25
+ """
26
+ self.ce_client = cost_explorer_client
27
+ self.has_permissions = None # Cache permission check
28
+
29
+ def check_permissions(self) -> bool:
30
+ """
31
+ Check if the authenticated user has Cost Explorer permissions.
32
+
33
+ Returns:
34
+ True if user has permissions, False otherwise
35
+ """
36
+ if self.has_permissions is not None:
37
+ return self.has_permissions
38
+
39
+ try:
40
+ # Try a minimal query to check permissions
41
+ end_date = datetime.now().date()
42
+ start_date = end_date - timedelta(days=1)
43
+
44
+ self.ce_client.get_cost_and_usage(
45
+ TimePeriod={
46
+ 'Start': start_date.strftime('%Y-%m-%d'),
47
+ 'End': end_date.strftime('%Y-%m-%d')
48
+ },
49
+ Granularity='DAILY',
50
+ Metrics=['UnblendedCost']
51
+ )
52
+
53
+ self.has_permissions = True
54
+ logging.info("Cost Explorer permissions verified")
55
+ return True
56
+
57
+ except ClientError as e:
58
+ error_code = e.response.get('Error', {}).get('Code', '')
59
+ if error_code in ['AccessDeniedException', 'UnauthorizedException']:
60
+ logging.info("User does not have Cost Explorer permissions")
61
+ self.has_permissions = False
62
+ return False
63
+ else:
64
+ logging.warning(f"Error checking Cost Explorer permissions: {e}")
65
+ self.has_permissions = False
66
+ return False
67
+ except Exception as e:
68
+ logging.warning(f"Unexpected error checking permissions: {e}")
69
+ self.has_permissions = False
70
+ return False
71
+
72
+ def get_bedrock_costs(self) -> Optional[Dict]:
73
+ """
74
+ Retrieve Bedrock costs for current month, last month and last 24 hours.
75
+
76
+ Returns:
77
+ Dictionary with cost information, or None if unavailable
78
+ """
79
+ if not self.check_permissions():
80
+ return None
81
+
82
+ try:
83
+ # Calculate date ranges
84
+ today = datetime.now().date()
85
+ yesterday = today - timedelta(days=1)
86
+
87
+ # Current month (from 1st of this month to today)
88
+ first_day_this_month = today.replace(day=1)
89
+
90
+ # Last month (from 1st to last day of previous month)
91
+ last_day_last_month = first_day_this_month - timedelta(days=1)
92
+ first_day_last_month = last_day_last_month.replace(day=1)
93
+
94
+ # Get current month's costs (month-to-date)
95
+ current_month_costs = self._get_costs_for_period(
96
+ first_day_this_month,
97
+ today + timedelta(days=1), # End date is exclusive, so add 1 day to include today
98
+ 'Current Month'
99
+ )
100
+
101
+ # Get last month's costs
102
+ last_month_costs = self._get_costs_for_period(
103
+ first_day_last_month,
104
+ first_day_this_month, # End date is exclusive
105
+ 'Last Month'
106
+ )
107
+
108
+ # Get last 24 hours costs
109
+ last_24h_costs = self._get_costs_for_period(
110
+ yesterday,
111
+ today,
112
+ 'Last 24 Hours'
113
+ )
114
+
115
+ return {
116
+ 'current_month': current_month_costs,
117
+ 'last_month': last_month_costs,
118
+ 'last_24h': last_24h_costs,
119
+ 'currency': 'USD' # Cost Explorer returns USD by default
120
+ }
121
+
122
+ except Exception as e:
123
+ logging.error(f"Error retrieving Bedrock costs: {e}")
124
+ return None
125
+
126
+ def _get_costs_for_period(self, start_date, end_date, period_name: str) -> Dict:
127
+ """
128
+ Get Bedrock costs for a specific time period with model breakdown.
129
+
130
+ Args:
131
+ start_date: Start date (inclusive)
132
+ end_date: End date (exclusive)
133
+ period_name: Human-readable period name for logging
134
+
135
+ Returns:
136
+ Dictionary with total cost and breakdown by usage type
137
+ """
138
+ try:
139
+ # Query 1: Get total Bedrock costs
140
+ # Note: AWS Cost Explorer uses model-specific service names for Bedrock
141
+ # We search for services containing "Amazon Bedrock Edition"
142
+ response = self.ce_client.get_cost_and_usage(
143
+ TimePeriod={
144
+ 'Start': start_date.strftime('%Y-%m-%d'),
145
+ 'End': end_date.strftime('%Y-%m-%d')
146
+ },
147
+ Granularity='MONTHLY' if period_name in ['Last Month', 'Current Month'] else 'DAILY',
148
+ Metrics=['UnblendedCost'],
149
+ GroupBy=[{
150
+ 'Type': 'DIMENSION',
151
+ 'Key': 'SERVICE'
152
+ }]
153
+ )
154
+
155
+ # Extract total cost and breakdown by filtering for Bedrock services
156
+ # AWS uses service names like "Claude 3.5 Sonnet (Amazon Bedrock Edition)"
157
+ total_cost = 0.0
158
+ breakdown = {}
159
+
160
+ if response.get('ResultsByTime'):
161
+ for result in response['ResultsByTime']:
162
+ for group in result.get('Groups', []):
163
+ service_name = group.get('Keys', [''])[0]
164
+
165
+ # Filter for Bedrock services (contain "Amazon Bedrock Edition")
166
+ if 'Amazon Bedrock Edition' in service_name or 'Bedrock' in service_name:
167
+ amount = float(group.get('Metrics', {}).get('UnblendedCost', {}).get('Amount', '0'))
168
+
169
+ if amount > 0:
170
+ total_cost += amount
171
+
172
+ # Extract model name from service name
173
+ # Example: "Claude 3.5 Sonnet (Amazon Bedrock Edition)" -> "Claude 3.5 Sonnet"
174
+ model_name = service_name.replace(' (Amazon Bedrock Edition)', '').strip()
175
+
176
+ if model_name in breakdown:
177
+ breakdown[model_name] += amount
178
+ else:
179
+ breakdown[model_name] = amount
180
+
181
+ logging.info(f"{period_name} Bedrock costs: ${total_cost:.4f}")
182
+ if breakdown:
183
+ logging.debug(f"{period_name} breakdown: {breakdown}")
184
+
185
+ return {
186
+ 'total': total_cost,
187
+ 'breakdown': breakdown,
188
+ 'period': period_name,
189
+ 'start_date': start_date.strftime('%Y-%m-%d'),
190
+ 'end_date': end_date.strftime('%Y-%m-%d')
191
+ }
192
+
193
+ except Exception as e:
194
+ logging.error(f"Error getting costs for {period_name}: {e}")
195
+ return {
196
+ 'total': 0.0,
197
+ 'breakdown': {},
198
+ 'period': period_name,
199
+ 'error': str(e)
200
+ }
201
+
202
+ def _parse_model_from_usage_type(self, usage_type: str) -> str:
203
+ """
204
+ Parse model name from AWS usage type string.
205
+
206
+ Usage types typically look like:
207
+ - "APS2-ModelInference-Claude-3-5-Sonnet-v2"
208
+ - "USE1-ModelInference-Titan-Text-Express"
209
+ - "APS2-OnDemand-Throughput"
210
+
211
+ Args:
212
+ usage_type: AWS usage type string
213
+
214
+ Returns:
215
+ Human-readable model name
216
+ """
217
+ # Remove region prefix (e.g., "APS2-", "USE1-")
218
+ parts = usage_type.split('-')
219
+
220
+ if 'ModelInference' in usage_type:
221
+ # Find the index of ModelInference and take everything after it
222
+ try:
223
+ idx = parts.index('ModelInference')
224
+ model_parts = parts[idx + 1:]
225
+
226
+ # Join and clean up
227
+ model_name = ' '.join(model_parts)
228
+
229
+ # Common model name mappings for readability
230
+ replacements = {
231
+ 'Claude 3 5 Sonnet v2': 'Claude 3.5 Sonnet v2',
232
+ 'Claude 3 5 Sonnet': 'Claude 3.5 Sonnet',
233
+ 'Claude 3 Opus': 'Claude 3 Opus',
234
+ 'Claude 3 Haiku': 'Claude 3 Haiku',
235
+ 'Titan Text Express': 'Titan Text Express',
236
+ 'Titan Text Lite': 'Titan Text Lite',
237
+ }
238
+
239
+ for old, new in replacements.items():
240
+ if old in model_name:
241
+ model_name = new
242
+ break
243
+
244
+ return model_name
245
+ except (ValueError, IndexError):
246
+ pass
247
+
248
+ # If we can't parse it, return the usage type
249
+ return usage_type
250
+
251
+ def format_cost_report(self, costs: Optional[Dict]) -> List[str]:
252
+ """
253
+ Format cost information into human-readable lines.
254
+
255
+ Args:
256
+ costs: Cost dictionary from get_bedrock_costs()
257
+
258
+ Returns:
259
+ List of formatted strings for display
260
+ """
261
+ if not costs:
262
+ return ["Financial tracking not available with current user permissions"]
263
+
264
+ lines = []
265
+ currency = costs.get('currency', 'USD')
266
+
267
+ # Current Month
268
+ current_month = costs.get('current_month', {})
269
+ if current_month:
270
+ total = current_month.get('total', 0.0)
271
+ breakdown = current_month.get('breakdown', {})
272
+ period = current_month.get('period', 'Current Month')
273
+
274
+ lines.append(f"{period}: ${total:.2f} {currency}")
275
+
276
+ if breakdown and total > 0:
277
+ # Sort by cost (descending)
278
+ sorted_breakdown = sorted(breakdown.items(), key=lambda x: x[1], reverse=True)
279
+ for model, cost in sorted_breakdown:
280
+ percentage = (cost / total * 100) if total > 0 else 0
281
+ lines.append(f" • {model}: ${cost:.2f} ({percentage:.1f}%)")
282
+
283
+ # Last Month
284
+ last_month = costs.get('last_month', {})
285
+ if last_month:
286
+ total = last_month.get('total', 0.0)
287
+ breakdown = last_month.get('breakdown', {})
288
+ period = last_month.get('period', 'Last Month')
289
+
290
+ lines.append(f"{period}: ${total:.2f} {currency}")
291
+
292
+ if breakdown and total > 0:
293
+ # Sort by cost (descending)
294
+ sorted_breakdown = sorted(breakdown.items(), key=lambda x: x[1], reverse=True)
295
+ for model, cost in sorted_breakdown:
296
+ percentage = (cost / total * 100) if total > 0 else 0
297
+ lines.append(f" • {model}: ${cost:.2f} ({percentage:.1f}%)")
298
+
299
+ # Last 24 Hours
300
+ last_24h = costs.get('last_24h', {})
301
+ if last_24h:
302
+ total = last_24h.get('total', 0.0)
303
+ breakdown = last_24h.get('breakdown', {})
304
+ period = last_24h.get('period', 'Last 24 Hours')
305
+
306
+ lines.append(f"{period}: ${total:.4f} {currency}")
307
+
308
+ if breakdown and total > 0:
309
+ # Sort by cost (descending)
310
+ sorted_breakdown = sorted(breakdown.items(), key=lambda x: x[1], reverse=True)
311
+ for model, cost in sorted_breakdown:
312
+ percentage = (cost / total * 100) if total > 0 else 0
313
+ lines.append(f" • {model}: ${cost:.4f} ({percentage:.1f}%)")
314
+
315
+ if not lines:
316
+ lines.append("No Bedrock usage costs found for the specified periods")
317
+
318
+ return lines