janito 3.0.0__py3-none-any.whl → 3.1.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.
@@ -35,4 +35,30 @@ class KeyBindingsFactory:
35
35
  buf.text = "Do It"
36
36
  buf.validate_and_handle()
37
37
 
38
+ @bindings.add("enter", eager=True)
39
+ def _(event):
40
+ """Handle Enter key to interrupt current request."""
41
+ import threading
42
+
43
+ # Get the current session context
44
+ from prompt_toolkit.application import get_app
45
+ app = get_app()
46
+
47
+ # Use global cancellation manager for robust cancellation
48
+ from janito.llm.cancellation_manager import get_cancellation_manager
49
+ cancel_manager = get_cancellation_manager()
50
+
51
+ cancelled = cancel_manager.cancel_current_request()
52
+ if cancelled:
53
+ # Provide user feedback
54
+ from rich.console import Console
55
+ console = Console()
56
+ console.print("[red]Request cancelled by Enter key[/red]")
57
+
58
+ # Prevent the Enter key from being processed as input
59
+ event.app.output.flush()
60
+ return
61
+
62
+ # If no active request to cancel, let normal Enter behavior proceed
63
+
38
64
  return bindings
@@ -283,7 +283,13 @@ class ChatSession:
283
283
  )
284
284
  )
285
285
 
286
- self._prompt_handler.run_prompt(cmd_input)
286
+ try:
287
+ self._prompt_handler.run_prompt(cmd_input)
288
+ finally:
289
+ # Ensure cancellation manager is cleared
290
+ cancel_manager = get_cancellation_manager()
291
+ cancel_manager.clear_current_request()
292
+
287
293
  end_time = time.time()
288
294
  elapsed = end_time - start_time
289
295
  self.msg_count += 1
janito/cli/prompt_core.py CHANGED
@@ -207,15 +207,25 @@ 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
+ cancel_manager = get_cancellation_manager()
214
+ driver_cancel_event = cancel_manager.start_new_request()
215
+
216
+ try:
217
+ final_event = self.agent.chat(prompt=user_prompt)
218
+ if hasattr(self.agent, "set_latest_event"):
219
+ self.agent.set_latest_event(final_event)
220
+ self.agent.last_event = final_event
221
+ self._print_verbose_debug(f"agent.chat() returned: {final_event}")
222
+ self._print_verbose_final_event(final_event)
223
+ if on_event and final_event is not None:
224
+ on_event(final_event)
225
+ global_event_bus.publish(final_event)
226
+ finally:
227
+ cancel_manager.clear_current_request()
228
+
219
229
  except KeyboardInterrupt:
220
230
  # Capture user interrupt / cancellation
221
231
  self.console.print("[red]Interrupted by the user.[/red]")
janito/llm/agent.py CHANGED
@@ -318,23 +318,36 @@ 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}"
334
- )
335
- if self._should_exit_chat_loop(result, added_tool_results):
336
- return result
337
- loop_count += 1
321
+ # Use global cancellation manager
322
+ from janito.llm.cancellation_manager import get_cancellation_manager
323
+ cancel_manager = get_cancellation_manager()
324
+ driver_cancel_event = cancel_manager.start_new_request()
325
+
326
+ # Store cancellation event on agent for external access
327
+ self.cancel_event = driver_cancel_event
328
+
329
+ try:
330
+ while True:
331
+ self._print_verbose_chat_loop(loop_count)
332
+ driver_input = self._prepare_driver_input(config, cancel_event=driver_cancel_event)
333
+ self.input_queue.put(driver_input)
334
+ try:
335
+ result, added_tool_results = self._process_next_response()
336
+ except KeyboardInterrupt:
337
+ cancel_manager.cancel_current_request()
338
+ raise
339
+ if getattr(self, "verbose_agent", False):
340
+ print(
341
+ f"[agent] [DEBUG] Returned from _process_next_response: result={result}, added_tool_results={added_tool_results}"
342
+ )
343
+ if self._should_exit_chat_loop(result, added_tool_results):
344
+ return result
345
+ loop_count += 1
346
+ finally:
347
+ cancel_manager.clear_current_request()
348
+ # Clean up cancellation event
349
+ if hasattr(self, 'cancel_event'):
350
+ delattr(self, 'cancel_event')
338
351
 
