janito 3.3.0__py3-none-any.whl → 3.5.0__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 (170) hide show
  1. janito/README.md +3 -0
  2. janito/cli/chat_mode/bindings.py +50 -0
  3. janito/cli/chat_mode/session.py +12 -1
  4. janito/cli/chat_mode/shell/commands/multi.py +5 -0
  5. janito/cli/chat_mode/shell/commands/security/allowed_sites.py +47 -33
  6. janito/cli/cli_commands/list_plugins.py +13 -8
  7. janito/cli/core/model_guesser.py +40 -24
  8. janito/cli/prompt_core.py +47 -9
  9. janito/cli/rich_terminal_reporter.py +2 -2
  10. janito/hello.txt +0 -0
  11. janito/i18n/it.py +46 -46
  12. janito/llm/agent.py +32 -16
  13. janito/llm/cancellation_manager.py +63 -0
  14. janito/llm/driver.py +8 -0
  15. janito/llm/enter_cancellation.py +107 -0
  16. janito/{plugin_system_backup_20250825_070018 → plugin_system}/core_loader.py +76 -3
  17. janito/{plugin_system_backup_20250825_070018 → plugin_system}/core_loader_fixed.py +79 -3
  18. janito/plugins/__init__.py +29 -21
  19. janito/{plugins_backup_20250825_070018 → plugins}/auto_loader.py +12 -11
  20. janito/plugins/builtin.py +1 -1
  21. janito/plugins/core/filemanager/tools/copy_file.py +1 -25
  22. janito/plugins/core/filemanager/tools/create_directory.py +1 -28
  23. janito/plugins/core/filemanager/tools/create_file.py +3 -27
  24. janito/plugins/core/filemanager/tools/delete_text_in_file.py +0 -1
  25. janito/plugins/core/imagedisplay/plugin.py +1 -1
  26. janito/plugins/core_adapter.py +131 -0
  27. janito/plugins/discovery.py +3 -3
  28. janito/{plugins_backup_20250825_070018 → plugins}/discovery_core.py +14 -9
  29. janito/plugins/example_plugin.py +1 -1
  30. janito/plugins/manager.py +1 -1
  31. janito/{plugins_backup_20250825_070018 → plugins}/tools/core_tools_plugin.py +9 -10
  32. janito/{plugins_backup_20250825_070018 → plugins}/tools/create_file.py +2 -2
  33. janito/{plugins_backup_20250825_070018 → plugins}/tools/delete_text_in_file.py +0 -1
  34. janito/providers/__init__.py +1 -0
  35. janito/providers/together/__init__.py +1 -0
  36. janito/providers/together/model_info.py +69 -0
  37. janito/providers/together/provider.py +108 -0
  38. janito/tools/base.py +1 -31
  39. janito/tools/cli_initializer.py +1 -1
  40. janito/tools/initialize.py +1 -1
  41. janito/tools/loop_protection_decorator.py +114 -117
  42. janito/tools/tool_base.py +114 -142
  43. janito/tools/tools_schema.py +6 -12
  44. janito-3.5.0.dist-info/METADATA +229 -0
  45. {janito-3.3.0.dist-info → janito-3.5.0.dist-info}/RECORD +98 -162
  46. janito/plugins/__main__.py +0 -85
  47. janito/plugins/base.py +0 -57
  48. janito/plugins/core_loader.py +0 -144
  49. janito/plugins_backup_20250825_070018/__init__.py +0 -36
  50. janito/plugins_backup_20250825_070018/builtin.py +0 -102
  51. janito/plugins_backup_20250825_070018/config.py +0 -84
  52. janito/plugins_backup_20250825_070018/core/__init__.py +0 -7
  53. janito/plugins_backup_20250825_070018/core/codeanalyzer/__init__.py +0 -43
  54. janito/plugins_backup_20250825_070018/core/codeanalyzer/tools/get_file_outline/__init__.py +0 -1
  55. janito/plugins_backup_20250825_070018/core/codeanalyzer/tools/get_file_outline/core.py +0 -122
  56. janito/plugins_backup_20250825_070018/core/codeanalyzer/tools/search_text/__init__.py +0 -1
  57. janito/plugins_backup_20250825_070018/core/codeanalyzer/tools/search_text/core.py +0 -205
  58. janito/plugins_backup_20250825_070018/core/filemanager/__init__.py +0 -124
  59. janito/plugins_backup_20250825_070018/core/filemanager/tools/copy_file.py +0 -87
  60. janito/plugins_backup_20250825_070018/core/filemanager/tools/create_directory.py +0 -70
  61. janito/plugins_backup_20250825_070018/core/filemanager/tools/create_file.py +0 -87
  62. janito/plugins_backup_20250825_070018/core/filemanager/tools/delete_text_in_file.py +0 -135
  63. janito/plugins_backup_20250825_070018/core/filemanager/tools/find_files.py +0 -143
  64. janito/plugins_backup_20250825_070018/core/filemanager/tools/move_file.py +0 -131
  65. janito/plugins_backup_20250825_070018/core/filemanager/tools/read_files.py +0 -58
  66. janito/plugins_backup_20250825_070018/core/filemanager/tools/remove_directory.py +0 -55
  67. janito/plugins_backup_20250825_070018/core/filemanager/tools/remove_file.py +0 -58
  68. janito/plugins_backup_20250825_070018/core/filemanager/tools/replace_text_in_file.py +0 -270
  69. janito/plugins_backup_20250825_070018/core/filemanager/tools/validate_file_syntax/__init__.py +0 -1
  70. janito/plugins_backup_20250825_070018/core/filemanager/tools/validate_file_syntax/core.py +0 -114
  71. janito/plugins_backup_20250825_070018/core/filemanager/tools/view_file.py +0 -172
  72. janito/plugins_backup_20250825_070018/core/imagedisplay/__init__.py +0 -14
  73. janito/plugins_backup_20250825_070018/core/imagedisplay/plugin.py +0 -51
  74. janito/plugins_backup_20250825_070018/core/imagedisplay/tools/__init__.py +0 -1
  75. janito/plugins_backup_20250825_070018/core/imagedisplay/tools/show_image.py +0 -83
  76. janito/plugins_backup_20250825_070018/core/imagedisplay/tools/show_image_grid.py +0 -84
  77. janito/plugins_backup_20250825_070018/core/system/__init__.py +0 -23
  78. janito/plugins_backup_20250825_070018/core/system/tools/run_bash_command.py +0 -183
  79. janito/plugins_backup_20250825_070018/core/system/tools/run_powershell_command.py +0 -218
  80. janito/plugins_backup_20250825_070018/core_adapter.py +0 -55
  81. janito/plugins_backup_20250825_070018/dev/__init__.py +0 -7
  82. janito/plugins_backup_20250825_070018/dev/pythondev/__init__.py +0 -37
  83. janito/plugins_backup_20250825_070018/dev/pythondev/tools/python_code_run.py +0 -172
  84. janito/plugins_backup_20250825_070018/dev/pythondev/tools/python_command_run.py +0 -171
  85. janito/plugins_backup_20250825_070018/dev/pythondev/tools/python_file_run.py +0 -172
  86. janito/plugins_backup_20250825_070018/dev/visualization/__init__.py +0 -23
  87. janito/plugins_backup_20250825_070018/dev/visualization/tools/read_chart.py +0 -259
  88. janito/plugins_backup_20250825_070018/discovery.py +0 -289
  89. janito/plugins_backup_20250825_070018/example_plugin.py +0 -108
  90. janito/plugins_backup_20250825_070018/manager.py +0 -243
  91. janito/plugins_backup_20250825_070018/tools/get_file_outline/java_outline.py +0 -47
  92. janito/plugins_backup_20250825_070018/tools/get_file_outline/markdown_outline.py +0 -14
  93. janito/plugins_backup_20250825_070018/tools/get_file_outline/python_outline.py +0 -303
  94. janito/plugins_backup_20250825_070018/tools/get_file_outline/search_outline.py +0 -36
  95. janito/plugins_backup_20250825_070018/tools/search_text/match_lines.py +0 -67
  96. janito/plugins_backup_20250825_070018/tools/search_text/pattern_utils.py +0 -73
  97. janito/plugins_backup_20250825_070018/tools/search_text/traverse_directory.py +0 -145
  98. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/css_validator.py +0 -35
  99. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/html_validator.py +0 -100
  100. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/jinja2_validator.py +0 -50
  101. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/js_validator.py +0 -27
  102. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/json_validator.py +0 -6
  103. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/markdown_validator.py +0 -109
  104. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/ps1_validator.py +0 -32
  105. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/python_validator.py +0 -5
  106. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/xml_validator.py +0 -11
  107. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/yaml_validator.py +0 -6
  108. janito/plugins_backup_20250825_070018/ui/__init__.py +0 -7
  109. janito/plugins_backup_20250825_070018/ui/userinterface/__init__.py +0 -16
  110. janito/plugins_backup_20250825_070018/ui/userinterface/tools/ask_user.py +0 -110
  111. janito/plugins_backup_20250825_070018/web/__init__.py +0 -7
  112. janito/plugins_backup_20250825_070018/web/webtools/__init__.py +0 -33
  113. janito/plugins_backup_20250825_070018/web/webtools/tools/fetch_url.py +0 -458
  114. janito/plugins_backup_20250825_070018/web/webtools/tools/open_html_in_browser.py +0 -51
  115. janito/plugins_backup_20250825_070018/web/webtools/tools/open_url.py +0 -37
  116. janito/tools/function_adapter.py +0 -176
  117. janito-3.3.0.dist-info/METADATA +0 -83
  118. /janito/{plugin_system_backup_20250825_070018 → plugin_system}/__init__.py +0 -0
  119. /janito/{plugin_system_backup_20250825_070018 → plugin_system}/base.py +0 -0
  120. /janito/{plugins_backup_20250825_070018 → plugins}/auto_loader_fixed.py +0 -0
  121. /janito/{plugins_backup_20250825_070018 → plugins}/tools/__init__.py +0 -0
  122. /janito/{plugins_backup_20250825_070018 → plugins}/tools/ask_user.py +0 -0
  123. /janito/{plugins_backup_20250825_070018 → plugins}/tools/copy_file.py +0 -0
  124. /janito/{plugins_backup_20250825_070018 → plugins}/tools/create_directory.py +0 -0
  125. /janito/{plugins_backup_20250825_070018 → plugins}/tools/decorators.py +0 -0
  126. /janito/{plugins_backup_20250825_070018 → plugins}/tools/fetch_url.py +0 -0
  127. /janito/{plugins_backup_20250825_070018 → plugins}/tools/find_files.py +0 -0
  128. /janito/{plugins_backup_20250825_070018 → plugins}/tools/get_file_outline/__init__.py +0 -0
  129. /janito/{plugins_backup_20250825_070018 → plugins}/tools/get_file_outline/core.py +0 -0
  130. /janito/{plugins_backup_20250825_070018/core/codeanalyzer → plugins}/tools/get_file_outline/java_outline.py +0 -0
  131. /janito/{plugins_backup_20250825_070018/core/codeanalyzer → plugins}/tools/get_file_outline/markdown_outline.py +0 -0
  132. /janito/{plugins_backup_20250825_070018/core/codeanalyzer → plugins}/tools/get_file_outline/python_outline.py +0 -0
  133. /janito/{plugins_backup_20250825_070018/core/codeanalyzer → plugins}/tools/get_file_outline/search_outline.py +0 -0
  134. /janito/{plugins_backup_20250825_070018 → plugins}/tools/move_file.py +0 -0
  135. /janito/{plugins_backup_20250825_070018 → plugins}/tools/open_html_in_browser.py +0 -0
  136. /janito/{plugins_backup_20250825_070018 → plugins}/tools/open_url.py +0 -0
  137. /janito/{plugins_backup_20250825_070018 → plugins}/tools/python_code_run.py +0 -0
  138. /janito/{plugins_backup_20250825_070018 → plugins}/tools/python_command_run.py +0 -0
  139. /janito/{plugins_backup_20250825_070018 → plugins}/tools/python_file_run.py +0 -0
  140. /janito/{plugins_backup_20250825_070018 → plugins}/tools/read_chart.py +0 -0
  141. /janito/{plugins_backup_20250825_070018 → plugins}/tools/read_files.py +0 -0
  142. /janito/{plugins_backup_20250825_070018 → plugins}/tools/remove_directory.py +0 -0
  143. /janito/{plugins_backup_20250825_070018 → plugins}/tools/remove_file.py +0 -0
  144. /janito/{plugins_backup_20250825_070018 → plugins}/tools/replace_text_in_file.py +0 -0
  145. /janito/{plugins_backup_20250825_070018 → plugins}/tools/run_bash_command.py +0 -0
  146. /janito/{plugins_backup_20250825_070018 → plugins}/tools/run_powershell_command.py +0 -0
  147. /janito/{plugins_backup_20250825_070018 → plugins}/tools/search_text/__init__.py +0 -0
  148. /janito/{plugins_backup_20250825_070018 → plugins}/tools/search_text/core.py +0 -0
  149. /janito/{plugins_backup_20250825_070018/core/codeanalyzer → plugins}/tools/search_text/match_lines.py +0 -0
  150. /janito/{plugins_backup_20250825_070018/core/codeanalyzer → plugins}/tools/search_text/pattern_utils.py +0 -0
  151. /janito/{plugins_backup_20250825_070018/core/codeanalyzer → plugins}/tools/search_text/traverse_directory.py +0 -0
  152. /janito/{plugins_backup_20250825_070018 → plugins}/tools/show_image.py +0 -0
  153. /janito/{plugins_backup_20250825_070018 → plugins}/tools/show_image_grid.py +0 -0
  154. /janito/{plugins_backup_20250825_070018 → plugins}/tools/validate_file_syntax/__init__.py +0 -0
  155. /janito/{plugins_backup_20250825_070018 → plugins}/tools/validate_file_syntax/core.py +0 -0
  156. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/css_validator.py +0 -0
  157. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/html_validator.py +0 -0
  158. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/jinja2_validator.py +0 -0
  159. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/js_validator.py +0 -0
  160. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/json_validator.py +0 -0
  161. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/markdown_validator.py +0 -0
  162. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/ps1_validator.py +0 -0
  163. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/python_validator.py +0 -0
  164. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/xml_validator.py +0 -0
  165. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/yaml_validator.py +0 -0
  166. /janito/{plugins_backup_20250825_070018 → plugins}/tools/view_file.py +0 -0
  167. {janito-3.3.0.dist-info → janito-3.5.0.dist-info}/WHEEL +0 -0
  168. {janito-3.3.0.dist-info → janito-3.5.0.dist-info}/entry_points.txt +0 -0
  169. {janito-3.3.0.dist-info → janito-3.5.0.dist-info}/licenses/LICENSE +0 -0
  170. {janito-3.3.0.dist-info → janito-3.5.0.dist-info}/top_level.txt +0 -0
