janito 3.1.0__py3-none-any.whl → 3.2.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.
- janito/README.md +3 -0
- janito/cli/chat_mode/bindings.py +14 -14
- janito/cli/chat_mode/session.py +6 -1
- janito/cli/chat_mode/shell/commands/security/allowed_sites.py +47 -33
- janito/cli/core/model_guesser.py +40 -24
- janito/cli/prompt_core.py +4 -3
- janito/i18n/it.py +46 -46
- janito/llm/agent.py +8 -5
- janito/llm/cancellation_manager.py +9 -8
- janito/llm/driver.py +1 -0
- janito/llm/enter_cancellation.py +58 -44
- janito/plugin_system/core_loader.py +76 -3
- janito/plugin_system/core_loader_fixed.py +79 -3
- janito/plugins/auto_loader.py +12 -11
- janito/plugins/core/filemanager/tools/create_file.py +2 -2
- janito/plugins/core_adapter.py +79 -3
- janito/plugins/discovery_core.py +14 -9
- janito/plugins/tools/core_tools_plugin.py +9 -10
- janito/plugins/tools/create_file.py +2 -2
- janito/providers/__init__.py +1 -0
- janito/providers/together/__init__.py +1 -0
- janito/providers/together/model_info.py +69 -0
- janito/providers/together/provider.py +108 -0
- janito/tools/loop_protection_decorator.py +114 -117
- {janito-3.1.0.dist-info → janito-3.2.0.dist-info}/METADATA +1 -1
- {janito-3.1.0.dist-info → janito-3.2.0.dist-info}/RECORD +30 -28
- janito/tools/function_adapter.py +0 -142
- {janito-3.1.0.dist-info → janito-3.2.0.dist-info}/WHEEL +0 -0
- {janito-3.1.0.dist-info → janito-3.2.0.dist-info}/entry_points.txt +0 -0
- {janito-3.1.0.dist-info → janito-3.2.0.dist-info}/licenses/LICENSE +0 -0
- {janito-3.1.0.dist-info → janito-3.2.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:
|
janito/cli/chat_mode/bindings.py
CHANGED
@@ -35,30 +35,30 @@ class KeyBindingsFactory:
|
|
35
35
|
buf.text = "Do It"
|
36
36
|
buf.validate_and_handle()
|
37
37
|
|
38
|
-
@bindings.add("
|
38
|
+
@bindings.add("c-c")
|
39
39
|
def _(event):
|
40
|
-
"""Handle
|
41
|
-
import threading
|
42
|
-
|
43
|
-
# Get the current session context
|
44
|
-
from prompt_toolkit.application import get_app
|
45
|
-
app = get_app()
|
46
|
-
|
40
|
+
"""Handle Ctrl+C to interrupt current request or exit chat."""
|
47
41
|
# Use global cancellation manager for robust cancellation
|
48
42
|
from janito.llm.cancellation_manager import get_cancellation_manager
|
43
|
+
|
49
44
|
cancel_manager = get_cancellation_manager()
|
50
|
-
|
45
|
+
|
51
46
|
cancelled = cancel_manager.cancel_current_request()
|
52
47
|
if cancelled:
|
53
48
|
# Provide user feedback
|
54
49
|
from rich.console import Console
|
50
|
+
|
55
51
|
console = Console()
|
56
|
-
console.print("[red]Request cancelled by
|
57
|
-
|
58
|
-
# Prevent the
|
52
|
+
console.print("[red]Request cancelled by Ctrl+C[/red]")
|
53
|
+
|
54
|
+
# Prevent the Ctrl+C from being processed as input
|
59
55
|
event.app.output.flush()
|
60
56
|
return
|
61
|
-
|
62
|
-
|
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
63
|
|
64
64
|
return bindings
|
janito/cli/chat_mode/session.py
CHANGED
@@ -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)
|
@@ -289,7 +294,7 @@ class ChatSession:
|
|
289
294
|
# Ensure cancellation manager is cleared
|
290
295
|
cancel_manager = get_cancellation_manager()
|
291
296
|
cancel_manager.clear_current_request()
|
292
|
-
|
297
|
+
|
293
298
|
end_time = time.time()
|
294
299
|
elapsed = end_time - start_time
|
295
300
|
self.msg_count += 1
|
@@ -59,39 +59,53 @@ Examples:
|
|
59
59
|
command = args[0].lower()
|
60
60
|
whitelist_manager = get_url_whitelist_manager()
|
61
61
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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)")
|
janito/cli/core/model_guesser.py
CHANGED
@@ -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
|
-
|
30
|
-
|
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,12 +207,13 @@ class PromptHandler:
|
|
207
207
|
"""
|
208
208
|
try:
|
209
209
|
self._print_verbose_debug("Calling agent.chat()...")
|
210
|
-
|
210
|
+
|
211
211
|
# Use global cancellation manager
|
212
212
|
from janito.llm.cancellation_manager import get_cancellation_manager
|
213
|
+
|
213
214
|
cancel_manager = get_cancellation_manager()
|
214
215
|
driver_cancel_event = cancel_manager.start_new_request()
|
215
|
-
|
216
|
+
|
216
217
|
try:
|
217
218
|
final_event = self.agent.chat(prompt=user_prompt)
|
218
219
|
if hasattr(self.agent, "set_latest_event"):
|
@@ -225,7 +226,7 @@ class PromptHandler:
|
|
225
226
|
global_event_bus.publish(final_event)
|
226
227
|
finally:
|
227
228
|
cancel_manager.clear_current_request()
|
228
|
-
|
229
|
+
|
229
230
|
except KeyboardInterrupt:
|
230
231
|
# Capture user interrupt / cancellation
|
231
232
|
self.console.print("[red]Interrupted by the user.[/red]")
|
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": "
|
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
@@ -320,16 +320,19 @@ class LLMAgent:
|
|
320
320
|
|
321
321
|
# Use global cancellation manager
|
322
322
|
from janito.llm.cancellation_manager import get_cancellation_manager
|
323
|
+
|
323
324
|
cancel_manager = get_cancellation_manager()
|
324
325
|
driver_cancel_event = cancel_manager.start_new_request()
|
325
|
-
|
326
|
+
|
326
327
|
# Store cancellation event on agent for external access
|
327
328
|
self.cancel_event = driver_cancel_event
|
328
|
-
|
329
|
+
|
329
330
|
try:
|
330
331
|
while True:
|
331
332
|
self._print_verbose_chat_loop(loop_count)
|
332
|
-
driver_input = self._prepare_driver_input(
|
333
|
+
driver_input = self._prepare_driver_input(
|
334
|
+
config, cancel_event=driver_cancel_event
|
335
|
+
)
|
333
336
|
self.input_queue.put(driver_input)
|
334
337
|
try:
|
335
338
|
result, added_tool_results = self._process_next_response()
|
@@ -346,8 +349,8 @@ class LLMAgent:
|
|
346
349
|
finally:
|
347
350
|
cancel_manager.clear_current_request()
|
348
351
|
# Clean up cancellation event
|
349
|
-
if hasattr(self,
|
350
|
-
delattr(self,
|
352
|
+
if hasattr(self, "cancel_event"):
|
353
|
+
delattr(self, "cancel_event")
|
351
354
|
|
352
355
|
def _clear_driver_queues(self):
|
353
356
|
if hasattr(self, "driver") and self.driver:
|
@@ -9,25 +9,26 @@ from typing import Optional
|
|
9
9
|
|
10
10
|
class CancellationManager:
|
11
11
|
"""Manages cancellation of LLM requests across the application."""
|
12
|
-
|
12
|
+
|
13
13
|
def __init__(self):
|
14
14
|
self._current_cancel_event: Optional[threading.Event] = None
|
15
15
|
self._lock = threading.Lock()
|
16
16
|
self._keyboard_cancellation = None
|
17
|
-
|
17
|
+
|
18
18
|
def start_new_request(self) -> threading.Event:
|
19
19
|
"""Start a new request and return its cancellation event."""
|
20
20
|
with self._lock:
|
21
21
|
# Create new cancellation event for this request
|
22
22
|
self._current_cancel_event = threading.Event()
|
23
|
-
|
23
|
+
|
24
24
|
# Start keyboard monitoring
|
25
25
|
from janito.llm.enter_cancellation import get_enter_cancellation
|
26
|
+
|
26
27
|
self._keyboard_cancellation = get_enter_cancellation()
|
27
28
|
self._keyboard_cancellation.start_monitoring(self._current_cancel_event)
|
28
|
-
|
29
|
+
|
29
30
|
return self._current_cancel_event
|
30
|
-
|
31
|
+
|
31
32
|
def cancel_current_request(self) -> bool:
|
32
33
|
"""Cancel the current request if one is active."""
|
33
34
|
with self._lock:
|
@@ -35,12 +36,12 @@ class CancellationManager:
|
|
35
36
|
self._current_cancel_event.set()
|
36
37
|
return True
|
37
38
|
return False
|
38
|
-
|
39
|
+
|
39
40
|
def get_current_cancel_event(self) -> Optional[threading.Event]:
|
40
41
|
"""Get the current cancellation event."""
|
41
42
|
with self._lock:
|
42
43
|
return self._current_cancel_event
|
43
|
-
|
44
|
+
|
44
45
|
def clear_current_request(self):
|
45
46
|
"""Clear the current request cancellation event."""
|
46
47
|
with self._lock:
|
@@ -59,4 +60,4 @@ def get_cancellation_manager() -> CancellationManager:
|
|
59
60
|
global _global_manager
|
60
61
|
if _global_manager is None:
|
61
62
|
_global_manager = CancellationManager()
|
62
|
-
return _global_manager
|
63
|
+
return _global_manager
|
janito/llm/driver.py
CHANGED
@@ -257,5 +257,6 @@ class LLMDriver(ABC):
|
|
257
257
|
"""Cancel the current request being processed."""
|
258
258
|
# Use global cancellation manager to cancel the current request
|
259
259
|
from janito.llm.cancellation_manager import get_cancellation_manager
|
260
|
+
|
260
261
|
cancel_manager = get_cancellation_manager()
|
261
262
|
cancel_manager.cancel_current_request()
|
janito/llm/enter_cancellation.py
CHANGED
@@ -11,75 +11,89 @@ from typing import Optional
|
|
11
11
|
|
12
12
|
class EnterCancellation:
|
13
13
|
"""Handles Enter key cancellation of LLM requests."""
|
14
|
-
|
14
|
+
|
15
15
|
def __init__(self):
|
16
16
|
self._cancel_event: Optional[threading.Event] = None
|
17
17
|
self._listener_thread: Optional[threading.Thread] = None
|
18
18
|
self._listening = False
|
19
|
-
|
19
|
+
|
20
20
|
def start_monitoring(self, cancel_event: threading.Event):
|
21
21
|
"""Start monitoring for Enter key to cancel the request."""
|
22
22
|
if self._listening:
|
23
23
|
return
|
24
|
-
|
24
|
+
|
25
25
|
self._cancel_event = cancel_event
|
26
26
|
self._listening = True
|
27
|
-
|
27
|
+
|
28
28
|
self._listener_thread = threading.Thread(
|
29
29
|
target=self._monitor_enter_key, daemon=True
|
30
30
|
)
|
31
31
|
self._listener_thread.start()
|
32
|
-
|
32
|
+
|
33
33
|
def stop_monitoring(self):
|
34
34
|
"""Stop monitoring for keyboard input."""
|
35
35
|
self._listening = False
|
36
36
|
if self._listener_thread and self._listener_thread.is_alive():
|
37
37
|
self._listener_thread.join(timeout=0.1)
|
38
|
-
|
38
|
+
|
39
39
|
def _monitor_enter_key(self):
|
40
40
|
"""Monitor for Enter key press."""
|
41
41
|
try:
|
42
|
-
|
43
|
-
import select
|
44
|
-
import msvcrt # Windows-specific keyboard input
|
45
|
-
|
46
|
-
# Monitor for Enter key (Windows-specific implementation)
|
47
|
-
while self._listening and self._cancel_event and not self._cancel_event.is_set():
|
48
|
-
try:
|
49
|
-
# Windows-specific: check if key is available
|
50
|
-
if msvcrt.kbhit():
|
51
|
-
char = msvcrt.getch()
|
52
|
-
# Check for Enter key (carriage return or newline)
|
53
|
-
if char in [b'\r', b'\n']:
|
54
|
-
if self._cancel_event:
|
55
|
-
self._cancel_event.set()
|
56
|
-
break
|
57
|
-
else:
|
58
|
-
# Discard any other input
|
59
|
-
pass
|
60
|
-
else:
|
61
|
-
# Small delay to prevent high CPU usage
|
62
|
-
time.sleep(0.05)
|
63
|
-
|
64
|
-
except (IOError, OSError):
|
65
|
-
break
|
66
|
-
except Exception:
|
67
|
-
# Fallback to select-based approach for non-Windows
|
68
|
-
try:
|
69
|
-
if sys.stdin in select.select([sys.stdin], [], [], 0.05)[0]:
|
70
|
-
char = sys.stdin.read(1)
|
71
|
-
if char in ['\r', '\n']: # Enter key
|
72
|
-
if self._cancel_event:
|
73
|
-
self._cancel_event.set()
|
74
|
-
break
|
75
|
-
except:
|
76
|
-
pass
|
77
|
-
time.sleep(0.05)
|
78
|
-
|
42
|
+
self._handle_key_monitoring()
|
79
43
|
except Exception:
|
80
44
|
# Silently handle any errors
|
81
45
|
pass
|
82
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
|
+
|
83
97
|
|
84
98
|
# Global instance
|
85
99
|
_global_enter_cancellation = None
|
@@ -90,4 +104,4 @@ def get_enter_cancellation() -> EnterCancellation:
|
|
90
104
|
global _global_enter_cancellation
|
91
105
|
if _global_enter_cancellation is None:
|
92
106
|
_global_enter_cancellation = EnterCancellation()
|
93
|
-
return _global_enter_cancellation
|
107
|
+
return _global_enter_cancellation
|