339
352
  def _clear_driver_queues(self):
340
353
  if hasattr(self, "driver") and self.driver:
@@ -0,0 +1,62 @@
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
+ self._keyboard_cancellation = get_enter_cancellation()
27
+ self._keyboard_cancellation.start_monitoring(self._current_cancel_event)
28
+
29
+ return self._current_cancel_event
30
+
31
+ def cancel_current_request(self) -> bool:
32
+ """Cancel the current request if one is active."""
33
+ with self._lock:
34
+ if self._current_cancel_event is not None:
35
+ self._current_cancel_event.set()
36
+ return True
37
+ return False
38
+
39
+ def get_current_cancel_event(self) -> Optional[threading.Event]:
40
+ """Get the current cancellation event."""
41
+ with self._lock:
42
+ return self._current_cancel_event
43
+
44
+ def clear_current_request(self):
45
+ """Clear the current request cancellation event."""
46
+ with self._lock:
47
+ if self._keyboard_cancellation:
48
+ self._keyboard_cancellation.stop_monitoring()
49
+ self._keyboard_cancellation = None
50
+ self._current_cancel_event = None
51
+
52
+
53
+ # Global cancellation manager instance
54
+ _global_manager = None
55
+
56
+
57
+ def get_cancellation_manager() -> CancellationManager:
58
+ """Get the global cancellation manager instance."""
59
+ global _global_manager
60
+ if _global_manager is None:
61
+ _global_manager = CancellationManager()
62
+ return _global_manager
janito/llm/driver.py CHANGED
@@ -252,3 +252,10 @@ 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
+ cancel_manager = get_cancellation_manager()
261
+ cancel_manager.cancel_current_request()
@@ -0,0 +1,93 @@
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
+ import sys
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
+
79
+ except Exception:
80
+ # Silently handle any errors
81
+ pass
82
+
83
+
84
+ # Global instance
85
+ _global_enter_cancellation = None
86
+
87
+
88
+ def get_enter_cancellation() -> EnterCancellation:
89
+ """Get the global Enter cancellation instance."""
90
+ global _global_enter_cancellation
91
+ if _global_enter_cancellation is None:
92
+ _global_enter_cancellation = EnterCancellation()
93
+ return _global_enter_cancellation
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: janito
3
- Version: 3.0.0
3
+ Version: 3.1.0
4
4
  Summary: A new Python package called janito.
5
5
  Author-email: João Pinto <janito@ikignosis.org>
6
6
  Project-URL: Homepage, https://github.com/ikignosis/janito
@@ -30,17 +30,17 @@ janito/cli/config.py,sha256=HkZ14701HzIqrvaNyDcDhGlVHfpX_uHlLp2rHmhRm_k,872
30
30
  janito/cli/console.py,sha256=gJolqzWL7jEPLxeuH-CwBDRFpXt976KdZOEAB2tdBDs,64
31
31
  janito/cli/main.py,sha256=s5odou0txf8pzTf1ADk2yV7T5m8B6cejJ81e7iu776U,312
32
32
  janito/cli/main_cli.py,sha256=_OOQqeLiqvmYeB_928Av920Gk43rbXecMIOTL6JeT0Y,16674
33
- janito/cli/prompt_core.py,sha256=F68J4Xl6jZMYFN4oBBYZFj15Jp-HTYoLub4bw2XpNRU,11648
33
+ janito/cli/prompt_core.py,sha256=RRDhmiZTF1X-sxeGag2ODT9DSgtVDlqVwBlzl2voIIo,12082
34
34
  janito/cli/prompt_handler.py,sha256=SnPTlL64noeAMGlI08VBDD5IDD8jlVMIYA4-fS8zVLg,215