janito/README.md CHANGED
@@ -106,6 +106,9 @@ janito --set model=kimi-k1-8k
106
106
 
107
107
  ## Advanced Features
108
108
 
109
+ ### 🚀 New in v3.1.0: Enter Key Cancellation
110
+ **Chat Mode Enhancement**: Press **Enter** at any time to instantly cancel long-running requests in interactive chat mode. No more waiting for stuck requests!
111
+
109
112
  ### Tool Usage
110
113
 
111
114
  Janito includes powerful built-in tools for:
@@ -35,4 +35,54 @@ class KeyBindingsFactory:
35
35
  buf.text = "Do It"
36
36
  buf.validate_and_handle()
37
37
 
38
+ @bindings.add("c-c")
39
+ def _(event):
40
+ """Handle Ctrl+C to interrupt current request or exit chat."""
41
+ # Use global cancellation manager for robust cancellation
42
+ from janito.llm.cancellation_manager import get_cancellation_manager
43
+
44
+ cancel_manager = get_cancellation_manager()
45
+
46
+ cancelled = cancel_manager.cancel_current_request()
47
+ if cancelled:
48
+ # Provide user feedback
49
+ from rich.console import Console
50
+
51
+ console = Console()
52
+ console.print("[red]Request cancelled by Ctrl+C[/red]")
53
+
54
+ # Prevent the Ctrl+C from being processed as input
55
+ event.app.output.flush()
56
+ return
57
+ else:
58
+ # No active request to cancel, exit the chat
59
+ from rich.console import Console
60
+ console = Console()
61
+ console.print("[yellow]Goodbye![/yellow]")
62
+ event.app.exit()
63
+
64
+ @bindings.add("escape", eager=True)
65
+ def _(event):
66
+ """Handle ESC key to interrupt current request (like Ctrl+C)."""
67
+ import threading
68
+
69
+ # Use global cancellation manager for robust cancellation
70
+ from janito.llm.cancellation_manager import get_cancellation_manager
71
+
72
+ cancel_manager = get_cancellation_manager()
73
+
74
+ cancelled = cancel_manager.cancel_current_request()
75
+ if cancelled:
76
+ # Provide user feedback
77
+ from rich.console import Console
78
+
79
+ console = Console()
80
+ console.print("[red]Request cancelled by ESC key[/red]")
81
+
82
+ # Prevent the ESC key from being processed as input
83
+ event.app.output.flush()
84
+ return
85
+
86
+ # If no active request to cancel, ESC does nothing
87
+
38
88
  return bindings
