janito 3.5.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.
@@ -30,7 +30,11 @@ class RunBashCommandTool(ToolBase):
30
30
  tool_name = "run_bash_command"
31
31
 
32
32
  def _stream_output(self, stream, file_obj, report_func, count_func, counter):
33
+ import threading
33
34
  for line in stream:
35
+ # Check for cancellation
36
+ if hasattr(self, '_cancel_event') and self._cancel_event.is_set():
37
+ break
34
38
  file_obj.write(line)
35
39
  file_obj.flush()
36
40
  report_func(line.rstrip("\r\n"), ReportAction.EXECUTE)
@@ -87,6 +91,11 @@ class RunBashCommandTool(ToolBase):
87
91
  bufsize=1,
88
92
  env=env,
89
93
  )
94
+ # Set up cancellation event
95
+ from janito.llm.cancellation_manager import get_cancellation_manager
96
+ cancel_manager = get_cancellation_manager()
97
+ self._cancel_event = cancel_manager.get_current_cancel_event()
98
+
90
99
  counter = {"stdout": 0, "stderr": 0}
91
100
  stdout_thread = threading.Thread(
92
101
  target=self._stream_output,
@@ -112,6 +121,14 @@ class RunBashCommandTool(ToolBase):
112
121
  stderr_thread.start()
113
122
  try:
114
123
  return_code = process.wait(timeout=timeout)
124
+ # Check if cancelled
125
+ if self._cancel_event and self._cancel_event.is_set():
126
+ process.kill()
127
+ self.report_warning(
128
+ tr("Command cancelled by user"),
129
+ ReportAction.EXECUTE,
130
+ )
131
+ return tr("Command cancelled by user")
115
132
  except subprocess.TimeoutExpired:
116
133
  process.kill()
117
134
  self.report_error(
@@ -124,8 +141,12 @@ class RunBashCommandTool(ToolBase):
124
141
  return tr(
125
142
  "Command timed out after {timeout} seconds.", timeout=timeout
126
143
  )
127
- stdout_thread.join()
128
- stderr_thread.join()
144
+ finally:
145
+ # Ensure threads are stopped
146
+ if self._cancel_event:
147
+ self._cancel_event.set()
148
+ stdout_thread.join(timeout=0.1)
149
+ stderr_thread.join(timeout=0.1)
129
150
  stdout_file.flush()
130
151
  stderr_file.flush()
131
152
  if not silent:
@@ -71,6 +71,9 @@ class RunPowershellCommandTool(ToolBase):
71
71
 
72
72
  def _stream_output(self, stream, file_obj, report_func, count_func, counter):
73
73
  for line in stream:
74
+ # Check for cancellation
75
+ if hasattr(self, '_cancel_event') and self._cancel_event.is_set():
76
+ break
74
77
  file_obj.write(line)
75
78
  file_obj.flush()
76
79
  report_func(line.rstrip("\r\n"), ReportAction.EXECUTE)
@@ -163,6 +166,11 @@ class RunPowershellCommandTool(ToolBase):
163
166
  encoding="utf-8",
164
167
  ) as stderr_file,
165
168
  ):
169
+ # Set up cancellation event
170
+ from janito.llm.cancellation_manager import get_cancellation_manager
171
+ cancel_manager = get_cancellation_manager()
172
+ self._cancel_event = cancel_manager.get_current_cancel_event()
173
+
166
174
  process = self._launch_process(shell_exe, command_with_encoding)
167
175
  counter = {"stdout": 0, "stderr": 0}
