dtSpark 1.1.0a2__py3-none-any.whl → 1.1.0a6__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 (56) hide show
  1. dtSpark/_version.txt +1 -1
  2. dtSpark/aws/authentication.py +1 -1
  3. dtSpark/aws/bedrock.py +238 -239
  4. dtSpark/aws/costs.py +9 -5
  5. dtSpark/aws/pricing.py +25 -21
  6. dtSpark/cli_interface.py +69 -62
  7. dtSpark/conversation_manager.py +54 -47
  8. dtSpark/core/application.py +151 -111
  9. dtSpark/core/context_compaction.py +241 -226
  10. dtSpark/daemon/__init__.py +36 -22
  11. dtSpark/daemon/action_monitor.py +46 -17
  12. dtSpark/daemon/daemon_app.py +126 -104
  13. dtSpark/daemon/daemon_manager.py +59 -23
  14. dtSpark/daemon/pid_file.py +3 -2
  15. dtSpark/database/autonomous_actions.py +3 -0
  16. dtSpark/database/credential_prompt.py +52 -54
  17. dtSpark/files/manager.py +6 -12
  18. dtSpark/limits/__init__.py +1 -1
  19. dtSpark/limits/tokens.py +2 -2
  20. dtSpark/llm/anthropic_direct.py +246 -141
  21. dtSpark/llm/ollama.py +3 -1
  22. dtSpark/mcp_integration/manager.py +4 -4
  23. dtSpark/mcp_integration/tool_selector.py +83 -77
  24. dtSpark/resources/config.yaml.template +10 -0
  25. dtSpark/safety/patterns.py +45 -46
  26. dtSpark/safety/prompt_inspector.py +8 -1
  27. dtSpark/scheduler/creation_tools.py +273 -181
  28. dtSpark/scheduler/executor.py +503 -221
  29. dtSpark/tools/builtin.py +70 -53
  30. dtSpark/web/endpoints/autonomous_actions.py +12 -9
  31. dtSpark/web/endpoints/chat.py +18 -6
  32. dtSpark/web/endpoints/conversations.py +57 -17
  33. dtSpark/web/endpoints/main_menu.py +132 -105
  34. dtSpark/web/endpoints/streaming.py +2 -2
  35. dtSpark/web/server.py +65 -5
  36. dtSpark/web/ssl_utils.py +3 -3
  37. dtSpark/web/static/css/dark-theme.css +8 -29
  38. dtSpark/web/static/js/actions.js +2 -1
  39. dtSpark/web/static/js/chat.js +6 -8
  40. dtSpark/web/static/js/main.js +8 -8
  41. dtSpark/web/static/js/sse-client.js +130 -122
  42. dtSpark/web/templates/actions.html +5 -5
  43. dtSpark/web/templates/base.html +13 -0
  44. dtSpark/web/templates/chat.html +52 -50
  45. dtSpark/web/templates/conversations.html +50 -22
  46. dtSpark/web/templates/goodbye.html +2 -2
  47. dtSpark/web/templates/main_menu.html +17 -17
  48. dtSpark/web/templates/new_conversation.html +51 -20
  49. dtSpark/web/web_interface.py +2 -2
  50. {dtspark-1.1.0a2.dist-info → dtspark-1.1.0a6.dist-info}/METADATA +9 -2
  51. dtspark-1.1.0a6.dist-info/RECORD +96 -0
  52. dtspark-1.1.0a2.dist-info/RECORD +0 -96
  53. {dtspark-1.1.0a2.dist-info → dtspark-1.1.0a6.dist-info}/WHEEL +0 -0
  54. {dtspark-1.1.0a2.dist-info → dtspark-1.1.0a6.dist-info}/entry_points.txt +0 -0
  55. {dtspark-1.1.0a2.dist-info → dtspark-1.1.0a6.dist-info}/licenses/LICENSE +0 -0
  56. {dtspark-1.1.0a2.dist-info → dtspark-1.1.0a6.dist-info}/top_level.txt +0 -0
@@ -22,6 +22,13 @@ from dtSpark.database.tool_permissions import PERMISSION_ALLOWED, PERMISSION_DEN
22
22
  from dtSpark.llm.context_limits import ContextLimitResolver
23
23
  from dtSpark.core.context_compaction import ContextCompactor, get_provider_from_model_id
24
24
 
25
+ # String constants to avoid duplication
26
+ _ERR_NO_ACTIVE_CONVERSATION = "No active conversation. Create or load a conversation first."
27
+ _TOOL_RESULTS_MARKER = '[TOOL_RESULTS]'
28
+ _SUMMARY_MARKER = '[Summary of previous conversation]'
29
+ _ERR_NO_CONVERSATION_TO_EXPORT = "No conversation loaded to export"
30
+ _LABEL_TOKEN_COUNT = 'Token Count'
31
+
25
32
 