@@ -22,6 +22,7 @@ import time
22
22
 
23
23
  # Shared prompt/agent factory
24
24
  from janito.cli.prompt_setup import setup_agent_and_prompt_handler
25
+ from janito.llm.cancellation_manager import get_cancellation_manager
25
26
 
26
27
  import time
27
28
 
@@ -113,6 +114,10 @@ class ChatSession:
113
114
 
114
115
  # Check if multi-line mode should be enabled by default
115
116
  self.multi_line_mode = getattr(args, "multi", False) if args else False
117
+
118
+ # Default to single-line mode (Enter submits) unless explicitly enabled
119
+ if not self.multi_line_mode:
120
+ self.multi_line_mode = False
116
121
 
117
122
  def _select_profile_and_role(self, args, role):
118
123
  profile, role_arg, python_profile, market_profile = self._extract_args(args)
@@ -283,7 +288,13 @@ class ChatSession:
283
288
  )
284
289
  )
285
290
 
286
- self._prompt_handler.run_prompt(cmd_input)
291
+ try:
292
+ self._prompt_handler.run_prompt(cmd_input)
293
+ finally:
294
+ # Ensure cancellation manager is cleared
295
+ cancel_manager = get_cancellation_manager()
296
+ cancel_manager.clear_current_request()
297
+
287
298
  end_time = time.time()