168
176
  stdout_thread = threading.Thread(
@@ -189,6 +197,14 @@ class RunPowershellCommandTool(ToolBase):
189
197
  stderr_thread.start()
190
198
  try:
191
199
  return_code = process.wait(timeout=timeout)
200
+ # Check if cancelled
201
+ if self._cancel_event and self._cancel_event.is_set():
202
+ process.kill()
203
+ self.report_warning(
204
+ tr("Command cancelled by user"),
205
+ ReportAction.EXECUTE,
206
+ )
207
+ return tr("Command cancelled by user")
192
208
  except subprocess.TimeoutExpired:
193
209
  process.kill()
194
210
  self.report_error(
@@ -93,6 +93,9 @@ class PythonCodeRun(ToolBase):
93
93
  def stream_output(stream, file_obj, report_func, count_func):
94
94
  nonlocal stdout_lines, stderr_lines
95
95
  for line in stream:
96
+ # Check for cancellation
97
+ if hasattr(self, '_cancel_event') and self._cancel_event.is_set():
98
+ break
96
99
  file_obj.write(line)
97
100
  file_obj.flush()
98
101
  report_func(line.rstrip("\r\n"))
@@ -101,6 +104,11 @@ class PythonCodeRun(ToolBase):
101
104
  else:
102
105
  stderr_lines += 1
103
106
 
107
+ # Set up cancellation event
108
+ from janito.llm.cancellation_manager import get_cancellation_manager
109
+ cancel_manager = get_cancellation_manager()
110
+ self._cancel_event = cancel_manager.get_current_cancel_event()
111
+
104
112
  stdout_thread = threading.Thread(
105
113
  target=stream_output,
106
114
  args=(process.stdout, stdout_file, self.report_stdout, "stdout"),
@@ -111,10 +119,18 @@ class PythonCodeRun(ToolBase):
111
119
  )
112
120
  stdout_thread.start()
113
121
  stderr_thread.start()
114
- process.stdin.write(code)
115
- process.stdin.close()
116
- stdout_thread.join()
117
- stderr_thread.join()
122
+
123
+ try:
124
+ process.stdin.write(code)
125
+ process.stdin.close()
126
+ stdout_thread.join()
127
+ stderr_thread.join()
128
+ except Exception as e:
129
+ # Handle cancellation or other errors
130
+ if self._cancel_event and self._cancel_event.is_set():
131
+ process.kill()
132
+ self.report_warning(tr("Code execution cancelled by user"), ReportAction.EXECUTE)
133
+ raise
118
134
  return stdout_lines, stderr_lines
119
135
 
120
136
  def _wait_for_process(self, process, timeout):
@@ -92,6 +92,9 @@ class PythonCommandRun(ToolBase):
92
92
  def stream_output(stream, file_obj, report_func, count_func):
93
93
  nonlocal stdout_lines, stderr_lines
94
94
  for line in stream:
95
+ # Check for cancellation
96
+ if hasattr(self, '_cancel_event') and self._cancel_event.is_set():
97
+ break
95
98
  file_obj.write(line)
96
99
  file_obj.flush()
97
100
  from janito.tools.tool_base import ReportAction
@@ -102,6 +105,11 @@ class PythonCommandRun(ToolBase):
102
105
  else:
103
106
  stderr_lines += 1
104
107
 
108
+ # Set up cancellation event
109
+ from janito.llm.cancellation_manager import get_cancellation_manager
110
+ cancel_manager = get_cancellation_manager()
111
+ self._cancel_event = cancel_manager.get_current_cancel_event()
112
+
105
113
  stdout_thread = threading.Thread(
106
114
  target=stream_output,
107
115
  args=(process.stdout, stdout_file, self.report_stdout, "stdout"),
@@ -112,8 +120,16 @@ class PythonCommandRun(ToolBase):
112
120
  )
113
121
  stdout_thread.start()
114
122
  stderr_thread.start()
115
- stdout_thread.join()
116
- stderr_thread.join()
123
+
124
+ try:
125
+ stdout_thread.join()
126
+ stderr_thread.join()
127
+ except Exception as e:
128
+ # Handle cancellation
129
+ if self._cancel_event and self._cancel_event.is_set():
130
+ process.kill()
131
+ self.report_warning(tr("Code execution cancelled by user"), ReportAction.EXECUTE)
132
+ raise
117
133
  return stdout_lines, stderr_lines
118
134
 
119
135
  def _wait_for_process(self, process, timeout):
@@ -92,6 +92,9 @@ class PythonFileRun(ToolBase):
92
92
  def stream_output(stream, file_obj, report_func, count_func):
93
93
  nonlocal stdout_lines, stderr_lines
94
94
  for line in stream:
95
+ # Check for cancellation
96
+ if hasattr(self, '_cancel_event') and self._cancel_event.is_set():
97
+ break
95
98
  file_obj.write(line)
96
99
  file_obj.flush()
97
100
  # Always supply a default action for stdout/stderr reporting
@@ -103,6 +106,11 @@ class PythonFileRun(ToolBase):
103
106
  else:
104
107
  stderr_lines += 1
105
108
 
109
+ # Set up cancellation event
110
+ from janito.llm.cancellation_manager import get_cancellation_manager
111
+ cancel_manager = get_cancellation_manager()
112
+ self._cancel_event = cancel_manager.get_current_cancel_event()
113
+
106
114
  stdout_thread = threading.Thread(
107
115
  target=stream_output,
108
116
  args=(process.stdout, stdout_file, self.report_stdout, "stdout"),
@@ -113,8 +121,16 @@ class PythonFileRun(ToolBase):
113
121
  )
114
122
  stdout_thread.start()
115
123
  stderr_thread.start()
116
- stdout_thread.join()
117
- stderr_thread.join()
124
+
125
+ try:
126
+ stdout_thread.join()
127
+ stderr_thread.join()
128
+ except Exception as e:
129
+ # Handle cancellation
130
+ if self._cancel_event and self._cancel_event.is_set():
131
+ process.kill()
132
+ self.report_warning(tr("File execution cancelled by user"), ReportAction.EXECUTE)
133
+ raise
118
134
  return stdout_lines, stderr_lines
119
135
 
120
136
  def _wait_for_process(self, process, timeout):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: janito
3
- Version: 3.5.0
3
+ Version: 3.5.1
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
@@ -192,13 +192,10 @@ janito/plugins/core/imagedisplay/tools/__init__.py,sha256=z4xyfhiLUYaHBzus1AxaAW
192
192
  janito/plugins/core/imagedisplay/tools/show_image.py,sha256=NpQTCMfBZ4NM6DpMe0lTJYH4NHkBCruUR11E4SdLCUw,3095
193
193
  janito/plugins/core/imagedisplay/tools/show_image_grid.py,sha256=ZsTbDCBr38xYXbbAJBgRKTLuAOw-_C8799LwHA-1yYQ,3078
194
194
  janito/plugins/core/system/__init__.py,sha256=tuaUFus8jTljReloESjdYFWXIEfSEYDct59I-4gZAyc,584
195
- janito/plugins/core/system/tools/run_bash_command.py,sha256=7fABqAeAu7WJwzzwHmT54_m5OSwPMcgpQ74lQhPG7TA,7955
196
- janito/plugins/core/system/tools/run_powershell_command.py,sha256=uQSJVQe40wSGbesyvZxDmIKJthAbDJFaxXm1dEN3gBs,9313
195
+ janito/plugins/core/system/tools/run_bash_command.py,sha256=oxDbsLHkznN7WhW4WzIblABk27onJ5EgpAlzyrJ3AfU,9020
196
+ janito/plugins/core/system/tools/run_powershell_command.py,sha256=gWviIUtZvRUgde9TN_kUqYeseyVpsFmOJPMWLOb9_Nw,10152
197
197
  janito/plugins/dev/__init__.py,sha256=V7wIaP_LMGBtg6ldz5fmiWf0eL-lSz4vcFgZjKcHAZo,136
198
198
  janito/plugins/dev/pythondev/__init__.py,sha256=qAO8Ub1lwBAMcfIt6oX0clTTB2msjIIJNHBRZczhHy8,991
199
- janito/plugins/dev/pythondev/tools/python_code_run.py,sha256=HdDuiRb7Fd7WC6bFS292XnSI1EYhn9XKlh8Hs_4Cz8E,6981
200
- janito/plugins/dev/pythondev/tools/python_command_run.py,sha256=BT9emL3PsNGupkENNfUymPECiQEAm9tBhniCaOuatj0,6935
201
- janito/plugins/dev/pythondev/tools/python_file_run.py,sha256=p0iOoxByCoKqW7QqfxTdHbPbRzEd_KWyZqnzrSb1qLQ,6859
202
199
  janito/plugins/dev/visualization/__init__.py,sha256=2zJuePRWKBzuC1_XHg3cguh-JGh4GcvkdENUV3xplz4,547
203
200
  janito/plugins/dev/visualization/tools/read_chart.py,sha256=qQebp_MEE_x2AL_pl85uA58A4lbhLQsyGl7wiG_Cjhc,10411
204
201
  janito/plugins/tools/__init__.py,sha256=vNqUVWA0guwAYuPgdmiDsjmgibj9siP0e61pfTHZ8-8,271
@@ -214,9 +211,9 @@ janito/plugins/tools/find_files.py,sha256=S4CLC-WmifyEIO7B1Mk52TAEpy4fIFr9GW9QVQ
214
211
  janito/plugins/tools/move_file.py,sha256=IgBTH2V3z83K3HT0cHWxAag-E5FoUTKS5nJa-_akvvs,4685
215
212
  janito/plugins/tools/open_html_in_browser.py,sha256=82ONg0OSo_8puznWbrgaiZSyBK_G-TejYgsDQM8a2KU,2091
216
213
  janito/plugins/tools/open_url.py,sha256=X-fdPCTkJ5WBkNDAjV_XVUh3MDEuDGcv-5PbvfC8y68,1542
217
- janito/plugins/tools/python_code_run.py,sha256=mepw0_VsP-iE1uN6QoxfiRKp6jEkFLWNiGjRwUi0rDk,6971
218
- janito/plugins/tools/python_command_run.py,sha256=SxmamLHqmv6JX0VtOzM_JM2NXqHi3TM0g0duNtC8Hr4,6925
219
- janito/plugins/tools/python_file_run.py,sha256=y6oj636Ue0c5ybylDu4l7n-heLhegMGn1CP_B0_hc-M,6849
214
+ janito/plugins/tools/python_code_run.py,sha256=BIc7hPTsEqsCo3BTZviVZjoj8baQE9wNz7pQLUT8HFI,7715
215
+ janito/plugins/tools/python_command_run.py,sha256=nbXxuoDADPw5wR5ysNm5tIwFN9RrrshoQISBscXd_Ow,7645
216
+ janito/plugins/tools/python_file_run.py,sha256=jIbzBwjAHLY2xy2crQpdFj9KH3VjaD4qc7TxbkeNW88,7569
220
217
  janito/plugins/tools/read_chart.py,sha256=8KT-Zzfpdp68QN6Pl8enDLPrKRBY2lhcKsJ8scS6i3o,10401
221
218
  janito/plugins/tools/read_files.py,sha256=7RVj4qescT__VHe1k_VdnpHKHY5YeG0fqPiNIopd3Is,2319
222
219
  janito/plugins/tools/remove_directory.py,sha256=jmY0pSrOJVQqR-_P7OuZijovAKkNB5cDk8BAAKVvvLI,1903
@@ -328,9 +325,9 @@ janito/tools/url_whitelist.py,sha256=0CPLkHTp5HgnwgjxwgXnJmwPeZQ30q4j3YjW59hiUUE
328
325
  janito/tools/adapters/__init__.py,sha256=H25uYM2ETMLKpKPPEPAu9-AFjxkKfSyfx3pnoXSQlVA,255
329
326
  janito/tools/adapters/local/__init__.py,sha256=1DVnka4iEQp8Xrs7rJVVx45fPpuVahjTFmuJhv-gN8s,249
330
327
  janito/tools/adapters/local/adapter.py,sha256=u4nLHTaYdwZXMi1J8lsKvlG6rOmdq9xjey_3zeyCG4k,8707
331
- janito-3.5.0.dist-info/licenses/LICENSE,sha256=dXV4fOF2ZErugtN8l_Nrj5tsRTYgtjE3cgiya0UfBio,11356
332
- janito-3.5.0.dist-info/METADATA,sha256=voeu6zQPSo_wytQfWnCUoe6wYviDtAxgX_f9ktc6N_g,6093
333
- janito-3.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
334
- janito-3.5.0.dist-info/entry_points.txt,sha256=wIo5zZxbmu4fC-ZMrsKD0T0vq7IqkOOLYhrqRGypkx4,48
335
- janito-3.5.0.dist-info/top_level.txt,sha256=m0NaVCq0-ivxbazE2-ND0EA9Hmuijj_OGkmCbnBcCig,7
336
- janito-3.5.0.dist-info/RECORD,,
328
+ janito-3.5.1.dist-info/licenses/LICENSE,sha256=dXV4fOF2ZErugtN8l_Nrj5tsRTYgtjE3cgiya0UfBio,11356
329
+ janito-3.5.1.dist-info/METADATA,sha256=1r4YjdQ2OvewXvPBmWjeog5ssJCkjZR_xFgEO_gkJzI,6093
330
+ janito-3.5.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
331
+ janito-3.5.1.dist-info/entry_points.txt,sha256=wIo5zZxbmu4fC-ZMrsKD0T0vq7IqkOOLYhrqRGypkx4,48
332
+ janito-3.5.1.dist-info/top_level.txt,sha256=m0NaVCq0-ivxbazE2-ND0EA9Hmuijj_OGkmCbnBcCig,7
333
+ janito-3.5.1.dist-info/RECORD,,
@@ -1,172 +0,0 @@
1
- import subprocess
2
- import os
3
- import sys
4
- import tempfile
5
- import threading
6
- from janito.tools.tool_base import ToolBase, ToolPermissions
7
- from janito.report_events import ReportAction
8
- from janito.tools.adapters.local.adapter import register_local_tool
9
- from janito.i18n import tr
10
-
11
-
12
- @register_local_tool
13
- class PythonCodeRunTool(ToolBase):
14
- """
15
- Tool to execute Python code by passing it to the interpreter via standard input (stdin).
16
-
17
- Args:
18
- code (str): The Python code to execute as a string.
19
- timeout (int): Timeout in seconds for the command. Defaults to 60.
20
- silent (bool): If True, suppresses progress and status messages. Defaults to False.
21
-
22
- Returns:
23
- str: Output and status message, or file paths/line counts if output is large.
24
- """
25
-
26
- permissions = ToolPermissions(execute=True)
27
- tool_name = "python_code_run"
28
-
29
- def run(self, code: str, timeout: int = 60, silent: bool = False) -> str:
30
- if not code.strip():
31
- self.report_warning(tr("ℹ️ Empty code provided."), ReportAction.EXECUTE)
32
- return tr("Warning: Empty code provided. Operation skipped.")
33
- if not silent:
34
- self.report_action(
35
- tr("⚡ Running: python (stdin mode) ...\n{code}\n", code=code),
36
- ReportAction.EXECUTE,
37
- )
38
- self.report_stdout("\n")
39
- else:
40
- self.report_action(tr("⚡ Executing..."), ReportAction.EXECUTE)
41
- try:
42
- with (
43
- tempfile.NamedTemporaryFile(
44
- mode="w+",
45
- prefix="python_stdin_stdout_",
46
- delete=False,
47
- encoding="utf-8",
48
- ) as stdout_file,
49
- tempfile.NamedTemporaryFile(
50
- mode="w+",
51
- prefix="python_stdin_stderr_",
52
- delete=False,
53
- encoding="utf-8",
54
- ) as stderr_file,
55
- ):
56
- process = subprocess.Popen(
57
- [sys.executable],
58
- stdin=subprocess.PIPE,
59
- stdout=subprocess.PIPE,
60
- stderr=subprocess.PIPE,
61
- text=True,
62
- bufsize=1,
63
- universal_newlines=True,
64
- encoding="utf-8",
65
- env={**os.environ, "PYTHONIOENCODING": "utf-8"},
66
- )
67
- stdout_lines, stderr_lines = self._stream_process_output(
68
- process, stdout_file, stderr_file, code
69
- )
70
- return_code = self._wait_for_process(process, timeout)
71
- if return_code is None:
72
- return tr(
73
- "Code timed out after {timeout} seconds.", timeout=timeout
74
- )
75
- stdout_file.flush()
76
- stderr_file.flush()
77
- if not silent:
78
- self.report_success(
79
- tr("✅ Return code {return_code}", return_code=return_code),
80
- ReportAction.EXECUTE,
81
- )
82
- return self._format_result(
83
- stdout_file.name, stderr_file.name, return_code
84
- )
85
- except Exception as e:
86
- self.report_error(tr("❌ Error: {error}", error=e), ReportAction.EXECUTE)
87
- return tr("Error running code via stdin: {error}", error=e)
88
-
89
- def _stream_process_output(self, process, stdout_file, stderr_file, code):
90
- stdout_lines = 0
91
- stderr_lines = 0
92
-
93
- def stream_output(stream, file_obj, report_func, count_func):
94
- nonlocal stdout_lines, stderr_lines
95
- for line in stream:
96
- file_obj.write(line)
97
- file_obj.flush()
98
- report_func(line.rstrip("\r\n"))
99
- if count_func == "stdout":
100
- stdout_lines += 1
101
- else:
102
- stderr_lines += 1
103
-
104
- stdout_thread = threading.Thread(
105
- target=stream_output,
106
- args=(process.stdout, stdout_file, self.report_stdout, "stdout"),
107
- )
108
- stderr_thread = threading.Thread(
109
- target=stream_output,
110
- args=(process.stderr, stderr_file, self.report_stderr, "stderr"),
111
- )
112
- stdout_thread.start()
113
- stderr_thread.start()
114
- process.stdin.write(code)
115
- process.stdin.close()
116
- stdout_thread.join()
117
- stderr_thread.join()
118
- return stdout_lines, stderr_lines
119
-
120
- def _wait_for_process(self, process, timeout):
121
- try:
122
- return process.wait(timeout=timeout)
123
- except subprocess.TimeoutExpired:
124
- process.kill()
125
- self.report_error(
126
- tr("❌ Timed out after {timeout} seconds.", timeout=timeout),
127
- ReportAction.EXECUTE,
128
- )
129
- return None
130
-
131
- def _format_result(self, stdout_file_name, stderr_file_name, return_code):
132
- with open(stdout_file_name, "r", encoding="utf-8", errors="replace") as out_f:
133
- stdout_content = out_f.read()
134
- with open(stderr_file_name, "r", encoding="utf-8", errors="replace") as err_f:
135
- stderr_content = err_f.read()
136
- max_lines = 100
137
- stdout_lines = stdout_content.count("\n")
138
- stderr_lines = stderr_content.count("\n")
139
-
140
- def head_tail(text, n=10):
141
- lines = text.splitlines()
142
- if len(lines) <= 2 * n:
143
- return "\n".join(lines)
144
- return "\n".join(
145
- lines[:n]
146
- + ["... ({} lines omitted) ...".format(len(lines) - 2 * n)]
147
- + lines[-n:]
148
- )
149
-
150
- if stdout_lines <= max_lines and stderr_lines <= max_lines:
151
- result = f"Return code: {return_code}\n--- python_code_run: STDOUT ---\n{stdout_content}"
152
- if stderr_content.strip():
153
- result += f"\n--- python_code_run: STDERR ---\n{stderr_content}"
154
- return result
155
- else:
156
- result = f"stdout_file: {stdout_file_name} (lines: {stdout_lines})\n"
157
- if stderr_lines > 0 and stderr_content.strip():
158
- result += f"stderr_file: {stderr_file_name} (lines: {stderr_lines})\n"
159
- result += f"returncode: {return_code}\n"
160
- result += (
161
- "--- python_code_run: STDOUT (head/tail) ---\n"
162
- + head_tail(stdout_content)
163
- + "\n"
164
- )
165
- if stderr_content.strip():
166
- result += (
167
- "--- python_code_run: STDERR (head/tail) ---\n"
168
- + head_tail(stderr_content)
169
- + "\n"
170
- )
171
- result += "Use the view_file tool to inspect the contents of these files when needed."
172
- return result
@@ -1,171 +0,0 @@
1
- import subprocess
2
- import os
3
- import sys
4
- import tempfile
5
- import threading
6
- from janito.tools.tool_base import ToolBase, ToolPermissions
7
- from janito.report_events import ReportAction
8
- from janito.tools.adapters.local.adapter import register_local_tool
9
- from janito.i18n import tr
10
-
11
-
12
- @register_local_tool
13
- class PythonCommandRunTool(ToolBase):
14
- """
15
- Tool to execute Python code using the `python -c` command-line flag.
16
-
17
- Args:
18
- code (str): The Python code to execute as a string.
19
- timeout (int): Timeout in seconds for the command. Defaults to 60.
20
- silent (bool): If True, suppresses progress and status messages. Defaults to False.
21
-
22
- Returns:
23
- str: Output and status message, or file paths/line counts if output is large.
24
- """
25
-
26
- permissions = ToolPermissions(execute=True)
27
- tool_name = "python_command_run"
28
-
29
- def run(self, code: str, timeout: int = 60, silent: bool = False) -> str:
30
- if not code.strip():
31
- self.report_warning(tr("ℹ️ Empty code provided."), ReportAction.EXECUTE)
32
- return tr("Warning: Empty code provided. Operation skipped.")
33
- if not silent:
34
- self.report_action(
35
- tr("🐍 Running: python -c ...\n{code}\n", code=code),
36
- ReportAction.EXECUTE,
37
- )
38
- self.report_stdout("\n")
39
- else:
40
- self.report_action(tr("⚡ Executing..."), ReportAction.EXECUTE)
41
- try:
42
- with (
43
- tempfile.NamedTemporaryFile(
44
- mode="w+",
45
- prefix="python_cmd_stdout_",
46
- delete=False,
47
- encoding="utf-8",
48
- ) as stdout_file,
49
- tempfile.NamedTemporaryFile(
50
- mode="w+",
51
- prefix="python_cmd_stderr_",
52
- delete=False,
53
- encoding="utf-8",
54
- ) as stderr_file,
55
- ):
56
- process = subprocess.Popen(
57
- [sys.executable, "-c", code],
58
- stdout=subprocess.PIPE,
59
- stderr=subprocess.PIPE,
60
- text=True,
61
- bufsize=1,
62
- universal_newlines=True,
63
- encoding="utf-8",
64
- env={**os.environ, "PYTHONIOENCODING": "utf-8"},
65
- )
66
- stdout_lines, stderr_lines = self._stream_process_output(
67
- process, stdout_file, stderr_file
68
- )
69
- return_code = self._wait_for_process(process, timeout)
70
- if return_code is None:
71
- return tr(
72
- "Code timed out after {timeout} seconds.", timeout=timeout
73
- )
74
- stdout_file.flush()
75
- stderr_file.flush()
76
- if not silent:
77
- self.report_success(
78
- tr("✅ Return code {return_code}", return_code=return_code),
79
- ReportAction.EXECUTE,
80
- )
81
- return self._format_result(
82
- stdout_file.name, stderr_file.name, return_code
83
- )
84
- except Exception as e:
85
- self.report_error(tr("❌ Error: {error}", error=e), ReportAction.EXECUTE)
86
- return tr("Error running code: {error}", error=e)
87
-
88
- def _stream_process_output(self, process, stdout_file, stderr_file):
89
- stdout_lines = 0
90
- stderr_lines = 0
91
-
92
- def stream_output(stream, file_obj, report_func, count_func):
93
- nonlocal stdout_lines, stderr_lines
94
- for line in stream:
95
- file_obj.write(line)
96
- file_obj.flush()
97
- from janito.tools.tool_base import ReportAction
98
-
99
- report_func(line.rstrip("\r\n"), ReportAction.EXECUTE)
100
- if count_func == "stdout":
101
- stdout_lines += 1
102
- else:
103
- stderr_lines += 1
104
-
105
- stdout_thread = threading.Thread(
106
- target=stream_output,
107
- args=(process.stdout, stdout_file, self.report_stdout, "stdout"),
108
- )
109
- stderr_thread = threading.Thread(
110
- target=stream_output,
111
- args=(process.stderr, stderr_file, self.report_stderr, "stderr"),
112
- )
113
- stdout_thread.start()
114
- stderr_thread.start()
115
- stdout_thread.join()
116
- stderr_thread.join()
117
- return stdout_lines, stderr_lines
118
-
119
- def _wait_for_process(self, process, timeout):
120
- try:
121
- return process.wait(timeout=timeout)
122
- except subprocess.TimeoutExpired:
123
- process.kill()
124
- self.report_error(
125
- tr("❌ Timed out after {timeout} seconds.", timeout=timeout),
126
- ReportAction.EXECUTE,
127
- )
128
- return None
129
-
130
- def _format_result(self, stdout_file_name, stderr_file_name, return_code):
131
- with open(stdout_file_name, "r", encoding="utf-8", errors="replace") as out_f:
132
- stdout_content = out_f.read()
133
- with open(stderr_file_name, "r", encoding="utf-8", errors="replace") as err_f:
134
- stderr_content = err_f.read()
135
- max_lines = 100
136
- stdout_lines = stdout_content.count("\n")
137
- stderr_lines = stderr_content.count("\n")
138
-
139
- def head_tail(text, n=10):
140
- lines = text.splitlines()
141
- if len(lines) <= 2 * n:
142
- return "\n".join(lines)
143
- return "\n".join(
144
- lines[:n]
145
- + ["... ({} lines omitted) ...".format(len(lines) - 2 * n)]
146
- + lines[-n:]
147
- )
148
-
149
- if stdout_lines <= max_lines and stderr_lines <= max_lines:
150
- result = f"Return code: {return_code}\n--- python_command_run: STDOUT ---\n{stdout_content}"
151
- if stderr_content.strip():
152
- result += f"\n--- python_command_run: STDERR ---\n{stderr_content}"
153
- return result
154
- else:
155
- result = f"stdout_file: {stdout_file_name} (lines: {stdout_lines})\n"
156
- if stderr_lines > 0 and stderr_content.strip():
157
- result += f"stderr_file: {stderr_file_name} (lines: {stderr_lines})\n"
158
- result += f"returncode: {return_code}\n"
159
- result += (
160
- "--- python_command_run: STDOUT (head/tail) ---\n"
161
- + head_tail(stdout_content)
162
- + "\n"
163
- )
164
- if stderr_content.strip():
165
- result += (
166
- "--- python_command_run: STDERR (head/tail) ---\n"
167
- + head_tail(stderr_content)
168
- + "\n"
169
- )
170
- result += "Use the view_file tool to inspect the contents of these files when needed."
171
- return result
@@ -1,172 +0,0 @@
1
- import subprocess
2
- import os
3
- import sys
4
- import tempfile
5
- import threading
6
- from janito.tools.tool_base import ToolBase, ToolPermissions
7
- from janito.report_events import ReportAction
8
- from janito.tools.adapters.local.adapter import register_local_tool
9
- from janito.i18n import tr
10
-
11
-
12
- @register_local_tool
13
- class PythonFileRunTool(ToolBase):
14
- """
15
- Tool to execute a specified Python script file.
16
-
17
- Args:
18
- path (str): Path to the Python script file to execute.
19
- timeout (int): Timeout in seconds for the command. Defaults to 60.
20
- silent (bool): If True, suppresses progress and status messages. Defaults to False.
21
-
22
- Returns:
23
- str: Output and status message, or file paths/line counts if output is large.
24
- """
25
-
26
- permissions = ToolPermissions(execute=True)
27
- tool_name = "python_file_run"
28
-
29
- def run(self, path: str, timeout: int = 60, silent: bool = False) -> str:
30
- from janito.tools.path_utils import expand_path
31
-
32
- path = expand_path(path)
33
- if not silent:
34
- self.report_action(
35
- tr("🚀 Running: python {path}", path=path),
36
- ReportAction.EXECUTE,
37
- )
38
- self.report_stdout("\n")
39
- else:
40
- self.report_action(tr("⚡ Executing..."), ReportAction.EXECUTE)
41
- try:
42
- with (
43
- tempfile.NamedTemporaryFile(
44
- mode="w+",
45
- prefix="python_file_stdout_",
46
- delete=False,
47
- encoding="utf-8",
48
- ) as stdout_file,
49
- tempfile.NamedTemporaryFile(
50
- mode="w+",
51
- prefix="python_file_stderr_",
52
- delete=False,
53
- encoding="utf-8",
54
- ) as stderr_file,
55
- ):
56
- process = subprocess.Popen(
57
- [sys.executable, path],
58
- stdout=subprocess.PIPE,
59
- stderr=subprocess.PIPE,
60
- text=True,
61
- bufsize=1,
62
- universal_newlines=True,
63
- encoding="utf-8",
64
- env={**os.environ, "PYTHONIOENCODING": "utf-8"},
65
- )
66
- stdout_lines, stderr_lines = self._stream_process_output(
67
- process, stdout_file, stderr_file
68
- )
69
- return_code = self._wait_for_process(process, timeout)
70
- if return_code is None:
71
- return tr(
72
- "Code timed out after {timeout} seconds.", timeout=timeout
73
- )
74
- stdout_file.flush()
75
- stderr_file.flush()
76
- if not silent:
77
- self.report_success(
78
- tr("✅ Return code {return_code}", return_code=return_code),
79
- ReportAction.EXECUTE,
80
- )
81
- return self._format_result(
82
- stdout_file.name, stderr_file.name, return_code
83
- )
84
- except Exception as e:
85
- self.report_error(tr("❌ Error: {error}", error=e), ReportAction.EXECUTE)
86
- return tr("Error running file: {error}", error=e)
87
-
88
- def _stream_process_output(self, process, stdout_file, stderr_file):
89
- stdout_lines = 0
90
- stderr_lines = 0
91
-
92
- def stream_output(stream, file_obj, report_func, count_func):
93
- nonlocal stdout_lines, stderr_lines
94
- for line in stream:
95
- file_obj.write(line)
96
- file_obj.flush()
97
- # Always supply a default action for stdout/stderr reporting
98
- from janito.report_events import ReportAction
99
-
100
- report_func(line.rstrip("\r\n"), ReportAction.EXECUTE)
101
- if count_func == "stdout":
102
- stdout_lines += 1
103
- else:
104
- stderr_lines += 1
105
-
106
- stdout_thread = threading.Thread(
107
- target=stream_output,
108
- args=(process.stdout, stdout_file, self.report_stdout, "stdout"),
109
- )
110
- stderr_thread = threading.Thread(
111
- target=stream_output,
112
- args=(process.stderr, stderr_file, self.report_stderr, "stderr"),
113
- )
114
- stdout_thread.start()
115
- stderr_thread.start()
116
- stdout_thread.join()
117
- stderr_thread.join()
118
- return stdout_lines, stderr_lines
119
-
120
- def _wait_for_process(self, process, timeout):
121
- try:
122
- return process.wait(timeout=timeout)
123
- except subprocess.TimeoutExpired:
124
- process.kill()
125
- self.report_error(
126
- tr("❌ Timed out after {timeout} seconds.", timeout=timeout),
127
- ReportAction.EXECUTE,
128
- )
129
- return None
130
-
131
- def _format_result(self, stdout_file_name, stderr_file_name, return_code):
132
- with open(stdout_file_name, "r", encoding="utf-8", errors="replace") as out_f:
133
- stdout_content = out_f.read()
134
- with open(stderr_file_name, "r", encoding="utf-8", errors="replace") as err_f:
135
- stderr_content = err_f.read()
136
- max_lines = 100
137
- stdout_lines = stdout_content.count("\n")
138
- stderr_lines = stderr_content.count("\n")
139
-
140
- def head_tail(text, n=10):
141
- lines = text.splitlines()
142
- if len(lines) <= 2 * n:
143
- return "\n".join(lines)
144
- return "\n".join(
145
- lines[:n]
146
- + ["... ({} lines omitted) ...".format(len(lines) - 2 * n)]
147
- + lines[-n:]
148
- )
149
-
150
- if stdout_lines <= max_lines and stderr_lines <= max_lines:
151
- result = f"Return code: {return_code}\n--- python_file_run: STDOUT ---\n{stdout_content}"
152
- if stderr_content.strip():
153
- result += f"\n--- python_file_run: STDERR ---\n{stderr_content}"
154
- return result
155
- else:
156
- result = f"stdout_file: {stdout_file_name} (lines: {stdout_lines})\n"
157
- if stderr_lines > 0 and stderr_content.strip():
158
- result += f"stderr_file: {stderr_file_name} (lines: {stderr_lines})\n"
159
- result += f"returncode: {return_code}\n"
160
- result += (
161
- "--- python_file_run: STDOUT (head/tail) ---\n"
162
- + head_tail(stdout_content)
163
- + "\n"
164
- )
165
- if stderr_content.strip():
166
- result += (
167
- "--- python_file_run: STDERR (head/tail) ---\n"
168
- + head_tail(stderr_content)
169
- + "\n"
170
- )
171
- result += "Use the view_file tool to inspect the contents of these files when needed."
172
- return result
File without changes