26
33
  class ConversationManager:
27
34
  """Manages conversation state and automatic rollup for token management."""
@@ -254,7 +261,7 @@ class ConversationManager:
254
261
  Message ID
255
262
  """
256
263
  if not self.current_conversation_id:
257
- raise ValueError("No active conversation. Create or load a conversation first.")
264
+ raise ValueError(_ERR_NO_ACTIVE_CONVERSATION)
258
265
 
259
266
  token_count = self.bedrock_service.count_tokens(content)
260
267
  message_id = self.database.add_message(
@@ -282,7 +289,7 @@ class ConversationManager:
282
289
  Message ID
283
290
  """
284
291
  if not self.current_conversation_id:
285
- raise ValueError("No active conversation. Create or load a conversation first.")
292
+ raise ValueError(_ERR_NO_ACTIVE_CONVERSATION)
286
293
 
287
294
  token_count = self.bedrock_service.count_tokens(content)
288
295
  message_id = self.database.add_message(
@@ -334,10 +341,10 @@ class ConversationManager:
334
341
  except json.JSONDecodeError:
335
342
  pass # Not JSON, treat as regular message
336
343
 
337
- if msg['role'] == 'user' and content.startswith('[TOOL_RESULTS]'):
344
+ if msg['role'] == 'user' and content.startswith(_TOOL_RESULTS_MARKER):
338
345
  # This is a tool results message
339
346
  try:
340
- tool_results_json = content.replace('[TOOL_RESULTS]', '', 1)
347
+ tool_results_json = content.replace(_TOOL_RESULTS_MARKER, '', 1)
341
348
  tool_results = json.loads(tool_results_json)
342
349
  formatted_messages.append({
343
350
  'role': 'user',
@@ -472,12 +479,12 @@ class ConversationManager:
472
479
  text_parts.append(block.get('text', ''))
473
480
  if text_parts:
474
481
  return '\n'.join(text_parts)
475
- except (json.JSONDecodeError, ValueError):
482
+ except ValueError:
476
483
  # Not JSON, return as-is
477
484
  pass
478
485
 
479
486
  # Check if it's a rollup summary
480
- if content.startswith('[Summary of previous conversation]'):
487
+ if content.startswith(_SUMMARY_MARKER):
481
488
  return content
482
489
 
483
490
  # Regular assistant message
@@ -575,7 +582,7 @@ class ConversationManager:
575
582
  while cutoff_index > 0:
576
583
  cutoff_msg = messages[cutoff_index] if cutoff_index < len(messages) else None
577
584
 
578
- if cutoff_msg and cutoff_msg['content'].startswith('[TOOL_RESULTS]'):
585
+ if cutoff_msg and cutoff_msg['content'].startswith(_TOOL_RESULTS_MARKER):
579
586
  # This is a tool_result message, we need to keep the preceding tool_use
580
587
  # Move cutoff back one more message
581
588
  cutoff_index -= 1
@@ -595,7 +602,7 @@ class ConversationManager:
595
602
  # Move cutoff back to include this tool_use and its result
596
603
  cutoff_index -= 1
597
604
  messages_to_keep_count += 1
598
- except:
605
+ except ValueError:
599
606
  pass
600
607
 
601
608
  messages_to_summarise = messages[:cutoff_index] if cutoff_index > 0 else []
@@ -621,7 +628,7 @@ class ConversationManager:
621
628
  self.database.add_message(
622
629
  self.current_conversation_id,
623
630
  'user',
624
- f"[Summary of previous conversation]\n{summary_content}",
631
+ f"{_SUMMARY_MARKER}\n{summary_content}",
625
632
  summary_token_count
626
633
  )
627
634
 
@@ -782,13 +789,13 @@ class ConversationManager:
782
789
  import re
783
790
 
784
791
  # Remove [TOOL_RESULTS] prefix if present
785
- if content.startswith('[TOOL_RESULTS]'):
786
- content = content[len('[TOOL_RESULTS]'):].strip()
792
+ if content.startswith(_TOOL_RESULTS_MARKER):
793
+ content = content[len(_TOOL_RESULTS_MARKER):].strip()
787
794
 
788
795
  # Try to parse as JSON
789
796
  try:
790
797
  data = json.loads(content)
791
- except:
798
+ except ValueError:
792
799
  # Not JSON, try to extract numbers from text
793
800
  data = None
794
801
 
@@ -861,7 +868,7 @@ class ConversationManager:
861
868
  content = msg['content']
862
869
 
863
870
  # Parse and clean tool-related content for better summarization
864
- if content.startswith('[TOOL_RESULTS]'):
871
+ if content.startswith(_TOOL_RESULTS_MARKER):
865
872
  # Extract numerical data from tool results
866
873
  numerical_summary = self._extract_numerical_data(content)
867
874
  if numerical_summary:
@@ -887,7 +894,7 @@ class ConversationManager:
887
894
  conversation_text.append(f"{role}: {' '.join(text_parts)}")
888
895
  if tool_parts:
889
896
  conversation_text.append(f"{role}: [Called tools: {', '.join(tool_parts)}]")
890
- except:
897
+ except ValueError:
891
898
  # If parsing fails, include content as-is
892
899
  conversation_text.append(f"{role}: {content}")
893
900
  else:
@@ -1506,7 +1513,7 @@ Current date and time: {datetime_str}"""
1506
1513
  Assistant's response content or None on failure
1507
1514
  """
1508
1515
  if not self.current_conversation_id:
1509
- raise ValueError("No active conversation. Create or load a conversation first.")
1516
+ raise ValueError(_ERR_NO_ACTIVE_CONVERSATION)
1510
1517
 
1511
1518
  logging.debug(f"send_message called with: {user_message[:50]}...")
1512
1519
 
@@ -1898,7 +1905,7 @@ Current date and time: {datetime_str}"""
1898
1905
 
1899
1906
  # Add tool results as a user message
1900
1907
  tool_results_json = json.dumps(tool_results)
1901
- self.add_user_message(f"[TOOL_RESULTS]{tool_results_json}")
1908
+ self.add_user_message(f"{_TOOL_RESULTS_MARKER}{tool_results_json}")
1902
1909
 
1903
1910
  # Continue loop to get model's next response
1904
1911
  continue
@@ -2339,7 +2346,7 @@ Current date and time: {datetime_str}"""
2339
2346
  Markdown-formatted string of the conversation
2340
2347
  """
2341
2348
  if not self.current_conversation_id:
2342
- logging.warning("No conversation loaded to export")
2349
+ logging.warning(_ERR_NO_CONVERSATION_TO_EXPORT)
2343
2350
  return ""
2344
2351
 
2345
2352
  # Get conversation info
@@ -2389,8 +2396,8 @@ Current date and time: {datetime_str}"""
2389
2396
  content = msg['content']
2390
2397
 
2391
2398
  # Detect special message types
2392
- is_rollup_summary = content.startswith('[Summary of previous conversation]')
2393
- is_tool_result = content.startswith('[TOOL_RESULTS]')
2399
+ is_rollup_summary = content.startswith(_SUMMARY_MARKER)
2400
+ is_tool_result = content.startswith(_TOOL_RESULTS_MARKER)
2394
2401
 
2395
2402
  # Check if this is a tool call message (assistant with tool_use blocks)
2396
2403
  is_tool_call = False
@@ -2399,7 +2406,7 @@ Current date and time: {datetime_str}"""
2399
2406
  content_blocks = json.loads(content)
2400
2407
  if isinstance(content_blocks, list) and any(block.get('type') == 'tool_use' for block in content_blocks):
2401
2408
  is_tool_call = True
2402
- except:
2409
+ except ValueError:
2403
2410
  pass
2404
2411
 
2405
2412
  # Format role header based on message type
@@ -2420,10 +2427,10 @@ Current date and time: {datetime_str}"""
2420
2427
  md_lines.append("")
2421
2428
 
2422
2429
  # Clean up content if it's tool-related
2423
- if content.startswith('[TOOL_RESULTS]') and include_tools:
2430
+ if content.startswith(_TOOL_RESULTS_MARKER) and include_tools:
2424
2431
  # Parse and format tool results
2425
2432
  try:
2426
- tool_results_json = content.replace('[TOOL_RESULTS]', '', 1)
2433
+ tool_results_json = content.replace(_TOOL_RESULTS_MARKER, '', 1)
2427
2434
  tool_results = json.loads(tool_results_json)
2428
2435
  md_lines.append("**Tool Results:**")
2429
2436
  md_lines.append("")
@@ -2431,10 +2438,10 @@ Current date and time: {datetime_str}"""
2431
2438
  md_lines.append(f"- Tool: `{result.get('tool_use_id', 'unknown')}`")
2432
2439
  md_lines.append(f" Result: {result.get('content', '')}")
2433
2440
  md_lines.append("")
2434
- except:
2441
+ except ValueError:
2435
2442
  md_lines.append(content)
2436
2443
  md_lines.append("")
2437
- elif content.startswith('[TOOL_RESULTS]') and not include_tools:
2444
+ elif content.startswith(_TOOL_RESULTS_MARKER) and not include_tools:
2438
2445
  # Skip tool results if not including tools
2439
2446
  md_lines.append("*[Tool execution details omitted]*")
2440
2447
  md_lines.append("")
@@ -2450,7 +2457,7 @@ Current date and time: {datetime_str}"""
2450
2457
  md_lines.append(f"**Tool Call:** `{block.get('name')}`")
2451
2458
  md_lines.append(f"**Input:** {json.dumps(block.get('input', {}), indent=2)}")
2452
2459
  md_lines.append("")
2453
- except:
2460
+ except ValueError:
2454
2461
  md_lines.append(content)
2455
2462
  md_lines.append("")
2456
2463
  else:
@@ -2574,7 +2581,7 @@ Current date and time: {datetime_str}"""
2574
2581
  True if successful, False otherwise
2575
2582
  """
2576
2583
  if not self.current_conversation_id:
2577
- logging.warning("No conversation loaded to export")
2584
+ logging.warning(_ERR_NO_CONVERSATION_TO_EXPORT)
2578
2585
  return False
2579
2586
 
2580
2587
  try:
@@ -2814,8 +2821,8 @@ Current date and time: {datetime_str}"""
2814
2821
  content = msg['content']
2815
2822
 
2816
2823
  # Detect special message types
2817
- is_rollup_summary = content.startswith('[Summary of previous conversation]')
2818
- is_tool_result = content.startswith('[TOOL_RESULTS]')
2824
+ is_rollup_summary = content.startswith(_SUMMARY_MARKER)
2825
+ is_tool_result = content.startswith(_TOOL_RESULTS_MARKER)
2819
2826
 
2820
2827
  # Check if this is a tool call message (assistant with tool_use blocks)
2821
2828
  is_tool_call = False
@@ -2824,7 +2831,7 @@ Current date and time: {datetime_str}"""
2824
2831
  content_blocks = json.loads(content)
2825
2832
  if isinstance(content_blocks, list) and any(block.get('type') == 'tool_use' for block in content_blocks):
2826
2833
  is_tool_call = True
2827
- except:
2834
+ except ValueError:
2828
2835
  pass
2829
2836
 
2830
2837
  # Assign message class and labels based on type
@@ -2854,9 +2861,9 @@ Current date and time: {datetime_str}"""
2854
2861
  ''')
2855
2862
 
2856
2863
  # Process content
2857
- if content.startswith('[TOOL_RESULTS]') and include_tools:
2864
+ if content.startswith(_TOOL_RESULTS_MARKER) and include_tools:
2858
2865
  try:
2859
- tool_results_json = content.replace('[TOOL_RESULTS]', '', 1)
2866
+ tool_results_json = content.replace(_TOOL_RESULTS_MARKER, '', 1)
2860
2867
  tool_results = json.loads(tool_results_json)
2861
2868
  html_parts.append(''' <div class="tool-section">
2862
2869
  <div class="tool-title">🔧 Tool Results:</div>
@@ -2874,10 +2881,10 @@ Current date and time: {datetime_str}"""
2874
2881
  ''')
2875
2882
  html_parts.append(''' </div>
2876
2883
  ''')
2877
- except:
2884
+ except ValueError:
2878
2885
  html_parts.append(f''' {content.replace('<', '&lt;').replace('>', '&gt;')}
2879
2886
  ''')
2880
- elif content.startswith('[TOOL_RESULTS]') and not include_tools:
2887
+ elif content.startswith(_TOOL_RESULTS_MARKER) and not include_tools:
2881
2888
  html_parts.append(''' <em>[Tool execution details omitted]</em>
2882
2889
  ''')
2883
2890
  elif content.startswith('['):
@@ -2893,7 +2900,7 @@ Current date and time: {datetime_str}"""
2893
2900
  <pre>{json.dumps(block.get('input', {}), indent=2)}</pre>
2894
2901
  </div>
2895
2902
  ''')
2896
- except:
2903
+ except ValueError:
2897
2904
  html_parts.append(f''' {content.replace('<', '&lt;').replace('>', '&gt;')}
2898
2905
  ''')
2899
2906
  else:
@@ -2936,7 +2943,7 @@ Current date and time: {datetime_str}"""
2936
2943
  True if successful, False otherwise
2937
2944
  """
2938
2945
  if not self.current_conversation_id:
2939
- logging.warning("No conversation loaded to export")
2946
+ logging.warning(_ERR_NO_CONVERSATION_TO_EXPORT)
2940
2947
  return False
2941
2948
 
2942
2949
  try:
@@ -2951,7 +2958,7 @@ Current date and time: {datetime_str}"""
2951
2958
  messages = self.get_conversation_history(include_rolled_up=True)
2952
2959
 
2953
2960
  with open(file_path, 'w', newline='', encoding='utf-8') as csvfile:
2954
- fieldnames = ['Timestamp', 'Type', 'Role', 'Content', 'Token Count']
2961
+ fieldnames = ['Timestamp', 'Type', 'Role', 'Content', _LABEL_TOKEN_COUNT]
2955
2962
  writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
2956
2963
 
2957
2964
  # Write header
@@ -2963,21 +2970,21 @@ Current date and time: {datetime_str}"""
2963
2970
  'Type': '',
2964
2971
  'Role': 'Conversation',
2965
2972
  'Content': conv_info['name'],
2966
- 'Token Count': ''
2973
+ _LABEL_TOKEN_COUNT: ''
2967
2974
  })