288
299
  elapsed = end_time - start_time
289
300
  self.msg_count += 1
@@ -23,6 +23,11 @@ class MultiShellHandler(ShellCmdHandler):
23
23
  submitted["value"] = buffer.text
24
24
  event.app.exit(result=buffer.text)
25
25
 
26
+ # Support single ESC to cancel/interrupt (like Ctrl+C)
27
+ @bindings.add("escape")
28
+ def _(event):
29
+ raise KeyboardInterrupt
30
+
26
31
  # Support Ctrl+D
27
32
  @bindings.add("c-d")
28
33
  def _(event):
@@ -59,39 +59,53 @@ Examples:
59
59
  command = args[0].lower()
60
60
  whitelist_manager = get_url_whitelist_manager()
61
61
 
62
- if command == "list":
63
- sites = whitelist_manager.get_allowed_sites()
64
- if sites:
65
- print("Allowed sites:")
66
- for site in sites:
67
- print(f" • {site}")
68
- else:
69
- print("No sites are whitelisted (all sites are allowed)")
70
-
71
- elif command == "add":
72
- if len(args) < 2:
73
- print("Error: Please specify a site to add")
74
- return
75
- site = args[1]
76
- if whitelist_manager.add_allowed_site(site):
77
- print(f"✅ Added '{site}' to allowed sites")
78
- else:
79
- print(f"ℹ️ '{site}' is already in allowed sites")
80
-
81
- elif command == "remove":
82
- if len(args) < 2:
83
- print("Error: Please specify a site to remove")
84
- return
85
- site = args[1]
86
- if whitelist_manager.remove_allowed_site(site):
87
- print(f"✅ Removed '{site}' from allowed sites")
88
- else:
89
- print(f"ℹ️ '{site}' was not in allowed sites")
90
-
91
- elif command == "clear":
92
- whitelist_manager.clear_whitelist()
93
- print("✅ Cleared all allowed sites (all sites are now allowed)")
94
-
62
+ handlers = {
63
+ "list": self._handle_list,
64
+ "add": self._handle_add,
65
+ "remove": self._handle_remove,
66
+ "clear": self._handle_clear,
67
+ }
68
+
69
+ handler = handlers.get(command)
70
+ if handler:
71
+ handler(args, whitelist_manager)
95
72
  else:
96
73
  print(f"Error: Unknown command '{command}'")
97
74
  print(self.get_usage())
75
+
76
+ def _handle_list(self, args, whitelist_manager):
77
+ """Handle list command."""
78
+ sites = whitelist_manager.get_allowed_sites()
79
+ if sites:
80
+ print("Allowed sites:")
81
+ for site in sites:
82
+ print(f" • {site}")
83
+ else:
84
+ print("No sites are whitelisted (all sites are allowed)")
85
+
86
+ def _handle_add(self, args, whitelist_manager):
87
+ """Handle add command."""
88
+ if len(args) < 2:
89
+ print("Error: Please specify a site to add")
90
+ return
91
+ site = args[1]
92
+ if whitelist_manager.add_allowed_site(site):
93
+ print(f"✅ Added '{site}' to allowed sites")
94
+ else:
95
+ print(f"ℹ️ '{site}' is already in allowed sites")
96
+
97
+ def _handle_remove(self, args, whitelist_manager):
98
+ """Handle remove command."""
99
+ if len(args) < 2:
100
+ print("Error: Please specify a site to remove")
101
+ return
102
+ site = args[1]
103
+ if whitelist_manager.remove_allowed_site(site):
104
+ print(f"✅ Removed '{site}' from allowed sites")
105
+ else:
106
+ print(f"ℹ️ '{site}' was not in allowed sites")
107
+
108
+ def _handle_clear(self, args, whitelist_manager):
109
+ """Handle clear command."""
110
+ whitelist_manager.clear_whitelist()
111
+ print("✅ Cleared all allowed sites (all sites are now allowed)")
@@ -8,8 +8,11 @@ from janito.plugins.discovery import list_available_plugins
8
8
  import os
9
9
  from janito.plugins.manager import PluginManager
10
10
  from janito.plugins.builtin import BuiltinPluginRegistry
11
- from janito.plugins.manager import PluginManager
12
- from janito.plugins.core_loader import get_core_plugins
11
+ from janito.plugins.auto_loader_fixed import (
12
+ load_core_plugins,
13
+ get_loaded_core_plugins,
14
+ is_core_plugin,
15
+ )
13
16
  from rich.console import Console
14
17
  from rich.table import Table
15
18
  from rich.panel import Panel
@@ -52,7 +55,7 @@ def _list_available_plugins():
52
55
  console.print(table)
53
56
 
54
57
  # Show core plugins
55
- from janito.plugins.core_loader import get_core_plugins
58
+ from janito.plugin_system.core_loader_fixed import get_core_plugins
56
59
 
57
60
  core_plugins = get_core_plugins()
58
61
  core_table = Table(title="Core Plugins (Enabled by Default)")
@@ -95,8 +98,10 @@ def _print_external_plugins(available, builtin_plugins):
95
98
 
96
99
  def _list_plugin_resources():
97
100
  """List all resources from loaded plugins using rich formatting."""
101
+ from janito.plugins.auto_loader_fixed import get_plugin_manager
102
+
98
103
  console = Console()
