janito 3.4.0__py3-none-any.whl → 3.5.1__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 (159) 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/check_tools.py +212 -0
  7. janito/cli/cli_commands/list_plugins.py +52 -43
  8. janito/cli/core/getters.py +3 -0
  9. janito/cli/core/model_guesser.py +40 -24
  10. janito/cli/main_cli.py +9 -12
  11. janito/cli/prompt_core.py +47 -9
  12. janito/cli/rich_terminal_reporter.py +2 -2
  13. janito/drivers/openai/driver.py +1 -0
  14. janito/drivers/zai/driver.py +1 -0
  15. janito/i18n/it.py +46 -46
  16. janito/llm/agent.py +32 -16
  17. janito/llm/auth_utils.py +14 -5
  18. janito/llm/cancellation_manager.py +63 -0
  19. janito/llm/driver.py +8 -0
  20. janito/llm/enter_cancellation.py +107 -0
  21. janito/plugin_system/__init__.py +10 -0
  22. janito/{plugins → plugin_system}/base.py +5 -2
  23. janito/plugin_system/core_loader.py +217 -0
  24. janito/plugin_system/core_loader_fixed.py +225 -0
  25. janito/plugins/__init__.py +31 -12
  26. janito/plugins/auto_loader.py +12 -11
  27. janito/plugins/auto_loader_fixed.py +12 -11
  28. janito/plugins/builtin.py +15 -1
  29. janito/plugins/core/__init__.py +7 -0
  30. janito/plugins/core/codeanalyzer/__init__.py +43 -0
  31. janito/plugins/core/filemanager/__init__.py +124 -0
  32. janito/plugins/core/filemanager/tools/create_file.py +87 -0
  33. janito/plugins/core/filemanager/tools/replace_text_in_file.py +270 -0
  34. janito/plugins/core/imagedisplay/__init__.py +14 -0
  35. janito/plugins/core/imagedisplay/plugin.py +51 -0
  36. janito/plugins/core/imagedisplay/tools/__init__.py +1 -0
  37. janito/plugins/core/imagedisplay/tools/show_image.py +83 -0
  38. janito/{tools/adapters/local → plugins/core/imagedisplay/tools}/show_image_grid.py +13 -5
  39. janito/plugins/core/system/__init__.py +23 -0
  40. janito/plugins/core/system/tools/run_bash_command.py +204 -0
  41. janito/plugins/core/system/tools/run_powershell_command.py +234 -0
  42. janito/plugins/core_adapter.py +89 -11
  43. janito/plugins/dev/__init__.py +7 -0
  44. janito/plugins/dev/pythondev/__init__.py +37 -0
  45. janito/plugins/dev/visualization/__init__.py +23 -0
  46. janito/plugins/discovery.py +5 -5
  47. janito/plugins/discovery_core.py +14 -9
  48. janito/plugins/example_plugin.py +108 -0
  49. janito/plugins/manager.py +1 -1
  50. janito/plugins/tools/__init__.py +10 -0
  51. janito/{tools/adapters/local → plugins/tools}/ask_user.py +3 -3
  52. janito/plugins/tools/copy_file.py +87 -0
  53. janito/plugins/tools/core_tools_plugin.py +87 -0
  54. janito/plugins/tools/create_directory.py +70 -0
  55. janito/{tools/adapters/local → plugins/tools}/create_file.py +6 -6
  56. janito/plugins/tools/decorators.py +19 -0
  57. janito/plugins/tools/delete_text_in_file.py +134 -0
  58. janito/{tools/adapters/local → plugins/tools}/fetch_url.py +3 -3
  59. janito/plugins/tools/find_files.py +143 -0
  60. janito/plugins/tools/get_file_outline/__init__.py +7 -0
  61. janito/plugins/tools/get_file_outline/core.py +122 -0
  62. janito/plugins/tools/get_file_outline/java_outline.py +47 -0
  63. janito/plugins/tools/get_file_outline/markdown_outline.py +14 -0
  64. janito/plugins/tools/get_file_outline/python_outline.py +303 -0
  65. janito/plugins/tools/get_file_outline/search_outline.py +36 -0
  66. janito/plugins/tools/move_file.py +131 -0
  67. janito/plugins/tools/open_html_in_browser.py +51 -0
  68. janito/plugins/tools/open_url.py +37 -0
  69. janito/{tools/adapters/local → plugins/tools}/python_code_run.py +23 -7
  70. janito/{tools/adapters/local → plugins/tools}/python_command_run.py +21 -5
  71. janito/{tools/adapters/local → plugins/tools}/python_file_run.py +21 -5
  72. janito/plugins/tools/read_chart.py +259 -0
  73. janito/plugins/tools/read_files.py +58 -0
  74. janito/plugins/tools/remove_directory.py +55 -0
  75. janito/plugins/tools/remove_file.py +58 -0
  76. janito/{tools/adapters/local → plugins/tools}/replace_text_in_file.py +4 -4
  77. janito/{tools/adapters/local → plugins/tools}/run_bash_command.py +3 -3
  78. janito/{tools/adapters/local → plugins/tools}/run_powershell_command.py +3 -3
  79. janito/plugins/tools/search_text/__init__.py +7 -0
  80. janito/plugins/tools/search_text/core.py +205 -0
  81. janito/plugins/tools/search_text/match_lines.py +67 -0
  82. janito/plugins/tools/search_text/pattern_utils.py +73 -0
  83. janito/plugins/tools/search_text/traverse_directory.py +145 -0
  84. janito/{tools/adapters/local → plugins/tools}/show_image.py +15 -6
  85. janito/plugins/tools/show_image_grid.py +85 -0
  86. janito/plugins/tools/validate_file_syntax/__init__.py +7 -0
  87. janito/plugins/tools/validate_file_syntax/core.py +114 -0
  88. janito/plugins/tools/validate_file_syntax/css_validator.py +35 -0
  89. janito/plugins/tools/validate_file_syntax/html_validator.py +100 -0
  90. janito/plugins/tools/validate_file_syntax/jinja2_validator.py +50 -0
  91. janito/plugins/tools/validate_file_syntax/js_validator.py +27 -0
  92. janito/plugins/tools/validate_file_syntax/json_validator.py +6 -0
  93. janito/plugins/tools/validate_file_syntax/markdown_validator.py +109 -0
  94. janito/plugins/tools/validate_file_syntax/ps1_validator.py +32 -0
  95. janito/plugins/tools/validate_file_syntax/python_validator.py +5 -0
  96. janito/plugins/tools/validate_file_syntax/xml_validator.py +11 -0
  97. janito/plugins/tools/validate_file_syntax/yaml_validator.py +6 -0
  98. janito/plugins/tools/view_file.py +172 -0
  99. janito/plugins/ui/__init__.py +7 -0
  100. janito/plugins/ui/userinterface/__init__.py +16 -0
  101. janito/plugins/ui/userinterface/tools/ask_user.py +110 -0
  102. janito/plugins/web/__init__.py +7 -0
  103. janito/plugins/web/webtools/__init__.py +33 -0
  104. janito/plugins/web/webtools/tools/fetch_url.py +458 -0
  105. janito/providers/__init__.py +1 -0
  106. janito/providers/together/__init__.py +1 -0
  107. janito/providers/together/model_info.py +69 -0
  108. janito/providers/together/provider.py +108 -0
  109. janito/tools/__init__.py +31 -7
  110. janito/tools/adapters/__init__.py +6 -1
  111. janito/tools/adapters/local/__init__.py +7 -70
  112. janito/tools/cli_initializer.py +88 -0
  113. janito/tools/initialize.py +70 -0
  114. janito/tools/loop_protection_decorator.py +114 -117
  115. janito-3.5.1.dist-info/METADATA +229 -0
  116. {janito-3.4.0.dist-info → janito-3.5.1.dist-info}/RECORD +155 -86
  117. janito/plugins/core_loader.py +0 -120
  118. janito/plugins/core_loader_fixed.py +0 -125
  119. janito/tools/function_adapter.py +0 -65
  120. janito-3.4.0.dist-info/METADATA +0 -84
  121. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/__init__.py +0 -0
  122. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/core.py +0 -0
  123. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/java_outline.py +0 -0
  124. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/markdown_outline.py +0 -0
  125. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/python_outline.py +0 -0
  126. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/search_outline.py +0 -0
  127. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/__init__.py +0 -0
  128. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/core.py +0 -0
  129. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/match_lines.py +0 -0
  130. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/pattern_utils.py +0 -0
  131. /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/traverse_directory.py +0 -0
  132. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/copy_file.py +0 -0
  133. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/create_directory.py +0 -0
  134. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/delete_text_in_file.py +0 -0
  135. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/find_files.py +0 -0
  136. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/move_file.py +0 -0
  137. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/read_files.py +0 -0
  138. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/remove_directory.py +0 -0
  139. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/remove_file.py +0 -0
  140. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/__init__.py +0 -0
  141. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/core.py +0 -0
  142. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/css_validator.py +0 -0
  143. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/html_validator.py +0 -0
  144. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/jinja2_validator.py +0 -0
  145. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/js_validator.py +0 -0
  146. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/json_validator.py +0 -0
  147. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/markdown_validator.py +0 -0
  148. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/ps1_validator.py +0 -0
  149. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/python_validator.py +0 -0
  150. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/xml_validator.py +0 -0
  151. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/yaml_validator.py +0 -0
  152. /janito/{tools/adapters/local → plugins/core/filemanager/tools}/view_file.py +0 -0
  153. /janito/{tools/adapters/local → plugins/dev/visualization/tools}/read_chart.py +0 -0
  154. /janito/{tools/adapters/local → plugins/web/webtools/tools}/open_html_in_browser.py +0 -0
  155. /janito/{tools/adapters/local → plugins/web/webtools/tools}/open_url.py +0 -0
  156. {janito-3.4.0.dist-info → janito-3.5.1.dist-info}/WHEEL +0 -0
  157. {janito-3.4.0.dist-info → janito-3.5.1.dist-info}/entry_points.txt +0 -0
  158. {janito-3.4.0.dist-info → janito-3.5.1.dist-info}/licenses/LICENSE +0 -0
  159. {janito-3.4.0.dist-info → janito-3.5.1.dist-info}/top_level.txt +0 -0