2968
2975
  writer.writerow({
2969
2976
  'Timestamp': 'METADATA',
2970
2977
  'Type': '',
2971
2978
  'Role': 'Model',
2972
2979
  'Content': conv_info['model_id'],
2973
- 'Token Count': ''
2980
+ _LABEL_TOKEN_COUNT: ''
2974
2981
  })
2975
2982
  writer.writerow({
2976
2983
  'Timestamp': 'METADATA',
2977
2984
  'Type': '',
2978
2985
  'Role': 'Total Tokens',
2979
2986
  'Content': str(conv_info['total_tokens']),
2980
- 'Token Count': ''
2987
+ _LABEL_TOKEN_COUNT: ''
2981
2988
  })
2982
2989
 
2983
2990
  # Write messages
@@ -2987,8 +2994,8 @@ Current date and time: {datetime_str}"""
2987
2994
  content = msg['content']
2988
2995
 
2989
2996
  # Detect special message types
2990
- is_rollup_summary = content.startswith('[Summary of previous conversation]')
2991
- is_tool_result = content.startswith('[TOOL_RESULTS]')
2997
+ is_rollup_summary = content.startswith(_SUMMARY_MARKER)
2998
+ is_tool_result = content.startswith(_TOOL_RESULTS_MARKER)
2992
2999
 
2993
3000
  # Check if this is a tool call message
2994
3001
  is_tool_call = False
@@ -2997,7 +3004,7 @@ Current date and time: {datetime_str}"""
2997
3004
  content_blocks = json.loads(content)