99
- manager = PluginManager()
104
+ manager = get_plugin_manager()
100
105
  all_resources = manager.list_all_resources()
101
106
 
102
107
  if all_resources:
@@ -169,8 +174,10 @@ def _print_resources_by_type(resources):
169
174
 
170
175
  def _list_loaded_plugins():
171
176
  """List loaded plugins using rich formatting."""
177
+ from janito.plugins.auto_loader_fixed import get_plugin_manager
178
+
172
179
  console = Console()
173
- manager = PluginManager()
180
+ manager = get_plugin_manager()
174
181
  loaded = manager.list_plugins()
175
182
 
176
183
  if loaded:
@@ -185,9 +192,7 @@ def _list_loaded_plugins():
185
192
  other_plugins = []
186
193
 
187
194
  for plugin_name in loaded:
188
- from janito.plugins.core_loader import get_core_plugins
189
- core_plugin_list = get_core_plugins()
190
- if plugin_name in core_plugin_list:
195
+ if is_core_plugin(plugin_name):
191
196
  core_plugins.append(plugin_name)
192
197
  else:
193
198
  other_plugins.append(plugin_name)
@@ -21,34 +21,50 @@ def guess_provider_from_model(model_name: str) -> str:
21
21
  model_name = model_name.lower()
22
22
 
23
23
  # Check each provider's models
24
+ return _find_provider_for_model(model_name)
25
+
26
+
27
+ def _find_provider_for_model(model_name: str) -> str:
28
+ """Find provider for given model name."""
24
29
  for provider_name in LLMProviderRegistry.list_providers():
25
30
  provider_class = LLMProviderRegistry.get(provider_name)
26
31
  if not provider_class:
27
32
  continue
28
33
 
29
- # Get model specs for this provider
30
- try:
31
- if hasattr(provider_class, "MODEL_SPECS"):
32
- model_specs = provider_class.MODEL_SPECS
33
- for spec_model_name in model_specs.keys():
34
- if spec_model_name.lower() == model_name:
35
- return provider_name
36
-
37
- # Handle special cases like moonshot
38
- if provider_name == "moonshot":
39
- try:
40
- from janito.providers.moonshot.model_info import (
41
- MOONSHOT_MODEL_SPECS,
42
- )
43
-
44
- for spec_model_name in MOONSHOT_MODEL_SPECS.keys():
45
- if spec_model_name.lower() == model_name:
46
- return "moonshot"
47
- except ImportError:
48
- pass
49
-
50
- except Exception:
51
- # Skip providers that have issues accessing model specs
52
- continue
34
+ if _check_provider_models(provider_name, provider_class, model_name):
35
+ return provider_name
53
36
 
54
37
  return None
