janito 1.8.1__py3-none-any.whl → 1.10.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/api_exceptions.py +4 -0
- janito/agent/config.py +1 -1
- janito/agent/config_defaults.py +2 -3
- janito/agent/config_utils.py +0 -9
- janito/agent/conversation.py +177 -114
- janito/agent/conversation_api.py +179 -159
- janito/agent/conversation_tool_calls.py +11 -8
- janito/agent/llm_conversation_history.py +70 -0
- janito/agent/openai_client.py +44 -21
- janito/agent/openai_schema_generator.py +164 -128
- janito/agent/platform_discovery.py +134 -77
- janito/agent/profile_manager.py +5 -5
- janito/agent/rich_message_handler.py +80 -31
- janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +9 -8
- janito/agent/test_openai_schema_generator.py +93 -0
- janito/agent/tool_base.py +7 -2
- janito/agent/tool_executor.py +63 -50
- janito/agent/tool_registry.py +5 -2
- janito/agent/tool_use_tracker.py +42 -5
- janito/agent/tools/__init__.py +13 -12
- janito/agent/tools/create_directory.py +9 -6
- janito/agent/tools/create_file.py +35 -54
- janito/agent/tools/delete_text_in_file.py +97 -0
- janito/agent/tools/fetch_url.py +50 -5
- janito/agent/tools/find_files.py +40 -26
- janito/agent/tools/get_file_outline/__init__.py +1 -0
- janito/agent/tools/{outline_file/__init__.py → get_file_outline/core.py} +14 -18
- janito/agent/tools/get_file_outline/python_outline.py +134 -0
- janito/agent/tools/{search_outline.py → get_file_outline/search_outline.py} +11 -0
- janito/agent/tools/get_lines.py +21 -12
- janito/agent/tools/move_file.py +13 -12
- janito/agent/tools/present_choices.py +3 -1
- janito/agent/tools/python_command_runner.py +150 -0
- janito/agent/tools/python_file_runner.py +148 -0
- janito/agent/tools/python_stdin_runner.py +154 -0
- janito/agent/tools/remove_directory.py +4 -2
- janito/agent/tools/remove_file.py +15 -13
- janito/agent/tools/replace_file.py +72 -0
- janito/agent/tools/replace_text_in_file.py +7 -5
- janito/agent/tools/run_bash_command.py +29 -72
- janito/agent/tools/run_powershell_command.py +142 -102
- janito/agent/tools/search_text.py +177 -131
- janito/agent/tools/validate_file_syntax/__init__.py +1 -0
- janito/agent/tools/validate_file_syntax/core.py +94 -0
- janito/agent/tools/validate_file_syntax/css_validator.py +35 -0
- janito/agent/tools/validate_file_syntax/html_validator.py +77 -0
- janito/agent/tools/validate_file_syntax/js_validator.py +27 -0
- janito/agent/tools/validate_file_syntax/json_validator.py +6 -0
- janito/agent/tools/validate_file_syntax/markdown_validator.py +66 -0
- janito/agent/tools/validate_file_syntax/ps1_validator.py +32 -0
- janito/agent/tools/validate_file_syntax/python_validator.py +5 -0
- janito/agent/tools/validate_file_syntax/xml_validator.py +11 -0
- janito/agent/tools/validate_file_syntax/yaml_validator.py +6 -0
- janito/agent/tools_utils/__init__.py +1 -0
- janito/agent/tools_utils/action_type.py +7 -0
- janito/agent/tools_utils/dir_walk_utils.py +24 -0
- janito/agent/tools_utils/formatting.py +49 -0
- janito/agent/tools_utils/gitignore_utils.py +69 -0
- janito/agent/tools_utils/test_gitignore_utils.py +46 -0
- janito/agent/tools_utils/utils.py +30 -0
- janito/cli/_livereload_log_utils.py +13 -0
- janito/cli/_print_config.py +63 -61
- janito/cli/arg_parser.py +57 -14
- janito/cli/cli_main.py +270 -0
- janito/cli/livereload_starter.py +60 -0
- janito/cli/main.py +166 -99
- janito/cli/one_shot.py +80 -0
- janito/cli/termweb_starter.py +2 -2
- janito/i18n/__init__.py +1 -1
- janito/livereload/app.py +25 -0
- janito/rich_utils.py +41 -25
- janito/{cli_chat_shell → shell}/commands/__init__.py +19 -14
- janito/{cli_chat_shell → shell}/commands/config.py +4 -4
- janito/shell/commands/conversation_restart.py +74 -0
- janito/shell/commands/edit.py +24 -0
- janito/shell/commands/history_view.py +18 -0
- janito/{cli_chat_shell → shell}/commands/lang.py +3 -0
- janito/shell/commands/livelogs.py +42 -0
- janito/{cli_chat_shell → shell}/commands/prompt.py +16 -6
- janito/shell/commands/session.py +35 -0
- janito/{cli_chat_shell → shell}/commands/session_control.py +3 -5
- janito/{cli_chat_shell → shell}/commands/termweb_log.py +18 -10
- janito/shell/commands/tools.py +26 -0
- janito/shell/commands/track.py +36 -0
- janito/shell/commands/utility.py +28 -0
- janito/{cli_chat_shell → shell}/commands/verbose.py +4 -5
- janito/shell/commands.py +40 -0
- janito/shell/input_history.py +62 -0
- janito/shell/main.py +257 -0
- janito/{cli_chat_shell/shell_command_completer.py → shell/prompt/completer.py} +1 -1
- janito/{cli_chat_shell/chat_ui.py → shell/prompt/session_setup.py} +19 -5
- janito/shell/session/manager.py +101 -0
- janito/{cli_chat_shell/ui.py → shell/ui/interactive.py} +23 -17
- janito/termweb/app.py +3 -3
- janito/termweb/static/editor.css +142 -0
- janito/termweb/static/editor.css.bak +27 -0
- janito/termweb/static/editor.html +15 -213
- janito/termweb/static/editor.html.bak +16 -215
- janito/termweb/static/editor.js +209 -0
- janito/termweb/static/editor.js.bak +227 -0
- janito/termweb/static/index.html +2 -3
- janito/termweb/static/index.html.bak +2 -3
- janito/termweb/static/termweb.css.bak +33 -84
- janito/termweb/static/termweb.js +15 -34
- janito/termweb/static/termweb.js.bak +18 -36
- janito/tests/test_rich_utils.py +44 -0
- janito/web/app.py +0 -75
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/METADATA +62 -42
- janito-1.10.0.dist-info/RECORD +158 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/WHEEL +1 -1
- janito/agent/tools/dir_walk_utils.py +0 -16
- janito/agent/tools/gitignore_utils.py +0 -46
- janito/agent/tools/memory.py +0 -48
- janito/agent/tools/outline_file/formatting.py +0 -20
- janito/agent/tools/outline_file/python_outline.py +0 -71
- janito/agent/tools/present_choices_test.py +0 -18
- janito/agent/tools/rich_live.py +0 -44
- janito/agent/tools/run_python_command.py +0 -163
- janito/agent/tools/tools_utils.py +0 -56
- janito/agent/tools/utils.py +0 -33
- janito/agent/tools/validate_file_syntax.py +0 -163
- janito/cli/runner/cli_main.py +0 -180
- janito/cli_chat_shell/chat_loop.py +0 -163
- janito/cli_chat_shell/chat_state.py +0 -38
- janito/cli_chat_shell/commands/history_start.py +0 -37
- janito/cli_chat_shell/commands/session.py +0 -48
- janito/cli_chat_shell/commands/sum.py +0 -49
- janito/cli_chat_shell/commands/utility.py +0 -32
- janito/cli_chat_shell/session_manager.py +0 -72
- janito-1.8.1.dist-info/RECORD +0 -127
- /janito/agent/tools/{outline_file → get_file_outline}/markdown_outline.py +0 -0
- /janito/cli/{runner/_termweb_log_utils.py → _termweb_log_utils.py} +0 -0
- /janito/cli/{runner/config.py → config_runner.py} +0 -0
- /janito/cli/{runner/formatting.py → formatting_runner.py} +0 -0
- /janito/{cli/runner → shell}/__init__.py +0 -0
- /janito/{cli_chat_shell → shell/prompt}/load_prompt.py +0 -0
- /janito/{cli_chat_shell/config_shell.py → shell/session/config.py} +0 -0
- /janito/{cli_chat_shell/__init__.py → shell/session/history.py} +0 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/entry_points.txt +0 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,16 @@ from janito.agent.message_handler_protocol import MessageHandlerProtocol
|
|
5
5
|
console = Console()
|
6
6
|
|
7
7
|
|
8
|
+
def _print_metadata_if_verbose(msg):
|
9
|
+
if unified_config.get("verbose_messages", False):
|
10
|
+
# Exclude 'message' field from metadata
|
11
|
+
meta = {k: v for k, v in msg.items() if k != "message"}
|
12
|
+
if meta:
|
13
|
+
console.print("[bold][cyan]Message metadata:[/cyan][/bold]")
|
14
|
+
for k, v in meta.items():
|
15
|
+
console.print(f" [green]{k}[/green]: [magenta]{v}[/magenta]")
|
16
|
+
|
17
|
+
|
8
18
|
class RichMessageHandler(MessageHandlerProtocol):
|
9
19
|
"""
|
10
20
|
Unified message handler for all output (tool, agent, system) using Rich for styled output.
|
@@ -18,13 +28,10 @@ class RichMessageHandler(MessageHandlerProtocol):
|
|
18
28
|
Handles a dict with 'type' and 'message'.
|
19
29
|
All messages must be dicts. Raises if not.
|
20
30
|
"""
|
21
|
-
# Check trust config: suppress all output except 'content' if enabled
|
22
31
|
trust = runtime_config.get("trust")
|
23
32
|
if trust is None:
|
24
33
|
trust = unified_config.get("trust", False)
|
25
34
|
|
26
|
-
from rich.markdown import Markdown
|
27
|
-
|
28
35
|
if not isinstance(msg, dict):
|
29
36
|
raise TypeError(
|
30
37
|
f"RichMessageHandler.handle_message expects a dict with 'type' and 'message', got {type(msg)}: {msg!r}"
|
@@ -35,32 +42,74 @@ class RichMessageHandler(MessageHandlerProtocol):
|
|
35
42
|
|
36
43
|
if trust and msg_type != "content":
|
37
44
|
return # Suppress all except content
|
38
|
-
if msg_type == "content":
|
39
|
-
self.console.print(Markdown(message))
|
40
|
-
elif msg_type == "info":
|
41
|
-
self.console.print(message, style="cyan", end="")
|
42
|
-
elif msg_type == "success":
|
43
|
-
self.console.print(message, style="bold green", end="\n")
|
44
|
-
elif msg_type == "error":
|
45
|
-
self.console.print(message, style="bold red", end="\n")
|
46
|
-
elif msg_type == "progress":
|
47
|
-
self._handle_progress(message)
|
48
|
-
elif msg_type == "warning":
|
49
|
-
self.console.print(message, style="bold yellow", end="\n")
|
50
|
-
elif msg_type == "stdout":
|
51
|
-
from rich.text import Text
|
52
|
-
|
53
|
-
self.console.print(
|
54
|
-
Text(message, style="on #003300", no_wrap=True, overflow=None),
|
55
|
-
end="",
|
56
|
-
)
|
57
|
-
elif msg_type == "stderr":
|
58
|
-
from rich.text import Text
|
59
45
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
46
|
+
handler_map = {
|
47
|
+
"content": self._handle_content,
|
48
|
+
"info": self._handle_info,
|
49
|
+
"success": self._handle_success,
|
50
|
+
"error": self._handle_error,
|
51
|
+
"progress": self._handle_progress,
|
52
|
+
"warning": self._handle_warning,
|
53
|
+
"stdout": self._handle_stdout,
|
54
|
+
"stderr": self._handle_stderr,
|
55
|
+
}
|
56
|
+
|
57
|
+
handler = handler_map.get(msg_type)
|
58
|
+
if handler:
|
59
|
+
handler(msg, message)
|
60
|
+
# Ignore unsupported message types silently
|
61
|
+
|
62
|
+
def _handle_content(self, msg, message):
|
63
|
+
from rich.markdown import Markdown
|
64
|
+
|
65
|
+
_print_metadata_if_verbose(msg)
|
66
|
+
self.console.print(Markdown(message))
|
67
|
+
|
68
|
+
def _handle_info(self, msg, message):
|
69
|
+
action_type = msg.get("action_type", None)
|
70
|
+
style = "cyan" # default
|
71
|
+
action_type_name = action_type.name if action_type else None
|
72
|
+
if action_type_name == "READ":
|
73
|
+
style = "cyan"
|
74
|
+
elif action_type_name == "WRITE":
|
75
|
+
style = "bright_magenta"
|
76
|
+
elif action_type_name == "EXECUTE":
|
77
|
+
style = "yellow"
|
78
|
+
_print_metadata_if_verbose(msg)
|
79
|
+
self.console.print(f" {message}", style=style, end="")
|
80
|
+
|
81
|
+
def _handle_success(self, msg, message):
|
82
|
+
_print_metadata_if_verbose(msg)
|
83
|
+
self.console.print(message, style="bold green", end="\n")
|
84
|
+
|
85
|
+
def _handle_error(self, msg, message):
|
86
|
+
_print_metadata_if_verbose(msg)
|
87
|
+
self.console.print(message, style="bold red", end="\n")
|
88
|
+
|
89
|
+
def _handle_progress(self, msg, message=None):
|
90
|
+
_print_metadata_if_verbose(msg)
|
91
|
+
# Existing logic for progress messages (if any)
|
92
|
+
# Placeholder: implement as needed
|
93
|
+
pass
|
94
|
+
|
95
|
+
def _handle_warning(self, msg, message):
|
96
|
+
_print_metadata_if_verbose(msg)
|
97
|
+
self.console.print(message, style="bold yellow", end="\n")
|
98
|
+
|
99
|
+
def _handle_stdout(self, msg, message):
|
100
|
+
from rich.text import Text
|
101
|
+
|
102
|
+
_print_metadata_if_verbose(msg)
|
103
|
+
self.console.print(
|
104
|
+
Text(message, style="on #003300", no_wrap=True, overflow=None),
|
105
|
+
end="",
|
106
|
+
)
|
107
|
+
|
108
|
+
def _handle_stderr(self, msg, message):
|
109
|
+
from rich.text import Text
|
110
|
+
|
111
|
+
_print_metadata_if_verbose(msg)
|
112
|
+
self.console.print(
|
113
|
+
Text(message, style="on #330000", no_wrap=True, overflow=None),
|
114
|
+
end="",
|
115
|
+
)
|
@@ -1,14 +1,15 @@
|
|
1
|
-
You are
|
1
|
+
You are: {{ role }}
|
2
2
|
|
3
|
-
You
|
3
|
+
You will be developing and testing in the following environment:
|
4
4
|
Platform: {{ platform }}
|
5
5
|
Python version: {{ python_version }}
|
6
6
|
Shell/Environment: {{ shell_info }}
|
7
7
|
|
8
|
-
|
9
|
-
-
|
8
|
+
Respond according to the following guidelines:
|
9
|
+
- Before answering check for files that might be related to the question
|
10
10
|
- Before using your namespace functions, provide a concise explanation.
|
11
|
-
-
|
12
|
-
-
|
13
|
-
-
|
14
|
-
|
11
|
+
- When planning to get the entire file content, set the range line numbers to None
|
12
|
+
- Do not provide the code in the messages, use the namespace functions to provide the code changes.
|
13
|
+
- Prefer making localized edits using string replacements. If the required change is extensive, replace the entire file instead, provide full content without placeholders.
|
14
|
+
- Before creating files search the code for the location related to the file purpose
|
15
|
+
- Once development or updates are finished, ensure that all new or updated packages, modules, functions, and methods
|
@@ -0,0 +1,93 @@
|
|
1
|
+
"""
|
2
|
+
Tests for OpenAISchemaGenerator class-based API.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import pytest
|
6
|
+
from janito.agent.openai_schema_generator import OpenAISchemaGenerator
|
7
|
+
|
8
|
+
|
9
|
+
class DummyTool:
|
10
|
+
"""
|
11
|
+
Dummy tool for testing.
|
12
|
+
|
13
|
+
Args:
|
14
|
+
foo (str): Foo parameter.
|
15
|
+
bar (int): Bar parameter.
|
16
|
+
Returns:
|
17
|
+
The result as a string.
|
18
|
+
"""
|
19
|
+
|
20
|
+
name = "dummy_tool"
|
21
|
+
|
22
|
+
def run(self, foo: str, bar: int) -> str:
|
23
|
+
"""Run the dummy tool."""
|
24
|
+
return f"{foo}-{bar}"
|
25
|
+
|
26
|
+
|
27
|
+
# Simulate decorator metadata for tests
|
28
|
+
DummyTool._tool_run_method = DummyTool().run
|
29
|
+
DummyTool._tool_name = DummyTool.name
|
30
|
+
|
31
|
+
|
32
|
+
def test_generate_schema_success():
|
33
|
+
generator = OpenAISchemaGenerator()
|
34
|
+
tool = DummyTool
|
35
|
+
schema = generator.generate_schema(tool)
|
36
|
+
assert schema["name"] == tool.name
|
37
|
+
assert "foo" in schema["parameters"]["properties"]
|
38
|
+
assert "bar" in schema["parameters"]["properties"]
|
39
|
+
assert schema["parameters"]["properties"]["foo"]["type"] == "string"
|
40
|
+
assert schema["parameters"]["properties"]["bar"]["type"] == "integer"
|
41
|
+
assert schema["description"].startswith("Dummy tool for testing.")
|
42
|
+
|
43
|
+
|
44
|
+
def test_generate_schema_missing_type():
|
45
|
+
class BadTool:
|
46
|
+
"""
|
47
|
+
Args:
|
48
|
+
foo (str): Foo parameter.
|
49
|
+
Returns:
|
50
|
+
String result.
|
51
|
+
"""
|
52
|
+
|
53
|
+
name = "bad_tool"
|
54
|
+
|
55
|
+
def run(self, foo):
|
56
|
+
return str(foo)
|
57
|
+
|
58
|
+
BadTool._tool_run_method = BadTool().run
|
59
|
+
BadTool._tool_name = BadTool.name
|
60
|
+
generator = OpenAISchemaGenerator()
|
61
|
+
with pytest.raises(ValueError):
|
62
|
+
generator.generate_schema(BadTool)
|
63
|
+
|
64
|
+
|
65
|
+
def test_generate_schema_missing_doc():
|
66
|
+
class BadTool2:
|
67
|
+
"""
|
68
|
+
Args:
|
69
|
+
foo (str): Foo parameter.
|
70
|
+
Returns:
|
71
|
+
String result.
|
72
|
+
"""
|
73
|
+
|
74
|
+
name = "bad_tool2"
|
75
|
+
|
76
|
+
def run(self, foo: str, bar: int) -> str:
|
77
|
+
return str(foo)
|
78
|
+
|
79
|
+
BadTool2._tool_run_method = BadTool2().run
|
80
|
+
BadTool2._tool_name = BadTool2.name
|
81
|
+
generator = OpenAISchemaGenerator()
|
82
|
+
with pytest.raises(ValueError):
|
83
|
+
generator.generate_schema(BadTool2)
|
84
|
+
|
85
|
+
|
86
|
+
def test_generate_schema_requires_metadata():
|
87
|
+
class NotRegisteredTool:
|
88
|
+
def run(self, foo: str) -> str:
|
89
|
+
return foo
|
90
|
+
|
91
|
+
generator = OpenAISchemaGenerator()
|
92
|
+
with pytest.raises(ValueError):
|
93
|
+
generator.generate_schema(NotRegisteredTool)
|
janito/agent/tool_base.py
CHANGED
@@ -37,9 +37,14 @@ class ToolBase(ABC):
|
|
37
37
|
if hasattr(self, "_progress_callback") and self._progress_callback:
|
38
38
|
self._progress_callback(progress)
|
39
39
|
|
40
|
-
def report_info(self, message: str):
|
40
|
+
def report_info(self, action_type, message: str):
|
41
41
|
self.update_progress(
|
42
|
-
{
|
42
|
+
{
|
43
|
+
"type": "info",
|
44
|
+
"tool": self.__class__.__name__,
|
45
|
+
"action_type": action_type,
|
46
|
+
"message": message,
|
47
|
+
}
|
43
48
|
)
|
44
49
|
|
45
50
|
def report_success(self, message: str):
|
janito/agent/tool_executor.py
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
ToolExecutor: Responsible for executing tools, validating arguments, handling errors, and reporting progress.
|
4
4
|
"""
|
5
5
|
|
6
|
-
import json
|
7
6
|
from janito.i18n import tr
|
8
7
|
import inspect
|
9
8
|
from janito.agent.tool_base import ToolBase
|
@@ -14,24 +13,35 @@ class ToolExecutor:
|
|
14
13
|
def __init__(self, message_handler=None):
|
15
14
|
self.message_handler = message_handler
|
16
15
|
|
17
|
-
def execute(self, tool_entry, tool_call):
|
16
|
+
def execute(self, tool_entry, tool_call, arguments):
|
18
17
|
import uuid
|
19
18
|
|
20
|
-
call_id = getattr(tool_call, "id", None)
|
19
|
+
call_id = getattr(tool_call, "id", None)
|
20
|
+
if call_id is None:
|
21
|
+
raise ValueError("Tool call is missing required 'id' from server.")
|
21
22
|
func = tool_entry["function"]
|
22
|
-
args =
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
23
|
+
args = arguments
|
24
|
+
if runtime_config.get("no_tools_tracking", False):
|
25
|
+
tool_call_reason = None
|
26
|
+
else:
|
27
|
+
tool_call_reason = args.pop(
|
28
|
+
"tool_call_reason", None
|
29
|
+
) # Extract and remove 'tool_call_reason' if present
|
29
30
|
|
30
|
-
|
31
|
+
self._maybe_log_tool_call(tool_call, args, tool_call_reason)
|
32
|
+
instance = self._maybe_set_progress_callback(func)
|
33
|
+
self._emit_tool_call_event(tool_call, call_id, args, tool_call_reason)
|
34
|
+
self._validate_arguments(func, args, tool_call, call_id, tool_call_reason)
|
35
|
+
try:
|
36
|
+
result = func(**args)
|
37
|
+
self._emit_tool_result_event(tool_call, call_id, result, tool_call_reason)
|
38
|
+
self._record_tool_usage(tool_call, args, result)
|
39
|
+
return result
|
31
40
|
except Exception as e:
|
32
|
-
|
33
|
-
|
41
|
+
self._emit_tool_error_event(tool_call, call_id, str(e), tool_call_reason)
|
42
|
+
raise
|
34
43
|
|
44
|
+
def _maybe_log_tool_call(self, tool_call, args, tool_call_reason):
|
35
45
|
verbose = runtime_config.get("verbose", False)
|
36
46
|
if verbose:
|
37
47
|
print(
|
@@ -48,12 +58,16 @@ class ToolExecutor:
|
|
48
58
|
tool_call_reason=tool_call_reason,
|
49
59
|
)
|
50
60
|
)
|
61
|
+
|
62
|
+
def _maybe_set_progress_callback(self, func):
|
51
63
|
instance = None
|
52
64
|
if hasattr(func, "__self__") and isinstance(func.__self__, ToolBase):
|
53
65
|
instance = func.__self__
|
54
66
|
if self.message_handler:
|
55
67
|
instance._progress_callback = self.message_handler.handle_message
|
56
|
-
|
68
|
+
return instance
|
69
|
+
|
70
|
+
def _emit_tool_call_event(self, tool_call, call_id, args, tool_call_reason):
|
57
71
|
if self.message_handler:
|
58
72
|
event = {
|
59
73
|
"type": "tool_call",
|
@@ -61,49 +75,48 @@ class ToolExecutor:
|
|
61
75
|
"call_id": call_id,
|
62
76
|
"arguments": args,
|
63
77
|
}
|
64
|
-
if tool_call_reason:
|
78
|
+
if tool_call_reason and not runtime_config.get("no_tools_tracking", False):
|
65
79
|
event["tool_call_reason"] = tool_call_reason
|
66
80
|
self.message_handler.handle_message(event)
|
67
|
-
|
81
|
+
|
82
|
+
def _validate_arguments(self, func, args, tool_call, call_id, tool_call_reason):
|
68
83
|
sig = inspect.signature(func)
|
69
84
|
try:
|
70
85
|
sig.bind(**args)
|
71
86
|
except TypeError as e:
|
72
87
|
error_msg = f"Argument validation error for tool '{tool_call.function.name}': {str(e)}"
|
73
|
-
|
74
|
-
error_event = {
|
75
|
-
"type": "tool_error",
|
76
|
-
"tool": tool_call.function.name,
|
77
|
-
"call_id": call_id,
|
78
|
-
"error": error_msg,
|
79
|
-
}
|
80
|
-
if tool_call_reason:
|
81
|
-
error_event["tool_call_reason"] = tool_call_reason
|
82
|
-
self.message_handler.handle_message(error_event)
|
88
|
+
self._emit_tool_error_event(tool_call, call_id, error_msg, tool_call_reason)
|
83
89
|
raise TypeError(error_msg)
|
84
|
-
|
90
|
+
|
91
|
+
def _emit_tool_result_event(self, tool_call, call_id, result, tool_call_reason):
|
92
|
+
if self.message_handler:
|
93
|
+
result_event = {
|
94
|
+
"type": "tool_result",
|
95
|
+
"tool": tool_call.function.name,
|
96
|
+
"call_id": call_id,
|
97
|
+
"result": result,
|
98
|
+
}
|
99
|
+
if tool_call_reason and not runtime_config.get("no_tools_tracking", False):
|
100
|
+
result_event["tool_call_reason"] = tool_call_reason
|
101
|
+
self.message_handler.handle_message(result_event)
|
102
|
+
|
103
|
+
def _emit_tool_error_event(self, tool_call, call_id, error, tool_call_reason):
|
104
|
+
if self.message_handler:
|
105
|
+
error_event = {
|
106
|
+
"type": "tool_error",
|
107
|
+
"tool": tool_call.function.name,
|
108
|
+
"call_id": call_id,
|
109
|
+
"error": error,
|
110
|
+
}
|
111
|
+
if tool_call_reason and not runtime_config.get("no_tools_tracking", False):
|
112
|
+
error_event["tool_call_reason"] = tool_call_reason
|
113
|
+
self.message_handler.handle_message(error_event)
|
114
|
+
|
115
|
+
def _record_tool_usage(self, tool_call, args, result):
|
85
116
|
try:
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
"type": "tool_result",
|
90
|
-
"tool": tool_call.function.name,
|
91
|
-
"call_id": call_id,
|
92
|
-
"result": result,
|
93
|
-
}
|
94
|
-
if tool_call_reason:
|
95
|
-
result_event["tool_call_reason"] = tool_call_reason
|
96
|
-
self.message_handler.handle_message(result_event)
|
97
|
-
return result
|
117
|
+
from janito.agent.tool_use_tracker import ToolUseTracker
|
118
|
+
|
119
|
+
ToolUseTracker().record(tool_call.function.name, dict(args), result)
|
98
120
|
except Exception as e:
|
99
|
-
if
|
100
|
-
|
101
|
-
"type": "tool_error",
|
102
|
-
"tool": tool_call.function.name,
|
103
|
-
"call_id": call_id,
|
104
|
-
"error": str(e),
|
105
|
-
}
|
106
|
-
if tool_call_reason:
|
107
|
-
error_event["tool_call_reason"] = tool_call_reason
|
108
|
-
self.message_handler.handle_message(error_event)
|
109
|
-
raise
|
121
|
+
if runtime_config.get("verbose", False):
|
122
|
+
print(f"[ToolExecutor] ToolUseTracker record failed: {e}")
|
janito/agent/tool_registry.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# janito/agent/tool_registry.py
|
2
2
|
from janito.agent.tool_base import ToolBase
|
3
|
-
from janito.agent.openai_schema_generator import
|
3
|
+
from janito.agent.openai_schema_generator import OpenAISchemaGenerator
|
4
4
|
|
5
5
|
_tool_registry = {}
|
6
6
|
|
@@ -17,9 +17,12 @@ def register_tool(tool=None, *, name: str = None):
|
|
17
17
|
f"Tool '{tool.__name__}' must implement a callable 'call' method."
|
18
18
|
)
|
19
19
|
tool_name = override_name or instance.name
|
20
|
+
# Add metadata for schema generation
|
21
|
+
tool._tool_run_method = instance.run
|
22
|
+
tool._tool_name = tool_name
|
20
23
|
if tool_name in _tool_registry:
|
21
24
|
raise ValueError(f"Tool '{tool_name}' is already registered.")
|
22
|
-
schema =
|
25
|
+
schema = OpenAISchemaGenerator().generate_schema(tool)
|
23
26
|
_tool_registry[tool_name] = {
|
24
27
|
"function": instance.run,
|
25
28
|
"description": schema["description"],
|
janito/agent/tool_use_tracker.py
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
import threading
|
2
|
+
import os
|
2
3
|
from typing import Any, Dict, List
|
3
4
|
|
4
5
|
|
6
|
+
def normalize_path(path: str) -> str:
|
7
|
+
if not isinstance(path, str):
|
8
|
+
return path
|
9
|
+
return os.path.normcase(os.path.abspath(path))
|
10
|
+
|
11
|
+
|
5
12
|
class ToolUseTracker:
|
6
13
|
_instance = None
|
7
14
|
_lock = threading.Lock()
|
@@ -14,25 +21,39 @@ class ToolUseTracker:
|
|
14
21
|
cls._instance._history = []
|
15
22
|
return cls._instance
|
16
23
|
|
17
|
-
def record(self, tool_name: str, params: Dict[str, Any]):
|
18
|
-
|
24
|
+
def record(self, tool_name: str, params: Dict[str, Any], result: Any = None):
|
25
|
+
# Normalize file_path in params if present
|
26
|
+
norm_params = params.copy()
|
27
|
+
if "file_path" in norm_params:
|
28
|
+
norm_params["file_path"] = normalize_path(norm_params["file_path"])
|
29
|
+
self._history.append(
|
30
|
+
{"tool": tool_name, "params": norm_params, "result": result}
|
31
|
+
)
|
19
32
|
|
20
33
|
def get_history(self) -> List[Dict[str, Any]]:
|
21
34
|
return list(self._history)
|
22
35
|
|
23
36
|
def get_operations_on_file(self, file_path: str) -> List[Dict[str, Any]]:
|
37
|
+
norm_file_path = normalize_path(file_path)
|
24
38
|
ops = []
|
25
39
|
for entry in self._history:
|
26
40
|
params = entry["params"]
|
27
|
-
|
28
|
-
|
41
|
+
# Normalize any string param values for comparison
|
42
|
+
for v in params.values():
|
43
|
+
if isinstance(v, str) and normalize_path(v) == norm_file_path:
|
44
|
+
ops.append(entry)
|
45
|
+
break
|
29
46
|
return ops
|
30
47
|
|
31
48
|
def file_fully_read(self, file_path: str) -> bool:
|
49
|
+
norm_file_path = normalize_path(file_path)
|
32
50
|
for entry in self._history:
|
33
51
|
if entry["tool"] == "get_lines":
|
34
52
|
params = entry["params"]
|
35
|
-
if
|
53
|
+
if (
|
54
|
+
"file_path" in params
|
55
|
+
and normalize_path(params["file_path"]) == norm_file_path
|
56
|
+
):
|
36
57
|
# If both from_line and to_line are None, full file was read
|
37
58
|
if (
|
38
59
|
params.get("from_line") is None
|
@@ -41,6 +62,22 @@ class ToolUseTracker:
|
|
41
62
|
return True
|
42
63
|
return False
|
43
64
|
|
65
|
+
def last_operation_is_full_read_or_replace(self, file_path: str) -> bool:
|
66
|
+
ops = self.get_operations_on_file(file_path)
|
67
|
+
if not ops:
|
68
|
+
return False
|
69
|
+
last = ops[-1]
|
70
|
+
if last["tool"] == "replace_file":
|
71
|
+
return True
|
72
|
+
if last["tool"] == "get_lines":
|
73
|
+
params = last["params"]
|
74
|
+
if params.get("from_line") is None and params.get("to_line") is None:
|
75
|
+
return True
|
76
|
+
return False
|
77
|
+
|
78
|
+
def clear_history(self):
|
79
|
+
self._history.clear()
|
80
|
+
|
44
81
|
@classmethod
|
45
82
|
def instance(cls):
|
46
83
|
return cls()
|
janito/agent/tools/__init__.py
CHANGED
@@ -1,22 +1,24 @@
|
|
1
1
|
from . import ask_user
|
2
2
|
from . import create_directory
|
3
3
|
from . import create_file
|
4
|
+
from . import replace_file
|
4
5
|
from . import fetch_url
|
5
6
|
from . import find_files
|
6
7
|
from . import get_lines
|
7
|
-
from . import
|
8
|
-
from . import gitignore_utils
|
8
|
+
from .get_file_outline import core # noqa: F401,F811
|
9
9
|
from . import move_file
|
10
|
-
from . import
|
10
|
+
from .validate_file_syntax import core # noqa: F401,F811
|
11
11
|
from . import remove_directory
|
12
12
|
from . import remove_file
|
13
13
|
from . import replace_text_in_file
|
14
|
-
from . import
|
14
|
+
from . import delete_text_in_file
|
15
15
|
from . import run_bash_command
|
16
16
|
from . import run_powershell_command
|
17
|
-
from . import run_python_command
|
18
17
|
from . import present_choices
|
19
18
|
from . import search_text
|
19
|
+
from . import python_command_runner
|
20
|
+
from . import python_file_runner
|
21
|
+
from . import python_stdin_runner
|
20
22
|
|
21
23
|
__all__ = [
|
22
24
|
"ask_user",
|
@@ -24,21 +26,20 @@ __all__ = [
|
|
24
26
|
"create_file",
|
25
27
|
"fetch_url",
|
26
28
|
"find_files",
|
27
|
-
"
|
29
|
+
"GetFileOutlineTool",
|
28
30
|
"get_lines",
|
29
|
-
"gitignore_utils",
|
30
31
|
"move_file",
|
31
32
|
"validate_file_syntax",
|
32
33
|
"remove_directory",
|
33
34
|
"remove_file",
|
35
|
+
"replace_file",
|
34
36
|
"replace_text_in_file",
|
35
|
-
"
|
37
|
+
"delete_text_in_file",
|
36
38
|
"run_bash_command",
|
37
39
|
"run_powershell_command",
|
38
|
-
"run_python_command",
|
39
|
-
"search_files",
|
40
|
-
"tools_utils",
|
41
|
-
"memory",
|
42
40
|
"present_choices",
|
43
41
|
"search_text",
|
42
|
+
"python_command_runner",
|
43
|
+
"python_file_runner",
|
44
|
+
"python_stdin_runner",
|
44
45
|
]
|
@@ -1,6 +1,9 @@
|
|
1
1
|
from janito.agent.tool_registry import register_tool
|
2
|
-
|
2
|
+
|
3
|
+
# from janito.agent.tools_utils.expand_path import expand_path
|
4
|
+
from janito.agent.tools_utils.utils import display_path
|
3
5
|
from janito.agent.tool_base import ToolBase
|
6
|
+
from janito.agent.tools_utils.action_type import ActionType
|
4
7
|
from janito.i18n import tr
|
5
8
|
import os
|
6
9
|
|
@@ -18,10 +21,12 @@ class CreateDirectoryTool(ToolBase):
|
|
18
21
|
"""
|
19
22
|
|
20
23
|
def run(self, file_path: str) -> str:
|
21
|
-
file_path = expand_path(file_path)
|
24
|
+
# file_path = expand_path(file_path)
|
25
|
+
# Using file_path as is
|
22
26
|
disp_path = display_path(file_path)
|
23
27
|
self.report_info(
|
24
|
-
|
28
|
+
ActionType.WRITE,
|
29
|
+
tr("📁 Creating directory '{disp_path}' ...", disp_path=disp_path),
|
25
30
|
)
|
26
31
|
try:
|
27
32
|
if os.path.exists(file_path):
|
@@ -47,9 +52,7 @@ class CreateDirectoryTool(ToolBase):
|
|
47
52
|
disp_path=disp_path,
|
48
53
|
)
|
49
54
|
os.makedirs(file_path, exist_ok=True)
|
50
|
-
self.report_success(
|
51
|
-
tr("✅ Directory created at '{disp_path}'", disp_path=disp_path)
|
52
|
-
)
|
55
|
+
self.report_success(tr("✅ Directory created"))
|
53
56
|
return tr(
|
54
57
|
"✅ Successfully created the directory at '{disp_path}'.",
|
55
58
|
disp_path=disp_path,
|