35
35
  janito/cli/prompt_setup.py,sha256=s48gvNfZhKjsEhf4EzL1tKIGm4wDidPMDvlM6TAPYes,2116
36
36
  janito/cli/rich_terminal_reporter.py,sha256=Hitf5U13gncad4GPVAcDMfdSwlfzQzOn9KdeX4TjTWU,6806
37
37
  janito/cli/utils.py,sha256=plCQiDKIf3V8mFhhX5H9-MF2W86i-xRdWf8Xi117Z0w,677
38
38
  janito/cli/verbose_output.py,sha256=wY_B4of5e8Vv7w1fRwOZzNGU2JqbMdcFnGjtEr4hLus,7686
39
- janito/cli/chat_mode/bindings.py,sha256=odjc5_-YW1t2FRhBUNRNoBMoQIg5sMz3ktV7xG0ADFU,975
39
+ janito/cli/chat_mode/bindings.py,sha256=m2KA-0TKMPpmfaYF3XQ9qsAIfDRJ40a1FGVoPs8FaHo,2074
40
40
  janito/cli/chat_mode/chat_entry.py,sha256=RFdPd23jsA2DMHRacpjAdwI_1dFBaWrtnwyQEgb2fHA,475
41
41
  janito/cli/chat_mode/prompt_style.py,sha256=vsqQ9xxmrYjj1pWuVe9CayQf39fo2EIXrkKPkflSVn4,805
42
42
  janito/cli/chat_mode/script_runner.py,sha256=WFTFVWzg_VQrD2Ujj02XWjscfGgHwmjBeRxaEjWw9ps,6505
43
- janito/cli/chat_mode/session.py,sha256=1mCET4V9u1FGEMnr8HJGOc6X8lhTNkhAYlZ3cvIvefw,18540
43
+ janito/cli/chat_mode/session.py,sha256=7RSFM6VccZLEHPaZTmZCpyEUe0JArto9DoHQCiYkUDg,18777
44
44
  janito/cli/chat_mode/toolbar.py,sha256=SzdWAJdcY1g2rTPZCPL6G5X8jO6ZQYjwko2-nw54_nU,3397
45
45
  janito/cli/chat_mode/shell/autocomplete.py,sha256=lE68MaVaodbA2VfUM0_YLqQVLBJAE_BJsd5cMtwuD-g,793
46
46
  janito/cli/chat_mode/shell/commands.bak.zip,sha256=I7GFjXg2ORT5NzFpicH1vQ3kchhduQsZinzqo0xO8wU,74238
@@ -122,13 +122,15 @@ janito/i18n/messages.py,sha256=fBuwOTFoygyHPkYphm6Y0r1iE8497Z4iryVAmPhMEkg,1851
122
122
  janito/i18n/pt.py,sha256=NlTgpDSftUfFG7FGbs7TK54vQlJVMyaZDHGcWjelwMc,4168
123
123
  janito/llm/README.md,sha256=6GRqCu_a9va5HCB1YqNqbshyWKFyAGlnXugrjom-xj8,1213
124
124
  janito/llm/__init__.py,sha256=dpyVH51qVRCw-PDyAFLAxq0zd4jl5MDcuV6Cri0D-dQ,134
125
- janito/llm/agent.py,sha256=T0JfeMoOudTWsHwWCcaocrHyq9k0TvkL4_YePlXvZfo,21269
125
+ janito/llm/agent.py,sha256=zUmax9uJCvfNMfzDLQjGDdCZMFmmXzaRz2M_cG0HdMo,21914
126
126
  janito/llm/auth.py,sha256=8Dl_orUEPhn2X6XjkO2Nr-j1HFT2YDxk1qJl9hSFI88,2286
127
127
  janito/llm/auth_utils.py,sha256=mcPWT2p2kGMhvR9QsHqk84utDejPJh3uyjrDOJ0dKYA,1068