38
+
39
+
40
+ def _check_provider_models(provider_name: str, provider_class, model_name: str) -> bool:
41
+ """Check if provider has matching model."""
42
+ try:
43
+ if hasattr(provider_class, "MODEL_SPECS"):
44
+ model_specs = provider_class.MODEL_SPECS
45
+ for spec_model_name in model_specs.keys():
46
+ if spec_model_name.lower() == model_name:
47
+ return True
48
+
49
+ # Handle special cases like moonshot
50
+ if provider_name == "moonshot":
51
+ return _check_moonshot_models(model_name)
52
+
53
+ except Exception:
54
+ # Skip providers that have issues accessing model specs
55
+ pass
56
+
57
+ return False
58
+
59
+
60
+ def _check_moonshot_models(model_name: str) -> bool:
61
+ """Check moonshot models specifically."""
62
+ try:
63
+ from janito.providers.moonshot.model_info import MOONSHOT_MODEL_SPECS
64
+
65
+ for spec_model_name in MOONSHOT_MODEL_SPECS.keys():
66
+ if spec_model_name.lower() == model_name:
67
+ return True
68
+ except ImportError:
69
+ pass
70
+ return False
janito/cli/prompt_core.py CHANGED
@@ -207,15 +207,53 @@ class PromptHandler:
207
207
  """
208
208
  try:
209
209
  self._print_verbose_debug("Calling agent.chat()...")
210
- final_event = self.agent.chat(prompt=user_prompt)
211
- if hasattr(self.agent, "set_latest_event"):
212
- self.agent.set_latest_event(final_event)
213
- self.agent.last_event = final_event
214
- self._print_verbose_debug(f"agent.chat() returned: {final_event}")
215
- self._print_verbose_final_event(final_event)
216
- if on_event and final_event is not None:
217
- on_event(final_event)
218
- global_event_bus.publish(final_event)
210
+
211
+ # Use global cancellation manager
212
+ from janito.llm.cancellation_manager import get_cancellation_manager
213
+ import time
214
+ from threading import Thread, Event
215
+
216
+ cancel_manager = get_cancellation_manager()
217
+ driver_cancel_event = cancel_manager.start_new_request()
218
+
219
+ # Timer setup with live status display
220
+ start_time = time.time()
221
+ stop_timer = Event()
222
+
223
+ # Create a status display with timer
224
+ from rich.live import Live
225
+ from rich.text import Text
226
+
227
+ def get_timer_text():
228
+ elapsed = time.time() - start_time
229
+ minutes, seconds = divmod(int(elapsed), 60)
230
+ return Text(f"⏱️ Waiting for response... {minutes:02d}:{seconds:02d}", style="cyan")
231
+
232
+ # Use Live display for updating timer
233
+ with Live(get_timer_text(), refresh_per_second=4, console=self.console) as live:
234
+ def update_timer():
235
+ while not stop_timer.wait(0.25): # Update every 250ms
236
+ live.update(get_timer_text())
237
+
238
+ timer_thread = Thread(target=update_timer, daemon=True)
239
+ timer_thread.start()
240
+
241
+ try:
242
+ final_event = self.agent.chat(prompt=user_prompt)
243
+ stop_timer.set() # Stop the timer
244
+
245
+ if hasattr(self.agent, "set_latest_event"):
246
+ self.agent.set_latest_event(final_event)
247
+ self.agent.last_event = final_event
248
+ self._print_verbose_debug(f"agent.chat() returned: {final_event}")
249
+ self._print_verbose_final_event(final_event)
250
+ if on_event and final_event is not None:
251
+ on_event(final_event)
252
+ global_event_bus.publish(final_event)
253
+ finally:
254
+ stop_timer.set() # Ensure timer stops
255
+ cancel_manager.clear_current_request()
256
+
219
257
  except KeyboardInterrupt:
220
258
  # Capture user interrupt / cancellation
221
259
  self.console.print("[red]Interrupted by the user.[/red]")
@@ -161,10 +161,10 @@ class RichTerminalReporter(EventHandlerBase):
161
161
  self.console.print(msg)
162
162
  self.console.file.flush()
163
163
  elif subtype == ReportSubtype.STDOUT:
164
- self.console.print(msg)
164
+ self.console.print(msg, style=None)
165
165
  self.console.file.flush()
166
166
  elif subtype == ReportSubtype.STDERR:
167
- self.console.print(Text(msg, style="on red"))
167
+ self.console.print(msg, style=None)
168
168
  self.console.file.flush()
169
169
  else:
170
170
  self.console.print(msg)
janito/hello.txt ADDED
File without changes
janito/i18n/it.py CHANGED
@@ -1,46 +1,46 @@
1
- # pragma: allowlist secret
2
- translations = {
3
- "36107ed78ab25f6fb12ad8ce13018cd1ce6735d1": "Avvio del server web...",
4
- "70a0d194687568a47aa617fd85036ace1e69a982": "Vuoi davvero uscire? (s/n): ",
5
- "5c9ebcbbd7632ecb328bd52958b17158afaa32c6": "F12 = Azione Rapida (segue l'azione raccomandata)",
6
- "fe21121e2934234b68d19b2757532117d440c1e3": "Chiave API non trovata. Si prega di configurare 'api_key' nel file di configurazione.",
7
- "c9e3759b1756eba35b381ce2b72cd659e132b01f": "Ciao, {name}!",
8
- "ca1fee2f55baabdc2e4b0e9529c89ee024e62079": "Nessun prompt fornito nei messaggi",
9
- "f7449d23d0c500ae2a0b31e04f92b47a4d8ae845": "max_tokens deve essere un intero, ricevuto: {resolved_max_tokens!r}",
10
- "70a9ed8edb6da12e208431a31aa16ba54419b26f": "Risposta non valida/malformed da OpenAI (tentativo {attempt}/{max_retries}). Riprovo tra {wait_time} secondi...",
11
- "a873085e3b06184fb5d27e842f97b06b6190976d": "Numero massimo di tentativi per risposta non valida raggiunto. Generazione errore.",
12
- "66a34568bbe846bb1bde3619eb4d6dfa10211104": "L'API non supporta l'uso degli strumenti.",
13
- "09b81476b75586da4116b83f8be70d77b174cec3": "Limite di richieste API OpenAI (429) (tentativo {attempt}/{max_retries}): {e}. Riprovo tra {wait_time} secondi...",
14
- "5717a35dd2a1533fb7e15edc8c9329cb69f3410b": "Errore server API OpenAI (tentativo {attempt}/{max_retries}): {e}. Riprovo tra {wait_time} secondi...",
15
- "02e760ba15ed863176c1290ac8a9b923963103cd": "Errore client API OpenAI {status_code}: {e}. Nessun nuovo tentativo.",
16
- "2e52b0bbc8f16226b70e3e20f95c9245d2bcdb47": "Errore API OpenAI (tentativo {attempt}/{max_retries}): {e}. Riprovo tra {wait_time} secondi...",
17
- "012cc970e039fdd79c452fc676202c814ffc76ae": "Numero massimo di tentativi per errore API OpenAI raggiunto. Generazione errore.",
18
- "d0438e45667d31e0022b2497b5901cd4300f084b": "QueuedMessageHandler.handle_message si aspetta un dizionario con 'type' e 'message', ricevuto {msg_type}: {msg!r}",
19
- "9d3460187ffa19c7c8a4020157072b1087e1bd2f": "[QueuedMessageHandler] {msg_type}: {msg}",
20
- "3813833343430e8afa8fce33385c5e39fb24dd60": "[QueuedMessageHandler] {msg_type}: {message}",
21
- "0be9a22226e16a40797010d23a0f581542dca40e": "[ToolExecutor] {tool_name} chiamato con argomenti: {args}",
22
- "42c68edcb25442f518b1af77c6a2ddc07461aae0": "[ToolExecutor] Motivo chiamata: {tool_call_reason}",
23
- "002ff598115d84595ffeee6219cb5c03d3a1d4a6": "Domanda",
24
- "35747d13dcd91e8e8790c7f767d5ed764f541b9e": "procedi",
25
- "33dde3a1afbc418768a69fa53168d9b0638fe1aa": "avanti",
26
- "eee0bbba4ff92adbeb038a77df0466d660f15716": "continua",
27
- "edee9402d198b04ac77dcf5dc9cc3dac44573782": "prossimo",
28
- "8fdb7e2fa84f4faf0d9b92f466df424ec47a165b": "ok",
29
- "5f8f924671cda79b5205a6bf1b776f347c4a7a07": "Opzioni di configurazione disponibili:\n",
30
- "cef780a309cd234750764d42697882c24168ddab": "{key:15} {desc} (predefinito: {default})",
31
- "68c2cc7f0ceaa3e499ecb4db331feb4debbbcc23": "Modello",
32
- "c3f104d1365744b538bfde9f4adb6a6df4b80355": "Funzione",
33
- "99a0efc6cfd85d8ff2732a6718140f822cb90472": "Stile",
34
- "f1702b4686278becffc88baabe6f4b7a8355532c": "Messaggi",
35
- "c38c6c1f3a2743f8626703abb302e403d20ff81c": "Token",
36
- "a817d7eb8e0f1dab755ab5203a082e5c3c094fce": "Prompt",
37
- "2ff255684a2277f806fcebf3fe338ed27857f350": "Completamento",
38
- "b25928c69902557b0ef0a628490a3a1768d7b82f": "Totale",
39
- "76e63d65c883ed50df40ac3aeef0c2d6a1c4ad60": "Azione Rapida",
40
- "5397e0583f14f6c88de06b1ef28f460a1fb5b0ae": "Sì",
41
- "816c52fd2bdd94a63cd0944823a6c0aa9384c103": "No",
42
- "c47ae15370cfe1ed2781eedc1dc2547d12d9e972": "Aiuto",
43
- "cc3dbd47e1cf9003a55d3366b3adbcd72275e525": "Nuovo Task",
44
- "efa5a8b84e1afe65c81ecfce28c398c48f19ddc2": "Benvenuto su Janito{version_str}! Modalità chat attiva. Digita /exit per uscire.",
45
- "b314d6e1460f86e0f21abc5aceb7935a2a0667e8": "Benvenuto su Janito{version_str} in modalità [white on magenta]VANILLA[/white on magenta]! Strumenti, prompt di sistema e temperatura sono disattivati, a meno che non siano sovrascritti.",
46
- }
1
+ # pragma: allowlist secret
2
+ translations = {
3
+ "36107ed78ab25f6fb12ad8ce13018cd1ce6735d1": "Avvio del server web...",
4
+ "70a0d194687568a47aa617fd85036ace1e69a982": "Vuoi davvero uscire? (s/n): ",
5
+ "5c9ebcbbd7632ecb328bd52958b17158afaa32c6": "F12 = Azione Rapida (segue l'azione raccomandata)",
6
+ "fe21121e2934234b68d19b2757532117d440c1e3": "Chiave API non trovata. Si prega di configurare 'api_key' nel file di configurazione.",
7
+ "c9e3759b1756eba35b381ce2b72cd659e132b01f": "Ciao, {name}!",
8
+ "ca1fee2f55baabdc2e4b0e9529c89ee024e62079": "Nessun prompt fornito nei messaggi",
9
+ "f7449d23d0c500ae2a0b31e04f92b47a4d8ae845": "max_tokens deve essere un intero, ricevuto: {resolved_max_tokens!r}",
10
+ "70a9ed8edb6da12e208431a31aa16ba54419b26f": "Risposta non valida/malformed da OpenAI (tentativo {attempt}/{max_retries}). Riprovo tra {wait_time} secondi...",
11
+ "a873085e3b06184fb5d27e842f97b06b6190976d": "Numero massimo di tentativi per risposta non valida raggiunto. Generazione errore.",
12
+ "66a34568bbe846bb1bde3619eb4d6dfa10211104": "L'API non supporta l'uso degli strumenti.",
13
+ "09b81476b75586da4116b83f8be70d77b174cec3": "Limit di richieste API OpenAI (429) (tentativo {attempt}/{max_retries}): {e}. Riprovo tra {wait_time} secondi...",
14
+ "5717a35dd2a1533fb7e15edc8c9329cb69f3410b": "Errore server API OpenAI (tentativo {attempt}/{max_retries}): {e}. Riprovo tra {wait_time} secondi...",
15
+ "02e760ba15ed863176c1290ac8a9b923963103cd": "Errore client API OpenAI {status_code}: {e}. Nessun nuovo tentativo.",
16
+ "2e52b0bbc8f16226b70e3e20f95c9245d2bcdb47": "Errore API OpenAI (tentativo {attempt}/{max_retries}): {e}. Riprovo tra {wait_time} secondi...",
17
+ "012cc970e039fdd79c452fc676202c814ffc76ae": "Numero massimo di tentativi per errore API OpenAI raggiunto. Generazione errore.",
18
+ "d0438e45667d31e0022b2497b5901cd4300f084b": "QueuedMessageHandler.handle_message si aspetta un dizionario con 'type' e 'message', ricevuto {msg_type}: {msg!r}",
19
+ "9d3460187ffa19c7c8a4020157072b1087e1bd2f": "[QueuedMessageHandler] {msg_type}: {msg}",
20
+ "3813833343430e8afa8fce33385c5e39fb24dd60": "[QueuedMessageHandler] {msg_type}: {message}",
21
+ "0be9a22226e16a40797010d23a0f581542dca40e": "[ToolExecutor] {tool_name} chiamato con argomenti: {args}",
22
+ "42c68edcb25442f518b1af77c6a2ddc07461aae0": "[ToolExecutor] Motivo chiamata: {tool_call_reason}",
23
+ "002ff598115d84595ffeee6219cb5c03d3a1d4a6": "Domanda",
24
+ "35747d13dcd91e8e8790c7f767d5ed764f541b9e": "procedi",
25
+ "33dde3a1afbc418768a69fa53168d9b0638fe1aa": "avanti",
26
+ "eee0bbba4ff92adbeb038a77df0466d660f15716": "continua",
27
+ "edee9402d198b04ac77dcf5dc9cc3dac44573782": "prossimo",
28
+ "8fdb7e2fa84f4faf0d9b92f466df424ec47a165b": "ok",
29
+ "5f8f924671cda79b5205a6bf1b776f347c4a7a07": "Opzioni di configurazione disponibili:\n",
30
+ "cef780a309cd234750764d42697882c24168ddab": "{key:15} {desc} (predefinito: {default})",
31
+ "68c2cc7f0ceaa3e499ecb4db331feb4debbbcc23": "Modello",
32
+ "c3f104d1365744b538bfde9f4adb6a6df4b80355": "Funzione",
33
+ "99a0efc6cfd85d8ff2732a6718140f822cb90472": "Stile",
34
+ "f1702b4686278becffc88baabe6f4b7a8355532c": "Messaggi",
35
+ "c38c6c1f3a2743f8626703abb302e403d20ff81c": "Token",
36
+ "a817d7eb8e0f1dab755ab5203a082e5c3c094fce": "Prompt",
37
+ "2ff255684a2277f806fcebf3fe338ed27857f350": "Completamento",
38
+ "b25928c69902557b0ef0a628490a3a1768d7b82f": "Totale",
39
+ "76e63d65c883ed50df40ac3aeef0c2d6a1c4ad60": "Azione Rapida",
40
+ "5397e0583f14f6c88de06b1ef28f460a1fb5b0ae": "Sì",
41
+ "816c52fd2bdd94a63cd0944823a6c0aa9384c103": "No",
42
+ "c47ae15370cfe1ed2781eedc1dc2547d12d9e972": "Aiuto",
43
+ "cc3dbd47e1cf9003a55d3366b3adbcd72275e525": "Nuovo Task",
44
+ "efa5a8b84e1afe65c81ecfce28c398c48f19ddc2": "Benvenuto su Janito{version_str}! Modalità chat attiva. Digita /exit per uscire.",
45
+ "b314d6e1460f86e0f21abc5aceb7935a2a0667e8": "Benvenuto su Janito{version_str} in modalità [white on magenta]VANILLA[/white on magenta]! Strumenti, prompt di sistema e temperatura sono disattivati, a meno che non siano sovrascritti.",
46
+ }
janito/llm/agent.py CHANGED
@@ -318,23 +318,39 @@ class LLMAgent:
318
318
  loop_count = 1
319
319
  import threading
320
320
 
321
- cancel_event = threading.Event()
322
- while True:
323
- self._print_verbose_chat_loop(loop_count)
324
- driver_input = self._prepare_driver_input(config, cancel_event=cancel_event)
325
- self.input_queue.put(driver_input)
326
- try:
327
- result, added_tool_results = self._process_next_response()
328
- except KeyboardInterrupt:
329
- cancel_event.set()
330
- raise
331
- if getattr(self, "verbose_agent", False):
332
- print(
333
- f"[agent] [DEBUG] Returned from _process_next_response: result={result}, added_tool_results={added_tool_results}"
321
+ # Use global cancellation manager
322
+ from janito.llm.cancellation_manager import get_cancellation_manager
323
+
324
+ cancel_manager = get_cancellation_manager()
325
+ driver_cancel_event = cancel_manager.start_new_request()
326
+
327
+ # Store cancellation event on agent for external access
328
+ self.cancel_event = driver_cancel_event
329
+
330
+ try:
331
+ while True:
332
+ self._print_verbose_chat_loop(loop_count)
333
+ driver_input = self._prepare_driver_input(
334
+ config, cancel_event=driver_cancel_event
334
335
  )
335
- if self._should_exit_chat_loop(result, added_tool_results):
336
- return result
337
- loop_count += 1
336
+ self.input_queue.put(driver_input)
337
+ try:
338
+ result, added_tool_results = self._process_next_response()
339
+ except KeyboardInterrupt:
340
+ cancel_manager.cancel_current_request()
341
+ raise
342
+ if getattr(self, "verbose_agent", False):
343
+ print(
344
+ f"[agent] [DEBUG] Returned from _process_next_response: result={result}, added_tool_results={added_tool_results}"
345
+ )
346
+ if self._should_exit_chat_loop(result, added_tool_results):
347
+ return result
348
+ loop_count += 1
349
+ finally:
350
+ cancel_manager.clear_current_request()
351
+ # Clean up cancellation event
352
+ if hasattr(self, "cancel_event"):
353
+ delattr(self, "cancel_event")
338
354
 
339
355
  def _clear_driver_queues(self):
340
356
  if hasattr(self, "driver") and self.driver:
@@ -0,0 +1,63 @@
1
+ """
2
+ Global cancellation manager for LLM requests.
3
+ Provides a centralized way to cancel ongoing requests.
4
+ """
5
+
6
+ import threading
7
+ from typing import Optional
8
+
9
+
10
+ class CancellationManager:
11
+ """Manages cancellation of LLM requests across the application."""
12
+
13
+ def __init__(self):
14
+ self._current_cancel_event: Optional[threading.Event] = None
15
+ self._lock = threading.Lock()
16
+ self._keyboard_cancellation = None
17
+
18
+ def start_new_request(self) -> threading.Event:
19
+ """Start a new request and return its cancellation event."""
20
+ with self._lock:
21
+ # Create new cancellation event for this request
22
+ self._current_cancel_event = threading.Event()
23
+
24
+ # Start keyboard monitoring
25
+ from janito.llm.enter_cancellation import get_enter_cancellation
26
+
27
+ self._keyboard_cancellation = get_enter_cancellation()
28
+ self._keyboard_cancellation.start_monitoring(self._current_cancel_event)
29
+
30
+ return self._current_cancel_event
31
+
32
+ def cancel_current_request(self) -> bool:
33
+ """Cancel the current request if one is active."""
34
+ with self._lock:
35
+ if self._current_cancel_event is not None:
36
+ self._current_cancel_event.set()
37
+ return True
38
+ return False
39
+
40
+ def get_current_cancel_event(self) -> Optional[threading.Event]:
41
+ """Get the current cancellation event."""
42
+ with self._lock:
43
+ return self._current_cancel_event
44
+
45
+ def clear_current_request(self):
46
+ """Clear the current request cancellation event."""
47
+ with self._lock:
48
+ if self._keyboard_cancellation:
49
+ self._keyboard_cancellation.stop_monitoring()
50
+ self._keyboard_cancellation = None
51
+ self._current_cancel_event = None
52
+
53
+
54
+ # Global cancellation manager instance
55
+ _global_manager = None
56
+
57
+
58
+ def get_cancellation_manager() -> CancellationManager:
59
+ """Get the global cancellation manager instance."""
60
+ global _global_manager
61
+ if _global_manager is None:
62
+ _global_manager = CancellationManager()
63
+ return _global_manager
janito/llm/driver.py CHANGED
@@ -252,3 +252,11 @@ class LLMDriver(ABC):
252
252
  def _get_message_from_result(self, result):
253
253
  """Extract the message object from the provider result. Subclasses must implement this."""
254
254
  raise NotImplementedError("Subclasses must implement _get_message_from_result.")
255
+
256
+ def cancel_current_request(self):
257
+ """Cancel the current request being processed."""
258
+ # Use global cancellation manager to cancel the current request
259
+ from janito.llm.cancellation_manager import get_cancellation_manager
260
+
261
+ cancel_manager = get_cancellation_manager()
262
+ cancel_manager.cancel_current_request()