janito 1.12.1__py3-none-any.whl → 1.13.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/__init__.py +1 -1
- janito/agent/conversation.py +1 -1
- janito/agent/tools/find_files.py +13 -3
- janito/agent/tools/get_lines.py +7 -7
- janito/agent/tools/python_command_runner.py +80 -28
- janito/agent/tools/python_file_runner.py +79 -27
- janito/agent/tools/python_stdin_runner.py +83 -28
- janito/agent/tools/replace_text_in_file.py +13 -8
- janito/agent/tools/run_bash_command.py +172 -82
- janito/agent/tools/run_powershell_command.py +149 -111
- janito/agent/tools/search_text/core.py +6 -1
- janito/agent/tools/search_text/pattern_utils.py +1 -1
- janito/agent/tools/validate_file_syntax/core.py +8 -8
- janito/agent/tools/validate_file_syntax/markdown_validator.py +2 -2
- janito/cli/arg_parser.py +5 -0
- janito/cli/cli_main.py +28 -17
- janito/shell/commands/utility.py +3 -0
- janito/shell/main.py +56 -31
- {janito-1.12.1.dist-info → janito-1.13.0.dist-info}/METADATA +1 -1
- {janito-1.12.1.dist-info → janito-1.13.0.dist-info}/RECORD +24 -25
- {janito-1.12.1.dist-info → janito-1.13.0.dist-info}/WHEEL +1 -1
- janito/shell/commands.py +0 -40
- {janito-1.12.1.dist-info → janito-1.13.0.dist-info}/entry_points.txt +0 -0
- {janito-1.12.1.dist-info → janito-1.13.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.12.1.dist-info → janito-1.13.0.dist-info}/top_level.txt +0 -0
janito/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "1.
|
1
|
+
__version__ = "1.13.0"
|
janito/agent/conversation.py
CHANGED
@@ -93,7 +93,7 @@ class ConversationHandler:
|
|
93
93
|
|
94
94
|
def _handle_no_tool_support(self, messages, max_tokens, spinner):
|
95
95
|
print(
|
96
|
-
"
|
96
|
+
"⚠️ Endpoint does not support tool use. Proceeding in vanilla mode (tools disabled)."
|
97
97
|
)
|
98
98
|
runtime_config.set("vanilla_mode", True)
|
99
99
|
resolved_max_tokens = 8000
|
janito/agent/tools/find_files.py
CHANGED
@@ -45,7 +45,9 @@ class FindFilesTool(ToolBase):
|
|
45
45
|
dir_output.add(os.path.join(root, d))
|
46
46
|
return dir_output
|
47
47
|
|
48
|
-
def run(
|
48
|
+
def run(
|
49
|
+
self, paths: str, pattern: str, max_depth: int = None, max_results: int = 0
|
50
|
+
) -> str:
|
49
51
|
if not pattern:
|
50
52
|
self.report_warning(tr("ℹ️ Empty file pattern provided."))
|
51
53
|
return tr("Warning: Empty file pattern provided. Operation skipped.")
|
@@ -61,13 +63,15 @@ class FindFilesTool(ToolBase):
|
|
61
63
|
self.report_info(
|
62
64
|
ActionType.READ,
|
63
65
|
tr(
|
64
|
-
"🔍 Search
|
66
|
+
"🔍 Search files '{pattern}' in '{disp_path}'{depth_msg} ...",
|
65
67
|
pattern=pattern,
|
66
68
|
disp_path=disp_path,
|
67
69
|
depth_msg=depth_msg,
|
68
70
|
),
|
69
71
|
)
|
70
72
|
dir_output = set()
|
73
|
+
count_scanned = 0
|
74
|
+
limit_reached = False
|
71
75
|
for root, dirs, files in walk_dir_with_gitignore(
|
72
76
|
directory, max_depth=max_depth
|
73
77
|
):
|
@@ -79,11 +83,17 @@ class FindFilesTool(ToolBase):
|
|
79
83
|
dir_output.update(
|
80
84
|
self._match_dirs_without_slash(root, dirs, pat)
|
81
85
|
)
|
86
|
+
if max_results > 0 and len(dir_output) >= max_results:
|
87
|
+
limit_reached = True
|
88
|
+
# Truncate to max_results
|
89
|
+
dir_output = set(list(dir_output)[:max_results])
|
90
|
+
break
|
82
91
|
self.report_success(
|
83
92
|
tr(
|
84
|
-
" ✅ {count} {file_word}",
|
93
|
+
" ✅ {count} {file_word}{max_flag}",
|
85
94
|
count=len(dir_output),
|
86
95
|
file_word=pluralize("file", len(dir_output)),
|
96
|
+
max_flag=" (max)" if limit_reached else "",
|
87
97
|
)
|
88
98
|
)
|
89
99
|
if directory.strip() == ".":
|
janito/agent/tools/get_lines.py
CHANGED
@@ -22,7 +22,7 @@ class GetLinesTool(ToolBase):
|
|
22
22
|
- "---\nFile: /path/to/file.py | Lines: 1-10 (of 100)\n---\n<lines...>"
|
23
23
|
- "---\nFile: /path/to/file.py | All lines (total: 100 (all))\n---\n<all lines...>"
|
24
24
|
- "Error reading file: <error message>"
|
25
|
-
- "
|
25
|
+
- "❗ not found"
|
26
26
|
"""
|
27
27
|
|
28
28
|
def run(self, file_path: str, from_line: int = None, to_line: int = None) -> str:
|
@@ -83,7 +83,7 @@ class GetLinesTool(ToolBase):
|
|
83
83
|
if at_end:
|
84
84
|
self.report_success(
|
85
85
|
tr(
|
86
|
-
"
|
86
|
+
" ✅ {selected_len} {line_word} (end)",
|
87
87
|
selected_len=selected_len,
|
88
88
|
line_word=pluralize("line", selected_len),
|
89
89
|
)
|
@@ -91,7 +91,7 @@ class GetLinesTool(ToolBase):
|
|
91
91
|
elif to_line < total_lines:
|
92
92
|
self.report_success(
|
93
93
|
tr(
|
94
|
-
"
|
94
|
+
" ✅ {selected_len} {line_word} ({remaining} to end)",
|
95
95
|
selected_len=selected_len,
|
96
96
|
line_word=pluralize("line", selected_len),
|
97
97
|
remaining=total_lines - to_line,
|
@@ -100,7 +100,7 @@ class GetLinesTool(ToolBase):
|
|
100
100
|
else:
|
101
101
|
self.report_success(
|
102
102
|
tr(
|
103
|
-
"
|
103
|
+
" ✅ {selected_len} {line_word} (all)",
|
104
104
|
selected_len=selected_len,
|
105
105
|
line_word=pluralize("line", selected_len),
|
106
106
|
)
|
@@ -143,7 +143,7 @@ class GetLinesTool(ToolBase):
|
|
143
143
|
def _handle_read_error(self, e):
|
144
144
|
"""Handle file read errors and report appropriately."""
|
145
145
|
if isinstance(e, FileNotFoundError):
|
146
|
-
self.report_error(tr("
|
147
|
-
return tr("
|
148
|
-
self.report_error(tr("
|
146
|
+
self.report_error(tr("❗ not found"))
|
147
|
+
return tr("❗ not found")
|
148
|
+
self.report_error(tr(" ❌ Error: {error}", error=e))
|
149
149
|
return tr("Error reading file: {error}", error=e)
|
@@ -7,6 +7,7 @@ from janito.agent.tool_base import ToolBase
|
|
7
7
|
from janito.agent.tools_utils.action_type import ActionType
|
8
8
|
from janito.agent.tool_registry import register_tool
|
9
9
|
from janito.i18n import tr
|
10
|
+
from janito.agent.runtime_config import runtime_config
|
10
11
|
|
11
12
|
|
12
13
|
@register_tool(name="python_command_runner")
|
@@ -22,26 +23,13 @@ class PythonCommandRunnerTool(ToolBase):
|
|
22
23
|
|
23
24
|
def run(self, code: str, timeout: int = 60) -> str:
|
24
25
|
if not code.strip():
|
25
|
-
self.report_warning(tr("
|
26
|
+
self.report_warning(tr("539 Empty code provided."))
|
26
27
|
return tr("Warning: Empty code provided. Operation skipped.")
|
27
28
|
self.report_info(
|
28
|
-
ActionType.EXECUTE, tr("
|
29
|
+
ActionType.EXECUTE, tr("40d Running: python -c ...\n{code}\n", code=code)
|
29
30
|
)
|
30
31
|
try:
|
31
|
-
|
32
|
-
tempfile.NamedTemporaryFile(
|
33
|
-
mode="w+",
|
34
|
-
prefix="python_cmd_stdout_",
|
35
|
-
delete=False,
|
36
|
-
encoding="utf-8",
|
37
|
-
) as stdout_file,
|
38
|
-
tempfile.NamedTemporaryFile(
|
39
|
-
mode="w+",
|
40
|
-
prefix="python_cmd_stderr_",
|
41
|
-
delete=False,
|
42
|
-
encoding="utf-8",
|
43
|
-
) as stderr_file,
|
44
|
-
):
|
32
|
+
if runtime_config.get("all_out"):
|
45
33
|
process = subprocess.Popen(
|
46
34
|
[sys.executable, "-c", code],
|
47
35
|
stdout=subprocess.PIPE,
|
@@ -52,24 +40,88 @@ class PythonCommandRunnerTool(ToolBase):
|
|
52
40
|
encoding="utf-8",
|
53
41
|
env={**os.environ, "PYTHONIOENCODING": "utf-8"},
|
54
42
|
)
|
55
|
-
|
56
|
-
|
43
|
+
stdout_accum = []
|
44
|
+
stderr_accum = []
|
45
|
+
|
46
|
+
def read_stream(stream, report_func, accum):
|
47
|
+
for line in stream:
|
48
|
+
accum.append(line)
|
49
|
+
report_func(line)
|
50
|
+
|
51
|
+
stdout_thread = threading.Thread(
|
52
|
+
target=read_stream,
|
53
|
+
args=(process.stdout, self.report_stdout, stdout_accum),
|
54
|
+
)
|
55
|
+
stderr_thread = threading.Thread(
|
56
|
+
target=read_stream,
|
57
|
+
args=(process.stderr, self.report_stderr, stderr_accum),
|
57
58
|
)
|
58
|
-
|
59
|
-
|
59
|
+
stdout_thread.start()
|
60
|
+
stderr_thread.start()
|
61
|
+
try:
|
62
|
+
return_code = process.wait(timeout=timeout)
|
63
|
+
except subprocess.TimeoutExpired:
|
64
|
+
process.kill()
|
65
|
+
self.report_error(
|
66
|
+
tr("6d1 Timed out after {timeout} seconds.", timeout=timeout)
|
67
|
+
)
|
60
68
|
return tr(
|
61
69
|
"Code timed out after {timeout} seconds.", timeout=timeout
|
62
70
|
)
|
63
|
-
|
64
|
-
|
71
|
+
stdout_thread.join()
|
72
|
+
stderr_thread.join()
|
65
73
|
self.report_success(
|
66
|
-
tr("
|
67
|
-
)
|
68
|
-
return self._format_result(
|
69
|
-
stdout_file.name, stderr_file.name, return_code
|
74
|
+
tr("197 Return code {return_code}", return_code=return_code)
|
70
75
|
)
|
76
|
+
stdout = "".join(stdout_accum)
|
77
|
+
stderr = "".join(stderr_accum)
|
78
|
+
result = f"Return code: {return_code}\n--- STDOUT ---\n{stdout}"
|
79
|
+
if stderr and stderr.strip():
|
80
|
+
result += f"\n--- STDERR ---\n{stderr}"
|
81
|
+
return result
|
82
|
+
else:
|
83
|
+
with (
|
84
|
+
tempfile.NamedTemporaryFile(
|
85
|
+
mode="w+",
|
86
|
+
prefix="python_cmd_stdout_",
|
87
|
+
delete=False,
|
88
|
+
encoding="utf-8",
|
89
|
+
) as stdout_file,
|
90
|
+
tempfile.NamedTemporaryFile(
|
91
|
+
mode="w+",
|
92
|
+
prefix="python_cmd_stderr_",
|
93
|
+
delete=False,
|
94
|
+
encoding="utf-8",
|
95
|
+
) as stderr_file,
|
96
|
+
):
|
97
|
+
process = subprocess.Popen(
|
98
|
+
[sys.executable, "-c", code],
|
99
|
+
stdout=subprocess.PIPE,
|
100
|
+
stderr=subprocess.PIPE,
|
101
|
+
text=True,
|
102
|
+
bufsize=1,
|
103
|
+
universal_newlines=True,
|
104
|
+
encoding="utf-8",
|
105
|
+
env={**os.environ, "PYTHONIOENCODING": "utf-8"},
|
106
|
+
)
|
107
|
+
stdout_lines, stderr_lines = self._stream_process_output(
|
108
|
+
process, stdout_file, stderr_file
|
109
|
+
)
|
110
|
+
return_code = self._wait_for_process(process, timeout)
|
111
|
+
if return_code is None:
|
112
|
+
return tr(
|
113
|
+
"Code timed out after {timeout} seconds.", timeout=timeout
|
114
|
+
)
|
115
|
+
stdout_file.flush()
|
116
|
+
stderr_file.flush()
|
117
|
+
self.report_success(
|
118
|
+
tr("197 Return code {return_code}", return_code=return_code)
|
119
|
+
)
|
120
|
+
return self._format_result(
|
121
|
+
stdout_file.name, stderr_file.name, return_code
|
122
|
+
)
|
71
123
|
except Exception as e:
|
72
|
-
self.report_error(tr("
|
124
|
+
self.report_error(tr("534 Error: {error}", error=e))
|
73
125
|
return tr("Error running code: {error}", error=e)
|
74
126
|
|
75
127
|
def _stream_process_output(self, process, stdout_file, stderr_file):
|
@@ -107,7 +159,7 @@ class PythonCommandRunnerTool(ToolBase):
|
|
107
159
|
except subprocess.TimeoutExpired:
|
108
160
|
process.kill()
|
109
161
|
self.report_error(
|
110
|
-
tr("
|
162
|
+
tr("6d1 Timed out after {timeout} seconds.", timeout=timeout)
|
111
163
|
)
|
112
164
|
return None
|
113
165
|
|
@@ -7,6 +7,7 @@ from janito.agent.tool_base import ToolBase
|
|
7
7
|
from janito.agent.tools_utils.action_type import ActionType
|
8
8
|
from janito.agent.tool_registry import register_tool
|
9
9
|
from janito.i18n import tr
|
10
|
+
from janito.agent.runtime_config import runtime_config
|
10
11
|
|
11
12
|
|
12
13
|
@register_tool(name="python_file_runner")
|
@@ -23,23 +24,10 @@ class PythonFileRunnerTool(ToolBase):
|
|
23
24
|
def run(self, file_path: str, timeout: int = 60) -> str:
|
24
25
|
self.report_info(
|
25
26
|
ActionType.EXECUTE,
|
26
|
-
tr("
|
27
|
+
tr("680 Running: python {file_path}", file_path=file_path),
|
27
28
|
)
|
28
29
|
try:
|
29
|
-
|
30
|
-
tempfile.NamedTemporaryFile(
|
31
|
-
mode="w+",
|
32
|
-
prefix="python_file_stdout_",
|
33
|
-
delete=False,
|
34
|
-
encoding="utf-8",
|
35
|
-
) as stdout_file,
|
36
|
-
tempfile.NamedTemporaryFile(
|
37
|
-
mode="w+",
|
38
|
-
prefix="python_file_stderr_",
|
39
|
-
delete=False,
|
40
|
-
encoding="utf-8",
|
41
|
-
) as stderr_file,
|
42
|
-
):
|
30
|
+
if runtime_config.get("all_out"):
|
43
31
|
process = subprocess.Popen(
|
44
32
|
[sys.executable, file_path],
|
45
33
|
stdout=subprocess.PIPE,
|
@@ -50,24 +38,88 @@ class PythonFileRunnerTool(ToolBase):
|
|
50
38
|
encoding="utf-8",
|
51
39
|
env={**os.environ, "PYTHONIOENCODING": "utf-8"},
|
52
40
|
)
|
53
|
-
|
54
|
-
|
41
|
+
stdout_accum = []
|
42
|
+
stderr_accum = []
|
43
|
+
|
44
|
+
def read_stream(stream, report_func, accum):
|
45
|
+
for line in stream:
|
46
|
+
accum.append(line)
|
47
|
+
report_func(line)
|
48
|
+
|
49
|
+
stdout_thread = threading.Thread(
|
50
|
+
target=read_stream,
|
51
|
+
args=(process.stdout, self.report_stdout, stdout_accum),
|
52
|
+
)
|
53
|
+
stderr_thread = threading.Thread(
|
54
|
+
target=read_stream,
|
55
|
+
args=(process.stderr, self.report_stderr, stderr_accum),
|
55
56
|
)
|
56
|
-
|
57
|
-
|
57
|
+
stdout_thread.start()
|
58
|
+
stderr_thread.start()
|
59
|
+
try:
|
60
|
+
return_code = process.wait(timeout=timeout)
|
61
|
+
except subprocess.TimeoutExpired:
|
62
|
+
process.kill()
|
63
|
+
self.report_error(
|
64
|
+
tr("6d1 Timed out after {timeout} seconds.", timeout=timeout)
|
65
|
+
)
|
58
66
|
return tr(
|
59
67
|
"Code timed out after {timeout} seconds.", timeout=timeout
|
60
68
|
)
|
61
|
-
|
62
|
-
|
69
|
+
stdout_thread.join()
|
70
|
+
stderr_thread.join()
|
63
71
|
self.report_success(
|
64
|
-
tr("
|
65
|
-
)
|
66
|
-
return self._format_result(
|
67
|
-
stdout_file.name, stderr_file.name, return_code
|
72
|
+
tr("197 Return code {return_code}", return_code=return_code)
|
68
73
|
)
|
74
|
+
stdout = "".join(stdout_accum)
|
75
|
+
stderr = "".join(stderr_accum)
|
76
|
+
result = f"Return code: {return_code}\n--- STDOUT ---\n{stdout}"
|
77
|
+
if stderr and stderr.strip():
|
78
|
+
result += f"\n--- STDERR ---\n{stderr}"
|
79
|
+
return result
|
80
|
+
else:
|
81
|
+
with (
|
82
|
+
tempfile.NamedTemporaryFile(
|
83
|
+
mode="w+",
|
84
|
+
prefix="python_file_stdout_",
|
85
|
+
delete=False,
|
86
|
+
encoding="utf-8",
|
87
|
+
) as stdout_file,
|
88
|
+
tempfile.NamedTemporaryFile(
|
89
|
+
mode="w+",
|
90
|
+
prefix="python_file_stderr_",
|
91
|
+
delete=False,
|
92
|
+
encoding="utf-8",
|
93
|
+
) as stderr_file,
|
94
|
+
):
|
95
|
+
process = subprocess.Popen(
|
96
|
+
[sys.executable, file_path],
|
97
|
+
stdout=subprocess.PIPE,
|
98
|
+
stderr=subprocess.PIPE,
|
99
|
+
text=True,
|
100
|
+
bufsize=1,
|
101
|
+
universal_newlines=True,
|
102
|
+
encoding="utf-8",
|
103
|
+
env={**os.environ, "PYTHONIOENCODING": "utf-8"},
|
104
|
+
)
|
105
|
+
stdout_lines, stderr_lines = self._stream_process_output(
|
106
|
+
process, stdout_file, stderr_file
|
107
|
+
)
|
108
|
+
return_code = self._wait_for_process(process, timeout)
|
109
|
+
if return_code is None:
|
110
|
+
return tr(
|
111
|
+
"Code timed out after {timeout} seconds.", timeout=timeout
|
112
|
+
)
|
113
|
+
stdout_file.flush()
|
114
|
+
stderr_file.flush()
|
115
|
+
self.report_success(
|
116
|
+
tr("197 Return code {return_code}", return_code=return_code)
|
117
|
+
)
|
118
|
+
return self._format_result(
|
119
|
+
stdout_file.name, stderr_file.name, return_code
|
120
|
+
)
|
69
121
|
except Exception as e:
|
70
|
-
self.report_error(tr("
|
122
|
+
self.report_error(tr("534 Error: {error}", error=e))
|
71
123
|
return tr("Error running file: {error}", error=e)
|
72
124
|
|
73
125
|
def _stream_process_output(self, process, stdout_file, stderr_file):
|
@@ -105,7 +157,7 @@ class PythonFileRunnerTool(ToolBase):
|
|
105
157
|
except subprocess.TimeoutExpired:
|
106
158
|
process.kill()
|
107
159
|
self.report_error(
|
108
|
-
tr("
|
160
|
+
tr("6d1 Timed out after {timeout} seconds.", timeout=timeout)
|
109
161
|
)
|
110
162
|
return None
|
111
163
|
|
@@ -7,6 +7,7 @@ from janito.agent.tool_base import ToolBase
|
|
7
7
|
from janito.agent.tools_utils.action_type import ActionType
|
8
8
|
from janito.agent.tool_registry import register_tool
|
9
9
|
from janito.i18n import tr
|
10
|
+
from janito.agent.runtime_config import runtime_config
|
10
11
|
|
11
12
|
|
12
13
|
@register_tool(name="python_stdin_runner")
|
@@ -22,27 +23,14 @@ class PythonStdinRunnerTool(ToolBase):
|
|
22
23
|
|
23
24
|
def run(self, code: str, timeout: int = 60) -> str:
|
24
25
|
if not code.strip():
|
25
|
-
self.report_warning(tr("
|
26
|
+
self.report_warning(tr("ℹ️ Empty code provided."))
|
26
27
|
return tr("Warning: Empty code provided. Operation skipped.")
|
27
28
|
self.report_info(
|
28
29
|
ActionType.EXECUTE,
|
29
|
-
tr("
|
30
|
+
tr("5e1 Running: python (stdin mode) ...\n{code}\n", code=code),
|
30
31
|
)
|
31
32
|
try:
|
32
|
-
|
33
|
-
tempfile.NamedTemporaryFile(
|
34
|
-
mode="w+",
|
35
|
-
prefix="python_stdin_stdout_",
|
36
|
-
delete=False,
|
37
|
-
encoding="utf-8",
|
38
|
-
) as stdout_file,
|
39
|
-
tempfile.NamedTemporaryFile(
|
40
|
-
mode="w+",
|
41
|
-
prefix="python_stdin_stderr_",
|
42
|
-
delete=False,
|
43
|
-
encoding="utf-8",
|
44
|
-
) as stderr_file,
|
45
|
-
):
|
33
|
+
if runtime_config.get("all_out"):
|
46
34
|
process = subprocess.Popen(
|
47
35
|
[sys.executable],
|
48
36
|
stdin=subprocess.PIPE,
|
@@ -54,24 +42,91 @@ class PythonStdinRunnerTool(ToolBase):
|
|
54
42
|
encoding="utf-8",
|
55
43
|
env={**os.environ, "PYTHONIOENCODING": "utf-8"},
|
56
44
|
)
|
57
|
-
|
58
|
-
|
45
|
+
stdout_accum = []
|
46
|
+
stderr_accum = []
|
47
|
+
|
48
|
+
def read_stream(stream, report_func, accum):
|
49
|
+
for line in stream:
|
50
|
+
accum.append(line)
|
51
|
+
report_func(line)
|
52
|
+
|
53
|
+
stdout_thread = threading.Thread(
|
54
|
+
target=read_stream,
|
55
|
+
args=(process.stdout, self.report_stdout, stdout_accum),
|
56
|
+
)
|
57
|
+
stderr_thread = threading.Thread(
|
58
|
+
target=read_stream,
|
59
|
+
args=(process.stderr, self.report_stderr, stderr_accum),
|
59
60
|
)
|
60
|
-
|
61
|
-
|
61
|
+
stdout_thread.start()
|
62
|
+
stderr_thread.start()
|
63
|
+
process.stdin.write(code)
|
64
|
+
process.stdin.close()
|
65
|
+
try:
|
66
|
+
return_code = process.wait(timeout=timeout)
|
67
|
+
except subprocess.TimeoutExpired:
|
68
|
+
process.kill()
|
69
|
+
self.report_error(
|
70
|
+
tr("6d1 Timed out after {timeout} seconds.", timeout=timeout)
|
71
|
+
)
|
62
72
|
return tr(
|
63
73
|
"Code timed out after {timeout} seconds.", timeout=timeout
|
64
74
|
)
|
65
|
-
|
66
|
-
|
75
|
+
stdout_thread.join()
|
76
|
+
stderr_thread.join()
|
67
77
|
self.report_success(
|
68
|
-
tr("
|
69
|
-
)
|
70
|
-
return self._format_result(
|
71
|
-
stdout_file.name, stderr_file.name, return_code
|
78
|
+
tr("197 Return code {return_code}", return_code=return_code)
|
72
79
|
)
|
80
|
+
stdout = "".join(stdout_accum)
|
81
|
+
stderr = "".join(stderr_accum)
|
82
|
+
result = f"Return code: {return_code}\n--- STDOUT ---\n{stdout}"
|
83
|
+
if stderr and stderr.strip():
|
84
|
+
result += f"\n--- STDERR ---\n{stderr}"
|
85
|
+
return result
|
86
|
+
else:
|
87
|
+
with (
|
88
|
+
tempfile.NamedTemporaryFile(
|
89
|
+
mode="w+",
|
90
|
+
prefix="python_stdin_stdout_",
|
91
|
+
delete=False,
|
92
|
+
encoding="utf-8",
|
93
|
+
) as stdout_file,
|
94
|
+
tempfile.NamedTemporaryFile(
|
95
|
+
mode="w+",
|
96
|
+
prefix="python_stdin_stderr_",
|
97
|
+
delete=False,
|
98
|
+
encoding="utf-8",
|
99
|
+
) as stderr_file,
|
100
|
+
):
|
101
|
+
process = subprocess.Popen(
|
102
|
+
[sys.executable],
|
103
|
+
stdin=subprocess.PIPE,
|
104
|
+
stdout=subprocess.PIPE,
|
105
|
+
stderr=subprocess.PIPE,
|
106
|
+
text=True,
|
107
|
+
bufsize=1,
|
108
|
+
universal_newlines=True,
|
109
|
+
encoding="utf-8",
|
110
|
+
env={**os.environ, "PYTHONIOENCODING": "utf-8"},
|
111
|
+
)
|
112
|
+
stdout_lines, stderr_lines = self._stream_process_output(
|
113
|
+
process, stdout_file, stderr_file, code
|
114
|
+
)
|
115
|
+
return_code = self._wait_for_process(process, timeout)
|
116
|
+
if return_code is None:
|
117
|
+
return tr(
|
118
|
+
"Code timed out after {timeout} seconds.", timeout=timeout
|
119
|
+
)
|
120
|
+
stdout_file.flush()
|
121
|
+
stderr_file.flush()
|
122
|
+
self.report_success(
|
123
|
+
tr("197 Return code {return_code}", return_code=return_code)
|
124
|
+
)
|
125
|
+
return self._format_result(
|
126
|
+
stdout_file.name, stderr_file.name, return_code
|
127
|
+
)
|
73
128
|
except Exception as e:
|
74
|
-
self.report_error(tr("
|
129
|
+
self.report_error(tr("534 Error: {error}", error=e))
|
75
130
|
return tr("Error running code via stdin: {error}", error=e)
|
76
131
|
|
77
132
|
def _stream_process_output(self, process, stdout_file, stderr_file, code):
|
@@ -111,7 +166,7 @@ class PythonStdinRunnerTool(ToolBase):
|
|
111
166
|
except subprocess.TimeoutExpired:
|
112
167
|
process.kill()
|
113
168
|
self.report_error(
|
114
|
-
tr("
|
169
|
+
tr("6d1 Timed out after {timeout} seconds.", timeout=timeout)
|
115
170
|
)
|
116
171
|
return None
|
117
172
|
|
@@ -5,6 +5,8 @@ from janito.i18n import tr
|
|
5
5
|
import shutil
|
6
6
|
import re
|
7
7
|
|
8
|
+
from janito.agent.tools.validate_file_syntax.core import validate_file_syntax
|
9
|
+
|
8
10
|
|
9
11
|
@register_tool(name="replace_text_in_file")
|
10
12
|
class ReplaceTextInFileTool(ToolBase):
|
@@ -82,11 +84,16 @@ class ReplaceTextInFileTool(ToolBase):
|
|
82
84
|
line_delta_str,
|
83
85
|
replace_all,
|
84
86
|
)
|
85
|
-
|
87
|
+
final_msg = self._format_final_msg(
|
86
88
|
file_path, warning, backup_path, match_info, details
|
87
89
|
)
|
90
|
+
# Perform syntax validation and append result if file was changed
|
91
|
+
if file_changed:
|
92
|
+
validation_result = validate_file_syntax(file_path)
|
93
|
+
final_msg += f"\n{validation_result}"
|
94
|
+
return final_msg
|
88
95
|
except Exception as e:
|
89
|
-
self.report_error(tr("
|
96
|
+
self.report_error(tr(" ❌ Error"))
|
90
97
|
return tr("Error replacing text: {error}", error=e)
|
91
98
|
|
92
99
|
def _read_file_content(self, file_path):
|
@@ -140,12 +147,12 @@ class ReplaceTextInFileTool(ToolBase):
|
|
140
147
|
if replaced_count == 0:
|
141
148
|
warning = tr(" [Warning: Search text not found in file]")
|
142
149
|
if not file_changed:
|
143
|
-
self.report_warning(tr("
|
150
|
+
self.report_warning(tr(" ℹ️ No changes made. [not found]"))
|
144
151
|
concise_warning = tr(
|
145
152
|
"No changes made. The search text was not found. Expand your search context with surrounding lines if needed."
|
146
153
|
)
|
147
154
|
if occurrences > 1 and replaced_count == 0:
|
148
|
-
self.report_warning(tr("
|
155
|
+
self.report_warning(tr(" ℹ️ No changes made. [not unique]"))
|
149
156
|
concise_warning = tr(
|
150
157
|
"No changes made. The search text is not unique. Expand your search context with surrounding lines to ensure uniqueness."
|
151
158
|
)
|
@@ -155,11 +162,9 @@ class ReplaceTextInFileTool(ToolBase):
|
|
155
162
|
"""Report success with line numbers where replacements occurred."""
|
156
163
|
if match_lines:
|
157
164
|
lines_str = ", ".join(str(line_no) for line_no in match_lines)
|
158
|
-
self.report_success(
|
159
|
-
tr(" \u2705 replaced at {lines_str}", lines_str=lines_str)
|
160
|
-
)
|
165
|
+
self.report_success(tr(" ✅ replaced at {lines_str}", lines_str=lines_str))
|
161
166
|
else:
|
162
|
-
self.report_success(tr("
|
167
|
+
self.report_success(tr(" ✅ replaced (lines unknown)"))
|
163
168
|
|
164
169
|
def _get_line_delta_str(self, content, new_content):
|
165
170
|
"""Return a string describing the net line change after replacement."""
|