128
- janito/llm/driver.py,sha256=stiicPe_MXTuWW4q6MSwK7PCj8UZcA_30pGACu6xYUQ,10039
128
+ janito/llm/cancellation_manager.py,sha256=lkMFIqXEc1PjU2a96rOIcxcHVRx4WvSr3gk_L9oumi4,2238
129
+ janito/llm/driver.py,sha256=i6CIA4yEaMfsVJ0TIfwwO5S9gHn0IYyDxB9GjVa67og,10392
129
130
  janito/llm/driver_config.py,sha256=OW0ae49EfgKDqaThuDjZBiaN78voNzwiZ6szERMqJos,1406
130
131
  janito/llm/driver_config_builder.py,sha256=BvWGx7vaBR5NyvPY1XNAP3lAgo1uf-T25CSsIo2kkCU,1401
131
132
  janito/llm/driver_input.py,sha256=Zq7IO4KdQPUraeIo6XoOaRy1IdQAyYY15RQw4JU30uA,389
133
+ janito/llm/enter_cancellation.py,sha256=NrySoeXM8M6PO6rMUPd9atW49GjPGF30YsTWIcAuZ7M,3393
132
134
  janito/llm/message_parts.py,sha256=QY_0kDjaxdoErDgKPRPv1dNkkYJuXIBmHWNLiOEKAH4,1365
133
135
  janito/llm/model.py,sha256=EioBkdgn8hJ0iQaKN-0KbXlsrk3YKmwR9IbvoEbdVTE,1159
134
136
  janito/llm/provider.py,sha256=3FbhQPrWBSEoIdIi-5DWIh0DD_CM570EFf1NcuGyGko,7961
@@ -323,9 +325,9 @@ janito/tools/url_whitelist.py,sha256=0CPLkHTp5HgnwgjxwgXnJmwPeZQ30q4j3YjW59hiUUE
323
325
  janito/tools/adapters/__init__.py,sha256=H25uYM2ETMLKpKPPEPAu9-AFjxkKfSyfx3pnoXSQlVA,255
324
326
  janito/tools/adapters/local/__init__.py,sha256=1DVnka4iEQp8Xrs7rJVVx45fPpuVahjTFmuJhv-gN8s,249
325
327
  janito/tools/adapters/local/adapter.py,sha256=u4nLHTaYdwZXMi1J8lsKvlG6rOmdq9xjey_3zeyCG4k,8707
326
- janito-3.0.0.dist-info/licenses/LICENSE,sha256=dXV4fOF2ZErugtN8l_Nrj5tsRTYgtjE3cgiya0UfBio,11356
327
- janito-3.0.0.dist-info/METADATA,sha256=JLYLflkFEBonYah38ghiqq0OP4HzA1wYCNyXSQ0FRC4,2265
328
- janito-3.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
329
- janito-3.0.0.dist-info/entry_points.txt,sha256=wIo5zZxbmu4fC-ZMrsKD0T0vq7IqkOOLYhrqRGypkx4,48
330
- janito-3.0.0.dist-info/top_level.txt,sha256=m0NaVCq0-ivxbazE2-ND0EA9Hmuijj_OGkmCbnBcCig,7
331
- janito-3.0.0.dist-info/RECORD,,
328
+ janito-3.1.0.dist-info/licenses/LICENSE,sha256=dXV4fOF2ZErugtN8l_Nrj5tsRTYgtjE3cgiya0UfBio,11356
329
+ janito-3.1.0.dist-info/METADATA,sha256=lbkmz3Msw06EXZzWwc_Ur9At-zlyvSVNGqRv8tkp0eI,2265
330
+ janito-3.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
331
+ janito-3.1.0.dist-info/entry_points.txt,sha256=wIo5zZxbmu4fC-ZMrsKD0T0vq7IqkOOLYhrqRGypkx4,48
332
+ janito-3.1.0.dist-info/top_level.txt,sha256=m0NaVCq0-ivxbazE2-ND0EA9Hmuijj_OGkmCbnBcCig,7
333
+ janito-3.1.0.dist-info/RECORD,,
File without changes