janito 2.2.0__py3-none-any.whl → 2.3.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 +6 -6
- janito/agent/setup_agent.py +14 -5
- janito/agent/templates/profiles/system_prompt_template_main.txt.j2 +3 -1
- janito/cli/chat_mode/bindings.py +6 -0
- janito/cli/chat_mode/session.py +16 -0
- janito/cli/chat_mode/shell/autocomplete.py +21 -21
- janito/cli/chat_mode/shell/commands/__init__.py +3 -0
- janito/cli/chat_mode/shell/commands/clear.py +12 -12
- janito/cli/chat_mode/shell/commands/exec.py +27 -0
- janito/cli/chat_mode/shell/commands/multi.py +51 -51
- janito/cli/chat_mode/shell/commands/tools.py +17 -6
- janito/cli/chat_mode/shell/input_history.py +62 -62
- janito/cli/chat_mode/shell/session/manager.py +1 -0
- janito/cli/chat_mode/toolbar.py +1 -0
- janito/cli/cli_commands/list_models.py +35 -35
- janito/cli/cli_commands/list_providers.py +9 -9
- janito/cli/cli_commands/list_tools.py +53 -53
- janito/cli/cli_commands/model_selection.py +50 -50
- janito/cli/cli_commands/model_utils.py +13 -2
- janito/cli/cli_commands/set_api_key.py +19 -19
- janito/cli/cli_commands/show_config.py +51 -51
- janito/cli/cli_commands/show_system_prompt.py +62 -62
- janito/cli/config.py +2 -1
- janito/cli/core/__init__.py +4 -4
- janito/cli/core/event_logger.py +59 -59
- janito/cli/core/getters.py +3 -1
- janito/cli/core/runner.py +165 -148
- janito/cli/core/setters.py +5 -1
- janito/cli/core/unsetters.py +54 -54
- janito/cli/main_cli.py +12 -1
- janito/cli/prompt_core.py +5 -2
- janito/cli/rich_terminal_reporter.py +22 -3
- janito/cli/single_shot_mode/__init__.py +6 -6
- janito/cli/single_shot_mode/handler.py +11 -1
- janito/cli/verbose_output.py +1 -1
- janito/config.py +5 -5
- janito/config_manager.py +2 -0
- janito/driver_events.py +14 -0
- janito/drivers/anthropic/driver.py +113 -113
- janito/drivers/azure_openai/driver.py +38 -3
- janito/drivers/driver_registry.py +0 -2
- janito/drivers/openai/driver.py +196 -36
- janito/formatting_token.py +54 -54
- janito/i18n/__init__.py +35 -35
- janito/i18n/messages.py +23 -23
- janito/i18n/pt.py +47 -47
- janito/llm/__init__.py +5 -5
- janito/llm/agent.py +443 -443
- janito/llm/auth.py +1 -0
- janito/llm/driver.py +7 -1
- janito/llm/driver_config.py +1 -0
- janito/llm/driver_config_builder.py +34 -34
- janito/llm/driver_input.py +12 -12
- janito/llm/message_parts.py +60 -60
- janito/llm/model.py +38 -38
- janito/llm/provider.py +196 -196
- janito/provider_config.py +7 -3
- janito/provider_registry.py +176 -158
- janito/providers/__init__.py +1 -0
- janito/providers/anthropic/model_info.py +22 -22
- janito/providers/anthropic/provider.py +2 -2
- janito/providers/azure_openai/model_info.py +7 -6
- janito/providers/azure_openai/provider.py +30 -2
- janito/providers/deepseek/__init__.py +1 -1
- janito/providers/deepseek/model_info.py +16 -16
- janito/providers/deepseek/provider.py +91 -91
- janito/providers/google/model_info.py +21 -29
- janito/providers/google/provider.py +49 -38
- janito/providers/mistralai/provider.py +2 -2
- janito/providers/provider_static_info.py +2 -3
- janito/tools/adapters/__init__.py +1 -1
- janito/tools/adapters/local/adapter.py +33 -11
- janito/tools/adapters/local/ask_user.py +102 -102
- janito/tools/adapters/local/copy_file.py +84 -84
- janito/tools/adapters/local/create_directory.py +69 -69
- janito/tools/adapters/local/create_file.py +82 -82
- janito/tools/adapters/local/delete_text_in_file.py +4 -7
- janito/tools/adapters/local/fetch_url.py +97 -97
- janito/tools/adapters/local/find_files.py +138 -138
- janito/tools/adapters/local/get_file_outline/__init__.py +1 -1
- janito/tools/adapters/local/get_file_outline/core.py +117 -117
- janito/tools/adapters/local/get_file_outline/java_outline.py +40 -40
- janito/tools/adapters/local/get_file_outline/markdown_outline.py +14 -14
- janito/tools/adapters/local/get_file_outline/python_outline.py +303 -303
- janito/tools/adapters/local/get_file_outline/python_outline_v2.py +156 -156
- janito/tools/adapters/local/get_file_outline/search_outline.py +33 -33
- janito/tools/adapters/local/move_file.py +3 -13
- janito/tools/adapters/local/python_code_run.py +166 -166
- janito/tools/adapters/local/python_command_run.py +164 -164
- janito/tools/adapters/local/python_file_run.py +163 -163
- janito/tools/adapters/local/remove_directory.py +6 -17
- janito/tools/adapters/local/remove_file.py +4 -10
- janito/tools/adapters/local/replace_text_in_file.py +6 -9
- janito/tools/adapters/local/run_bash_command.py +176 -176
- janito/tools/adapters/local/run_powershell_command.py +219 -219
- janito/tools/adapters/local/search_text/__init__.py +1 -1
- janito/tools/adapters/local/search_text/core.py +201 -201
- janito/tools/adapters/local/search_text/match_lines.py +1 -1
- janito/tools/adapters/local/search_text/pattern_utils.py +73 -73
- janito/tools/adapters/local/search_text/traverse_directory.py +145 -145
- janito/tools/adapters/local/validate_file_syntax/__init__.py +1 -1
- janito/tools/adapters/local/validate_file_syntax/core.py +106 -106
- janito/tools/adapters/local/validate_file_syntax/css_validator.py +35 -35
- janito/tools/adapters/local/validate_file_syntax/html_validator.py +93 -93
- janito/tools/adapters/local/validate_file_syntax/js_validator.py +27 -27
- janito/tools/adapters/local/validate_file_syntax/json_validator.py +6 -6
- janito/tools/adapters/local/validate_file_syntax/markdown_validator.py +109 -109
- janito/tools/adapters/local/validate_file_syntax/ps1_validator.py +32 -32
- janito/tools/adapters/local/validate_file_syntax/python_validator.py +5 -5
- janito/tools/adapters/local/validate_file_syntax/xml_validator.py +11 -11
- janito/tools/adapters/local/validate_file_syntax/yaml_validator.py +6 -6
- janito/tools/adapters/local/view_file.py +167 -167
- janito/tools/inspect_registry.py +17 -17
- janito/tools/tool_base.py +105 -105
- janito/tools/tool_events.py +58 -58
- janito/tools/tool_run_exception.py +12 -12
- janito/tools/tool_use_tracker.py +81 -81
- janito/tools/tool_utils.py +45 -45
- janito/tools/tools_adapter.py +78 -6
- janito/tools/tools_schema.py +104 -104
- janito/version.py +4 -4
- {janito-2.2.0.dist-info → janito-2.3.0.dist-info}/METADATA +388 -251
- janito-2.3.0.dist-info/RECORD +181 -0
- janito/drivers/google_genai/driver.py +0 -54
- janito/drivers/google_genai/schema_generator.py +0 -67
- janito-2.2.0.dist-info/RECORD +0 -182
- {janito-2.2.0.dist-info → janito-2.3.0.dist-info}/WHEEL +0 -0
- {janito-2.2.0.dist-info → janito-2.3.0.dist-info}/entry_points.txt +0 -0
- {janito-2.2.0.dist-info → janito-2.3.0.dist-info}/licenses/LICENSE +0 -0
- {janito-2.2.0.dist-info → janito-2.3.0.dist-info}/top_level.txt +0 -0
janito/tools/tool_events.py
CHANGED
@@ -1,58 +1,58 @@
|
|
1
|
-
import attr
|
2
|
-
from typing import Any, ClassVar
|
3
|
-
from janito.event_bus.event import Event
|
4
|
-
|
5
|
-
|
6
|
-
@attr.s(auto_attribs=True, kw_only=True)
|
7
|
-
class ToolEvent(Event):
|
8
|
-
"""
|
9
|
-
Base class for events related to tool calls (external or internal tools).
|
10
|
-
Includes tool name and request ID for correlation.
|
11
|
-
"""
|
12
|
-
|
13
|
-
category: ClassVar[str] = "tool"
|
14
|
-
tool_name: str
|
15
|
-
request_id: str
|
16
|
-
|
17
|
-
|
18
|
-
@attr.s(auto_attribs=True, kw_only=True)
|
19
|
-
class ToolCallStarted(ToolEvent):
|
20
|
-
"""
|
21
|
-
Event indicating that a tool call has started.
|
22
|
-
Contains the arguments passed to the tool.
|
23
|
-
"""
|
24
|
-
|
25
|
-
arguments: Any
|
26
|
-
|
27
|
-
|
28
|
-
@attr.s(auto_attribs=True, kw_only=True)
|
29
|
-
class ToolCallFinished(ToolEvent):
|
30
|
-
"""
|
31
|
-
Event indicating that a tool call has finished.
|
32
|
-
Contains the result returned by the tool.
|
33
|
-
"""
|
34
|
-
|
35
|
-
result: Any
|
36
|
-
|
37
|
-
|
38
|
-
@attr.s(auto_attribs=True, kw_only=True)
|
39
|
-
class ToolRunError(ToolEvent):
|
40
|
-
"""
|
41
|
-
Event indicating that an error occurred during tool execution (for event bus, not exception handling).
|
42
|
-
"""
|
43
|
-
|
44
|
-
error: str
|
45
|
-
exception: Exception = None
|
46
|
-
arguments: Any = None
|
47
|
-
|
48
|
-
|
49
|
-
@attr.s(auto_attribs=True, kw_only=True)
|
50
|
-
class ToolCallError(ToolEvent):
|
51
|
-
"""
|
52
|
-
Event indicating that the tool could not be called (e.g., tool not found, invalid arguments, or invocation failure).
|
53
|
-
This is distinct from ToolRunError, which is for errors during execution after the tool has started running.
|
54
|
-
"""
|
55
|
-
|
56
|
-
error: str
|
57
|
-
exception: Exception = None
|
58
|
-
arguments: Any = None
|
1
|
+
import attr
|
2
|
+
from typing import Any, ClassVar
|
3
|
+
from janito.event_bus.event import Event
|
4
|
+
|
5
|
+
|
6
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
7
|
+
class ToolEvent(Event):
|
8
|
+
"""
|
9
|
+
Base class for events related to tool calls (external or internal tools).
|
10
|
+
Includes tool name and request ID for correlation.
|
11
|
+
"""
|
12
|
+
|
13
|
+
category: ClassVar[str] = "tool"
|
14
|
+
tool_name: str
|
15
|
+
request_id: str
|
16
|
+
|
17
|
+
|
18
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
19
|
+
class ToolCallStarted(ToolEvent):
|
20
|
+
"""
|
21
|
+
Event indicating that a tool call has started.
|
22
|
+
Contains the arguments passed to the tool.
|
23
|
+
"""
|
24
|
+
|
25
|
+
arguments: Any
|
26
|
+
|
27
|
+
|
28
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
29
|
+
class ToolCallFinished(ToolEvent):
|
30
|
+
"""
|
31
|
+
Event indicating that a tool call has finished.
|
32
|
+
Contains the result returned by the tool.
|
33
|
+
"""
|
34
|
+
|
35
|
+
result: Any
|
36
|
+
|
37
|
+
|
38
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
39
|
+
class ToolRunError(ToolEvent):
|
40
|
+
"""
|
41
|
+
Event indicating that an error occurred during tool execution (for event bus, not exception handling).
|
42
|
+
"""
|
43
|
+
|
44
|
+
error: str
|
45
|
+
exception: Exception = None
|
46
|
+
arguments: Any = None
|
47
|
+
|
48
|
+
|
49
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
50
|
+
class ToolCallError(ToolEvent):
|
51
|
+
"""
|
52
|
+
Event indicating that the tool could not be called (e.g., tool not found, invalid arguments, or invocation failure).
|
53
|
+
This is distinct from ToolRunError, which is for errors during execution after the tool has started running.
|
54
|
+
"""
|
55
|
+
|
56
|
+
error: str
|
57
|
+
exception: Exception = None
|
58
|
+
arguments: Any = None
|
@@ -1,12 +1,12 @@
|
|
1
|
-
class ToolRunException(Exception):
|
2
|
-
"""
|
3
|
-
Exception raised when a tool runs but fails due to an internal error or runtime exception.
|
4
|
-
This is distinct from ToolRunError event, which is for event bus notification.
|
5
|
-
"""
|
6
|
-
|
7
|
-
def __init__(self, tool_name, error, arguments=None, exception=None):
|
8
|
-
self.tool_name = tool_name
|
9
|
-
self.error = error
|
10
|
-
self.arguments = arguments
|
11
|
-
self.original_exception = exception
|
12
|
-
super().__init__(f"ToolRunException: {tool_name}: {error}")
|
1
|
+
class ToolRunException(Exception):
|
2
|
+
"""
|
3
|
+
Exception raised when a tool runs but fails due to an internal error or runtime exception.
|
4
|
+
This is distinct from ToolRunError event, which is for event bus notification.
|
5
|
+
"""
|
6
|
+
|
7
|
+
def __init__(self, tool_name, error, arguments=None, exception=None):
|
8
|
+
self.tool_name = tool_name
|
9
|
+
self.error = error
|
10
|
+
self.arguments = arguments
|
11
|
+
self.original_exception = exception
|
12
|
+
super().__init__(f"ToolRunException: {tool_name}: {error}")
|
janito/tools/tool_use_tracker.py
CHANGED
@@ -1,81 +1,81 @@
|
|
1
|
-
import threading
|
2
|
-
import os
|
3
|
-
from typing import Any, Dict, List
|
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
|
-
|
12
|
-
class ToolUseTracker:
|
13
|
-
_instance = None
|
14
|
-
_lock = threading.Lock()
|
15
|
-
|
16
|
-
def __new__(cls):
|
17
|
-
if not cls._instance:
|
18
|
-
with cls._lock:
|
19
|
-
if not cls._instance:
|
20
|
-
cls._instance = super().__new__(cls)
|
21
|
-
cls._instance._history = []
|
22
|
-
return cls._instance
|
23
|
-
|
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
|
-
)
|
32
|
-
|
33
|
-
def get_history(self) -> List[Dict[str, Any]]:
|
34
|
-
return list(self._history)
|
35
|
-
|
36
|
-
def get_operations_on_file(self, file_path: str) -> List[Dict[str, Any]]:
|
37
|
-
norm_file_path = normalize_path(file_path)
|
38
|
-
ops = []
|
39
|
-
for entry in self._history:
|
40
|
-
params = entry["params"]
|
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
|
46
|
-
return ops
|
47
|
-
|
48
|
-
def file_fully_read(self, file_path: str) -> bool:
|
49
|
-
norm_file_path = normalize_path(file_path)
|
50
|
-
for entry in self._history:
|
51
|
-
if entry["tool"] == "view_file":
|
52
|
-
params = entry["params"]
|
53
|
-
if (
|
54
|
-
"file_path" in params
|
55
|
-
and normalize_path(params["file_path"]) == norm_file_path
|
56
|
-
):
|
57
|
-
# If both from_line and to_line are None, full file was read
|
58
|
-
if (
|
59
|
-
params.get("from_line") is None
|
60
|
-
and params.get("to_line") is None
|
61
|
-
):
|
62
|
-
return True
|
63
|
-
return False
|
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"] == "view_file":
|
71
|
-
params = last["params"]
|
72
|
-
if params.get("from_line") is None and params.get("to_line") is None:
|
73
|
-
return True
|
74
|
-
return False
|
75
|
-
|
76
|
-
def clear_history(self):
|
77
|
-
self._history.clear()
|
78
|
-
|
79
|
-
@classmethod
|
80
|
-
def instance(cls):
|
81
|
-
return cls()
|
1
|
+
import threading
|
2
|
+
import os
|
3
|
+
from typing import Any, Dict, List
|
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
|
+
|
12
|
+
class ToolUseTracker:
|
13
|
+
_instance = None
|
14
|
+
_lock = threading.Lock()
|
15
|
+
|
16
|
+
def __new__(cls):
|
17
|
+
if not cls._instance:
|
18
|
+
with cls._lock:
|
19
|
+
if not cls._instance:
|
20
|
+
cls._instance = super().__new__(cls)
|
21
|
+
cls._instance._history = []
|
22
|
+
return cls._instance
|
23
|
+
|
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
|
+
)
|
32
|
+
|
33
|
+
def get_history(self) -> List[Dict[str, Any]]:
|
34
|
+
return list(self._history)
|
35
|
+
|
36
|
+
def get_operations_on_file(self, file_path: str) -> List[Dict[str, Any]]:
|
37
|
+
norm_file_path = normalize_path(file_path)
|
38
|
+
ops = []
|
39
|
+
for entry in self._history:
|
40
|
+
params = entry["params"]
|
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
|
46
|
+
return ops
|
47
|
+
|
48
|
+
def file_fully_read(self, file_path: str) -> bool:
|
49
|
+
norm_file_path = normalize_path(file_path)
|
50
|
+
for entry in self._history:
|
51
|
+
if entry["tool"] == "view_file":
|
52
|
+
params = entry["params"]
|
53
|
+
if (
|
54
|
+
"file_path" in params
|
55
|
+
and normalize_path(params["file_path"]) == norm_file_path
|
56
|
+
):
|
57
|
+
# If both from_line and to_line are None, full file was read
|
58
|
+
if (
|
59
|
+
params.get("from_line") is None
|
60
|
+
and params.get("to_line") is None
|
61
|
+
):
|
62
|
+
return True
|
63
|
+
return False
|
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"] == "view_file":
|
71
|
+
params = last["params"]
|
72
|
+
if params.get("from_line") is None and params.get("to_line") is None:
|
73
|
+
return True
|
74
|
+
return False
|
75
|
+
|
76
|
+
def clear_history(self):
|
77
|
+
self._history.clear()
|
78
|
+
|
79
|
+
@classmethod
|
80
|
+
def instance(cls):
|
81
|
+
return cls()
|
janito/tools/tool_utils.py
CHANGED
@@ -1,45 +1,45 @@
|
|
1
|
-
"""
|
2
|
-
Utility functions for the janito project.
|
3
|
-
Add your shared helper functions here.
|
4
|
-
"""
|
5
|
-
|
6
|
-
import os
|
7
|
-
import urllib.parse
|
8
|
-
|
9
|
-
|
10
|
-
def example_utility_function(x):
|
11
|
-
"""A simple example utility function."""
|
12
|
-
return f"Processed: {x}"
|
13
|
-
|
14
|
-
|
15
|
-
def display_path(path):
|
16
|
-
"""
|
17
|
-
Returns a display-friendly path. Injects an ANSI hyperlink to a local web file viewer using a hardcoded port.
|
18
|
-
Args:
|
19
|
-
path (str): Path to display.
|
20
|
-
Returns:
|
21
|
-
str: Display path, as an ANSI hyperlink.
|
22
|
-
"""
|
23
|
-
from janito.cli.config import get_termweb_port
|
24
|
-
|
25
|
-
port = get_termweb_port()
|
26
|
-
if os.path.isabs(path):
|
27
|
-
cwd = os.path.abspath(os.getcwd())
|
28
|
-
abs_path = os.path.abspath(path)
|
29
|
-
# Check if the absolute path is within the current working directory
|
30
|
-
if abs_path.startswith(cwd + os.sep):
|
31
|
-
disp = os.path.relpath(abs_path, cwd)
|
32
|
-
else:
|
33
|
-
disp = path
|
34
|
-
else:
|
35
|
-
disp = os.path.relpath(path)
|
36
|
-
url = f"http://localhost:{port}/?path={urllib.parse.quote(path)}"
|
37
|
-
# Use Rich markup for hyperlinks
|
38
|
-
return f"[link={url}]{disp}[/link]"
|
39
|
-
|
40
|
-
|
41
|
-
def pluralize(word: str, count: int) -> str:
|
42
|
-
"""Return the pluralized form of word if count != 1, unless word already ends with 's'."""
|
43
|
-
if count == 1 or word.endswith("s"):
|
44
|
-
return word
|
45
|
-
return word + "s"
|
1
|
+
"""
|
2
|
+
Utility functions for the janito project.
|
3
|
+
Add your shared helper functions here.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import os
|
7
|
+
import urllib.parse
|
8
|
+
|
9
|
+
|
10
|
+
def example_utility_function(x):
|
11
|
+
"""A simple example utility function."""
|
12
|
+
return f"Processed: {x}"
|
13
|
+
|
14
|
+
|
15
|
+
def display_path(path):
|
16
|
+
"""
|
17
|
+
Returns a display-friendly path. Injects an ANSI hyperlink to a local web file viewer using a hardcoded port.
|
18
|
+
Args:
|
19
|
+
path (str): Path to display.
|
20
|
+
Returns:
|
21
|
+
str: Display path, as an ANSI hyperlink.
|
22
|
+
"""
|
23
|
+
from janito.cli.config import get_termweb_port
|
24
|
+
|
25
|
+
port = get_termweb_port()
|
26
|
+
if os.path.isabs(path):
|
27
|
+
cwd = os.path.abspath(os.getcwd())
|
28
|
+
abs_path = os.path.abspath(path)
|
29
|
+
# Check if the absolute path is within the current working directory
|
30
|
+
if abs_path.startswith(cwd + os.sep):
|
31
|
+
disp = os.path.relpath(abs_path, cwd)
|
32
|
+
else:
|
33
|
+
disp = path
|
34
|
+
else:
|
35
|
+
disp = os.path.relpath(path)
|
36
|
+
url = f"http://localhost:{port}/?path={urllib.parse.quote(path)}"
|
37
|
+
# Use Rich markup for hyperlinks
|
38
|
+
return f"[link={url}]{disp}[/link]"
|
39
|
+
|
40
|
+
|
41
|
+
def pluralize(word: str, count: int) -> str:
|
42
|
+
"""Return the pluralized form of word if count != 1, unless word already ends with 's'."""
|
43
|
+
if count == 1 or word.endswith("s"):
|
44
|
+
return word
|
45
|
+
return word + "s"
|
janito/tools/tools_adapter.py
CHANGED
@@ -13,11 +13,11 @@ class ToolsAdapterBase:
|
|
13
13
|
"""
|
14
14
|
|
15
15
|
def __init__(
|
16
|
-
self, tools=None, event_bus=None,
|
16
|
+
self, tools=None, event_bus=None, enabled_tools: Optional[list] = None
|
17
17
|
):
|
18
18
|
self._tools = tools or []
|
19
19
|
self._event_bus = event_bus # event bus can be set on all adapters
|
20
|
-
self.
|
20
|
+
self._enabled_tools = set(enabled_tools) if enabled_tools is not None else None
|
21
21
|
self.verbose_tools = False
|
22
22
|
|
23
23
|
def set_verbose_tools(self, value: bool):
|
@@ -32,8 +32,10 @@ class ToolsAdapterBase:
|
|
32
32
|
self._event_bus = bus
|
33
33
|
|
34
34
|
def get_tools(self):
|
35
|
-
"""Return the list of tools managed by this provider."""
|
36
|
-
|
35
|
+
"""Return the list of enabled tools managed by this provider."""
|
36
|
+
if self._enabled_tools is None:
|
37
|
+
return self._tools
|
38
|
+
return [tool for tool in self._tools if getattr(tool, 'tool_name', None) in self._enabled_tools]
|
37
39
|
|
38
40
|
def add_tool(self, tool):
|
39
41
|
self._tools.append(tool)
|
@@ -84,12 +86,82 @@ class ToolsAdapterBase:
|
|
84
86
|
|
85
87
|
return result
|
86
88
|
|
89
|
+
def _get_tool_callable(self, tool):
|
90
|
+
"""Helper to retrieve the primary callable of a tool instance."""
|
91
|
+
if callable(tool):
|
92
|
+
return tool
|
93
|
+
if hasattr(tool, "execute") and callable(getattr(tool, "execute")):
|
94
|
+
return getattr(tool, "execute")
|
95
|
+
if hasattr(tool, "run") and callable(getattr(tool, "run")):
|
96
|
+
return getattr(tool, "run")
|
97
|
+
raise ValueError("Provided tool is not executable.")
|
98
|
+
|
99
|
+
def _validate_arguments_against_signature(self, func, arguments: dict):
|
100
|
+
"""Validate provided arguments against a callable signature.
|
101
|
+
|
102
|
+
Returns an error string if validation fails, otherwise ``None``.
|
103
|
+
"""
|
104
|
+
import inspect
|
105
|
+
|
106
|
+
if arguments is None:
|
107
|
+
arguments = {}
|
108
|
+
# Ensure the input is a dict to avoid breaking the inspect-based logic
|
109
|
+
if not isinstance(arguments, dict):
|
110
|
+
return "Tool arguments should be provided as an object / mapping"
|
111
|
+
|
112
|
+
sig = inspect.signature(func)
|
113
|
+
params = sig.parameters
|
114
|
+
|
115
|
+
# Check for unexpected arguments (unless **kwargs is accepted)
|
116
|
+
accepts_kwargs = any(
|
117
|
+
p.kind == inspect.Parameter.VAR_KEYWORD for p in params.values()
|
118
|
+
)
|
119
|
+
if not accepts_kwargs:
|
120
|
+
unexpected = [k for k in arguments.keys() if k not in params]
|
121
|
+
if unexpected:
|
122
|
+
return (
|
123
|
+
"Unexpected argument(s): " + ", ".join(sorted(unexpected))
|
124
|
+
)
|
125
|
+
|
126
|
+
# Check for missing required arguments (ignoring *args / **kwargs / self)
|
127
|
+
required_params = [
|
128
|
+
name
|
129
|
+
for name, p in params.items()
|
130
|
+
if p.kind in (
|
131
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
132
|
+
inspect.Parameter.KEYWORD_ONLY,
|
133
|
+
)
|
134
|
+
and p.default is inspect._empty
|
135
|
+
and name != "self"
|
136
|
+
]
|
137
|
+
missing = [name for name in required_params if name not in arguments]
|
138
|
+
if missing:
|
139
|
+
return "Missing required argument(s): " + ", ".join(sorted(missing))
|
140
|
+
|
141
|
+
return None
|
142
|
+
|
87
143
|
def execute_by_name(
|
88
144
|
self, tool_name: str, *args, request_id=None, arguments=None, **kwargs
|
89
145
|
):
|
90
146
|
self._check_tool_permissions(tool_name, request_id, arguments)
|
91
147
|
tool = self.get_tool(tool_name)
|
92
148
|
self._ensure_tool_exists(tool, tool_name, request_id, arguments)
|
149
|
+
func = self._get_tool_callable(tool)
|
150
|
+
# First, validate arguments against the callable signature to catch unexpected / missing params
|
151
|
+
sig_error = self._validate_arguments_against_signature(func, arguments)
|
152
|
+
if sig_error:
|
153
|
+
if self._event_bus:
|
154
|
+
self._event_bus.publish(
|
155
|
+
ToolCallError(
|
156
|
+
tool_name=tool_name,
|
157
|
+
request_id=request_id,
|
158
|
+
error=sig_error,
|
159
|
+
arguments=arguments,
|
160
|
+
)
|
161
|
+
)
|
162
|
+
return sig_error
|
163
|
+
|
164
|
+
# Optionally validate against JSON schema if available
|
93
165
|
schema = getattr(tool, "schema", None)
|
94
166
|
if schema and arguments is not None:
|
95
167
|
validation_error = self._validate_arguments_against_schema(
|
@@ -159,8 +231,8 @@ class ToolsAdapterBase:
|
|
159
231
|
)
|
160
232
|
|
161
233
|
def _check_tool_permissions(self, tool_name, request_id, arguments):
|
162
|
-
if self.
|
163
|
-
error_msg = f"Tool '{tool_name}' is not
|
234
|
+
if self._enabled_tools is not None and tool_name not in self._enabled_tools:
|
235
|
+
error_msg = f"Tool '{tool_name}' is not enabled in this adapter."
|
164
236
|
if self._event_bus:
|
165
237
|
self._event_bus.publish(
|
166
238
|
ToolCallError(
|