janito/cli/main_cli.py CHANGED
@@ -238,6 +238,13 @@ definition = [
238
238
  "help": "List all resources (tools, commands, config) from loaded plugins",
239
239
  },
240
240
  ),
241
+ (
242
+ ["--check-tools"],
243
+ {
244
+ "action": "store_true",
245
+ "help": "Check all registered tools for signature validation and availability",
246
+ },
247
+ ),
241
248
  ]
242
249
 
243
250
  MODIFIER_KEYS = [
@@ -269,18 +276,7 @@ GETTER_KEYS = [
269
276
  "region_info",
270
277
  "list_providers_region",
271
278
  "ping",
272
- ]
273
- GETTER_KEYS = [
274
- "show_config",
275
- "list_providers",
276
- "list_profiles",
277
- "list_models",
278
- "list_tools",
279
- "list_config",
280
- "list_drivers",
281
- "region_info",
282
- "list_providers_region",
283
- "ping",
279
+ "check_tools",
284
280
  ]
285
281
 
286
282
 
@@ -406,6 +402,7 @@ class JanitoCLI:
406
402
  or self.args.list_plugins_available
407
403
  or self.args.list_resources
408
404
  or self.args.ping
405
+ or self.args.check_tools
409
406
  ):
410
407
  self._maybe_print_verbose_provider_model()
