janito 2.3.0__py3-none-any.whl → 2.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- janito/__init__.py +6 -6
- janito/cli/chat_mode/shell/autocomplete.py +21 -21
- janito/cli/chat_mode/shell/commands/clear.py +12 -12
- janito/cli/chat_mode/shell/commands/multi.py +51 -51
- janito/cli/chat_mode/shell/input_history.py +62 -62
- janito/cli/cli_commands/list_models.py +35 -35
- janito/cli/cli_commands/list_providers.py +9 -9
- janito/cli/cli_commands/list_tools.py +53 -53
- janito/cli/cli_commands/model_selection.py +50 -50
- janito/cli/cli_commands/model_utils.py +95 -95
- janito/cli/cli_commands/set_api_key.py +19 -19
- janito/cli/cli_commands/show_config.py +51 -51
- janito/cli/cli_commands/show_system_prompt.py +62 -62
- janito/cli/core/__init__.py +4 -4
- janito/cli/core/event_logger.py +59 -59
- janito/cli/core/getters.py +33 -33
- janito/cli/core/unsetters.py +54 -54
- janito/cli/single_shot_mode/__init__.py +6 -6
- janito/config.py +5 -5
- janito/config_manager.py +112 -112
- janito/drivers/anthropic/driver.py +113 -113
- janito/formatting_token.py +54 -54
- janito/i18n/__init__.py +35 -35
- janito/i18n/messages.py +23 -23
- janito/i18n/pt.py +47 -47
- janito/llm/__init__.py +5 -5
- janito/llm/agent.py +443 -443
- janito/llm/auth.py +63 -63
- janito/llm/driver_config_builder.py +34 -34
- janito/llm/driver_input.py +12 -12
- janito/llm/message_parts.py +60 -60
- janito/llm/model.py +38 -38
- janito/llm/provider.py +196 -196
- janito/provider_registry.py +176 -176
- janito/providers/anthropic/model_info.py +22 -22
- janito/providers/anthropic/provider.py +2 -0
- janito/providers/azure_openai/model_info.py +16 -16
- janito/providers/azure_openai/provider.py +3 -0
- janito/providers/deepseek/__init__.py +1 -1
- janito/providers/deepseek/model_info.py +16 -16
- janito/providers/deepseek/provider.py +94 -91
- janito/providers/google/provider.py +3 -0
- janito/providers/mistralai/provider.py +3 -0
- janito/providers/openai/provider.py +4 -0
- janito/tools/adapters/__init__.py +1 -1
- janito/tools/adapters/local/ask_user.py +102 -102
- janito/tools/adapters/local/copy_file.py +84 -84
- janito/tools/adapters/local/create_directory.py +69 -69
- janito/tools/adapters/local/create_file.py +82 -82
- janito/tools/adapters/local/fetch_url.py +97 -97
- janito/tools/adapters/local/find_files.py +138 -138
- janito/tools/adapters/local/get_file_outline/__init__.py +1 -1
- janito/tools/adapters/local/get_file_outline/core.py +117 -117
- janito/tools/adapters/local/get_file_outline/java_outline.py +40 -40
- janito/tools/adapters/local/get_file_outline/markdown_outline.py +14 -14
- janito/tools/adapters/local/get_file_outline/python_outline.py +303 -303
- janito/tools/adapters/local/get_file_outline/python_outline_v2.py +156 -156
- janito/tools/adapters/local/get_file_outline/search_outline.py +33 -33
- janito/tools/adapters/local/python_code_run.py +166 -166
- janito/tools/adapters/local/python_command_run.py +164 -164
- janito/tools/adapters/local/python_file_run.py +163 -163
- janito/tools/adapters/local/run_bash_command.py +176 -176
- janito/tools/adapters/local/run_powershell_command.py +219 -219
- janito/tools/adapters/local/search_text/__init__.py +1 -1
- janito/tools/adapters/local/search_text/core.py +201 -201
- janito/tools/adapters/local/search_text/pattern_utils.py +73 -73
- janito/tools/adapters/local/search_text/traverse_directory.py +145 -145
- janito/tools/adapters/local/validate_file_syntax/__init__.py +1 -1
- janito/tools/adapters/local/validate_file_syntax/core.py +106 -106
- janito/tools/adapters/local/validate_file_syntax/css_validator.py +35 -35
- janito/tools/adapters/local/validate_file_syntax/html_validator.py +93 -93
- janito/tools/adapters/local/validate_file_syntax/js_validator.py +27 -27
- janito/tools/adapters/local/validate_file_syntax/json_validator.py +6 -6
- janito/tools/adapters/local/validate_file_syntax/markdown_validator.py +109 -109
- janito/tools/adapters/local/validate_file_syntax/ps1_validator.py +32 -32
- janito/tools/adapters/local/validate_file_syntax/python_validator.py +5 -5
- janito/tools/adapters/local/validate_file_syntax/xml_validator.py +11 -11
- janito/tools/adapters/local/validate_file_syntax/yaml_validator.py +6 -6
- janito/tools/adapters/local/view_file.py +167 -167
- janito/tools/inspect_registry.py +17 -17
- janito/tools/tool_base.py +105 -105
- janito/tools/tool_events.py +58 -58
- janito/tools/tool_run_exception.py +12 -12
- janito/tools/tool_use_tracker.py +81 -81
- janito/tools/tool_utils.py +45 -45
- janito/tools/tools_schema.py +104 -104
- janito/version.py +4 -4
- {janito-2.3.0.dist-info → janito-2.3.1.dist-info}/METADATA +390 -388
- {janito-2.3.0.dist-info → janito-2.3.1.dist-info}/RECORD +93 -93
- {janito-2.3.0.dist-info → janito-2.3.1.dist-info}/WHEEL +0 -0
- {janito-2.3.0.dist-info → janito-2.3.1.dist-info}/entry_points.txt +0 -0
- {janito-2.3.0.dist-info → janito-2.3.1.dist-info}/licenses/LICENSE +0 -0
- {janito-2.3.0.dist-info → janito-2.3.1.dist-info}/top_level.txt +0 -0
janito/tools/tool_events.py
CHANGED
@@ -1,58 +1,58 @@
|
|
1
|
-
import attr
|
2
|
-
from typing import Any, ClassVar
|
3
|
-
from janito.event_bus.event import Event
|
4
|
-
|
5
|
-
|
6
|
-
@attr.s(auto_attribs=True, kw_only=True)
|
7
|
-
class ToolEvent(Event):
|
8
|
-
"""
|
9
|
-
Base class for events related to tool calls (external or internal tools).
|
10
|
-
Includes tool name and request ID for correlation.
|
11
|
-
"""
|
12
|
-
|
13
|
-
category: ClassVar[str] = "tool"
|
14
|
-
tool_name: str
|
15
|
-
request_id: str
|
16
|
-
|
17
|
-
|
18
|
-
@attr.s(auto_attribs=True, kw_only=True)
|
19
|
-
class ToolCallStarted(ToolEvent):
|
20
|
-
"""
|
21
|
-
Event indicating that a tool call has started.
|
22
|
-
Contains the arguments passed to the tool.
|
23
|
-
"""
|
24
|
-
|
25
|
-
arguments: Any
|
26
|
-
|
27
|
-
|
28
|
-
@attr.s(auto_attribs=True, kw_only=True)
|
29
|
-
class ToolCallFinished(ToolEvent):
|
30
|
-
"""
|
31
|
-
Event indicating that a tool call has finished.
|
32
|
-
Contains the result returned by the tool.
|
33
|
-
"""
|
34
|
-
|
35
|
-
result: Any
|
36
|
-
|
37
|
-
|
38
|
-
@attr.s(auto_attribs=True, kw_only=True)
|
39
|
-
class ToolRunError(ToolEvent):
|
40
|
-
"""
|
41
|
-
Event indicating that an error occurred during tool execution (for event bus, not exception handling).
|
42
|
-
"""
|
43
|
-
|
44
|
-
error: str
|
45
|
-
exception: Exception = None
|
46
|
-
arguments: Any = None
|
47
|
-
|
48
|
-
|
49
|
-
@attr.s(auto_attribs=True, kw_only=True)
|
50
|
-
class ToolCallError(ToolEvent):
|
51
|
-
"""
|
52
|
-
Event indicating that the tool could not be called (e.g., tool not found, invalid arguments, or invocation failure).
|
53
|
-
This is distinct from ToolRunError, which is for errors during execution after the tool has started running.
|
54
|
-
"""
|
55
|
-
|
56
|
-
error: str
|
57
|
-
exception: Exception = None
|
58
|
-
arguments: Any = None
|
1
|
+
import attr
|
2
|
+
from typing import Any, ClassVar
|
3
|
+
from janito.event_bus.event import Event
|
4
|
+
|
5
|
+
|
6
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
7
|
+
class ToolEvent(Event):
|
8
|
+
"""
|
9
|
+
Base class for events related to tool calls (external or internal tools).
|
10
|
+
Includes tool name and request ID for correlation.
|
11
|
+
"""
|
12
|
+
|
13
|
+
category: ClassVar[str] = "tool"
|
14
|
+
tool_name: str
|
15
|
+
request_id: str
|
16
|
+
|
17
|
+
|
18
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
19
|
+
class ToolCallStarted(ToolEvent):
|
20
|
+
"""
|
21
|
+
Event indicating that a tool call has started.
|
22
|
+
Contains the arguments passed to the tool.
|
23
|
+
"""
|
24
|
+
|
25
|
+
arguments: Any
|
26
|
+
|
27
|
+
|
28
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
29
|
+
class ToolCallFinished(ToolEvent):
|
30
|
+
"""
|
31
|
+
Event indicating that a tool call has finished.
|
32
|
+
Contains the result returned by the tool.
|
33
|
+
"""
|
34
|
+
|
35
|
+
result: Any
|
36
|
+
|
37
|
+
|
38
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
39
|
+
class ToolRunError(ToolEvent):
|
40
|
+
"""
|
41
|
+
Event indicating that an error occurred during tool execution (for event bus, not exception handling).
|
42
|
+
"""
|
43
|
+
|
44
|
+
error: str
|
45
|
+
exception: Exception = None
|
46
|
+
arguments: Any = None
|
47
|
+
|
48
|
+
|
49
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
50
|
+
class ToolCallError(ToolEvent):
|
51
|
+
"""
|
52
|
+
Event indicating that the tool could not be called (e.g., tool not found, invalid arguments, or invocation failure).
|
53
|
+
This is distinct from ToolRunError, which is for errors during execution after the tool has started running.
|
54
|
+
"""
|
55
|
+
|
56
|
+
error: str
|
57
|
+
exception: Exception = None
|
58
|
+
arguments: Any = None
|
@@ -1,12 +1,12 @@
|
|
1
|
-
class ToolRunException(Exception):
|
2
|
-
"""
|
3
|
-
Exception raised when a tool runs but fails due to an internal error or runtime exception.
|
4
|
-
This is distinct from ToolRunError event, which is for event bus notification.
|
5
|
-
"""
|
6
|
-
|
7
|
-
def __init__(self, tool_name, error, arguments=None, exception=None):
|
8
|
-
self.tool_name = tool_name
|
9
|
-
self.error = error
|
10
|
-
self.arguments = arguments
|
11
|
-
self.original_exception = exception
|
12
|
-
super().__init__(f"ToolRunException: {tool_name}: {error}")
|
1
|
+
class ToolRunException(Exception):
|
2
|
+
"""
|
3
|
+
Exception raised when a tool runs but fails due to an internal error or runtime exception.
|
4
|
+
This is distinct from ToolRunError event, which is for event bus notification.
|
5
|
+
"""
|
6
|
+
|
7
|
+
def __init__(self, tool_name, error, arguments=None, exception=None):
|
8
|
+
self.tool_name = tool_name
|
9
|
+
self.error = error
|
10
|
+
self.arguments = arguments
|
11
|
+
self.original_exception = exception
|
12
|
+
super().__init__(f"ToolRunException: {tool_name}: {error}")
|
janito/tools/tool_use_tracker.py
CHANGED
@@ -1,81 +1,81 @@
|
|
1
|
-
import threading
|
2
|
-
import os
|
3
|
-
from typing import Any, Dict, List
|
4
|
-
|
5
|
-
|
6
|
-
def normalize_path(path: str) -> str:
|
7
|
-
if not isinstance(path, str):
|
8
|
-
return path
|
9
|
-
return os.path.normcase(os.path.abspath(path))
|
10
|
-
|
11
|
-
|
12
|
-
class ToolUseTracker:
|
13
|
-
_instance = None
|
14
|
-
_lock = threading.Lock()
|
15
|
-
|
16
|
-
def __new__(cls):
|
17
|
-
if not cls._instance:
|
18
|
-
with cls._lock:
|
19
|
-
if not cls._instance:
|
20
|
-
cls._instance = super().__new__(cls)
|
21
|
-
cls._instance._history = []
|
22
|
-
return cls._instance
|
23
|
-
|
24
|
-
def record(self, tool_name: str, params: Dict[str, Any], result: Any = None):
|
25
|
-
# Normalize file_path in params if present
|
26
|
-
norm_params = params.copy()
|
27
|
-
if "file_path" in norm_params:
|
28
|
-
norm_params["file_path"] = normalize_path(norm_params["file_path"])
|
29
|
-
self._history.append(
|
30
|
-
{"tool": tool_name, "params": norm_params, "result": result}
|
31
|
-
)
|
32
|
-
|
33
|
-
def get_history(self) -> List[Dict[str, Any]]:
|
34
|
-
return list(self._history)
|
35
|
-
|
36
|
-
def get_operations_on_file(self, file_path: str) -> List[Dict[str, Any]]:
|
37
|
-
norm_file_path = normalize_path(file_path)
|
38
|
-
ops = []
|
39
|
-
for entry in self._history:
|
40
|
-
params = entry["params"]
|
41
|
-
# Normalize any string param values for comparison
|
42
|
-
for v in params.values():
|
43
|
-
if isinstance(v, str) and normalize_path(v) == norm_file_path:
|
44
|
-
ops.append(entry)
|
45
|
-
break
|
46
|
-
return ops
|
47
|
-
|
48
|
-
def file_fully_read(self, file_path: str) -> bool:
|
49
|
-
norm_file_path = normalize_path(file_path)
|
50
|
-
for entry in self._history:
|
51
|
-
if entry["tool"] == "view_file":
|
52
|
-
params = entry["params"]
|
53
|
-
if (
|
54
|
-
"file_path" in params
|
55
|
-
and normalize_path(params["file_path"]) == norm_file_path
|
56
|
-
):
|
57
|
-
# If both from_line and to_line are None, full file was read
|
58
|
-
if (
|
59
|
-
params.get("from_line") is None
|
60
|
-
and params.get("to_line") is None
|
61
|
-
):
|
62
|
-
return True
|
63
|
-
return False
|
64
|
-
|
65
|
-
def last_operation_is_full_read_or_replace(self, file_path: str) -> bool:
|
66
|
-
ops = self.get_operations_on_file(file_path)
|
67
|
-
if not ops:
|
68
|
-
return False
|
69
|
-
last = ops[-1]
|
70
|
-
if last["tool"] == "view_file":
|
71
|
-
params = last["params"]
|
72
|
-
if params.get("from_line") is None and params.get("to_line") is None:
|
73
|
-
return True
|
74
|
-
return False
|
75
|
-
|
76
|
-
def clear_history(self):
|
77
|
-
self._history.clear()
|
78
|
-
|
79
|
-
@classmethod
|
80
|
-
def instance(cls):
|
81
|
-
return cls()
|
1
|
+
import threading
|
2
|
+
import os
|
3
|
+
from typing import Any, Dict, List
|
4
|
+
|
5
|
+
|
6
|
+
def normalize_path(path: str) -> str:
|
7
|
+
if not isinstance(path, str):
|
8
|
+
return path
|
9
|
+
return os.path.normcase(os.path.abspath(path))
|
10
|
+
|
11
|
+
|
12
|
+
class ToolUseTracker:
|
13
|
+
_instance = None
|
14
|
+
_lock = threading.Lock()
|
15
|
+
|
16
|
+
def __new__(cls):
|
17
|
+
if not cls._instance:
|
18
|
+
with cls._lock:
|
19
|
+
if not cls._instance:
|
20
|
+
cls._instance = super().__new__(cls)
|
21
|
+
cls._instance._history = []
|
22
|
+
return cls._instance
|
23
|
+
|
24
|
+
def record(self, tool_name: str, params: Dict[str, Any], result: Any = None):
|
25
|
+
# Normalize file_path in params if present
|
26
|
+
norm_params = params.copy()
|
27
|
+
if "file_path" in norm_params:
|
28
|
+
norm_params["file_path"] = normalize_path(norm_params["file_path"])
|
29
|
+
self._history.append(
|
30
|
+
{"tool": tool_name, "params": norm_params, "result": result}
|
31
|
+
)
|
32
|
+
|
33
|
+
def get_history(self) -> List[Dict[str, Any]]:
|
34
|
+
return list(self._history)
|
35
|
+
|
36
|
+
def get_operations_on_file(self, file_path: str) -> List[Dict[str, Any]]:
|
37
|
+
norm_file_path = normalize_path(file_path)
|
38
|
+
ops = []
|
39
|
+
for entry in self._history:
|
40
|
+
params = entry["params"]
|
41
|
+
# Normalize any string param values for comparison
|
42
|
+
for v in params.values():
|
43
|
+
if isinstance(v, str) and normalize_path(v) == norm_file_path:
|
44
|
+
ops.append(entry)
|
45
|
+
break
|
46
|
+
return ops
|
47
|
+
|
48
|
+
def file_fully_read(self, file_path: str) -> bool:
|
49
|
+
norm_file_path = normalize_path(file_path)
|
50
|
+
for entry in self._history:
|
51
|
+
if entry["tool"] == "view_file":
|
52
|
+
params = entry["params"]
|
53
|
+
if (
|
54
|
+
"file_path" in params
|
55
|
+
and normalize_path(params["file_path"]) == norm_file_path
|
56
|
+
):
|
57
|
+
# If both from_line and to_line are None, full file was read
|
58
|
+
if (
|
59
|
+
params.get("from_line") is None
|
60
|
+
and params.get("to_line") is None
|
61
|
+
):
|
62
|
+
return True
|
63
|
+
return False
|
64
|
+
|
65
|
+
def last_operation_is_full_read_or_replace(self, file_path: str) -> bool:
|
66
|
+
ops = self.get_operations_on_file(file_path)
|
67
|
+
if not ops:
|
68
|
+
return False
|
69
|
+
last = ops[-1]
|
70
|
+
if last["tool"] == "view_file":
|
71
|
+
params = last["params"]
|
72
|
+
if params.get("from_line") is None and params.get("to_line") is None:
|
73
|
+
return True
|
74
|
+
return False
|
75
|
+
|
76
|
+
def clear_history(self):
|
77
|
+
self._history.clear()
|
78
|
+
|
79
|
+
@classmethod
|
80
|
+
def instance(cls):
|
81
|
+
return cls()
|
janito/tools/tool_utils.py
CHANGED
@@ -1,45 +1,45 @@
|
|
1
|
-
"""
|
2
|
-
Utility functions for the janito project.
|
3
|
-
Add your shared helper functions here.
|
4
|
-
"""
|
5
|
-
|
6
|
-
import os
|
7
|
-
import urllib.parse
|
8
|
-
|
9
|
-
|
10
|
-
def example_utility_function(x):
|
11
|
-
"""A simple example utility function."""
|
12
|
-
return f"Processed: {x}"
|
13
|
-
|
14
|
-
|
15
|
-
def display_path(path):
|
16
|
-
"""
|
17
|
-
Returns a display-friendly path. Injects an ANSI hyperlink to a local web file viewer using a hardcoded port.
|
18
|
-
Args:
|
19
|
-
path (str): Path to display.
|
20
|
-
Returns:
|
21
|
-
str: Display path, as an ANSI hyperlink.
|
22
|
-
"""
|
23
|
-
from janito.cli.config import get_termweb_port
|
24
|
-
|
25
|
-
port = get_termweb_port()
|
26
|
-
if os.path.isabs(path):
|
27
|
-
cwd = os.path.abspath(os.getcwd())
|
28
|
-
abs_path = os.path.abspath(path)
|
29
|
-
# Check if the absolute path is within the current working directory
|
30
|
-
if abs_path.startswith(cwd + os.sep):
|
31
|
-
disp = os.path.relpath(abs_path, cwd)
|
32
|
-
else:
|
33
|
-
disp = path
|
34
|
-
else:
|
35
|
-
disp = os.path.relpath(path)
|
36
|
-
url = f"http://localhost:{port}/?path={urllib.parse.quote(path)}"
|
37
|
-
# Use Rich markup for hyperlinks
|
38
|
-
return f"[link={url}]{disp}[/link]"
|
39
|
-
|
40
|
-
|
41
|
-
def pluralize(word: str, count: int) -> str:
|
42
|
-
"""Return the pluralized form of word if count != 1, unless word already ends with 's'."""
|
43
|
-
if count == 1 or word.endswith("s"):
|
44
|
-
return word
|
45
|
-
return word + "s"
|
1
|
+
"""
|
2
|
+
Utility functions for the janito project.
|
3
|
+
Add your shared helper functions here.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import os
|
7
|
+
import urllib.parse
|
8
|
+
|
9
|
+
|
10
|
+
def example_utility_function(x):
|
11
|
+
"""A simple example utility function."""
|
12
|
+
return f"Processed: {x}"
|
13
|
+
|
14
|
+
|
15
|
+
def display_path(path):
|
16
|
+
"""
|
17
|
+
Returns a display-friendly path. Injects an ANSI hyperlink to a local web file viewer using a hardcoded port.
|
18
|
+
Args:
|
19
|
+
path (str): Path to display.
|
20
|
+
Returns:
|
21
|
+
str: Display path, as an ANSI hyperlink.
|
22
|
+
"""
|
23
|
+
from janito.cli.config import get_termweb_port
|
24
|
+
|
25
|
+
port = get_termweb_port()
|
26
|
+
if os.path.isabs(path):
|
27
|
+
cwd = os.path.abspath(os.getcwd())
|
28
|
+
abs_path = os.path.abspath(path)
|
29
|
+
# Check if the absolute path is within the current working directory
|
30
|
+
if abs_path.startswith(cwd + os.sep):
|
31
|
+
disp = os.path.relpath(abs_path, cwd)
|
32
|
+
else:
|
33
|
+
disp = path
|
34
|
+
else:
|
35
|
+
disp = os.path.relpath(path)
|
36
|
+
url = f"http://localhost:{port}/?path={urllib.parse.quote(path)}"
|
37
|
+
# Use Rich markup for hyperlinks
|
38
|
+
return f"[link={url}]{disp}[/link]"
|
39
|
+
|
40
|
+
|
41
|
+
def pluralize(word: str, count: int) -> str:
|
42
|
+
"""Return the pluralized form of word if count != 1, unless word already ends with 's'."""
|
43
|
+
if count == 1 or word.endswith("s"):
|
44
|
+
return word
|
45
|
+
return word + "s"
|
janito/tools/tools_schema.py
CHANGED
@@ -1,104 +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
|
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/version.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# janito/version.py
|
2
|
-
"""Single source of truth for the janito package version."""
|
3
|
-
|
4
|
-
__version__ = "2.3.
|
1
|
+
# janito/version.py
|
2
|
+
"""Single source of truth for the janito package version."""
|
3
|
+
|
4
|
+
__version__ = "2.3.1"
|