janito 3.4.0__py3-none-any.whl → 3.5.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/README.md +3 -0
- janito/cli/chat_mode/bindings.py +50 -0
- janito/cli/chat_mode/session.py +12 -1
- janito/cli/chat_mode/shell/commands/multi.py +5 -0
- janito/cli/chat_mode/shell/commands/security/allowed_sites.py +47 -33
- janito/cli/cli_commands/check_tools.py +212 -0
- janito/cli/cli_commands/list_plugins.py +52 -43
- janito/cli/core/getters.py +3 -0
- janito/cli/core/model_guesser.py +40 -24
- janito/cli/main_cli.py +9 -12
- janito/cli/prompt_core.py +47 -9
- janito/cli/rich_terminal_reporter.py +2 -2
- janito/drivers/openai/driver.py +1 -0
- janito/drivers/zai/driver.py +1 -0
- janito/i18n/it.py +46 -46
- janito/llm/agent.py +32 -16
- janito/llm/auth_utils.py +14 -5
- janito/llm/cancellation_manager.py +63 -0
- janito/llm/driver.py +8 -0
- janito/llm/enter_cancellation.py +107 -0
- janito/plugin_system/__init__.py +10 -0
- janito/{plugins → plugin_system}/base.py +5 -2
- janito/plugin_system/core_loader.py +217 -0
- janito/plugin_system/core_loader_fixed.py +225 -0
- janito/plugins/__init__.py +31 -12
- janito/plugins/auto_loader.py +12 -11
- janito/plugins/auto_loader_fixed.py +12 -11
- janito/plugins/builtin.py +15 -1
- janito/plugins/core/__init__.py +7 -0
- janito/plugins/core/codeanalyzer/__init__.py +43 -0
- janito/plugins/core/filemanager/__init__.py +124 -0
- janito/plugins/core/filemanager/tools/create_file.py +87 -0
- janito/plugins/core/filemanager/tools/replace_text_in_file.py +270 -0
- janito/plugins/core/imagedisplay/__init__.py +14 -0
- janito/plugins/core/imagedisplay/plugin.py +51 -0
- janito/plugins/core/imagedisplay/tools/__init__.py +1 -0
- janito/plugins/core/imagedisplay/tools/show_image.py +83 -0
- janito/{tools/adapters/local → plugins/core/imagedisplay/tools}/show_image_grid.py +13 -5
- janito/plugins/core/system/__init__.py +23 -0
- janito/plugins/core_adapter.py +89 -11
- janito/plugins/dev/__init__.py +7 -0
- janito/plugins/dev/pythondev/__init__.py +37 -0
- janito/plugins/dev/visualization/__init__.py +23 -0
- janito/plugins/discovery.py +5 -5
- janito/plugins/discovery_core.py +14 -9
- janito/plugins/example_plugin.py +108 -0
- janito/plugins/manager.py +1 -1
- janito/plugins/tools/__init__.py +10 -0
- janito/{tools/adapters/local → plugins/tools}/ask_user.py +3 -3
- janito/plugins/tools/copy_file.py +87 -0
- janito/plugins/tools/core_tools_plugin.py +87 -0
- janito/plugins/tools/create_directory.py +70 -0
- janito/{tools/adapters/local → plugins/tools}/create_file.py +6 -6
- janito/plugins/tools/decorators.py +19 -0
- janito/plugins/tools/delete_text_in_file.py +134 -0
- janito/{tools/adapters/local → plugins/tools}/fetch_url.py +3 -3
- janito/plugins/tools/find_files.py +143 -0
- janito/plugins/tools/get_file_outline/__init__.py +7 -0
- janito/plugins/tools/get_file_outline/core.py +122 -0
- janito/plugins/tools/get_file_outline/java_outline.py +47 -0
- janito/plugins/tools/get_file_outline/markdown_outline.py +14 -0
- janito/plugins/tools/get_file_outline/python_outline.py +303 -0
- janito/plugins/tools/get_file_outline/search_outline.py +36 -0
- janito/plugins/tools/move_file.py +131 -0
- janito/plugins/tools/open_html_in_browser.py +51 -0
- janito/plugins/tools/open_url.py +37 -0
- janito/plugins/tools/python_code_run.py +172 -0
- janito/plugins/tools/python_command_run.py +171 -0
- janito/plugins/tools/python_file_run.py +172 -0
- janito/plugins/tools/read_chart.py +259 -0
- janito/plugins/tools/read_files.py +58 -0
- janito/plugins/tools/remove_directory.py +55 -0
- janito/plugins/tools/remove_file.py +58 -0
- janito/{tools/adapters/local → plugins/tools}/replace_text_in_file.py +4 -4
- janito/plugins/tools/run_bash_command.py +183 -0
- janito/plugins/tools/run_powershell_command.py +218 -0
- janito/plugins/tools/search_text/__init__.py +7 -0
- janito/plugins/tools/search_text/core.py +205 -0
- janito/plugins/tools/search_text/match_lines.py +67 -0
- janito/plugins/tools/search_text/pattern_utils.py +73 -0
- janito/plugins/tools/search_text/traverse_directory.py +145 -0
- janito/{tools/adapters/local → plugins/tools}/show_image.py +15 -6
- janito/plugins/tools/show_image_grid.py +85 -0
- janito/plugins/tools/validate_file_syntax/__init__.py +7 -0
- janito/plugins/tools/validate_file_syntax/core.py +114 -0
- janito/plugins/tools/validate_file_syntax/css_validator.py +35 -0
- janito/plugins/tools/validate_file_syntax/html_validator.py +100 -0
- janito/plugins/tools/validate_file_syntax/jinja2_validator.py +50 -0
- janito/plugins/tools/validate_file_syntax/js_validator.py +27 -0
- janito/plugins/tools/validate_file_syntax/json_validator.py +6 -0
- janito/plugins/tools/validate_file_syntax/markdown_validator.py +109 -0
- janito/plugins/tools/validate_file_syntax/ps1_validator.py +32 -0
- janito/plugins/tools/validate_file_syntax/python_validator.py +5 -0
- janito/plugins/tools/validate_file_syntax/xml_validator.py +11 -0
- janito/plugins/tools/validate_file_syntax/yaml_validator.py +6 -0
- janito/plugins/tools/view_file.py +172 -0
- janito/plugins/ui/__init__.py +7 -0
- janito/plugins/ui/userinterface/__init__.py +16 -0
- janito/plugins/ui/userinterface/tools/ask_user.py +110 -0
- janito/plugins/web/__init__.py +7 -0
- janito/plugins/web/webtools/__init__.py +33 -0
- janito/plugins/web/webtools/tools/fetch_url.py +458 -0
- janito/providers/__init__.py +1 -0
- janito/providers/together/__init__.py +1 -0
- janito/providers/together/model_info.py +69 -0
- janito/providers/together/provider.py +108 -0
- janito/tools/__init__.py +31 -7
- janito/tools/adapters/__init__.py +6 -1
- janito/tools/adapters/local/__init__.py +7 -70
- janito/tools/cli_initializer.py +88 -0
- janito/tools/initialize.py +70 -0
- janito/tools/loop_protection_decorator.py +114 -117
- janito-3.5.0.dist-info/METADATA +229 -0
- {janito-3.4.0.dist-info → janito-3.5.0.dist-info}/RECORD +158 -86
- janito/plugins/core_loader.py +0 -120
- janito/plugins/core_loader_fixed.py +0 -125
- janito/tools/function_adapter.py +0 -65
- janito-3.4.0.dist-info/METADATA +0 -84
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/__init__.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/core.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/java_outline.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/markdown_outline.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/python_outline.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/get_file_outline/search_outline.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/__init__.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/core.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/match_lines.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/pattern_utils.py +0 -0
- /janito/{tools/adapters/local → plugins/core/codeanalyzer/tools}/search_text/traverse_directory.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/copy_file.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/create_directory.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/delete_text_in_file.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/find_files.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/move_file.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/read_files.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/remove_directory.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/remove_file.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/__init__.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/core.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/css_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/html_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/jinja2_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/js_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/json_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/markdown_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/ps1_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/python_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/xml_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/validate_file_syntax/yaml_validator.py +0 -0
- /janito/{tools/adapters/local → plugins/core/filemanager/tools}/view_file.py +0 -0
- /janito/{tools/adapters/local → plugins/core/system/tools}/run_bash_command.py +0 -0
- /janito/{tools/adapters/local → plugins/core/system/tools}/run_powershell_command.py +0 -0
- /janito/{tools/adapters/local → plugins/dev/pythondev/tools}/python_code_run.py +0 -0
- /janito/{tools/adapters/local → plugins/dev/pythondev/tools}/python_command_run.py +0 -0
- /janito/{tools/adapters/local → plugins/dev/pythondev/tools}/python_file_run.py +0 -0
- /janito/{tools/adapters/local → plugins/dev/visualization/tools}/read_chart.py +0 -0
- /janito/{tools/adapters/local → plugins/web/webtools/tools}/open_html_in_browser.py +0 -0
- /janito/{tools/adapters/local → plugins/web/webtools/tools}/open_url.py +0 -0
- {janito-3.4.0.dist-info → janito-3.5.0.dist-info}/WHEEL +0 -0
- {janito-3.4.0.dist-info → janito-3.5.0.dist-info}/entry_points.txt +0 -0
- {janito-3.4.0.dist-info → janito-3.5.0.dist-info}/licenses/LICENSE +0 -0
- {janito-3.4.0.dist-info → janito-3.5.0.dist-info}/top_level.txt +0 -0
janito/tools/__init__.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
from janito.tools.adapters.local import
|
2
|
-
|
3
|
-
LocalToolsAdapter,
|
1
|
+
from janito.tools.adapters.local import LocalToolsAdapter
|
2
|
+
from janito.tools.adapters.local.adapter import (
|
3
|
+
LocalToolsAdapter as _internal_local_tools_adapter,
|
4
4
|
)
|
5
5
|
|
6
6
|
|
@@ -11,8 +11,10 @@ def get_local_tools_adapter(workdir=None, allowed_permissions=None):
|
|
11
11
|
if workdir is not None and not os.path.exists(workdir):
|
12
12
|
os.makedirs(workdir, exist_ok=True)
|
13
13
|
# Permissions are now managed globally; ignore allowed_permissions argument except for backward compatibility
|
14
|
-
#
|
15
|
-
|
14
|
+
# Create and initialize adapter
|
15
|
+
from janito.tools.initialize import initialize_tools
|
16
|
+
|
17
|
+
registry = initialize_tools(LocalToolsAdapter(workdir=workdir))
|
16
18
|
# Change workdir if requested
|
17
19
|
if workdir is not None:
|
18
20
|
try:
|
@@ -27,10 +29,32 @@ def get_local_tools_adapter(workdir=None, allowed_permissions=None):
|
|
27
29
|
return registry
|
28
30
|
|
29
31
|
|
30
|
-
|
32
|
+
# Initialize the global adapter - defer import to avoid circular dependencies
|
33
|
+
local_tools_adapter = None
|
34
|
+
|
35
|
+
|
36
|
+
def get_local_tools_adapter(workdir=None, allowed_permissions=None):
|
37
|
+
"""Get the global tools adapter, initializing on first use."""
|
38
|
+
global local_tools_adapter
|
39
|
+
if local_tools_adapter is None:
|
40
|
+
from janito.tools.initialize import initialize_tools
|
41
|
+
|
42
|
+
adapter = LocalToolsAdapter(workdir=workdir)
|
43
|
+
local_tools_adapter = initialize_tools(adapter)
|
44
|
+
|
45
|
+
# Handle workdir if provided
|
46
|
+
if workdir is not None and local_tools_adapter is not None:
|
47
|
+
import os
|
48
|
+
|
49
|
+
if not os.path.exists(workdir):
|
50
|
+
os.makedirs(workdir, exist_ok=True)
|
51
|
+
os.chdir(workdir)
|
52
|
+
local_tools_adapter.workdir = workdir
|
53
|
+
|
54
|
+
return local_tools_adapter
|
55
|
+
|
31
56
|
|
32
57
|
__all__ = [
|
33
58
|
"LocalToolsAdapter",
|
34
59
|
"get_local_tools_adapter",
|
35
|
-
"local_tools_adapter",
|
36
60
|
]
|
@@ -1 +1,6 @@
|
|
1
|
-
|
1
|
+
"""
|
2
|
+
Tools providers package: for plug-and-play tool collections, integrations, and adapters.
|
3
|
+
|
4
|
+
This package contains the core adapter infrastructure for managing tools,
|
5
|
+
while the actual tool implementations have been moved to the plugins system.
|
6
|
+
"""
|
@@ -1,73 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
from .ask_user import AskUserTool
|
4
|
-
from .copy_file import CopyFileTool
|
5
|
-
from .create_directory import CreateDirectoryTool
|
6
|
-
from .create_file import CreateFileTool
|
7
|
-
from .fetch_url import FetchUrlTool
|
8
|
-
from .find_files import FindFilesTool
|
9
|
-
from .view_file import ViewFileTool
|
10
|
-
from .read_files import ReadFilesTool
|
11
|
-
from .move_file import MoveFileTool
|
12
|
-
from .open_url import OpenUrlTool
|
13
|
-
from .open_html_in_browser import OpenHtmlInBrowserTool
|
14
|
-
from .python_code_run import PythonCodeRunTool
|
15
|
-
from .python_command_run import PythonCommandRunTool
|
16
|
-
from .python_file_run import PythonFileRunTool
|
17
|
-
from .remove_directory import RemoveDirectoryTool
|
18
|
-
from .remove_file import RemoveFileTool
|
19
|
-
from .replace_text_in_file import ReplaceTextInFileTool
|
20
|
-
from .run_bash_command import RunBashCommandTool
|
21
|
-
from .run_powershell_command import RunPowershellCommandTool
|
22
|
-
from .get_file_outline.core import GetFileOutlineTool
|
23
|
-
from .get_file_outline.search_outline import SearchOutlineTool
|
24
|
-
from .search_text.core import SearchTextTool
|
25
|
-
from .validate_file_syntax.core import ValidateFileSyntaxTool
|
26
|
-
from .read_chart import ReadChartTool
|
27
|
-
from .show_image import ShowImageTool
|
28
|
-
from .show_image_grid import ShowImageGridTool
|
29
|
-
|
30
|
-
from janito.tools.tool_base import ToolPermissions
|
31
|
-
import os
|
32
|
-
from janito.tools.permissions import get_global_allowed_permissions
|
33
|
-
|
34
|
-
# Singleton tools adapter with all standard tools registered
|
35
|
-
local_tools_adapter = LocalToolsAdapter(workdir=os.getcwd())
|
1
|
+
"""
|
2
|
+
Local tools adapter for janito.
|
36
3
|
|
4
|
+
This package provides the LocalToolsAdapter class which manages tool registration
|
5
|
+
and execution for local, in-process tools.
|
6
|
+
"""
|
37
7
|
|
38
|
-
|
39
|
-
return LocalToolsAdapter(workdir=workdir or os.getcwd())
|
40
|
-
|
41
|
-
|
42
|
-
# Register tools
|
43
|
-
for tool_class in [
|
44
|
-
AskUserTool,
|
45
|
-
CopyFileTool,
|
46
|
-
CreateDirectoryTool,
|
47
|
-
CreateFileTool,
|
48
|
-
FetchUrlTool,
|
49
|
-
FindFilesTool,
|
50
|
-
ViewFileTool,
|
51
|
-
ReadFilesTool,
|
52
|
-
MoveFileTool,
|
53
|
-
OpenUrlTool,
|
54
|
-
OpenHtmlInBrowserTool,
|
55
|
-
PythonCodeRunTool,
|
56
|
-
PythonCommandRunTool,
|
57
|
-
PythonFileRunTool,
|
58
|
-
RemoveDirectoryTool,
|
59
|
-
RemoveFileTool,
|
60
|
-
ReplaceTextInFileTool,
|
61
|
-
RunBashCommandTool,
|
62
|
-
RunPowershellCommandTool,
|
63
|
-
GetFileOutlineTool,
|
64
|
-
SearchOutlineTool,
|
65
|
-
SearchTextTool,
|
66
|
-
ValidateFileSyntaxTool,
|
67
|
-
ReadChartTool,
|
68
|
-
ShowImageTool,
|
69
|
-
ShowImageGridTool,
|
70
|
-
]:
|
71
|
-
local_tools_adapter.register_tool(tool_class)
|
8
|
+
from .adapter import LocalToolsAdapter
|
72
9
|
|
73
|
-
|
10
|
+
__all__ = ["LocalToolsAdapter"]
|
@@ -0,0 +1,88 @@
|
|
1
|
+
"""
|
2
|
+
CLI-specific tool initialization for janito.
|
3
|
+
|
4
|
+
This module provides functions to initialize tools specifically for CLI usage,
|
5
|
+
handling circular imports and ensuring proper registration.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import sys
|
9
|
+
from pathlib import Path
|
10
|
+
from typing import List, Optional
|
11
|
+
|
12
|
+
# Ensure current directory is in path
|
13
|
+
sys.path.insert(0, str(Path.cwd()))
|
14
|
+
|
15
|
+
|
16
|
+
def initialize_cli_tools():
|
17
|
+
"""Initialize tools for CLI usage, avoiding circular imports."""
|
18
|
+
try:
|
19
|
+
from janito.tools.adapters.local.adapter import LocalToolsAdapter
|
20
|
+
from janito.plugin_system.core_loader_fixed import load_core_plugin
|
21
|
+
from janito.tools.permissions import set_global_allowed_permissions
|
22
|
+
from janito.tools.tool_base import ToolPermissions
|
23
|
+
|
24
|
+
# Create adapter
|
25
|
+
adapter = LocalToolsAdapter()
|
26
|
+
|
27
|
+
# Set permissions for CLI
|
28
|
+
set_global_allowed_permissions(
|
29
|
+
ToolPermissions(read=True, write=True, execute=True)
|
30
|
+
)
|
31
|
+
|
32
|
+
# Core plugins to load
|
33
|
+
core_plugins = [
|
34
|
+
"core.filemanager",
|
35
|
+
"core.codeanalyzer",
|
36
|
+
"core.system",
|
37
|
+
"core.imagedisplay",
|
38
|
+
"dev.pythondev",
|
39
|
+
"dev.visualization",
|
40
|
+
]
|
41
|
+
|
42
|
+
loaded_count = 0
|
43
|
+
for plugin_name in core_plugins:
|
44
|
+
plugin = load_core_plugin(plugin_name)
|
45
|
+
if plugin:
|
46
|
+
tools = plugin.get_tools()
|
47
|
+
for tool_class in tools:
|
48
|
+
try:
|
49
|
+
adapter.register_tool(tool_class)
|
50
|
+
loaded_count += 1
|
51
|
+
except ValueError:
|
52
|
+
# Tool already registered, skip
|
53
|
+
pass
|
54
|
+
|
55
|
+
return adapter, loaded_count
|
56
|
+
|
57
|
+
except Exception as e:
|
58
|
+
print(f"Error initializing CLI tools: {e}", file=sys.stderr)
|
59
|
+
return None, 0
|
60
|
+
|
61
|
+
|
62
|
+
def get_cli_tools_adapter():
|
63
|
+
"""Get a CLI-initialized tools adapter."""
|
64
|
+
adapter, count = initialize_cli_tools()
|
65
|
+
if adapter and count > 0:
|
66
|
+
return adapter
|
67
|
+
return None
|
68
|
+
|
69
|
+
|
70
|
+
def list_cli_tools():
|
71
|
+
"""List all available CLI tools."""
|
72
|
+
adapter = get_cli_tools_adapter()
|
73
|
+
if not adapter:
|
74
|
+
return []
|
75
|
+
|
76
|
+
return adapter.list_tools()
|
77
|
+
|
78
|
+
|
79
|
+
if __name__ == "__main__":
|
80
|
+
adapter, count = initialize_cli_tools()
|
81
|
+
if adapter:
|
82
|
+
tools = adapter.list_tools()
|
83
|
+
print(f"CLI initialized {count} tools")
|
84
|
+
print(f"Available tools: {len(tools)}")
|
85
|
+
for tool in sorted(tools):
|
86
|
+
print(f" - {tool}")
|
87
|
+
else:
|
88
|
+
print("Failed to initialize CLI tools")
|
@@ -0,0 +1,70 @@
|
|
1
|
+
"""
|
2
|
+
Initialize janito tools for CLI and programmatic usage.
|
3
|
+
|
4
|
+
This module provides functions to load core plugins and register their tools
|
5
|
+
with the local tools adapter.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import sys
|
9
|
+
from pathlib import Path
|
10
|
+
from typing import List, Optional
|
11
|
+
|
12
|
+
from janito.plugin_system.core_loader_fixed import load_core_plugin
|
13
|
+
from janito.tools.adapters.local.adapter import LocalToolsAdapter
|
14
|
+
|
15
|
+
|
16
|
+
def initialize_tools(adapter: Optional[LocalToolsAdapter] = None) -> LocalToolsAdapter:
|
17
|
+
"""
|
18
|
+
Initialize all janito tools by loading core plugins and registering tools.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
adapter: LocalToolsAdapter instance to register tools with.
|
22
|
+
If None, creates a new instance.
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
LocalToolsAdapter with all tools registered.
|
26
|
+
"""
|
27
|
+
if adapter is None:
|
28
|
+
adapter = LocalToolsAdapter()
|
29
|
+
|
30
|
+
# Core plugins to load
|
31
|
+
core_plugins = [
|
32
|
+
"core.filemanager",
|
33
|
+
"core.codeanalyzer",
|
34
|
+
"core.system",
|
35
|
+
"core.imagedisplay",
|
36
|
+
"dev.pythondev",
|
37
|
+
"dev.visualization",
|
38
|
+
]
|
39
|
+
|
40
|
+
loaded_count = 0
|
41
|
+
for plugin_name in core_plugins:
|
42
|
+
plugin = load_core_plugin(plugin_name)
|
43
|
+
if plugin:
|
44
|
+
tools = plugin.get_tools()
|
45
|
+
for tool_class in tools:
|
46
|
+
adapter.register_tool(tool_class)
|
47
|
+
loaded_count += 1
|
48
|
+
|
49
|
+
return adapter
|
50
|
+
|
51
|
+
|
52
|
+
def get_initialized_adapter() -> LocalToolsAdapter:
|
53
|
+
"""Get a pre-initialized LocalToolsAdapter with all tools registered."""
|
54
|
+
return initialize_tools()
|
55
|
+
|
56
|
+
|
57
|
+
def list_available_tools() -> List[str]:
|
58
|
+
"""Get a list of all available tool names."""
|
59
|
+
adapter = get_initialized_adapter()
|
60
|
+
return sorted(list(adapter._tools.keys()))
|
61
|
+
|
62
|
+
|
63
|
+
if __name__ == "__main__":
|
64
|
+
adapter = initialize_tools()
|
65
|
+
tools = sorted(list(adapter._tools.keys()))
|
66
|
+
|
67
|
+
print(f"Initialized {len(tools)} tools")
|
68
|
+
print("Available tools:")
|
69
|
+
for tool_name in tools:
|
70
|
+
print(f" - {tool_name}")
|
@@ -1,143 +1,140 @@
|
|
1
1
|
import functools
|
2
2
|
import time
|
3
3
|
import threading
|
4
|
-
from typing import
|
5
|
-
from janito.tools.loop_protection import LoopProtection
|
6
|
-
from janito.tools.tool_use_tracker import normalize_path
|
7
|
-
|
4
|
+
from typing import Any, Tuple
|
8
5
|
|
9
6
|
# Global tracking for decorator-based loop protection
|
10
7
|
_decorator_call_tracker = {}
|
11
8
|
_decorator_call_tracker_lock = threading.Lock()
|
12
9
|
|
13
10
|
|
11
|
+
def _normalize_key_value(key_field: str, key_value: Any) -> Any:
|
12
|
+
"""Normalize key values, especially paths, so different representations map to the same key."""
|
13
|
+
if key_value is None:
|
14
|
+
return None
|
15
|
+
|
16
|
+
try:
|
17
|
+
if isinstance(key_field, str) and "path" in key_field.lower():
|
18
|
+
from janito.tools.tool_use_tracker import (
|
19
|
+
normalize_path as _norm, # reuse existing normalization
|
20
|
+
)
|
21
|
+
|
22
|
+
if isinstance(key_value, str):
|
23
|
+
return _norm(key_value)
|
24
|
+
if isinstance(key_value, (list, tuple)):
|
25
|
+
return tuple(_norm(v) if isinstance(v, str) else v for v in key_value)
|
26
|
+
except Exception:
|
27
|
+
# Best-effort normalization – fall back to original value
|
28
|
+
pass
|
29
|
+
|
30
|
+
return key_value
|
31
|
+
|
32
|
+
|
33
|
+
def _get_param_value(func, args, kwargs, key_field: str):
|
34
|
+
"""Extract the watched parameter value from args/kwargs using function signature."""
|
35
|
+
if key_field in kwargs:
|
36
|
+
return kwargs[key_field]
|
37
|
+
|
38
|
+
# Handle positional arguments by mapping to parameter names
|
39
|
+
if len(args) > 1: # args[0] is self
|
40
|
+
import inspect
|
41
|
+
|
42
|
+
try:
|
43
|
+
sig = inspect.signature(func)
|
44
|
+
param_names = list(sig.parameters.keys())
|
45
|
+
if key_field in param_names:
|
46
|
+
idx = param_names.index(key_field)
|
47
|
+
if idx < len(args):
|
48
|
+
return args[idx]
|
49
|
+
except Exception:
|
50
|
+
return None
|
51
|
+
|
52
|
+
return None
|
53
|
+
|
54
|
+
|
55
|
+
def _determine_operation_name(func, args, kwargs, key_field: str) -> str:
|
56
|
+
"""Build the operation name for rate limiting, optionally including a normalized key value."""
|
57
|
+
if key_field:
|
58
|
+
raw_value = _get_param_value(func, args, kwargs, key_field)
|
59
|
+
if raw_value is not None:
|
60
|
+
norm_value = _normalize_key_value(key_field, raw_value)
|
61
|
+
return f"{func.__name__}_{norm_value}"
|
62
|
+
return func.__name__
|
63
|
+
|
64
|
+
|
65
|
+
def _check_and_record(
|
66
|
+
op_name: str,
|
67
|
+
current_time: float,
|
68
|
+
time_window: float,
|
69
|
+
max_calls: int,
|
70
|
+
tool_instance: Any,
|
71
|
+
) -> Tuple[bool, str]:
|
72
|
+
"""Check loop limits for op_name and record the call. Returns (exceeded, message)."""
|
73
|
+
with _decorator_call_tracker_lock:
|
74
|
+
# Clean old timestamps
|
75
|
+
if op_name in _decorator_call_tracker:
|
76
|
+
_decorator_call_tracker[op_name] = [
|
77
|
+
ts
|
78
|
+
for ts in _decorator_call_tracker[op_name]
|
79
|
+
if current_time - ts <= time_window
|
80
|
+
]
|
81
|
+
|
82
|
+
# Check limit
|
83
|
+
if (
|
84
|
+
op_name in _decorator_call_tracker
|
85
|
+
and len(_decorator_call_tracker[op_name]) >= max_calls
|
86
|
+
):
|
87
|
+
if all(
|
88
|
+
current_time - ts <= time_window
|
89
|
+
for ts in _decorator_call_tracker[op_name]
|
90
|
+
):
|
91
|
+
msg = (
|
92
|
+
f"Loop protection: Too many {op_name} operations in a short time period "
|
93
|
+
f"({max_calls} calls in {time_window}s). Please try a different approach or wait before retrying."
|
94
|
+
)
|
95
|
+
if hasattr(tool_instance, "report_error"):
|
96
|
+
try:
|
97
|
+
tool_instance.report_error(msg)
|
98
|
+
except Exception:
|
99
|
+
pass
|
100
|
+
return True, msg
|
101
|
+
|
102
|
+
# Record this call
|
103
|
+
if op_name not in _decorator_call_tracker:
|
104
|
+
_decorator_call_tracker[op_name] = []
|
105
|
+
_decorator_call_tracker[op_name].append(current_time)
|
106
|
+
|
107
|
+
return False, ""
|
108
|
+
|
109
|
+
|
14
110
|
def protect_against_loops(
|
15
111
|
max_calls: int = 5, time_window: float = 10.0, key_field: str = None
|
16
112
|
):
|
17
113
|
"""
|
18
114
|
Decorator that adds loop protection to tool run methods.
|
19
115
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
When the configured limits are exceeded, the decorator raises a RuntimeError
|
25
|
-
with a descriptive message. This exception will propagate up the call stack
|
26
|
-
unless caught by a try/except block in the calling code.
|
27
|
-
|
28
|
-
The decorator works by:
|
29
|
-
1. Tracking the number of calls to the decorated function
|
30
|
-
2. Checking if the calls exceed the configured limits
|
31
|
-
3. Raising a RuntimeError if a potential loop is detected
|
32
|
-
4. Allowing the method to proceed normally if the operation is safe
|
33
|
-
|
34
|
-
Args:
|
35
|
-
max_calls (int): Maximum number of calls allowed within the time window.
|
36
|
-
Defaults to 5 calls.
|
37
|
-
time_window (float): Time window in seconds for detecting excessive calls.
|
38
|
-
Defaults to 10.0 seconds.
|
39
|
-
key_field (str, optional): The parameter name to use for key matching instead of function name.
|
40
|
-
If provided, the decorator will track calls based on the value of this
|
41
|
-
parameter rather than the function name. Useful for tools that operate
|
42
|
-
on specific files or resources.
|
43
|
-
|
44
|
-
Example:
|
45
|
-
>>> @protect_against_loops(max_calls=3, time_window=5.0)
|
46
|
-
>>> def run(self, path: str) -> str:
|
47
|
-
>>> # Implementation here
|
48
|
-
>>> pass
|
49
|
-
|
50
|
-
>>> @protect_against_loops(max_calls=10, time_window=30.0)
|
51
|
-
>>> def run(self, file_paths: list) -> str:
|
52
|
-
>>> # Implementation here
|
53
|
-
>>> pass
|
54
|
-
|
55
|
-
>>> @protect_against_loops(max_calls=5, time_window=10.0, key_field='path')
|
56
|
-
>>> def run(self, path: str) -> str:
|
57
|
-
>>> # This will track calls per unique path value
|
58
|
-
>>> pass
|
59
|
-
|
60
|
-
Note:
|
61
|
-
When loop protection is triggered, a RuntimeError will be raised with a
|
62
|
-
descriptive message. This exception will propagate up the call stack
|
63
|
-
unless caught by a try/except block in the calling code.
|
116
|
+
Tracks calls within a sliding time window and prevents excessive repeated operations.
|
117
|
+
When key_field is provided, the limit is applied per unique normalized value of that parameter
|
118
|
+
(e.g., per-path protection for file tools).
|
64
119
|
"""
|
65
120
|
|
66
121
|
def decorator(func):
|
67
122
|
@functools.wraps(func)
|
68
123
|
def wrapper(*args, **kwargs):
|
69
|
-
#
|
124
|
+
# Methods should always have self; if not, execute directly.
|
70
125
|
if not args:
|
71
|
-
# This shouldn't happen in normal usage as methods need self
|
72
126
|
return func(*args, **kwargs)
|
73
127
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
try:
|
85
|
-
sig = inspect.signature(func)
|
86
|
-
param_names = list(sig.parameters.keys())
|
87
|
-
if key_field in param_names:
|
88
|
-
field_index = param_names.index(key_field)
|
89
|
-
if field_index < len(args):
|
90
|
-
key_value = args[field_index]
|
91
|
-
except (ValueError, TypeError):
|
92
|
-
pass
|
93
|
-
|
94
|
-
if key_value is not None:
|
95
|
-
op_name = f"{func.__name__}_{key_value}"
|
96
|
-
else:
|
97
|
-
op_name = func.__name__
|
98
|
-
else:
|
99
|
-
# Use the function name as the operation name
|
100
|
-
op_name = func.__name__
|
101
|
-
|
102
|
-
# Check call limits
|
103
|
-
current_time = time.time()
|
104
|
-
|
105
|
-
with _decorator_call_tracker_lock:
|
106
|
-
# Clean up old entries outside the time window
|
107
|
-
if op_name in _decorator_call_tracker:
|
108
|
-
_decorator_call_tracker[op_name] = [
|
109
|
-
timestamp
|
110
|
-
for timestamp in _decorator_call_tracker[op_name]
|
111
|
-
if current_time - timestamp <= time_window
|
112
|
-
]
|
113
|
-
|
114
|
-
# Check if we're exceeding the limit
|
115
|
-
if op_name in _decorator_call_tracker:
|
116
|
-
if len(_decorator_call_tracker[op_name]) >= max_calls:
|
117
|
-
# Check if all recent calls are within the time window
|
118
|
-
if all(
|
119
|
-
current_time - timestamp <= time_window
|
120
|
-
for timestamp in _decorator_call_tracker[op_name]
|
121
|
-
):
|
122
|
-
# Return loop protection message as string instead of raising exception
|
123
|
-
error_msg = f"Loop protection: Too many {op_name} operations in a short time period ({max_calls} calls in {time_window}s). Please try a different approach or wait before retrying."
|
124
|
-
|
125
|
-
# Try to report the error through the tool's reporting mechanism
|
126
|
-
tool_instance = args[0] if args else None
|
127
|
-
if hasattr(tool_instance, "report_error"):
|
128
|
-
try:
|
129
|
-
tool_instance.report_error(error_msg)
|
130
|
-
except Exception:
|
131
|
-
pass # If reporting fails, we still return the message
|
132
|
-
|
133
|
-
return error_msg
|
134
|
-
|
135
|
-
# Record this call
|
136
|
-
if op_name not in _decorator_call_tracker:
|
137
|
-
_decorator_call_tracker[op_name] = []
|
138
|
-
_decorator_call_tracker[op_name].append(current_time)
|
139
|
-
|
140
|
-
# Proceed with the original function
|
128
|
+
op_name = _determine_operation_name(func, args, kwargs, key_field)
|
129
|
+
exceeded, msg = _check_and_record(
|
130
|
+
op_name=op_name,
|
131
|
+
current_time=time.time(),
|
132
|
+
time_window=time_window,
|
133
|
+
max_calls=max_calls,
|
134
|
+
tool_instance=args[0],
|
135
|
+
)
|
136
|
+
if exceeded:
|
137
|
+
return msg
|
141
138
|
return func(*args, **kwargs)
|
142
139
|
|
143
140
|
return wrapper
|