411
408
  handle_getter(self.args)
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)
@@ -181,6 +181,7 @@ class OpenAIModelDriver(LLMDriver):
181
181
  is_insufficient_quota = (
182
182
  "insufficient_quota" in lower_err
183
183
  or "exceeded your current quota" in lower_err
184
+ or "exceeded_current_quota_error" in lower_err
184
185
  )
185
186
  is_rate_limit = (
186
187
  status_code == 429
@@ -174,6 +174,7 @@ class ZAIModelDriver(LLMDriver):
174
174
  is_insufficient_quota = (
175
175
  "insufficient_quota" in lower_err
176
176
  or "exceeded your current quota" in lower_err
177
+ or "exceeded_current_quota_error" in lower_err
177
178
  )
178
179
  is_rate_limit = (
179
180
  status_code == 429
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:
janito/llm/auth_utils.py CHANGED
@@ -13,9 +13,18 @@ def handle_missing_api_key(provider_name: str, env_var_name: str) -> None:
13
13
  provider_name: Name of the provider (e.g., 'alibaba', 'openai')
14
14
  env_var_name: Environment variable name (e.g., 'ALIBABA_API_KEY')
15
15
  """
16
- print(
17
- f"[ERROR] No API key found for provider '{provider_name}'. Please set the API key using:"
18
- )
19
- print(f" janito --set-api-key YOUR_API_KEY -p {provider_name}")
20
- print(f"Or set the {env_var_name} environment variable.")
16
+ if provider_name == "moonshot":
17
+ print(
18
+ f"[ERROR] You exceeded your current token quota for Moonshot. Please check your account balance at:"
19
+ )
20
+ print(f" https://platform.moonshot.ai/console/pay")
21
+ print()
22
+ print(f"To set a new API key, use:")
23
+ print(f" janito --set-api-key YOUR_NEW_API_KEY -p moonshot")
24
+ else:
25
+ print(
26
+ f"[ERROR] No API key found for provider '{provider_name}'. Please set the API key using:"
27
+ )
28
+ print(f" janito --set-api-key YOUR_API_KEY -p {provider_name}")
29
+ print(f"Or set the {env_var_name} environment variable.")
21
30
  sys.exit(1)
@@ -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()
@@ -0,0 +1,107 @@
1
+ """
2
+ Enter key cancellation for LLM requests.
3
+ Allows pressing Enter to cancel ongoing requests.
4
+ """
5
+
6
+ import threading
7
+ import sys
8
+ import time
9
+ from typing import Optional
10
+
11
+
12
+ class EnterCancellation:
13
+ """Handles Enter key cancellation of LLM requests."""
14
+
15
+ def __init__(self):
16
+ self._cancel_event: Optional[threading.Event] = None
17
+ self._listener_thread: Optional[threading.Thread] = None
18
+ self._listening = False
19
+
20
+ def start_monitoring(self, cancel_event: threading.Event):
21
+ """Start monitoring for Enter key to cancel the request."""
22
+ if self._listening:
23
+ return
24
+
25
+ self._cancel_event = cancel_event
26
+ self._listening = True
27
+
28
+ self._listener_thread = threading.Thread(
29
+ target=self._monitor_enter_key, daemon=True
30
+ )
31
+ self._listener_thread.start()
32
+
33
+ def stop_monitoring(self):
34
+ """Stop monitoring for keyboard input."""
35
+ self._listening = False
36
+ if self._listener_thread and self._listener_thread.is_alive():
37
+ self._listener_thread.join(timeout=0.1)
38
+
39
+ def _monitor_enter_key(self):
40
+ """Monitor for Enter key press."""
41
+ try:
42
+ self._handle_key_monitoring()
43
+ except Exception:
44
+ # Silently handle any errors
45
+ pass
46
+
47
+ def _handle_key_monitoring(self):
48
+ """Handle the actual key monitoring logic."""
49
+ import sys
50
+ import select
51
+
52
+ try:
53
+ import msvcrt # Windows-specific keyboard input
54
+
55
+ self._monitor_windows_keys()
56
+ except ImportError:
57
+ self._monitor_unix_keys()
58
+
59
+ def _monitor_windows_keys(self):
60
+ """Monitor keys on Windows systems."""
61
+ import msvcrt
62
+
63
+ while (
64
+ self._listening and self._cancel_event and not self._cancel_event.is_set()
65
+ ):
66
+ try:
67
+ if msvcrt.kbhit():
68
+ char = msvcrt.getch()
69
+ if char in [b"\r", b"\n"]:
70
+ if self._cancel_event:
71
+ self._cancel_event.set()
72
+ break
73
+ else:
74
+ time.sleep(0.05)
75
+ except (IOError, OSError):
76
+ break
77
+
78
+ def _monitor_unix_keys(self):
79
+ """Monitor keys on Unix-like systems."""
80
+ import sys
81
+ import select
82
+
83
+ while (
84
+ self._listening and self._cancel_event and not self._cancel_event.is_set()
85
+ ):
86
+ try:
87
+ if sys.stdin in select.select([sys.stdin], [], [], 0.05)[0]:
88
+ char = sys.stdin.read(1)
89
+ if char in ["\r", "\n"]:
90
+ if self._cancel_event:
91
+ self._cancel_event.set()
92
+ break
93
+ time.sleep(0.05)
94
+ except:
95
+ time.sleep(0.05)
96
+
97
+
98
+ # Global instance
99
+ _global_enter_cancellation = None
100
+
101
+
102
+ def get_enter_cancellation() -> EnterCancellation:
103
+ """Get the global Enter cancellation instance."""
104
+ global _global_enter_cancellation
105
+ if _global_enter_cancellation is None:
106
+ _global_enter_cancellation = EnterCancellation()
107
+ return _global_enter_cancellation
@@ -0,0 +1,10 @@
1
+ """
2
+ Plugin System Core Package
3
+
4
+ This package provides the foundational plugin system architecture.
5
+ It should not depend on any specific plugin implementations.
6
+ """
7
+
8
+ from .base import Plugin, PluginMetadata, PluginResource
9
+
10
+ __all__ = ["Plugin", "PluginMetadata", "PluginResource"]
@@ -5,7 +5,10 @@ Base classes for janito plugins.
5
5
  from abc import ABC, abstractmethod
6
6
  from dataclasses import dataclass
7
7
  from typing import Dict, Any, List, Optional, Type, Union
8
- from janito.tools.tool_base import ToolBase
8
+ from typing import TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from janito.tools.tool_base import ToolBase
9
12
 
10
13
 
11
14
  @dataclass
@@ -50,7 +53,7 @@ class Plugin(ABC):
50
53
  """Return metadata describing this plugin."""
51
54
  pass
52
55
 
53
- def get_tools(self) -> List[Type[ToolBase]]:
56
+ def get_tools(self) -> List[Type["ToolBase"]]:
54
57
  """
55
58
  Return a list of tool classes provided by this plugin.
56
59