janito 2.24.0__py3-none-any.whl → 2.25.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/cli/chat_mode/session.py +2 -2
- janito/cli/cli_commands/list_plugins.py +32 -0
- janito/cli/core/getters.py +2 -0
- janito/cli/main_cli.py +6 -2
- janito/cli/single_shot_mode/handler.py +2 -2
- janito/config_manager.py +8 -0
- janito/exceptions.py +19 -1
- janito/plugins/base.py +53 -2
- janito/plugins/config.py +87 -0
- janito/plugins/discovery.py +32 -0
- janito/plugins/manager.py +56 -2
- janito/tools/adapters/local/adapter.py +8 -26
- janito/tools/adapters/local/ask_user.py +1 -1
- janito/tools/adapters/local/copy_file.py +3 -1
- janito/tools/adapters/local/create_directory.py +2 -2
- janito/tools/adapters/local/create_file.py +8 -4
- janito/tools/adapters/local/fetch_url.py +25 -22
- janito/tools/adapters/local/find_files.py +3 -2
- janito/tools/adapters/local/get_file_outline/core.py +3 -1
- janito/tools/adapters/local/get_file_outline/search_outline.py +1 -1
- janito/tools/adapters/local/move_file.py +3 -2
- janito/tools/adapters/local/open_html_in_browser.py +1 -1
- janito/tools/adapters/local/open_url.py +1 -1
- janito/tools/adapters/local/python_file_run.py +2 -0
- janito/tools/adapters/local/read_chart.py +61 -54
- janito/tools/adapters/local/read_files.py +4 -3
- janito/tools/adapters/local/remove_directory.py +2 -0
- janito/tools/adapters/local/remove_file.py +3 -3
- janito/tools/adapters/local/run_powershell_command.py +1 -0
- janito/tools/adapters/local/search_text/core.py +3 -2
- janito/tools/adapters/local/validate_file_syntax/core.py +3 -1
- janito/tools/adapters/local/view_file.py +3 -1
- janito/tools/loop_protection_decorator.py +64 -25
- janito/tools/path_utils.py +39 -0
- janito/tools/tools_adapter.py +68 -22
- {janito-2.24.0.dist-info → janito-2.25.0.dist-info}/METADATA +1 -1
- {janito-2.24.0.dist-info → janito-2.25.0.dist-info}/RECORD +46 -39
- janito-2.25.0.dist-info/top_level.txt +2 -0
- janito-coder/janito_coder/__init__.py +9 -0
- janito-coder/janito_coder/plugins/__init__.py +27 -0
- janito-coder/janito_coder/plugins/code_navigator.py +618 -0
- janito-coder/janito_coder/plugins/git_analyzer.py +273 -0
- janito-coder/pyproject.toml +347 -0
- janito-2.24.0.dist-info/top_level.txt +0 -1
- {janito-2.24.0.dist-info → janito-2.25.0.dist-info}/WHEEL +0 -0
- {janito-2.24.0.dist-info → janito-2.25.0.dist-info}/entry_points.txt +0 -0
- {janito-2.24.0.dist-info → janito-2.25.0.dist-info}/licenses/LICENSE +0 -0
janito/cli/chat_mode/session.py
CHANGED
@@ -116,12 +116,12 @@ class ChatSession:
|
|
116
116
|
def _select_profile_and_role(self, args, role):
|
117
117
|
profile = getattr(args, "profile", None) if args is not None else None
|
118
118
|
role_arg = getattr(args, "role", None) if args is not None else None
|
119
|
-
python_profile = getattr(args, "
|
119
|
+
python_profile = getattr(args, "developer", False) if args is not None else False
|
120
120
|
market_profile = getattr(args, "market", False) if args is not None else False
|
121
121
|
profile_system_prompt = None
|
122
122
|
no_tools_mode = False
|
123
123
|
|
124
|
-
# Handle --
|
124
|
+
# Handle --developer flag
|
125
125
|
if python_profile and profile is None and role_arg is None:
|
126
126
|
profile = "Developer with Python Tools"
|
127
127
|
|
@@ -24,6 +24,38 @@ def handle_list_plugins(args: argparse.Namespace) -> None:
|
|
24
24
|
print("Search paths:")
|
25
25
|
print(f" - {os.getcwd()}/plugins")
|
26
26
|
print(f" - {os.path.expanduser('~')}/.janito/plugins")
|
27
|
+
elif getattr(args, 'list_resources', False):
|
28
|
+
# List all resources from loaded plugins
|
29
|
+
manager = PluginManager()
|
30
|
+
all_resources = manager.list_all_resources()
|
31
|
+
|
32
|
+
if all_resources:
|
33
|
+
print("Plugin Resources:")
|
34
|
+
for plugin_name, resources in all_resources.items():
|
35
|
+
metadata = manager.get_plugin_metadata(plugin_name)
|
36
|
+
print(f"\n{plugin_name} v{metadata.version if metadata else 'unknown'}:")
|
37
|
+
|
38
|
+
# Group resources by type
|
39
|
+
tools = [r for r in resources if r['type'] == 'tool']
|
40
|
+
commands = [r for r in resources if r['type'] == 'command']
|
41
|
+
configs = [r for r in resources if r['type'] == 'config']
|
42
|
+
|
43
|
+
if tools:
|
44
|
+
print(" Tools:")
|
45
|
+
for tool in tools:
|
46
|
+
print(f" - {tool['name']}: {tool['description']}")
|
47
|
+
|
48
|
+
if commands:
|
49
|
+
print(" Commands:")
|
50
|
+
for cmd in commands:
|
51
|
+
print(f" - {cmd['name']}: {cmd['description']}")
|
52
|
+
|
53
|
+
if configs:
|
54
|
+
print(" Configuration:")
|
55
|
+
for config in configs:
|
56
|
+
print(f" - {config['name']}: {config['description']}")
|
57
|
+
else:
|
58
|
+
print("No plugins loaded")
|
27
59
|
else:
|
28
60
|
# List loaded plugins
|
29
61
|
manager = PluginManager()
|
janito/cli/core/getters.py
CHANGED
@@ -26,6 +26,7 @@ GETTER_KEYS = [
|
|
26
26
|
"list_providers_region",
|
27
27
|
"list_plugins",
|
28
28
|
"list_plugins_available",
|
29
|
+
"list_resources",
|
29
30
|
]
|
30
31
|
|
31
32
|
|
@@ -55,6 +56,7 @@ def handle_getter(args, config_mgr=None):
|
|
55
56
|
"region_info": partial(handle_region_info, args),
|
56
57
|
"list_providers_region": partial(handle_list_providers_region, args),
|
57
58
|
"list_plugins": partial(handle_list_plugins, args),
|
59
|
+
"list_resources": partial(handle_list_plugins, args),
|
58
60
|
}
|
59
61
|
for arg in GETTER_KEYS:
|
60
62
|
if getattr(args, arg, False) and arg in GETTER_DISPATCH:
|
janito/cli/main_cli.py
CHANGED
@@ -38,7 +38,7 @@ definition = [
|
|
38
38
|
},
|
39
39
|
),
|
40
40
|
(
|
41
|
-
["--
|
41
|
+
["--developer"],
|
42
42
|
{
|
43
43
|
"action": "store_true",
|
44
44
|
"help": "Start with the Python developer profile (equivalent to --profile 'Developer with Python Tools')",
|
@@ -230,6 +230,10 @@ definition = [
|
|
230
230
|
["--list-plugins-available"],
|
231
231
|
{"action": "store_true", "help": "List all available plugins"},
|
232
232
|
),
|
233
|
+
(
|
234
|
+
["--list-resources"],
|
235
|
+
{"action": "store_true", "help": "List all resources (tools, commands, config) from loaded plugins"},
|
236
|
+
),
|
233
237
|
]
|
234
238
|
|
235
239
|
MODIFIER_KEYS = [
|
@@ -237,7 +241,7 @@ MODIFIER_KEYS = [
|
|
237
241
|
"model",
|
238
242
|
"role",
|
239
243
|
"profile",
|
240
|
-
"
|
244
|
+
"developer",
|
241
245
|
"market",
|
242
246
|
"system",
|
243
247
|
"temperature",
|
@@ -24,9 +24,9 @@ class PromptHandler:
|
|
24
24
|
self.llm_driver_config = llm_driver_config
|
25
25
|
self.role = role
|
26
26
|
# Instantiate agent together with prompt handler using the shared helper
|
27
|
-
# Handle --
|
27
|
+
# Handle --developer and --market flags for single shot mode
|
28
28
|
profile = getattr(args, "profile", None)
|
29
|
-
if profile is None and getattr(args, "
|
29
|
+
if profile is None and getattr(args, "developer", False):
|
30
30
|
profile = "Developer with Python Tools"
|
31
31
|
if profile is None and getattr(args, "market", False):
|
32
32
|
profile = "Market Analyst"
|
janito/config_manager.py
CHANGED
@@ -64,6 +64,14 @@ class ConfigManager:
|
|
64
64
|
plugin_manager.load_plugins_from_config({"plugins": plugins_config})
|
65
65
|
except Exception as e:
|
66
66
|
print(f"Warning: Failed to load plugins from config: {e}")
|
67
|
+
else:
|
68
|
+
# Try loading from user config directory
|
69
|
+
try:
|
70
|
+
from janito.plugins.manager import PluginManager
|
71
|
+
plugin_manager = PluginManager()
|
72
|
+
plugin_manager.load_plugins_from_user_config()
|
73
|
+
except Exception as e:
|
74
|
+
print(f"Warning: Failed to load plugins from user config: {e}")
|
67
75
|
|
68
76
|
# Load disabled tools from config - skip during startup to avoid circular imports
|
69
77
|
# This will be handled by the CLI when needed
|
janito/exceptions.py
CHANGED
@@ -9,7 +9,25 @@ class ToolCallException(Exception):
|
|
9
9
|
self.error = error
|
10
10
|
self.arguments = arguments
|
11
11
|
self.original_exception = exception
|
12
|
-
|
12
|
+
|
13
|
+
# Build detailed error message
|
14
|
+
details = []
|
15
|
+
details.append(f"ToolCallException: {tool_name}: {error}")
|
16
|
+
|
17
|
+
if arguments is not None:
|
18
|
+
details.append(f"Arguments received: {arguments}")
|
19
|
+
if isinstance(arguments, dict):
|
20
|
+
details.append("Parameters:")
|
21
|
+
for key, value in arguments.items():
|
22
|
+
details.append(f" {key}: {repr(value)} ({type(value).__name__})")
|
23
|
+
elif isinstance(arguments, (list, tuple)):
|
24
|
+
details.append(f"Positional arguments: {arguments}")
|
25
|
+
for i, value in enumerate(arguments):
|
26
|
+
details.append(f" [{i}]: {repr(value)} ({type(value).__name__})")
|
27
|
+
else:
|
28
|
+
details.append(f"Single argument: {repr(arguments)} ({type(arguments).__name__})")
|
29
|
+
|
30
|
+
super().__init__("\n".join(details))
|
13
31
|
|
14
32
|
|
15
33
|
class MissingProviderSelectionException(Exception):
|
janito/plugins/base.py
CHANGED
@@ -4,7 +4,7 @@ Base classes for janito plugins.
|
|
4
4
|
|
5
5
|
from abc import ABC, abstractmethod
|
6
6
|
from dataclasses import dataclass
|
7
|
-
from typing import Dict, Any, List, Optional, Type
|
7
|
+
from typing import Dict, Any, List, Optional, Type, Union
|
8
8
|
from janito.tools.tool_base import ToolBase
|
9
9
|
|
10
10
|
|
@@ -24,6 +24,15 @@ class PluginMetadata:
|
|
24
24
|
self.dependencies = []
|
25
25
|
|
26
26
|
|
27
|
+
@dataclass
|
28
|
+
class PluginResource:
|
29
|
+
"""Represents a resource provided by a plugin."""
|
30
|
+
name: str
|
31
|
+
type: str # "tool", "command", "config"
|
32
|
+
description: str
|
33
|
+
schema: Optional[Dict[str, Any]] = None
|
34
|
+
|
35
|
+
|
27
36
|
class Plugin(ABC):
|
28
37
|
"""
|
29
38
|
Base class for all janito plugins.
|
@@ -90,4 +99,46 @@ class Plugin(ABC):
|
|
90
99
|
Returns:
|
91
100
|
True if configuration is valid
|
92
101
|
"""
|
93
|
-
return True
|
102
|
+
return True
|
103
|
+
|
104
|
+
def get_resources(self) -> List[PluginResource]:
|
105
|
+
"""
|
106
|
+
Return a list of resources provided by this plugin.
|
107
|
+
|
108
|
+
Returns:
|
109
|
+
List of PluginResource objects describing the resources
|
110
|
+
"""
|
111
|
+
resources = []
|
112
|
+
|
113
|
+
# Add tools as resources
|
114
|
+
for tool_class in self.get_tools():
|
115
|
+
tool_instance = tool_class()
|
116
|
+
tool_name = getattr(tool_instance, 'tool_name', tool_class.__name__)
|
117
|
+
tool_desc = getattr(tool_class, '__doc__', f"Tool: {tool_name}")
|
118
|
+
resources.append(PluginResource(
|
119
|
+
name=tool_name,
|
120
|
+
type="tool",
|
121
|
+
description=tool_desc or f"Tool provided by {self.metadata.name}"
|
122
|
+
))
|
123
|
+
|
124
|
+
# Add commands as resources
|
125
|
+
commands = self.get_commands()
|
126
|
+
for cmd_name, cmd_handler in commands.items():
|
127
|
+
cmd_desc = getattr(cmd_handler, '__doc__', f"Command: {cmd_name}")
|
128
|
+
resources.append(PluginResource(
|
129
|
+
name=cmd_name,
|
130
|
+
type="command",
|
131
|
+
description=cmd_desc or f"Command provided by {self.metadata.name}"
|
132
|
+
))
|
133
|
+
|
134
|
+
# Add config schema as resource
|
135
|
+
config_schema = self.get_config_schema()
|
136
|
+
if config_schema:
|
137
|
+
resources.append(PluginResource(
|
138
|
+
name=f"{self.metadata.name}_config",
|
139
|
+
type="config",
|
140
|
+
description=f"Configuration schema for {self.metadata.name} plugin",
|
141
|
+
schema=config_schema
|
142
|
+
))
|
143
|
+
|
144
|
+
return resources
|
janito/plugins/config.py
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
"""
|
2
|
+
Configuration management for plugins using user directory.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import json
|
6
|
+
import os
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Dict, Any, Optional
|
9
|
+
|
10
|
+
|
11
|
+
def get_user_config_dir() -> Path:
|
12
|
+
"""Get the user configuration directory."""
|
13
|
+
return Path.home() / ".janito"
|
14
|
+
|
15
|
+
|
16
|
+
def get_plugins_config_path() -> Path:
|
17
|
+
"""Get the path to the plugins configuration file."""
|
18
|
+
return get_user_config_dir() / "plugins.json"
|
19
|
+
|
20
|
+
|
21
|
+
def load_plugins_config() -> Dict[str, Any]:
|
22
|
+
"""
|
23
|
+
Load plugins configuration from user directory.
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
Dict containing plugins configuration
|
27
|
+
"""
|
28
|
+
config_path = get_plugins_config_path()
|
29
|
+
|
30
|
+
if not config_path.exists():
|
31
|
+
# Create default config if it doesn't exist
|
32
|
+
default_config = {
|
33
|
+
"plugins": {
|
34
|
+
"paths": [
|
35
|
+
str(Path.home() / ".janito" / "plugins"),
|
36
|
+
"./plugins"
|
37
|
+
],
|
38
|
+
"load": {}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
# Ensure directory exists
|
43
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
44
|
+
|
45
|
+
# Save default config
|
46
|
+
with open(config_path, 'w') as f:
|
47
|
+
json.dump(default_config, f, indent=2)
|
48
|
+
|
49
|
+
return default_config
|
50
|
+
|
51
|
+
try:
|
52
|
+
with open(config_path, 'r') as f:
|
53
|
+
return json.load(f)
|
54
|
+
except (json.JSONDecodeError, IOError) as e:
|
55
|
+
print(f"Warning: Failed to load plugins config from {config_path}: {e}")
|
56
|
+
return {"plugins": {"paths": [], "load": {}}}
|
57
|
+
|
58
|
+
|
59
|
+
def save_plugins_config(config: Dict[str, Any]) -> bool:
|
60
|
+
"""
|
61
|
+
Save plugins configuration to user directory.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
config: Configuration dict to save
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
True if saved successfully
|
68
|
+
"""
|
69
|
+
config_path = get_plugins_config_path()
|
70
|
+
|
71
|
+
try:
|
72
|
+
# Ensure directory exists
|
73
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
74
|
+
|
75
|
+
with open(config_path, 'w') as f:
|
76
|
+
json.dump(config, f, indent=2)
|
77
|
+
return True
|
78
|
+
except IOError as e:
|
79
|
+
print(f"Error: Failed to save plugins config to {config_path}: {e}")
|
80
|
+
return False
|
81
|
+
|
82
|
+
|
83
|
+
def get_user_plugins_dir() -> Path:
|
84
|
+
"""Get the user plugins directory."""
|
85
|
+
plugins_dir = get_user_config_dir() / "plugins"
|
86
|
+
plugins_dir.mkdir(parents=True, exist_ok=True)
|
87
|
+
return plugins_dir
|
janito/plugins/discovery.py
CHANGED
@@ -1,5 +1,26 @@
|
|
1
1
|
"""
|
2
2
|
Plugin discovery utilities.
|
3
|
+
|
4
|
+
Plugins can be provided in several formats:
|
5
|
+
|
6
|
+
1. Single Python file: A .py file containing a Plugin class
|
7
|
+
Example: plugins/my_plugin.py
|
8
|
+
|
9
|
+
2. Python package directory: A directory with __init__.py or plugin.py
|
10
|
+
Example: plugins/my_plugin/__init__.py
|
11
|
+
Example: plugins/my_plugin/plugin.py
|
12
|
+
|
13
|
+
3. Installed Python package: An installed package with a Plugin class
|
14
|
+
Example: pip install janito-plugin-example
|
15
|
+
|
16
|
+
4. ZIP file: A .zip file containing a Python package structure
|
17
|
+
Example: plugins/my_plugin.zip (containing package structure)
|
18
|
+
|
19
|
+
The plugin discovery system searches these locations in order:
|
20
|
+
- Current working directory/plugins/
|
21
|
+
- ~/.janito/plugins/
|
22
|
+
- Python installation share/janito/plugins/
|
23
|
+
- Any additional paths specified via configuration
|
3
24
|
"""
|
4
25
|
|
5
26
|
import os
|
@@ -19,6 +40,12 @@ def discover_plugins(plugin_name: str, search_paths: List[Path] = None) -> Optio
|
|
19
40
|
"""
|
20
41
|
Discover and load a plugin by name.
|
21
42
|
|
43
|
+
Supports multiple plugin formats:
|
44
|
+
- Single .py files
|
45
|
+
- Python package directories
|
46
|
+
- Installed Python packages
|
47
|
+
- ZIP files containing packages
|
48
|
+
|
22
49
|
Args:
|
23
50
|
plugin_name: Name of the plugin to discover
|
24
51
|
search_paths: List of directories to search for plugins
|
@@ -126,6 +153,11 @@ def list_available_plugins(search_paths: List[Path] = None) -> List[str]:
|
|
126
153
|
"""
|
127
154
|
List all available plugins in search paths.
|
128
155
|
|
156
|
+
Scans for plugins in multiple formats:
|
157
|
+
- .py files (excluding __init__.py)
|
158
|
+
- Directories with __init__.py or plugin.py
|
159
|
+
- Any valid plugin structure in search paths
|
160
|
+
|
129
161
|
Args:
|
130
162
|
search_paths: List of directories to search for plugins
|
131
163
|
|
janito/plugins/manager.py
CHANGED
@@ -12,6 +12,7 @@ import logging
|
|
12
12
|
|
13
13
|
from .base import Plugin, PluginMetadata
|
14
14
|
from .discovery import discover_plugins
|
15
|
+
from .config import load_plugins_config, get_user_plugins_dir
|
15
16
|
from janito.tools.adapters.local import LocalToolsAdapter
|
16
17
|
|
17
18
|
logger = logging.getLogger(__name__)
|
@@ -158,6 +159,14 @@ class PluginManager:
|
|
158
159
|
else:
|
159
160
|
self.load_plugin(plugin_name, plugin_config)
|
160
161
|
|
162
|
+
def load_plugins_from_user_config(self) -> None:
|
163
|
+
"""
|
164
|
+
Load plugins from user configuration directory.
|
165
|
+
Uses ~/.janito/plugins.json instead of janito.json
|
166
|
+
"""
|
167
|
+
config = load_plugins_config()
|
168
|
+
self.load_plugins_from_config(config)
|
169
|
+
|
161
170
|
def reload_plugin(self, plugin_name: str) -> bool:
|
162
171
|
"""
|
163
172
|
Reload a plugin.
|
@@ -180,6 +189,51 @@ class PluginManager:
|
|
180
189
|
'metadata': plugin.metadata,
|
181
190
|
'tools': [tool.__name__ for tool in plugin.get_tools()],
|
182
191
|
'commands': list(plugin.get_commands().keys()),
|
183
|
-
'config': self.plugin_configs.get(name, {})
|
192
|
+
'config': self.plugin_configs.get(name, {}),
|
193
|
+
'resources': [
|
194
|
+
{
|
195
|
+
'name': resource.name,
|
196
|
+
'type': resource.type,
|
197
|
+
'description': resource.description,
|
198
|
+
'schema': resource.schema
|
199
|
+
}
|
200
|
+
for resource in plugin.get_resources()
|
201
|
+
]
|
202
|
+
}
|
203
|
+
return info
|
204
|
+
|
205
|
+
def get_plugin_resources(self, plugin_name: str) -> List[Dict[str, Any]]:
|
206
|
+
"""
|
207
|
+
Get resources provided by a specific plugin.
|
208
|
+
|
209
|
+
Args:
|
210
|
+
plugin_name: Name of the plugin
|
211
|
+
|
212
|
+
Returns:
|
213
|
+
List of resource dictionaries
|
214
|
+
"""
|
215
|
+
plugin = self.plugins.get(plugin_name)
|
216
|
+
if not plugin:
|
217
|
+
return []
|
218
|
+
|
219
|
+
return [
|
220
|
+
{
|
221
|
+
'name': resource.name,
|
222
|
+
'type': resource.type,
|
223
|
+
'description': resource.description,
|
224
|
+
'schema': resource.schema
|
184
225
|
}
|
185
|
-
|
226
|
+
for resource in plugin.get_resources()
|
227
|
+
]
|
228
|
+
|
229
|
+
def list_all_resources(self) -> Dict[str, List[Dict[str, Any]]]:
|
230
|
+
"""
|
231
|
+
List all resources from all loaded plugins.
|
232
|
+
|
233
|
+
Returns:
|
234
|
+
Dict mapping plugin names to their resources
|
235
|
+
"""
|
236
|
+
all_resources = {}
|
237
|
+
for plugin_name in self.plugins:
|
238
|
+
all_resources[plugin_name] = self.get_plugin_resources(plugin_name)
|
239
|
+
return all_resources
|
@@ -140,18 +140,18 @@ class LocalToolsAdapter(ToolsAdapter):
|
|
140
140
|
def execute_tool(self, name: str, **kwargs):
|
141
141
|
"""
|
142
142
|
Execute a tool with proper error handling.
|
143
|
-
|
143
|
+
|
144
144
|
This method extends the base execute_tool functionality by adding
|
145
145
|
error handling for RuntimeError exceptions that may be raised by
|
146
146
|
tools with loop protection decorators.
|
147
|
-
|
147
|
+
|
148
148
|
Args:
|
149
149
|
name: The name of the tool to execute
|
150
150
|
**kwargs: Arguments to pass to the tool
|
151
|
-
|
151
|
+
|
152
152
|
Returns:
|
153
153
|
The result of the tool execution
|
154
|
-
|
154
|
+
|
155
155
|
Raises:
|
156
156
|
ToolCallException: If tool execution fails for any reason
|
157
157
|
ValueError: If the tool is not found or not allowed
|
@@ -160,30 +160,12 @@ class LocalToolsAdapter(ToolsAdapter):
|
|
160
160
|
tool = self.get_tool(name)
|
161
161
|
if not tool:
|
162
162
|
raise ValueError(f"Tool '{name}' not found or not allowed.")
|
163
|
-
|
163
|
+
|
164
164
|
# Record tool usage
|
165
165
|
self.tool_tracker.record(name, kwargs)
|
166
|
-
|
167
|
-
# Execute the tool
|
168
|
-
|
169
|
-
return super().execute_tool(name, **kwargs)
|
170
|
-
except RuntimeError as e:
|
171
|
-
# Check if this is a loop protection error
|
172
|
-
if "Loop protection:" in str(e):
|
173
|
-
# Re-raise as ToolCallException to maintain consistent error flow
|
174
|
-
from janito.exceptions import ToolCallException
|
175
|
-
raise ToolCallException(
|
176
|
-
name,
|
177
|
-
f"Loop protection triggered: {str(e)}",
|
178
|
-
arguments=kwargs
|
179
|
-
)
|
180
|
-
# Re-raise other RuntimeError exceptions as ToolCallException
|
181
|
-
from janito.exceptions import ToolCallException
|
182
|
-
raise ToolCallException(
|
183
|
-
name,
|
184
|
-
f"Runtime error during tool execution: {str(e)}",
|
185
|
-
arguments=kwargs
|
186
|
-
)
|
166
|
+
|
167
|
+
# Execute the tool using execute_by_name which handles loop protection
|
168
|
+
return self.execute_by_name(name, arguments=kwargs)
|
187
169
|
|
188
170
|
# ------------------------------------------------------------------
|
189
171
|
# Convenience methods
|
@@ -32,7 +32,7 @@ class AskUserTool(ToolBase):
|
|
32
32
|
permissions = ToolPermissions(read=True)
|
33
33
|
tool_name = "ask_user"
|
34
34
|
|
35
|
-
@protect_against_loops(max_calls=5, time_window=10.0)
|
35
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="question")
|
36
36
|
def run(self, question: str) -> str:
|
37
37
|
|
38
38
|
print() # Print an empty line before the question panel
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import os
|
2
|
+
from janito.tools.path_utils import expand_path
|
2
3
|
import shutil
|
3
4
|
from typing import List, Union
|
4
5
|
from janito.tools.adapters.local.adapter import register_local_tool
|
@@ -26,7 +27,8 @@ class CopyFileTool(ToolBase):
|
|
26
27
|
tool_name = "copy_file"
|
27
28
|
|
28
29
|
def run(self, sources: str, target: str, overwrite: bool = False) -> str:
|
29
|
-
source_list = [src for src in sources.split() if src]
|
30
|
+
source_list = [expand_path(src) for src in sources.split() if src]
|
31
|
+
target = expand_path(target)
|
30
32
|
messages = []
|
31
33
|
if len(source_list) > 1:
|
32
34
|
if not os.path.isdir(target):
|
@@ -5,6 +5,7 @@ from janito.tools.tool_base import ToolBase, ToolPermissions
|
|
5
5
|
from janito.report_events import ReportAction
|
6
6
|
from janito.i18n import tr
|
7
7
|
import os
|
8
|
+
from janito.tools.path_utils import expand_path
|
8
9
|
|
9
10
|
|
10
11
|
@register_local_tool
|
@@ -23,8 +24,7 @@ class CreateDirectoryTool(ToolBase):
|
|
23
24
|
tool_name = "create_directory"
|
24
25
|
|
25
26
|
def run(self, path: str) -> str:
|
26
|
-
|
27
|
-
# Using path as is
|
27
|
+
path = expand_path(path)
|
28
28
|
disp_path = display_path(path)
|
29
29
|
self.report_action(
|
30
30
|
tr("📁 Create directory '{disp_path}' ...", disp_path=disp_path),
|
@@ -1,11 +1,12 @@
|
|
1
1
|
import os
|
2
|
+
from janito.tools.path_utils import expand_path
|
2
3
|
from janito.tools.adapters.local.adapter import register_local_tool
|
3
4
|
|
4
5
|
from janito.tools.tool_utils import display_path
|
5
6
|
from janito.tools.tool_base import ToolBase, ToolPermissions
|
6
7
|
from janito.report_events import ReportAction
|
7
8
|
from janito.i18n import tr
|
8
|
-
|
9
|
+
from janito.tools.loop_protection_decorator import protect_against_loops
|
9
10
|
|
10
11
|
from janito.tools.adapters.local.validate_file_syntax.core import validate_file_syntax
|
11
12
|
|
@@ -24,15 +25,18 @@ class CreateFileTool(ToolBase):
|
|
24
25
|
- "✅ Successfully created the file at ..."
|
25
26
|
|
26
27
|
Note: Syntax validation is automatically performed after this operation.
|
28
|
+
|
29
|
+
Security: This tool includes loop protection to prevent excessive file creation operations.
|
30
|
+
Maximum 5 calls per 10 seconds for the same file path.
|
27
31
|
"""
|
28
32
|
|
29
33
|
permissions = ToolPermissions(write=True)
|
30
34
|
tool_name = "create_file"
|
31
35
|
|
36
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="path")
|
32
37
|
def run(self, path: str, content: str, overwrite: bool = False) -> str:
|
33
|
-
|
34
|
-
disp_path = display_path(
|
35
|
-
path = expanded_path
|
38
|
+
path = expand_path(path)
|
39
|
+
disp_path = display_path(path)
|
36
40
|
if os.path.exists(path) and not overwrite:
|
37
41
|
try:
|
38
42
|
with open(path, "r", encoding="utf-8", errors="replace") as f:
|