janito 2.5.1__py3-none-any.whl → 2.6.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/agent/setup_agent.py +231 -223
- janito/agent/templates/profiles/system_prompt_template_software_developer.txt.j2 +39 -0
- janito/cli/chat_mode/bindings.py +1 -26
- janito/cli/chat_mode/session.py +282 -294
- janito/cli/chat_mode/session_profile_select.py +125 -55
- janito/cli/chat_mode/shell/commands/tools.py +51 -48
- janito/cli/chat_mode/toolbar.py +42 -68
- janito/cli/cli_commands/list_tools.py +41 -56
- janito/cli/cli_commands/show_system_prompt.py +70 -49
- janito/cli/core/runner.py +6 -1
- janito/cli/core/setters.py +43 -34
- janito/cli/main_cli.py +25 -1
- janito/cli/prompt_core.py +76 -69
- janito/cli/rich_terminal_reporter.py +22 -1
- janito/cli/single_shot_mode/handler.py +95 -94
- janito/drivers/driver_registry.py +27 -29
- janito/drivers/openai/driver.py +436 -494
- janito/llm/agent.py +54 -68
- janito/provider_registry.py +178 -178
- janito/providers/anthropic/model_info.py +41 -22
- janito/providers/anthropic/provider.py +80 -67
- janito/providers/provider_static_info.py +18 -17
- janito/tools/adapters/local/__init__.py +66 -65
- janito/tools/adapters/local/adapter.py +79 -18
- janito/tools/adapters/local/create_directory.py +9 -9
- janito/tools/adapters/local/create_file.py +12 -12
- janito/tools/adapters/local/delete_text_in_file.py +16 -16
- janito/tools/adapters/local/find_files.py +2 -2
- janito/tools/adapters/local/get_file_outline/core.py +5 -5
- janito/tools/adapters/local/get_file_outline/search_outline.py +4 -4
- janito/tools/adapters/local/open_html_in_browser.py +15 -15
- janito/tools/adapters/local/python_file_run.py +4 -4
- janito/tools/adapters/local/read_files.py +40 -0
- janito/tools/adapters/local/remove_directory.py +5 -5
- janito/tools/adapters/local/remove_file.py +4 -4
- janito/tools/adapters/local/replace_text_in_file.py +21 -21
- janito/tools/adapters/local/run_bash_command.py +1 -1
- janito/tools/adapters/local/search_text/pattern_utils.py +2 -2
- janito/tools/adapters/local/search_text/traverse_directory.py +10 -10
- janito/tools/adapters/local/validate_file_syntax/core.py +7 -7
- janito/tools/adapters/local/validate_file_syntax/css_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/html_validator.py +7 -7
- janito/tools/adapters/local/validate_file_syntax/js_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/json_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/markdown_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/ps1_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/python_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/xml_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/yaml_validator.py +2 -2
- janito/tools/adapters/local/view_file.py +12 -12
- janito/tools/path_security.py +204 -0
- janito/tools/tool_use_tracker.py +12 -12
- janito/tools/tools_adapter.py +66 -34
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/METADATA +412 -412
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/RECORD +59 -58
- janito/drivers/anthropic/driver.py +0 -113
- janito/tools/adapters/local/get_file_outline/python_outline_v2.py +0 -156
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/WHEEL +0 -0
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/entry_points.txt +0 -0
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/licenses/LICENSE +0 -0
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/top_level.txt +0 -0
@@ -11,11 +11,11 @@ class ViewFileTool(ToolBase):
|
|
11
11
|
Read lines from a file. You can specify a line range, or read the entire file by simply omitting the from_line and to_line parameters.
|
12
12
|
|
13
13
|
Args:
|
14
|
-
|
14
|
+
path (str): Path to the file to read lines from.
|
15
15
|
from_line (int, optional): Starting line number (1-based). Omit to start from the first line.
|
16
16
|
to_line (int, optional): Ending line number (1-based). Omit to read to the end of the file.
|
17
17
|
|
18
|
-
To read the full file, just provide
|
18
|
+
To read the full file, just provide path and leave from_line and to_line unset.
|
19
19
|
|
20
20
|
Returns:
|
21
21
|
str: File content with a header indicating the file name and line range. Example:
|
@@ -28,19 +28,19 @@ class ViewFileTool(ToolBase):
|
|
28
28
|
permissions = ToolPermissions(read=True)
|
29
29
|
tool_name = "view_file"
|
30
30
|
|
31
|
-
def run(self,
|
31
|
+
def run(self, path: str, from_line: int = None, to_line: int = None) -> str:
|
32
32
|
import os
|
33
33
|
from janito.tools.tool_utils import display_path
|
34
34
|
|
35
|
-
disp_path = display_path(
|
35
|
+
disp_path = display_path(path)
|
36
36
|
self.report_action(
|
37
37
|
tr("📖 View '{disp_path}'", disp_path=disp_path),
|
38
38
|
ReportAction.READ,
|
39
39
|
)
|
40
40
|
try:
|
41
|
-
if os.path.isdir(
|
42
|
-
return self._list_directory(
|
43
|
-
lines = self._read_file_lines(
|
41
|
+
if os.path.isdir(path):
|
42
|
+
return self._list_directory(path, disp_path)
|
43
|
+
lines = self._read_file_lines(path)
|
44
44
|
selected, selected_len, total_lines = self._select_lines(
|
45
45
|
lines, from_line, to_line
|
46
46
|
)
|
@@ -56,16 +56,16 @@ class ViewFileTool(ToolBase):
|
|
56
56
|
self.report_error(tr(" ❌ Error: {error}", error=e))
|
57
57
|
return tr("Error reading file: {error}", error=e)
|
58
58
|
|
59
|
-
def _list_directory(self,
|
59
|
+
def _list_directory(self, path, disp_path):
|
60
60
|
import os
|
61
61
|
|
62
62
|
try:
|
63
|
-
entries = os.listdir(
|
63
|
+
entries = os.listdir(path)
|
64
64
|
entries.sort()
|
65
65
|
# Suffix subdirectories with '/'
|
66
66
|
formatted_entries = []
|
67
67
|
for entry in entries:
|
68
|
-
full_path = os.path.join(
|
68
|
+
full_path = os.path.join(path, entry)
|
69
69
|
if os.path.isdir(full_path):
|
70
70
|
formatted_entries.append(entry + "/")
|
71
71
|
else:
|
@@ -80,9 +80,9 @@ class ViewFileTool(ToolBase):
|
|
80
80
|
self.report_error(tr(" ❌ Error listing directory: {error}", error=e))
|
81
81
|
return tr("Error listing directory: {error}", error=e)
|
82
82
|
|
83
|
-
def _read_file_lines(self,
|
83
|
+
def _read_file_lines(self, path):
|
84
84
|
"""Read all lines from the file."""
|
85
|
-
with open(
|
85
|
+
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
86
86
|
return f.readlines()
|
87
87
|
|
88
88
|
def _select_lines(self, lines, from_line, to_line):
|
@@ -0,0 +1,204 @@
|
|
1
|
+
"""janito.tools.path_security
|
2
|
+
================================
|
3
|
+
Utilities that ensure user-supplied file-system paths never escape the allowed
|
4
|
+
workspace.
|
5
|
+
|
6
|
+
Public interface
|
7
|
+
----------------
|
8
|
+
|
9
|
+
``is_path_within_workdir(path, workdir)``
|
10
|
+
Verify that *path* is located **inside** *workdir* (or equals it). If
|
11
|
+
*workdir* is *None* every path is accepted.
|
12
|
+
|
13
|
+
``validate_paths_in_arguments(arguments, workdir, *, schema=None)``
|
14
|
+
Inspect a mapping of arguments (typically the kwargs that will later be
|
15
|
+
passed to a tool adapter). Any item whose key *looks* like it refers to a
|
16
|
+
path is validated with :func:`is_path_within_workdir`. If a JSON Schema for
|
17
|
+
the tool is provided, the keys that explicitly represent paths are derived
|
18
|
+
from it. Otherwise a simple heuristic based on the key name is used.
|
19
|
+
|
20
|
+
Both helpers raise :class:`PathSecurityError` if a path tries to escape the
|
21
|
+
workspace.
|
22
|
+
"""
|
23
|
+
from __future__ import annotations
|
24
|
+
|
25
|
+
import os
|
26
|
+
from typing import Any, Mapping
|
27
|
+
|
28
|
+
__all__ = [
|
29
|
+
"PathSecurityError",
|
30
|
+
"is_path_within_workdir",
|
31
|
+
"validate_paths_in_arguments",
|
32
|
+
]
|
33
|
+
|
34
|
+
|
35
|
+
# ---------------------------------------------------------------------------
|
36
|
+
# Exceptions
|
37
|
+
# ---------------------------------------------------------------------------
|
38
|
+
|
39
|
+
|
40
|
+
class PathSecurityError(Exception):
|
41
|
+
"""Raised when an argument references a location outside the workspace."""
|
42
|
+
|
43
|
+
|
44
|
+
# ---------------------------------------------------------------------------
|
45
|
+
# Public helpers
|
46
|
+
# ---------------------------------------------------------------------------
|
47
|
+
|
48
|
+
|
49
|
+
def is_path_within_workdir(path: str, workdir: str | None) -> bool: # noqa: D401 – we start with an imperative verb # noqa: D401 – we start with an imperative verb
|
50
|
+
"""Return *True* if *path* is located inside *workdir* (or equals it).
|
51
|
+
|
52
|
+
Relative *path*s are **resolved relative to the *workdir***, *not* to the
|
53
|
+
current working directory. This behaviour makes the security checks
|
54
|
+
deterministic regardless of the directory from which the Python process was
|
55
|
+
started.
|
56
|
+
|
57
|
+
Implementation details
|
58
|
+
----------------------
|
59
|
+
The function converts both *workdir* and *path* to absolute paths and then
|
60
|
+
uses :func:`os.path.commonpath` to determine the longest common sub-path. A
|
61
|
+
path is considered *inside* the workspace when that common part equals the
|
62
|
+
workspace directory itself.
|
63
|
+
"""
|
64
|
+
if not workdir:
|
65
|
+
# No workdir configured – everything is implicitly allowed.
|
66
|
+
return True
|
67
|
+
|
68
|
+
abs_workdir = os.path.abspath(workdir)
|
69
|
+
|
70
|
+
# Resolve *path* – if it is *relative* we interpret it **relative to the
|
71
|
+
# workspace** (and *not* to the current working directory!) so that a value
|
72
|
+
# like '.' always points inside the workspace.
|
73
|
+
if os.path.isabs(path):
|
74
|
+
abs_path = os.path.abspath(path)
|
75
|
+
else:
|
76
|
+
abs_path = os.path.abspath(os.path.join(abs_workdir, path))
|
77
|
+
|
78
|
+
try:
|
79
|
+
common_part = os.path.commonpath([abs_workdir, abs_path])
|
80
|
+
except ValueError:
|
81
|
+
# On Windows different drive letters cause ValueError → definitely
|
82
|
+
# outside the workspace.
|
83
|
+
return False
|
84
|
+
|
85
|
+
# Additionally allow files located inside the system temporary directory.
|
86
|
+
import tempfile
|
87
|
+
abs_tempdir = os.path.abspath(tempfile.gettempdir())
|
88
|
+
try:
|
89
|
+
common_temp = os.path.commonpath([abs_tempdir, abs_path])
|
90
|
+
except ValueError:
|
91
|
+
common_temp = None
|
92
|
+
if common_temp == abs_tempdir:
|
93
|
+
return True
|
94
|
+
|
95
|
+
return common_part == abs_workdir
|
96
|
+
|
97
|
+
|
98
|
+
# ---------------------------------------------------------------------------
|
99
|
+
# Helper for tool adapters
|
100
|
+
# ---------------------------------------------------------------------------
|
101
|
+
|
102
|
+
|
103
|
+
def _looks_like_path_key(key: str) -> bool:
|
104
|
+
"""Return *True* when *key* likely refers to a file-system path."""
|
105
|
+
key_lower = key.lower()
|
106
|
+
if key_lower in {
|
107
|
+
"path",
|
108
|
+
"paths",
|
109
|
+
"filepath",
|
110
|
+
"file",
|
111
|
+
"filename",
|
112
|
+
"directory",
|
113
|
+
"directories",
|
114
|
+
"dir",
|
115
|
+
"dirs",
|
116
|
+
"target",
|
117
|
+
"targets",
|
118
|
+
"source",
|
119
|
+
"sources",
|
120
|
+
}:
|
121
|
+
return True
|
122
|
+
|
123
|
+
common_suffixes = ("path", "paths", "file", "dir", "dirs")
|
124
|
+
return key_lower.endswith(common_suffixes)
|
125
|
+
|
126
|
+
|
127
|
+
def _extract_path_keys_from_schema(schema: Mapping[str, Any]) -> set[str]:
|
128
|
+
"""Extract keys that represent paths from the provided JSON schema."""
|
129
|
+
path_keys: set[str] = set()
|
130
|
+
if schema is not None:
|
131
|
+
for k, v in schema.get("properties", {}).items():
|
132
|
+
if (
|
133
|
+
v.get("format") == "path"
|
134
|
+
or (
|
135
|
+
v.get("type") == "string"
|
136
|
+
and (
|
137
|
+
"path" in v.get("description", "").lower()
|
138
|
+
or k.endswith("path")
|
139
|
+
or k == "path"
|
140
|
+
)
|
141
|
+
)
|
142
|
+
):
|
143
|
+
path_keys.add(k)
|
144
|
+
return path_keys
|
145
|
+
|
146
|
+
def _validate_argument_value(key: str, value: Any, workdir: str) -> None:
|
147
|
+
"""Validate a single argument value (string or list of strings) for path security."""
|
148
|
+
# Single string argument → validate directly.
|
149
|
+
if isinstance(value, str) and value.strip():
|
150
|
+
if not is_path_within_workdir(value, workdir):
|
151
|
+
_raise_outside_workspace_error(key, value, workdir)
|
152
|
+
# Sequence of potential paths → validate every item.
|
153
|
+
elif isinstance(value, list):
|
154
|
+
for item in value:
|
155
|
+
if isinstance(item, str) and item.strip():
|
156
|
+
if not is_path_within_workdir(item, workdir):
|
157
|
+
_raise_outside_workspace_error(key, item, workdir)
|
158
|
+
|
159
|
+
def validate_paths_in_arguments(
|
160
|
+
arguments: Mapping[str, Any] | None,
|
161
|
+
workdir: str | None,
|
162
|
+
*,
|
163
|
+
schema: Mapping[str, Any] | None = None,
|
164
|
+
) -> None:
|
165
|
+
"""Ensure every *path-looking* value in *arguments* is inside *workdir*.
|
166
|
+
|
167
|
+
The function walks through *arguments* and raises :class:`PathSecurityError`
|
168
|
+
if it finds a suspicious path that points outside the allowed workspace.
|
169
|
+
|
170
|
+
If *schema* is given it is expected to be the JSON Schema describing the
|
171
|
+
tool's arguments (as produced by ``janito.tools.inspect_registry``). Keys
|
172
|
+
whose schema declares a ``"format": "path"`` or mentions "path" in the
|
173
|
+
description are treated as path parameters. Without a schema the function
|
174
|
+
falls back to a simple heuristic based on the argument name.
|
175
|
+
"""
|
176
|
+
if not workdir or not arguments:
|
177
|
+
return
|
178
|
+
|
179
|
+
path_keys = _extract_path_keys_from_schema(schema) if schema is not None else set()
|
180
|
+
|
181
|
+
for key, value in arguments.items():
|
182
|
+
key_is_path = key in path_keys or _looks_like_path_key(key)
|
183
|
+
if not key_is_path:
|
184
|
+
continue
|
185
|
+
_validate_argument_value(key, value, workdir)
|
186
|
+
|
187
|
+
|
188
|
+
# ---------------------------------------------------------------------------
|
189
|
+
# Internal helpers
|
190
|
+
# ---------------------------------------------------------------------------
|
191
|
+
|
192
|
+
|
193
|
+
def _raise_outside_workspace_error(key: str, path: str, workdir: str) -> None: # noqa: D401
|
194
|
+
"""Raise a consistent :class:`PathSecurityError` for *path*."""
|
195
|
+
abs_workdir = os.path.abspath(workdir)
|
196
|
+
attempted = (
|
197
|
+
os.path.abspath(path)
|
198
|
+
if os.path.isabs(path)
|
199
|
+
else os.path.abspath(os.path.join(abs_workdir, path))
|
200
|
+
)
|
201
|
+
raise PathSecurityError(
|
202
|
+
f"Argument '{key}' path '{path}' is not within allowed workdir '{workdir}' "
|
203
|
+
f"[attempted path: {attempted}]"
|
204
|
+
)
|
janito/tools/tool_use_tracker.py
CHANGED
@@ -22,10 +22,10 @@ class ToolUseTracker:
|
|
22
22
|
return cls._instance
|
23
23
|
|
24
24
|
def record(self, tool_name: str, params: Dict[str, Any], result: Any = None):
|
25
|
-
# Normalize
|
25
|
+
# Normalize path in params if present
|
26
26
|
norm_params = params.copy()
|
27
|
-
if "
|
28
|
-
norm_params["
|
27
|
+
if "path" in norm_params:
|
28
|
+
norm_params["path"] = normalize_path(norm_params["path"])
|
29
29
|
self._history.append(
|
30
30
|
{"tool": tool_name, "params": norm_params, "result": result}
|
31
31
|
)
|
@@ -33,26 +33,26 @@ class ToolUseTracker:
|
|
33
33
|
def get_history(self) -> List[Dict[str, Any]]:
|
34
34
|
return list(self._history)
|
35
35
|
|
36
|
-
def get_operations_on_file(self,
|
37
|
-
|
36
|
+
def get_operations_on_file(self, path: str) -> List[Dict[str, Any]]:
|
37
|
+
norm_path = normalize_path(path)
|
38
38
|
ops = []
|
39
39
|
for entry in self._history:
|
40
40
|
params = entry["params"]
|
41
41
|
# Normalize any string param values for comparison
|
42
42
|
for v in params.values():
|
43
|
-
if isinstance(v, str) and normalize_path(v) ==
|
43
|
+
if isinstance(v, str) and normalize_path(v) == norm_path:
|
44
44
|
ops.append(entry)
|
45
45
|
break
|
46
46
|
return ops
|
47
47
|
|
48
|
-
def file_fully_read(self,
|
49
|
-
|
48
|
+
def file_fully_read(self, path: str) -> bool:
|
49
|
+
norm_path = normalize_path(path)
|
50
50
|
for entry in self._history:
|
51
51
|
if entry["tool"] == "view_file":
|
52
52
|
params = entry["params"]
|
53
53
|
if (
|
54
|
-
"
|
55
|
-
and normalize_path(params["
|
54
|
+
"path" in params
|
55
|
+
and normalize_path(params["path"]) == norm_path
|
56
56
|
):
|
57
57
|
# If both from_line and to_line are None, full file was read
|
58
58
|
if (
|
@@ -62,8 +62,8 @@ class ToolUseTracker:
|
|
62
62
|
return True
|
63
63
|
return False
|
64
64
|
|
65
|
-
def last_operation_is_full_read_or_replace(self,
|
66
|
-
ops = self.get_operations_on_file(
|
65
|
+
def last_operation_is_full_read_or_replace(self, path: str) -> bool:
|
66
|
+
ops = self.get_operations_on_file(path)
|
67
67
|
if not ops:
|
68
68
|
return False
|
69
69
|
last = ops[-1]
|
janito/tools/tools_adapter.py
CHANGED
@@ -162,60 +162,92 @@ class ToolsAdapterBase:
|
|
162
162
|
tool = self.get_tool(tool_name)
|
163
163
|
self._ensure_tool_exists(tool, tool_name, request_id, arguments)
|
164
164
|
func = self._get_tool_callable(tool)
|
165
|
-
|
165
|
+
|
166
|
+
validation_error = self._validate_tool_arguments(tool, func, arguments, tool_name, request_id)
|
167
|
+
if validation_error:
|
168
|
+
return validation_error
|
169
|
+
|
170
|
+
# --- SECURITY: Path restriction enforcement ---
|
171
|
+
if not getattr(self, 'unrestricted_paths', False):
|
172
|
+
workdir = getattr(self, 'workdir', None)
|
173
|
+
# Ensure workdir is always set; default to current working directory.
|
174
|
+
if not workdir:
|
175
|
+
import os
|
176
|
+
workdir = os.getcwd()
|
177
|
+
from janito.tools.path_security import validate_paths_in_arguments, PathSecurityError
|
178
|
+
schema = getattr(tool, 'schema', None)
|
179
|
+
try:
|
180
|
+
validate_paths_in_arguments(arguments, workdir, schema=schema)
|
181
|
+
except PathSecurityError as sec_err:
|
182
|
+
# Publish both a ToolCallError and a user-facing ReportEvent for path security errors
|
183
|
+
self._publish_tool_call_error(tool_name, request_id, str(sec_err), arguments)
|
184
|
+
if self._event_bus:
|
185
|
+
from janito.report_events import ReportEvent, ReportSubtype, ReportAction
|
186
|
+
self._event_bus.publish(
|
187
|
+
ReportEvent(
|
188
|
+
subtype=ReportSubtype.ERROR,
|
189
|
+
message=f"[SECURITY] Path access denied: {sec_err}",
|
190
|
+
action=ReportAction.EXECUTE,
|
191
|
+
tool=tool_name,
|
192
|
+
context={"arguments": arguments, "request_id": request_id}
|
193
|
+
)
|
194
|
+
)
|
195
|
+
return f"Security error: {sec_err}"
|
196
|
+
# --- END SECURITY ---
|
197
|
+
|
198
|
+
self._publish_tool_call_started(tool_name, request_id, arguments)
|
199
|
+
self._print_verbose(f"[tools-adapter] Executing tool: {tool_name} with arguments: {arguments}")
|
200
|
+
try:
|
201
|
+
result = self.execute(tool, **(arguments or {}), **kwargs)
|
202
|
+
except Exception as e:
|
203
|
+
self._handle_execution_error(tool_name, request_id, e, arguments)
|
204
|
+
self._print_verbose(f"[tools-adapter] Tool execution finished: {tool_name} -> {result}")
|
205
|
+
self._publish_tool_call_finished(tool_name, request_id, result)
|
206
|
+
return result
|
207
|
+
|
208
|
+
def _validate_tool_arguments(self, tool, func, arguments, tool_name, request_id):
|
166
209
|
sig_error = self._validate_arguments_against_signature(func, arguments)
|
167
210
|
if sig_error:
|
168
|
-
|
169
|
-
self._event_bus.publish(
|
170
|
-
ToolCallError(
|
171
|
-
tool_name=tool_name,
|
172
|
-
request_id=request_id,
|
173
|
-
error=sig_error,
|
174
|
-
arguments=arguments,
|
175
|
-
)
|
176
|
-
)
|
211
|
+
self._publish_tool_call_error(tool_name, request_id, sig_error, arguments)
|
177
212
|
return sig_error
|
178
|
-
|
179
|
-
# Optionally validate against JSON schema if available
|
180
213
|
schema = getattr(tool, "schema", None)
|
181
214
|
if schema and arguments is not None:
|
182
|
-
validation_error = self._validate_arguments_against_schema(
|
183
|
-
arguments, schema
|
184
|
-
)
|
215
|
+
validation_error = self._validate_arguments_against_schema(arguments, schema)
|
185
216
|
if validation_error:
|
186
|
-
|
187
|
-
self._event_bus.publish(
|
188
|
-
ToolCallError(
|
189
|
-
tool_name=tool_name,
|
190
|
-
request_id=request_id,
|
191
|
-
error=validation_error,
|
192
|
-
arguments=arguments,
|
193
|
-
)
|
194
|
-
)
|
217
|
+
self._publish_tool_call_error(tool_name, request_id, validation_error, arguments)
|
195
218
|
return validation_error
|
196
|
-
|
197
|
-
|
198
|
-
|
219
|
+
return None
|
220
|
+
|
221
|
+
def _publish_tool_call_error(self, tool_name, request_id, error, arguments):
|
222
|
+
if self._event_bus:
|
223
|
+
self._event_bus.publish(
|
224
|
+
ToolCallError(
|
225
|
+
tool_name=tool_name,
|
226
|
+
request_id=request_id,
|
227
|
+
error=error,
|
228
|
+
arguments=arguments,
|
229
|
+
)
|
199
230
|
)
|
231
|
+
|
232
|
+
def _publish_tool_call_started(self, tool_name, request_id, arguments):
|
200
233
|
if self._event_bus:
|
201
234
|
self._event_bus.publish(
|
202
235
|
ToolCallStarted(
|
203
236
|
tool_name=tool_name, request_id=request_id, arguments=arguments
|
204
237
|
)
|
205
238
|
)
|
206
|
-
|
207
|
-
|
208
|
-
except Exception as e:
|
209
|
-
self._handle_execution_error(tool_name, request_id, e, arguments)
|
210
|
-
if self.verbose_tools:
|
211
|
-
print(f"[tools-adapter] Tool execution finished: {tool_name} -> {result}")
|
239
|
+
|
240
|
+
def _publish_tool_call_finished(self, tool_name, request_id, result):
|
212
241
|
if self._event_bus:
|
213
242
|
self._event_bus.publish(
|
214
243
|
ToolCallFinished(
|
215
244
|
tool_name=tool_name, request_id=request_id, result=result
|
216
245
|
)
|
217
246
|
)
|
218
|
-
|
247
|
+
|
248
|
+
def _print_verbose(self, message):
|
249
|
+
if self.verbose_tools:
|
250
|
+
print(message)
|
219
251
|
|
220
252
|
def execute_function_call_message_part(self, function_call_message_part):
|
221
253
|
"""
|