2998
3005
  if isinstance(content_blocks, list) and any(block.get('type') == 'tool_use' for block in content_blocks):
2999
3006
  is_tool_call = True
3000
- except:
3007
+ except ValueError:
3001
3008
  pass
3002
3009
 
3003
3010
  # Assign type label
@@ -3011,13 +3018,13 @@ Current date and time: {datetime_str}"""
3011
3018
  msg_type = 'Message'
3012
3019
 
3013
3020
  # Process tool-related content
3014
- if content.startswith('[TOOL_RESULTS]'):
3021
+ if content.startswith(_TOOL_RESULTS_MARKER):
3015
3022
  if include_tools:
3016
3023
  try:
3017
- tool_results_json = content.replace('[TOOL_RESULTS]', '', 1)
3024
+ tool_results_json = content.replace(_TOOL_RESULTS_MARKER, '', 1)
3018
3025
  tool_results = json.loads(tool_results_json)
3019
3026
  content = f"Tool Results: {json.dumps(tool_results, indent=2)}"
3020
- except:
3027
+ except ValueError:
3021
3028
  pass
3022
3029
  else:
3023
3030
  content = "[Tool execution details omitted]"
@@ -3031,7 +3038,7 @@ Current date and time: {datetime_str}"""
3031
3038
  elif block.get('type') == 'tool_use' and include_tools:
3032
3039
  text_parts.append(f"Tool Call: {block.get('name')} - Input: {json.dumps(block.get('input', {}))}")
3033
3040
  content = '\n'.join(text_parts) if text_parts else content
3034
- except:
3041
+ except ValueError:
3035
3042
  pass
3036
3043
 
3037
3044
  writer.writerow({
@@ -3039,7 +3046,7 @@ Current date and time: {datetime_str}"""
3039
3046
  'Type': msg_type,
3040
3047
  'Role': role,
3041
3048
  'Content': content,
3042
- 'Token Count': msg.get('token_count', '')
3049
+ _LABEL_TOKEN_COUNT: msg.get('token_count', '')
3043
3050
  })
3044
3051
 
3045
3052
  logging.info(f"Exported conversation to CSV: {file_path}")