janito 1.14.2__py3-none-any.whl → 2.0.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 -1
- janito/__main__.py +1 -1
- janito/agent/setup_agent.py +139 -0
- janito/agent/templates/profiles/{system_prompt_template_base.txt.j2 → system_prompt_template_main.txt.j2} +1 -1
- janito/cli/__init__.py +9 -0
- janito/cli/chat_mode/bindings.py +37 -0
- janito/cli/chat_mode/chat_entry.py +23 -0
- janito/cli/chat_mode/prompt_style.py +19 -0
- janito/cli/chat_mode/session.py +272 -0
- janito/{shell/prompt/completer.py → cli/chat_mode/shell/autocomplete.py} +7 -6
- janito/cli/chat_mode/shell/commands/__init__.py +55 -0
- janito/cli/chat_mode/shell/commands/base.py +9 -0
- janito/cli/chat_mode/shell/commands/clear.py +12 -0
- janito/{shell → cli/chat_mode/shell}/commands/conversation_restart.py +34 -30
- janito/cli/chat_mode/shell/commands/edit.py +25 -0
- janito/cli/chat_mode/shell/commands/help.py +16 -0
- janito/cli/chat_mode/shell/commands/history_view.py +93 -0
- janito/cli/chat_mode/shell/commands/lang.py +25 -0
- janito/cli/chat_mode/shell/commands/last.py +137 -0
- janito/cli/chat_mode/shell/commands/livelogs.py +49 -0
- janito/cli/chat_mode/shell/commands/multi.py +51 -0
- janito/cli/chat_mode/shell/commands/prompt.py +64 -0
- janito/cli/chat_mode/shell/commands/role.py +36 -0
- janito/cli/chat_mode/shell/commands/session.py +40 -0
- janito/{shell → cli/chat_mode/shell}/commands/session_control.py +2 -2
- janito/cli/chat_mode/shell/commands/termweb_log.py +92 -0
- janito/cli/chat_mode/shell/commands/tools.py +32 -0
- janito/{shell → cli/chat_mode/shell}/commands/utility.py +4 -7
- janito/{shell → cli/chat_mode/shell}/commands/verbose.py +5 -5
- janito/cli/chat_mode/shell/session/__init__.py +1 -0
- janito/{shell → cli/chat_mode/shell}/session/manager.py +9 -1
- janito/cli/chat_mode/toolbar.py +90 -0
- janito/cli/cli_commands/list_models.py +35 -0
- janito/cli/cli_commands/list_providers.py +9 -0
- janito/cli/cli_commands/list_tools.py +53 -0
- janito/cli/cli_commands/model_selection.py +50 -0
- janito/cli/cli_commands/model_utils.py +84 -0
- janito/cli/cli_commands/set_api_key.py +19 -0
- janito/cli/cli_commands/show_config.py +51 -0
- janito/cli/cli_commands/show_system_prompt.py +62 -0
- janito/cli/config.py +28 -0
- janito/cli/console.py +3 -0
- janito/cli/core/__init__.py +4 -0
- janito/cli/core/event_logger.py +59 -0
- janito/cli/core/getters.py +31 -0
- janito/cli/core/runner.py +141 -0
- janito/cli/core/setters.py +174 -0
- janito/cli/core/unsetters.py +54 -0
- janito/cli/main.py +8 -196
- janito/cli/main_cli.py +312 -0
- janito/cli/prompt_core.py +230 -0
- janito/cli/prompt_handler.py +6 -0
- janito/cli/rich_terminal_reporter.py +101 -0
- janito/cli/single_shot_mode/__init__.py +6 -0
- janito/cli/single_shot_mode/handler.py +137 -0
- janito/cli/termweb_starter.py +73 -24
- janito/cli/utils.py +25 -0
- janito/cli/verbose_output.py +196 -0
- janito/config.py +5 -0
- janito/config_manager.py +110 -0
- janito/conversation_history.py +30 -0
- janito/{agent/tools_utils/dir_walk_utils.py → dir_walk_utils.py} +3 -2
- janito/driver_events.py +98 -0
- janito/drivers/anthropic/driver.py +113 -0
- janito/drivers/azure_openai/driver.py +36 -0
- janito/drivers/driver_registry.py +33 -0
- janito/drivers/google_genai/driver.py +54 -0
- janito/drivers/google_genai/schema_generator.py +67 -0
- janito/drivers/mistralai/driver.py +41 -0
- janito/drivers/openai/driver.py +334 -0
- janito/event_bus/__init__.py +2 -0
- janito/event_bus/bus.py +68 -0
- janito/event_bus/event.py +15 -0
- janito/event_bus/handler.py +31 -0
- janito/event_bus/queue_bus.py +57 -0
- janito/exceptions.py +23 -0
- janito/formatting_token.py +54 -0
- janito/i18n/pt.py +1 -0
- janito/llm/__init__.py +5 -0
- janito/llm/agent.py +443 -0
- janito/llm/auth.py +62 -0
- janito/llm/driver.py +239 -0
- janito/llm/driver_config.py +34 -0
- janito/llm/driver_config_builder.py +34 -0
- janito/llm/driver_input.py +12 -0
- janito/llm/message_parts.py +60 -0
- janito/llm/model.py +38 -0
- janito/llm/provider.py +187 -0
- janito/perf_singleton.py +3 -0
- janito/performance_collector.py +167 -0
- janito/provider_config.py +98 -0
- janito/provider_registry.py +152 -0
- janito/providers/__init__.py +7 -0
- janito/providers/anthropic/model_info.py +22 -0
- janito/providers/anthropic/provider.py +65 -0
- janito/providers/azure_openai/model_info.py +15 -0
- janito/providers/azure_openai/provider.py +72 -0
- janito/providers/deepseek/__init__.py +1 -0
- janito/providers/deepseek/model_info.py +16 -0
- janito/providers/deepseek/provider.py +91 -0
- janito/providers/google/__init__.py +1 -0
- janito/providers/google/model_info.py +40 -0
- janito/providers/google/provider.py +69 -0
- janito/providers/mistralai/model_info.py +37 -0
- janito/providers/mistralai/provider.py +69 -0
- janito/providers/openai/__init__.py +1 -0
- janito/providers/openai/model_info.py +137 -0
- janito/providers/openai/provider.py +107 -0
- janito/providers/openai/schema_generator.py +63 -0
- janito/providers/provider_static_info.py +21 -0
- janito/providers/registry.py +26 -0
- janito/report_events.py +38 -0
- janito/termweb/app.py +1 -1
- janito/tools/__init__.py +16 -0
- janito/tools/adapters/__init__.py +1 -0
- janito/tools/adapters/local/__init__.py +54 -0
- janito/tools/adapters/local/adapter.py +92 -0
- janito/{agent/tools → tools/adapters/local}/ask_user.py +30 -13
- janito/tools/adapters/local/copy_file.py +84 -0
- janito/{agent/tools → tools/adapters/local}/create_directory.py +11 -10
- janito/tools/adapters/local/create_file.py +82 -0
- janito/tools/adapters/local/delete_text_in_file.py +136 -0
- janito/{agent/tools → tools/adapters/local}/fetch_url.py +18 -19
- janito/tools/adapters/local/find_files.py +140 -0
- janito/tools/adapters/local/get_file_outline/core.py +151 -0
- janito/{agent/tools → tools/adapters/local}/get_file_outline/python_outline.py +125 -0
- janito/tools/adapters/local/get_file_outline/python_outline_v2.py +156 -0
- janito/{agent/tools → tools/adapters/local}/get_file_outline/search_outline.py +12 -7
- janito/{agent/tools → tools/adapters/local}/move_file.py +13 -9
- janito/{agent/tools → tools/adapters/local}/open_url.py +7 -5
- janito/tools/adapters/local/python_code_run.py +165 -0
- janito/tools/adapters/local/python_command_run.py +163 -0
- janito/tools/adapters/local/python_file_run.py +162 -0
- janito/{agent/tools → tools/adapters/local}/remove_directory.py +15 -9
- janito/{agent/tools → tools/adapters/local}/remove_file.py +17 -14
- janito/{agent/tools → tools/adapters/local}/replace_text_in_file.py +27 -22
- janito/tools/adapters/local/run_bash_command.py +176 -0
- janito/tools/adapters/local/run_powershell_command.py +219 -0
- janito/{agent/tools → tools/adapters/local}/search_text/core.py +32 -12
- janito/{agent/tools → tools/adapters/local}/search_text/match_lines.py +13 -4
- janito/{agent/tools → tools/adapters/local}/search_text/pattern_utils.py +12 -4
- janito/{agent/tools → tools/adapters/local}/search_text/traverse_directory.py +15 -2
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/core.py +12 -11
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/css_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/html_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/js_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/json_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/markdown_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/ps1_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/python_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/xml_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/yaml_validator.py +1 -1
- janito/{agent/tools/get_lines.py → tools/adapters/local/view_file.py} +45 -27
- janito/tools/inspect_registry.py +17 -0
- janito/tools/tool_base.py +105 -0
- janito/tools/tool_events.py +58 -0
- janito/tools/tool_run_exception.py +12 -0
- janito/{agent → tools}/tool_use_tracker.py +2 -4
- janito/{agent/tools_utils/utils.py → tools/tool_utils.py} +18 -9
- janito/tools/tools_adapter.py +207 -0
- janito/tools/tools_schema.py +104 -0
- janito/utils.py +11 -0
- janito/version.py +4 -0
- janito-2.0.0.dist-info/METADATA +232 -0
- janito-2.0.0.dist-info/RECORD +180 -0
- janito/agent/__init__.py +0 -0
- janito/agent/api_exceptions.py +0 -4
- janito/agent/config.py +0 -147
- janito/agent/config_defaults.py +0 -12
- janito/agent/config_utils.py +0 -0
- janito/agent/content_handler.py +0 -0
- janito/agent/conversation.py +0 -238
- janito/agent/conversation_api.py +0 -306
- janito/agent/conversation_exceptions.py +0 -18
- janito/agent/conversation_tool_calls.py +0 -39
- janito/agent/conversation_ui.py +0 -17
- janito/agent/event.py +0 -24
- janito/agent/event_dispatcher.py +0 -24
- janito/agent/event_handler_protocol.py +0 -5
- janito/agent/event_system.py +0 -15
- janito/agent/llm_conversation_history.py +0 -82
- janito/agent/message_handler.py +0 -20
- janito/agent/message_handler_protocol.py +0 -5
- janito/agent/openai_client.py +0 -149
- janito/agent/openai_schema_generator.py +0 -187
- janito/agent/profile_manager.py +0 -96
- janito/agent/queued_message_handler.py +0 -50
- janito/agent/rich_live.py +0 -32
- janito/agent/rich_message_handler.py +0 -115
- janito/agent/runtime_config.py +0 -36
- janito/agent/test_handler_protocols.py +0 -47
- janito/agent/test_openai_schema_generator.py +0 -93
- janito/agent/tests/__init__.py +0 -1
- janito/agent/tool_base.py +0 -63
- janito/agent/tool_executor.py +0 -122
- janito/agent/tool_registry.py +0 -49
- janito/agent/tools/__init__.py +0 -47
- janito/agent/tools/create_file.py +0 -59
- janito/agent/tools/delete_text_in_file.py +0 -97
- janito/agent/tools/find_files.py +0 -106
- janito/agent/tools/get_file_outline/core.py +0 -81
- janito/agent/tools/present_choices.py +0 -64
- janito/agent/tools/python_command_runner.py +0 -201
- janito/agent/tools/python_file_runner.py +0 -199
- janito/agent/tools/python_stdin_runner.py +0 -208
- janito/agent/tools/replace_file.py +0 -72
- janito/agent/tools/run_bash_command.py +0 -218
- janito/agent/tools/run_powershell_command.py +0 -251
- janito/agent/tools_utils/__init__.py +0 -1
- janito/agent/tools_utils/action_type.py +0 -7
- janito/agent/tools_utils/test_gitignore_utils.py +0 -46
- janito/cli/_livereload_log_utils.py +0 -13
- janito/cli/_print_config.py +0 -96
- janito/cli/_termweb_log_utils.py +0 -17
- janito/cli/_utils.py +0 -9
- janito/cli/arg_parser.py +0 -272
- janito/cli/cli_main.py +0 -281
- janito/cli/config_commands.py +0 -211
- janito/cli/config_runner.py +0 -35
- janito/cli/formatting_runner.py +0 -12
- janito/cli/livereload_starter.py +0 -60
- janito/cli/logging_setup.py +0 -38
- janito/cli/one_shot.py +0 -80
- janito/livereload/app.py +0 -25
- janito/rich_utils.py +0 -59
- janito/shell/__init__.py +0 -0
- janito/shell/commands/__init__.py +0 -61
- janito/shell/commands/config.py +0 -22
- janito/shell/commands/edit.py +0 -24
- janito/shell/commands/history_view.py +0 -18
- janito/shell/commands/lang.py +0 -19
- janito/shell/commands/livelogs.py +0 -42
- janito/shell/commands/prompt.py +0 -62
- janito/shell/commands/termweb_log.py +0 -94
- janito/shell/commands/tools.py +0 -26
- janito/shell/commands/track.py +0 -36
- janito/shell/main.py +0 -326
- janito/shell/prompt/load_prompt.py +0 -57
- janito/shell/prompt/session_setup.py +0 -57
- janito/shell/session/config.py +0 -109
- janito/shell/session/history.py +0 -0
- janito/shell/ui/interactive.py +0 -226
- janito/termweb/static/editor.css +0 -158
- janito/termweb/static/editor.css.bak +0 -145
- janito/termweb/static/editor.html +0 -46
- janito/termweb/static/editor.html.bak +0 -46
- janito/termweb/static/editor.js +0 -265
- janito/termweb/static/editor.js.bak +0 -259
- janito/termweb/static/explorer.html.bak +0 -59
- janito/termweb/static/favicon.ico +0 -0
- janito/termweb/static/favicon.ico.bak +0 -0
- janito/termweb/static/index.html +0 -53
- janito/termweb/static/index.html.bak +0 -54
- janito/termweb/static/index.html.bak.bak +0 -175
- janito/termweb/static/landing.html.bak +0 -36
- janito/termweb/static/termicon.svg +0 -1
- janito/termweb/static/termweb.css +0 -214
- janito/termweb/static/termweb.css.bak +0 -237
- janito/termweb/static/termweb.js +0 -162
- janito/termweb/static/termweb.js.bak +0 -168
- janito/termweb/static/termweb.js.bak.bak +0 -157
- janito/termweb/static/termweb_quickopen.js +0 -135
- janito/termweb/static/termweb_quickopen.js.bak +0 -125
- janito/tests/test_rich_utils.py +0 -44
- janito/web/__init__.py +0 -0
- janito/web/__main__.py +0 -25
- janito/web/app.py +0 -145
- janito-1.14.2.dist-info/METADATA +0 -306
- janito-1.14.2.dist-info/RECORD +0 -162
- janito-1.14.2.dist-info/licenses/LICENSE +0 -21
- /janito/{shell → cli/chat_mode/shell}/input_history.py +0 -0
- /janito/{shell/commands/session.py → cli/chat_mode/shell/session/history.py} +0 -0
- /janito/{agent/tools_utils/formatting.py → formatting.py} +0 -0
- /janito/{agent/tools_utils/gitignore_utils.py → gitignore_utils.py} +0 -0
- /janito/{agent/platform_discovery.py → platform_discovery.py} +0 -0
- /janito/{agent/tools → tools/adapters/local}/get_file_outline/__init__.py +0 -0
- /janito/{agent/tools → tools/adapters/local}/get_file_outline/markdown_outline.py +0 -0
- /janito/{agent/tools → tools/adapters/local}/search_text/__init__.py +0 -0
- /janito/{agent/tools → tools/adapters/local}/validate_file_syntax/__init__.py +0 -0
- {janito-1.14.2.dist-info → janito-2.0.0.dist-info}/WHEEL +0 -0
- {janito-1.14.2.dist-info → janito-2.0.0.dist-info}/entry_points.txt +0 -0
- {janito-1.14.2.dist-info → janito-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +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
|
@@ -0,0 +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}")
|
@@ -48,7 +48,7 @@ class ToolUseTracker:
|
|
48
48
|
def file_fully_read(self, file_path: str) -> bool:
|
49
49
|
norm_file_path = normalize_path(file_path)
|
50
50
|
for entry in self._history:
|
51
|
-
if entry["tool"] == "
|
51
|
+
if entry["tool"] == "view_file":
|
52
52
|
params = entry["params"]
|
53
53
|
if (
|
54
54
|
"file_path" in params
|
@@ -67,9 +67,7 @@ class ToolUseTracker:
|
|
67
67
|
if not ops:
|
68
68
|
return False
|
69
69
|
last = ops[-1]
|
70
|
-
if last["tool"] == "
|
71
|
-
return True
|
72
|
-
if last["tool"] == "get_lines":
|
70
|
+
if last["tool"] == "view_file":
|
73
71
|
params = last["params"]
|
74
72
|
if params.get("from_line") is None and params.get("to_line") is None:
|
75
73
|
return True
|
@@ -1,16 +1,28 @@
|
|
1
|
+
"""
|
2
|
+
Utility functions for the janito project.
|
3
|
+
Add your shared helper functions here.
|
4
|
+
"""
|
5
|
+
|
1
6
|
import os
|
2
7
|
import urllib.parse
|
3
|
-
|
8
|
+
|
9
|
+
|
10
|
+
def example_utility_function(x):
|
11
|
+
"""A simple example utility function."""
|
12
|
+
return f"Processed: {x}"
|
4
13
|
|
5
14
|
|
6
15
|
def display_path(path):
|
7
16
|
"""
|
8
|
-
Returns a display-friendly path.
|
17
|
+
Returns a display-friendly path. Injects an ANSI hyperlink to a local web file viewer using a hardcoded port.
|
9
18
|
Args:
|
10
19
|
path (str): Path to display.
|
11
20
|
Returns:
|
12
|
-
str: Display path,
|
21
|
+
str: Display path, as an ANSI hyperlink.
|
13
22
|
"""
|
23
|
+
from janito.cli.config import get_termweb_port
|
24
|
+
|
25
|
+
port = get_termweb_port()
|
14
26
|
if os.path.isabs(path):
|
15
27
|
cwd = os.path.abspath(os.getcwd())
|
16
28
|
abs_path = os.path.abspath(path)
|
@@ -21,12 +33,9 @@ def display_path(path):
|
|
21
33
|
disp = path
|
22
34
|
else:
|
23
35
|
disp = os.path.relpath(path)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
# Use Rich markup for hyperlinks
|
28
|
-
return f"[link={url}]{disp}[/link]"
|
29
|
-
return disp
|
36
|
+
url = f"http://localhost:{port}/?path={urllib.parse.quote(path)}"
|
37
|
+
# Use Rich markup for hyperlinks
|
38
|
+
return f"[link={url}]{disp}[/link]"
|
30
39
|
|
31
40
|
|
32
41
|
def pluralize(word: str, count: int) -> str:
|
@@ -0,0 +1,207 @@
|
|
1
|
+
from janito.tools.tool_base import ToolBase
|
2
|
+
from janito.tools.tool_events import ToolCallStarted, ToolCallFinished, ToolCallError
|
3
|
+
from janito.exceptions import ToolCallException
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
|
7
|
+
class ToolsAdapterBase:
|
8
|
+
"""
|
9
|
+
Composable entry point for tools management and provisioning in LLM pipelines.
|
10
|
+
This class represents an external or plugin-based provider of tool definitions.
|
11
|
+
Extend and customize this to load, register, or serve tool implementations dynamically.
|
12
|
+
After refactor, also responsible for tool execution.
|
13
|
+
"""
|
14
|
+
|
15
|
+
def __init__(
|
16
|
+
self, tools=None, event_bus=None, allowed_tools: Optional[list] = None
|
17
|
+
):
|
18
|
+
self._tools = tools or []
|
19
|
+
self._event_bus = event_bus # event bus can be set on all adapters
|
20
|
+
self._allowed_tools = set(allowed_tools) if allowed_tools is not None else None
|
21
|
+
self.verbose_tools = False
|
22
|
+
|
23
|
+
def set_verbose_tools(self, value: bool):
|
24
|
+
self.verbose_tools = value
|
25
|
+
|
26
|
+
@property
|
27
|
+
def event_bus(self):
|
28
|
+
return self._event_bus
|
29
|
+
|
30
|
+
@event_bus.setter
|
31
|
+
def event_bus(self, bus):
|
32
|
+
self._event_bus = bus
|
33
|
+
|
34
|
+
def get_tools(self):
|
35
|
+
"""Return the list of tools managed by this provider."""
|
36
|
+
return self._tools
|
37
|
+
|
38
|
+
def add_tool(self, tool):
|
39
|
+
self._tools.append(tool)
|
40
|
+
|
41
|
+
def clear_tools(self):
|
42
|
+
self._tools = []
|
43
|
+
|
44
|
+
def _validate_arguments_against_schema(self, arguments: dict, schema: dict):
|
45
|
+
properties = schema.get("properties", {})
|
46
|
+
required = schema.get("required", [])
|
47
|
+
missing = [field for field in required if field not in arguments]
|
48
|
+
if missing:
|
49
|
+
return f"Missing required argument(s): {', '.join(missing)}"
|
50
|
+
type_map = {
|
51
|
+
"string": str,
|
52
|
+
"integer": int,
|
53
|
+
"number": (int, float),
|
54
|
+
"boolean": bool,
|
55
|
+
"array": list,
|
56
|
+
"object": dict,
|
57
|
+
}
|
58
|
+
for key, value in arguments.items():
|
59
|
+
if key not in properties:
|
60
|
+
continue
|
61
|
+
expected_type = properties[key].get("type")
|
62
|
+
if expected_type and expected_type in type_map:
|
63
|
+
if not isinstance(value, type_map[expected_type]):
|
64
|
+
return f"Argument '{key}' should be of type '{expected_type}', got '{type(value).__name__}'"
|
65
|
+
return None
|
66
|
+
|
67
|
+
def execute(self, tool, *args, **kwargs):
|
68
|
+
|
69
|
+
if self.verbose_tools:
|
70
|
+
print(
|
71
|
+
f"[tools-adapter] [execute] Executing tool: {getattr(tool, 'tool_name', repr(tool))} with args: {args}, kwargs: {kwargs}"
|
72
|
+
)
|
73
|
+
if isinstance(tool, ToolBase):
|
74
|
+
tool.event_bus = self._event_bus
|
75
|
+
result = None
|
76
|
+
if callable(tool):
|
77
|
+
result = tool(*args, **kwargs)
|
78
|
+
elif hasattr(tool, "execute") and callable(getattr(tool, "execute")):
|
79
|
+
result = tool.execute(*args, **kwargs)
|
80
|
+
elif hasattr(tool, "run") and callable(getattr(tool, "run")):
|
81
|
+
result = tool.run(*args, **kwargs)
|
82
|
+
else:
|
83
|
+
raise ValueError("Provided tool is not executable.")
|
84
|
+
|
85
|
+
return result
|
86
|
+
|
87
|
+
def execute_by_name(
|
88
|
+
self, tool_name: str, *args, request_id=None, arguments=None, **kwargs
|
89
|
+
):
|
90
|
+
self._check_tool_permissions(tool_name, request_id, arguments)
|
91
|
+
tool = self.get_tool(tool_name)
|
92
|
+
self._ensure_tool_exists(tool, tool_name, request_id, arguments)
|
93
|
+
schema = getattr(tool, "schema", None)
|
94
|
+
if schema and arguments is not None:
|
95
|
+
validation_error = self._validate_arguments_against_schema(
|
96
|
+
arguments, schema
|
97
|
+
)
|
98
|
+
if validation_error:
|
99
|
+
if self._event_bus:
|
100
|
+
self._event_bus.publish(
|
101
|
+
ToolCallError(
|
102
|
+
tool_name=tool_name,
|
103
|
+
request_id=request_id,
|
104
|
+
error=validation_error,
|
105
|
+
arguments=arguments,
|
106
|
+
)
|
107
|
+
)
|
108
|
+
return validation_error
|
109
|
+
if self.verbose_tools:
|
110
|
+
print(
|
111
|
+
f"[tools-adapter] Executing tool: {tool_name} with arguments: {arguments}"
|
112
|
+
)
|
113
|
+
if self._event_bus:
|
114
|
+
self._event_bus.publish(
|
115
|
+
ToolCallStarted(
|
116
|
+
tool_name=tool_name, request_id=request_id, arguments=arguments
|
117
|
+
)
|
118
|
+
)
|
119
|
+
try:
|
120
|
+
result = self.execute(tool, **(arguments or {}), **kwargs)
|
121
|
+
except Exception as e:
|
122
|
+
self._handle_execution_error(tool_name, request_id, e, arguments)
|
123
|
+
if self.verbose_tools:
|
124
|
+
print(f"[tools-adapter] Tool execution finished: {tool_name} -> {result}")
|
125
|
+
if self._event_bus:
|
126
|
+
self._event_bus.publish(
|
127
|
+
ToolCallFinished(
|
128
|
+
tool_name=tool_name, request_id=request_id, result=result
|
129
|
+
)
|
130
|
+
)
|
131
|
+
return result
|
132
|
+
|
133
|
+
def execute_function_call_message_part(self, function_call_message_part):
|
134
|
+
"""
|
135
|
+
Execute a FunctionCallMessagePart by extracting the tool name and arguments and dispatching to execute_by_name.
|
136
|
+
"""
|
137
|
+
import json
|
138
|
+
|
139
|
+
function = getattr(function_call_message_part, "function", None)
|
140
|
+
tool_call_id = getattr(function_call_message_part, "tool_call_id", None)
|
141
|
+
if function is None or not hasattr(function, "name"):
|
142
|
+
raise ValueError(
|
143
|
+
"FunctionCallMessagePart does not contain a valid function object."
|
144
|
+
)
|
145
|
+
tool_name = function.name
|
146
|
+
arguments = function.arguments
|
147
|
+
# Parse arguments if they are a JSON string
|
148
|
+
if isinstance(arguments, str):
|
149
|
+
try:
|
150
|
+
arguments = json.loads(arguments)
|
151
|
+
except Exception:
|
152
|
+
pass # Leave as string if not JSON
|
153
|
+
if self.verbose_tools:
|
154
|
+
print(
|
155
|
+
f"[tools-adapter] Executing FunctionCallMessagePart: tool={tool_name}, arguments={arguments}, tool_call_id={tool_call_id}"
|
156
|
+
)
|
157
|
+
return self.execute_by_name(
|
158
|
+
tool_name, request_id=tool_call_id, arguments=arguments
|
159
|
+
)
|
160
|
+
|
161
|
+
def _check_tool_permissions(self, tool_name, request_id, arguments):
|
162
|
+
if self._allowed_tools is not None and tool_name not in self._allowed_tools:
|
163
|
+
error_msg = f"Tool '{tool_name}' is not permitted by adapter allow-list."
|
164
|
+
if self._event_bus:
|
165
|
+
self._event_bus.publish(
|
166
|
+
ToolCallError(
|
167
|
+
tool_name=tool_name,
|
168
|
+
request_id=request_id,
|
169
|
+
error=error_msg,
|
170
|
+
arguments=arguments,
|
171
|
+
)
|
172
|
+
)
|
173
|
+
raise ToolCallException(tool_name, error_msg, arguments=arguments)
|
174
|
+
|
175
|
+
def _ensure_tool_exists(self, tool, tool_name, request_id, arguments):
|
176
|
+
if tool is None:
|
177
|
+
error_msg = f"Tool '{tool_name}' not found in registry."
|
178
|
+
if self._event_bus:
|
179
|
+
self._event_bus.publish(
|
180
|
+
ToolCallError(
|
181
|
+
tool_name=tool_name,
|
182
|
+
request_id=request_id,
|
183
|
+
error=error_msg,
|
184
|
+
arguments=arguments,
|
185
|
+
)
|
186
|
+
)
|
187
|
+
raise ToolCallException(tool_name, error_msg, arguments=arguments)
|
188
|
+
|
189
|
+
def _handle_execution_error(self, tool_name, request_id, exception, arguments):
|
190
|
+
error_msg = f"Exception during execution of tool '{tool_name}': {exception}"
|
191
|
+
if self._event_bus:
|
192
|
+
self._event_bus.publish(
|
193
|
+
ToolCallError(
|
194
|
+
tool_name=tool_name,
|
195
|
+
request_id=request_id,
|
196
|
+
error=error_msg,
|
197
|
+
exception=exception,
|
198
|
+
arguments=arguments,
|
199
|
+
)
|
200
|
+
)
|
201
|
+
raise ToolCallException(
|
202
|
+
tool_name, error_msg, arguments=arguments, exception=exception
|
203
|
+
)
|
204
|
+
|
205
|
+
def get_tool(self, tool_name):
|
206
|
+
"""Abstract method: implement in subclass to return tool instance by name"""
|
207
|
+
raise NotImplementedError()
|
@@ -0,0 +1,104 @@
|
|
1
|
+
import inspect
|
2
|
+
import typing
|
3
|
+
import re
|
4
|
+
|
5
|
+
|
6
|
+
class ToolSchemaBase:
|
7
|
+
def parse_param_section(self, lines, param_section_headers):
|
8
|
+
param_descs = {}
|
9
|
+
in_params = False
|
10
|
+
for line in lines:
|
11
|
+
stripped_line = line.strip()
|
12
|
+
if any(
|
13
|
+
stripped_line.lower().startswith(h + ":") or stripped_line.lower() == h
|
14
|
+
for h in param_section_headers
|
15
|
+
):
|
16
|
+
in_params = True
|
17
|
+
continue
|
18
|
+
if in_params:
|
19
|
+
m = re.match(
|
20
|
+
r"([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:\(([^)]+)\))?\s*[:\-]?\s*(.+)",
|
21
|
+
stripped_line,
|
22
|
+
)
|
23
|
+
if m:
|
24
|
+
param, _, desc = m.groups()
|
25
|
+
param_descs[param] = desc.strip()
|
26
|
+
elif stripped_line and stripped_line[0] != "-":
|
27
|
+
if param_descs:
|
28
|
+
last = list(param_descs)[-1]
|
29
|
+
param_descs[last] += " " + stripped_line
|
30
|
+
if (
|
31
|
+
stripped_line.lower().startswith("returns:")
|
32
|
+
or stripped_line.lower() == "returns"
|
33
|
+
):
|
34
|
+
break
|
35
|
+
return param_descs
|
36
|
+
|
37
|
+
def parse_return_section(self, lines):
|
38
|
+
in_returns = False
|
39
|
+
return_desc = ""
|
40
|
+
for line in lines:
|
41
|
+
stripped_line = line.strip()
|
42
|
+
if (
|
43
|
+
stripped_line.lower().startswith("returns:")
|
44
|
+
or stripped_line.lower() == "returns"
|
45
|
+
):
|
46
|
+
in_returns = True
|
47
|
+
continue
|
48
|
+
if in_returns:
|
49
|
+
if stripped_line:
|
50
|
+
return_desc += (" " if return_desc else "") + stripped_line
|
51
|
+
return return_desc
|
52
|
+
|
53
|
+
def parse_docstring(self, docstring: str):
|
54
|
+
if not docstring:
|
55
|
+
return "", {}, ""
|
56
|
+
lines = docstring.strip().split("\n")
|
57
|
+
summary = lines[0].strip()
|
58
|
+
param_section_headers = ("args", "arguments", "params", "parameters")
|
59
|
+
param_descs = self.parse_param_section(lines[1:], param_section_headers)
|
60
|
+
return_desc = self.parse_return_section(lines[1:])
|
61
|
+
return summary, param_descs, return_desc
|
62
|
+
|
63
|
+
def validate_tool_class(self, tool_class):
|
64
|
+
if not hasattr(tool_class, "tool_name") or not isinstance(
|
65
|
+
tool_class.tool_name, str
|
66
|
+
):
|
67
|
+
raise ValueError(
|
68
|
+
"Tool class must have a class-level 'tool_name' attribute (str) for registry and schema generation."
|
69
|
+
)
|
70
|
+
if not hasattr(tool_class, "run") or not callable(getattr(tool_class, "run")):
|
71
|
+
raise ValueError("Tool class must have a callable 'run' method.")
|
72
|
+
func = tool_class.run
|
73
|
+
tool_name = tool_class.tool_name
|
74
|
+
sig = inspect.signature(func)
|
75
|
+
if sig.return_annotation is inspect._empty or sig.return_annotation is not str:
|
76
|
+
raise ValueError(
|
77
|
+
f"Tool '{tool_name}' must have an explicit return type of 'str'. Found: {sig.return_annotation}"
|
78
|
+
)
|
79
|
+
missing_type_hints = [
|
80
|
+
name
|
81
|
+
for name, param in sig.parameters.items()
|
82
|
+
if name != "self" and param.annotation is inspect._empty
|
83
|
+
]
|
84
|
+
if missing_type_hints:
|
85
|
+
raise ValueError(
|
86
|
+
f"Tool '{tool_name}' is missing type hints for parameter(s): {', '.join(missing_type_hints)}.\nAll parameters must have explicit type hints for schema generation."
|
87
|
+
)
|
88
|
+
class_doc = (
|
89
|
+
tool_class.__doc__.strip() if tool_class and tool_class.__doc__ else ""
|
90
|
+
)
|
91
|
+
summary, param_descs, return_desc = self.parse_docstring(class_doc)
|
92
|
+
description = summary
|
93
|
+
if return_desc:
|
94
|
+
description += f"\n\nReturns: {return_desc}"
|
95
|
+
undocumented = [
|
96
|
+
name
|
97
|
+
for name, param in sig.parameters.items()
|
98
|
+
if name != "self" and name not in param_descs
|
99
|
+
]
|
100
|
+
if undocumented:
|
101
|
+
raise ValueError(
|
102
|
+
f"Tool '{tool_name}' is missing docstring documentation for parameter(s): {', '.join(undocumented)}.\nParameter documentation must be provided in the Tool class docstring, not the method docstring."
|
103
|
+
)
|
104
|
+
return func, tool_name, sig, summary, param_descs, return_desc, description
|
janito/utils.py
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
import inspect
|
2
|
+
|
3
|
+
|
4
|
+
def kwargs_from_locals(*names):
|
5
|
+
"""
|
6
|
+
Return a dict of {name: value} for each name from the caller's local scope.
|
7
|
+
Usage:
|
8
|
+
event = GenerationStarted(**kwargs_from_locals('driver_name', 'request_id', 'conversation_history'))
|
9
|
+
"""
|
10
|
+
frame = inspect.currentframe().f_back
|
11
|
+
return {name: frame.f_locals[name] for name in names}
|
janito/version.py
ADDED