janito 1.5.2__py3-none-any.whl → 1.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/__init__.py +1 -1
- janito/__main__.py +0 -1
- janito/agent/config.py +11 -10
- janito/agent/config_defaults.py +3 -2
- janito/agent/conversation.py +93 -119
- janito/agent/conversation_api.py +98 -0
- janito/agent/conversation_exceptions.py +12 -0
- janito/agent/conversation_tool_calls.py +22 -0
- janito/agent/conversation_ui.py +17 -0
- janito/agent/message_handler.py +8 -9
- janito/agent/{agent.py → openai_client.py} +48 -16
- janito/agent/openai_schema_generator.py +53 -37
- janito/agent/profile_manager.py +172 -0
- janito/agent/queued_message_handler.py +13 -14
- janito/agent/rich_live.py +32 -0
- janito/agent/rich_message_handler.py +64 -0
- janito/agent/runtime_config.py +6 -1
- janito/agent/{tools/tool_base.py → tool_base.py} +15 -8
- janito/agent/tool_registry.py +118 -132
- janito/agent/tools/__init__.py +41 -2
- janito/agent/tools/ask_user.py +43 -33
- janito/agent/tools/create_directory.py +18 -16
- janito/agent/tools/create_file.py +31 -36
- janito/agent/tools/fetch_url.py +23 -19
- janito/agent/tools/find_files.py +40 -36
- janito/agent/tools/get_file_outline.py +100 -22
- janito/agent/tools/get_lines.py +40 -32
- janito/agent/tools/gitignore_utils.py +9 -6
- janito/agent/tools/move_file.py +22 -13
- janito/agent/tools/py_compile_file.py +40 -0
- janito/agent/tools/remove_directory.py +34 -24
- janito/agent/tools/remove_file.py +22 -20
- janito/agent/tools/replace_file.py +51 -0
- janito/agent/tools/replace_text_in_file.py +69 -42
- janito/agent/tools/rich_live.py +9 -2
- janito/agent/tools/run_bash_command.py +155 -107
- janito/agent/tools/run_python_command.py +139 -0
- janito/agent/tools/search_files.py +51 -34
- janito/agent/tools/tools_utils.py +4 -2
- janito/agent/tools/utils.py +6 -2
- janito/cli/_print_config.py +42 -16
- janito/cli/_utils.py +1 -0
- janito/cli/arg_parser.py +182 -29
- janito/cli/config_commands.py +54 -22
- janito/cli/logging_setup.py +9 -3
- janito/cli/main.py +11 -10
- janito/cli/runner/__init__.py +2 -0
- janito/cli/runner/cli_main.py +148 -0
- janito/cli/runner/config.py +33 -0
- janito/cli/runner/formatting.py +12 -0
- janito/cli/runner/scan.py +44 -0
- janito/cli_chat_shell/__init__.py +0 -1
- janito/cli_chat_shell/chat_loop.py +71 -92
- janito/cli_chat_shell/chat_state.py +38 -0
- janito/cli_chat_shell/chat_ui.py +43 -0
- janito/cli_chat_shell/commands/__init__.py +45 -0
- janito/cli_chat_shell/commands/config.py +22 -0
- janito/cli_chat_shell/commands/history_reset.py +29 -0
- janito/cli_chat_shell/commands/session.py +48 -0
- janito/cli_chat_shell/commands/session_control.py +12 -0
- janito/cli_chat_shell/commands/system.py +73 -0
- janito/cli_chat_shell/commands/utility.py +29 -0
- janito/cli_chat_shell/config_shell.py +39 -10
- janito/cli_chat_shell/load_prompt.py +5 -2
- janito/cli_chat_shell/session_manager.py +24 -27
- janito/cli_chat_shell/ui.py +75 -40
- janito/rich_utils.py +15 -2
- janito/web/__main__.py +10 -2
- janito/web/app.py +88 -52
- {janito-1.5.2.dist-info → janito-1.6.0.dist-info}/METADATA +76 -11
- janito-1.6.0.dist-info/RECORD +81 -0
- {janito-1.5.2.dist-info → janito-1.6.0.dist-info}/WHEEL +1 -1
- janito/agent/rich_tool_handler.py +0 -43
- janito/agent/templates/system_instructions.j2 +0 -38
- janito/agent/tool_auto_imports.py +0 -5
- janito/agent/tools/append_text_to_file.py +0 -41
- janito/agent/tools/py_compile.py +0 -39
- janito/agent/tools/python_exec.py +0 -83
- janito/cli/runner.py +0 -137
- janito/cli_chat_shell/commands.py +0 -204
- janito/render_prompt.py +0 -13
- janito-1.5.2.dist-info/RECORD +0 -66
- {janito-1.5.2.dist-info → janito-1.6.0.dist-info}/entry_points.txt +0 -0
- {janito-1.5.2.dist-info → janito-1.6.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.5.2.dist-info → janito-1.6.0.dist-info}/top_level.txt +0 -0
janito/agent/tool_registry.py
CHANGED
@@ -1,132 +1,118 @@
|
|
1
|
-
# janito/agent/tool_registry.py
|
2
|
-
import json
|
3
|
-
from janito.agent.
|
4
|
-
from janito.agent.openai_schema_generator import generate_openai_function_schema
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
})
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
'result': result
|
120
|
-
})
|
121
|
-
if verbose:
|
122
|
-
preview = result
|
123
|
-
if isinstance(result, str):
|
124
|
-
lines = result.splitlines()
|
125
|
-
if len(lines) > 10:
|
126
|
-
preview = "\n".join(lines[:10]) + "\n... (truncated)"
|
127
|
-
elif len(result) > 500:
|
128
|
-
preview = result[:500] + "... (truncated)"
|
129
|
-
print(f"[Tool Result] {tool_call.function.name} returned:\n{preview}")
|
130
|
-
if instance is not None:
|
131
|
-
instance._progress_callback = None
|
132
|
-
return result
|
1
|
+
# janito/agent/tool_registry.py
|
2
|
+
import json
|
3
|
+
from janito.agent.tool_base import ToolBase
|
4
|
+
from janito.agent.openai_schema_generator import generate_openai_function_schema
|
5
|
+
import inspect
|
6
|
+
|
7
|
+
_tool_registry = {}
|
8
|
+
|
9
|
+
|
10
|
+
def register_tool(tool=None, *, name: str = None):
|
11
|
+
if tool is None:
|
12
|
+
return lambda t: register_tool(t, name=name)
|
13
|
+
override_name = name
|
14
|
+
if not (isinstance(tool, type) and issubclass(tool, ToolBase)):
|
15
|
+
raise TypeError("Tool must be a class derived from ToolBase.")
|
16
|
+
instance = tool()
|
17
|
+
if not hasattr(instance, "call") or not callable(instance.call):
|
18
|
+
raise TypeError(
|
19
|
+
f"Tool '{tool.__name__}' must implement a callable 'call' method."
|
20
|
+
)
|
21
|
+
tool_name = override_name or instance.name
|
22
|
+
if tool_name in _tool_registry:
|
23
|
+
raise ValueError(f"Tool '{tool_name}' is already registered.")
|
24
|
+
schema = generate_openai_function_schema(instance.call, tool_name, tool_class=tool)
|
25
|
+
_tool_registry[tool_name] = {
|
26
|
+
"function": instance.call,
|
27
|
+
"description": schema["description"],
|
28
|
+
"parameters": schema["parameters"],
|
29
|
+
"class": tool,
|
30
|
+
"instance": instance,
|
31
|
+
}
|
32
|
+
return tool
|
33
|
+
|
34
|
+
|
35
|
+
def get_tool_schemas():
|
36
|
+
schemas = []
|
37
|
+
for name, entry in _tool_registry.items():
|
38
|
+
schemas.append(
|
39
|
+
{
|
40
|
+
"type": "function",
|
41
|
+
"function": {
|
42
|
+
"name": name,
|
43
|
+
"description": entry["description"],
|
44
|
+
"parameters": entry["parameters"],
|
45
|
+
},
|
46
|
+
}
|
47
|
+
)
|
48
|
+
return schemas
|
49
|
+
|
50
|
+
|
51
|
+
def handle_tool_call(tool_call, message_handler=None, verbose=False):
|
52
|
+
import uuid
|
53
|
+
|
54
|
+
call_id = getattr(tool_call, "id", None) or str(uuid.uuid4())
|
55
|
+
tool_entry = _tool_registry.get(tool_call.function.name)
|
56
|
+
if not tool_entry:
|
57
|
+
return f"Unknown tool: {tool_call.function.name}"
|
58
|
+
func = tool_entry["function"]
|
59
|
+
args = json.loads(tool_call.function.arguments)
|
60
|
+
if verbose:
|
61
|
+
print(f"[Tool Call] {tool_call.function.name} called with arguments: {args}")
|
62
|
+
instance = None
|
63
|
+
if hasattr(func, "__self__") and isinstance(func.__self__, ToolBase):
|
64
|
+
instance = func.__self__
|
65
|
+
if message_handler:
|
66
|
+
instance._progress_callback = message_handler.handle_message
|
67
|
+
# Emit tool_call event before calling the tool
|
68
|
+
if message_handler:
|
69
|
+
message_handler.handle_message(
|
70
|
+
{
|
71
|
+
"type": "tool_call",
|
72
|
+
"tool": tool_call.function.name,
|
73
|
+
"call_id": call_id,
|
74
|
+
"arguments": args,
|
75
|
+
}
|
76
|
+
)
|
77
|
+
# --- Argument validation start ---
|
78
|
+
sig = inspect.signature(func)
|
79
|
+
try:
|
80
|
+
sig.bind(**args)
|
81
|
+
except TypeError as e:
|
82
|
+
error_msg = (
|
83
|
+
f"Argument validation error for tool '{tool_call.function.name}': {str(e)}"
|
84
|
+
)
|
85
|
+
if message_handler:
|
86
|
+
message_handler.handle_message(
|
87
|
+
{
|
88
|
+
"type": "tool_error",
|
89
|
+
"tool": tool_call.function.name,
|
90
|
+
"call_id": call_id,
|
91
|
+
"error": error_msg,
|
92
|
+
}
|
93
|
+
)
|
94
|
+
raise TypeError(error_msg)
|
95
|
+
# --- Argument validation end ---
|
96
|
+
try:
|
97
|
+
result = func(**args)
|
98
|
+
if message_handler:
|
99
|
+
message_handler.handle_message(
|
100
|
+
{
|
101
|
+
"type": "tool_result",
|
102
|
+
"tool": tool_call.function.name,
|
103
|
+
"call_id": call_id,
|
104
|
+
"result": result,
|
105
|
+
}
|
106
|
+
)
|
107
|
+
return result
|
108
|
+
except Exception as e:
|
109
|
+
if message_handler:
|
110
|
+
message_handler.handle_message(
|
111
|
+
{
|
112
|
+
"type": "tool_error",
|
113
|
+
"tool": tool_call.function.name,
|
114
|
+
"call_id": call_id,
|
115
|
+
"error": str(e),
|
116
|
+
}
|
117
|
+
)
|
118
|
+
raise
|
janito/agent/tools/__init__.py
CHANGED
@@ -1,2 +1,41 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
from . import ask_user
|
2
|
+
from . import create_directory
|
3
|
+
from . import create_file
|
4
|
+
from . import fetch_url
|
5
|
+
from . import find_files
|
6
|
+
from . import get_file_outline
|
7
|
+
from . import get_lines
|
8
|
+
from . import gitignore_utils
|
9
|
+
from . import move_file
|
10
|
+
from . import py_compile_file
|
11
|
+
from . import remove_directory
|
12
|
+
from . import remove_file
|
13
|
+
from . import replace_text_in_file
|
14
|
+
from . import rich_live
|
15
|
+
from . import run_bash_command
|
16
|
+
from . import run_python_command
|
17
|
+
from . import search_files
|
18
|
+
from . import tools_utils
|
19
|
+
from . import replace_file
|
20
|
+
|
21
|
+
__all__ = [
|
22
|
+
"ask_user",
|
23
|
+
"create_directory",
|
24
|
+
"create_file",
|
25
|
+
"fetch_url",
|
26
|
+
"find_files",
|
27
|
+
"get_file_outline",
|
28
|
+
"get_lines",
|
29
|
+
"gitignore_utils",
|
30
|
+
"move_file",
|
31
|
+
"py_compile_file",
|
32
|
+
"remove_directory",
|
33
|
+
"remove_file",
|
34
|
+
"replace_text_in_file",
|
35
|
+
"rich_live",
|
36
|
+
"run_bash_command",
|
37
|
+
"run_python_command",
|
38
|
+
"search_files",
|
39
|
+
"tools_utils",
|
40
|
+
"replace_file",
|
41
|
+
]
|
janito/agent/tools/ask_user.py
CHANGED
@@ -1,23 +1,22 @@
|
|
1
|
-
from janito.agent.
|
1
|
+
from janito.agent.tool_base import ToolBase
|
2
2
|
from janito.agent.tool_registry import register_tool
|
3
3
|
|
4
|
+
|
4
5
|
@register_tool(name="ask_user")
|
5
6
|
class AskUserTool(ToolBase):
|
6
|
-
"""
|
7
|
-
|
8
|
-
"""
|
9
|
-
Ask the user a question and return their response.
|
10
|
-
|
11
|
-
Args:
|
12
|
-
question (str): The question to ask the user.
|
7
|
+
"""
|
8
|
+
Request clarification or input from the user whenever there is uncertainty, ambiguity, missing information, or multiple valid options. Returns the user's response as a string.
|
13
9
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
10
|
+
Args:
|
11
|
+
question (str): The question to ask the user.
|
12
|
+
Returns:
|
13
|
+
str: The user's response as a string. Example:
|
14
|
+
- "Yes"
|
15
|
+
- "No"
|
16
|
+
- "Some detailed answer..."
|
17
|
+
"""
|
20
18
|
|
19
|
+
def call(self, question: str) -> str:
|
21
20
|
from rich import print as rich_print
|
22
21
|
from rich.panel import Panel
|
23
22
|
from prompt_toolkit import PromptSession
|
@@ -29,46 +28,57 @@ class AskUserTool(ToolBase):
|
|
29
28
|
rich_print(Panel.fit(question, title="Question", style="cyan"))
|
30
29
|
|
31
30
|
bindings = KeyBindings()
|
32
|
-
mode = {
|
31
|
+
mode = {"multiline": False}
|
33
32
|
|
34
|
-
@bindings.add(
|
33
|
+
@bindings.add("c-r")
|
35
34
|
def _(event):
|
36
35
|
pass
|
37
36
|
|
38
|
-
|
39
|
-
|
40
|
-
'
|
41
|
-
|
42
|
-
|
37
|
+
@bindings.add("f12")
|
38
|
+
def _(event):
|
39
|
+
"""When F12 is pressed, send 'proceed' as input immediately."""
|
40
|
+
buf = event.app.current_buffer
|
41
|
+
buf.text = "proceed"
|
42
|
+
buf.validate_and_handle()
|
43
|
+
|
44
|
+
style = Style.from_dict(
|
45
|
+
{
|
46
|
+
"bottom-toolbar": "bg:#333333 #ffffff",
|
47
|
+
"b": "bold",
|
48
|
+
"prompt": "bold bg:#000080 #ffffff",
|
49
|
+
}
|
50
|
+
)
|
43
51
|
|
44
52
|
def get_toolbar():
|
45
|
-
|
46
|
-
|
53
|
+
f12_hint = " Press <b>F12</b> to auto-fill 'proceed' and submit."
|
54
|
+
if mode["multiline"]:
|
55
|
+
return HTML(
|
56
|
+
f"<b>Multiline mode (Esc+Enter to submit). Type /single to switch.</b>{f12_hint}"
|
57
|
+
)
|
47
58
|
else:
|
48
|
-
return HTML(
|
59
|
+
return HTML(
|
60
|
+
f"<b>Single-line mode (Enter to submit). Type /multi for multiline.</b>{f12_hint}"
|
61
|
+
)
|
49
62
|
|
50
63
|
session = PromptSession(
|
51
64
|
multiline=False,
|
52
65
|
key_bindings=bindings,
|
53
66
|
editing_mode=EditingMode.EMACS,
|
54
67
|
bottom_toolbar=get_toolbar,
|
55
|
-
style=style
|
68
|
+
style=style,
|
56
69
|
)
|
57
70
|
|
58
|
-
prompt_icon = HTML(
|
71
|
+
prompt_icon = HTML("<prompt>💬 </prompt>")
|
59
72
|
|
60
73
|
while True:
|
61
74
|
response = session.prompt(prompt_icon)
|
62
|
-
if not mode[
|
63
|
-
mode[
|
75
|
+
if not mode["multiline"] and response.strip() == "/multi":
|
76
|
+
mode["multiline"] = True
|
64
77
|
session.multiline = True
|
65
78
|
continue
|
66
|
-
elif mode[
|
67
|
-
mode[
|
79
|
+
elif mode["multiline"] and response.strip() == "/single":
|
80
|
+
mode["multiline"] = False
|
68
81
|
session.multiline = False
|
69
82
|
continue
|
70
83
|
else:
|
71
84
|
return response
|
72
|
-
|
73
|
-
|
74
|
-
from janito.agent.tool_registry import register_tool
|
@@ -1,31 +1,33 @@
|
|
1
1
|
from janito.agent.tool_registry import register_tool
|
2
2
|
from janito.agent.tools.utils import expand_path, display_path
|
3
|
-
from janito.agent.
|
3
|
+
from janito.agent.tool_base import ToolBase
|
4
|
+
|
4
5
|
|
5
6
|
@register_tool(name="create_directory")
|
6
7
|
class CreateDirectoryTool(ToolBase):
|
7
8
|
"""
|
8
9
|
Create a new directory at the specified path.
|
9
|
-
"""
|
10
|
-
def call(self, path: str, overwrite: bool = False) -> str:
|
11
|
-
"""
|
12
|
-
Create a new directory at the specified path.
|
13
10
|
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
Args:
|
12
|
+
path (str): Path for the new directory.
|
13
|
+
overwrite (bool, optional): Whether to overwrite if the directory exists. Defaults to False.
|
14
|
+
Returns:
|
15
|
+
str: Status message indicating the result. Example:
|
16
|
+
- "\u2705 Successfully created the directory at ..."
|
17
|
+
- "\u2757 Cannot create directory: ..."
|
18
|
+
"""
|
17
19
|
|
18
|
-
|
19
|
-
str: Status message indicating the result. Example:
|
20
|
-
- "✅ Successfully created the directory at ..."
|
21
|
-
- "❗ Cannot create directory: ..."
|
22
|
-
"""
|
20
|
+
def call(self, path: str, overwrite: bool = False) -> str:
|
23
21
|
original_path = path
|
24
22
|
path = expand_path(path)
|
25
23
|
disp_path = display_path(original_path, path)
|
24
|
+
import os
|
25
|
+
|
26
26
|
if os.path.exists(path):
|
27
27
|
if not os.path.isdir(path):
|
28
|
-
self.report_error(
|
29
|
-
|
28
|
+
self.report_error(
|
29
|
+
f"\u274c Path '{disp_path}' exists and is not a directory."
|
30
|
+
)
|
31
|
+
return f"\u274c Path '{disp_path}' exists and is not a directory."
|
30
32
|
# Directory creation logic would go here
|
31
|
-
return f"
|
33
|
+
return f"\u2705 Successfully created the directory at '{disp_path}'."
|
@@ -1,52 +1,47 @@
|
|
1
1
|
import os
|
2
|
+
import shutil
|
2
3
|
from janito.agent.tool_registry import register_tool
|
3
4
|
from janito.agent.tools.utils import expand_path, display_path
|
4
|
-
from janito.agent.
|
5
|
+
from janito.agent.tool_base import ToolBase
|
5
6
|
from janito.agent.tools.tools_utils import pluralize
|
6
7
|
|
8
|
+
|
7
9
|
@register_tool(name="create_file")
|
8
10
|
class CreateFileTool(ToolBase):
|
9
11
|
"""
|
10
|
-
Create a new file
|
11
|
-
"""
|
12
|
-
def call(self, path: str, content: str, overwrite: bool = False) -> str:
|
13
|
-
"""
|
14
|
-
Create or update a file with the given content.
|
12
|
+
Create a new file with the given content. Fails if the file already exists.
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
This tool will NOT overwrite existing files. If the file already exists, the operation fails and no changes are made to the file itself.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
path (str): Path to the file to create.
|
18
|
+
content (str): Content to write to the file.
|
19
|
+
backup (bool, optional): If True, create a backup (.bak) before returning an error if the file exists. Defaults to False.
|
20
|
+
Returns:
|
21
|
+
str: Status message indicating the result. Example:
|
22
|
+
- "\u2705 Successfully created the file at ..."
|
23
|
+
- "\u2757 Cannot create file: ..."
|
24
|
+
"""
|
20
25
|
|
21
|
-
|
22
|
-
str: Status message indicating the result. Example:
|
23
|
-
- "✅ Successfully created the file at ..."
|
24
|
-
- "❗ Cannot create file: ..."
|
25
|
-
"""
|
26
|
+
def call(self, path: str, content: str, backup: bool = False) -> str:
|
26
27
|
original_path = path
|
27
28
|
path = expand_path(path)
|
28
|
-
updating = os.path.exists(path) and not os.path.isdir(path)
|
29
29
|
disp_path = display_path(original_path, path)
|
30
30
|
if os.path.exists(path):
|
31
31
|
if os.path.isdir(path):
|
32
|
-
self.report_error("
|
33
|
-
return f"
|
34
|
-
if
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
old_lines = sum(1 for _ in f)
|
45
|
-
with open(path, "w", encoding="utf-8") as f:
|
32
|
+
self.report_error("\u274c Error: is a directory")
|
33
|
+
return f"\u274c Cannot create file: '{disp_path}' is an existing directory."
|
34
|
+
if backup:
|
35
|
+
shutil.copy2(path, path + ".bak")
|
36
|
+
self.report_error(f"\u2757 Error: file '{disp_path}' already exists")
|
37
|
+
return f"\u2757 Cannot create file: '{disp_path}' already exists."
|
38
|
+
# Ensure parent directories exist
|
39
|
+
dir_name = os.path.dirname(path)
|
40
|
+
if dir_name:
|
41
|
+
os.makedirs(dir_name, exist_ok=True)
|
42
|
+
self.report_info(f"\U0001f4dd Creating file: '{disp_path}' ... ")
|
43
|
+
with open(path, "w", encoding="utf-8", errors="replace") as f:
|
46
44
|
f.write(content)
|
47
|
-
new_lines = content.count(
|
48
|
-
|
49
|
-
|
50
|
-
return f"✅ Successfully updated the file at '{disp_path}' ({old_lines} > {new_lines} lines)."
|
51
|
-
self.report_success(f"✅ {new_lines} {pluralize('line', new_lines)}")
|
52
|
-
return f"✅ Successfully created the file at '{disp_path}' ({new_lines} lines)."
|
45
|
+
new_lines = content.count("\n") + 1 if content else 0
|
46
|
+
self.report_success(f"\u2705 {new_lines} {pluralize('line', new_lines)}")
|
47
|
+
return f"\u2705 Successfully created the file at '{disp_path}' ({new_lines} lines)."
|
janito/agent/tools/fetch_url.py
CHANGED
@@ -2,34 +2,39 @@ import requests
|
|
2
2
|
from bs4 import BeautifulSoup
|
3
3
|
from janito.agent.tool_registry import register_tool
|
4
4
|
|
5
|
-
from janito.agent.
|
5
|
+
from janito.agent.tool_base import ToolBase
|
6
|
+
|
6
7
|
|
7
8
|
@register_tool(name="fetch_url")
|
8
9
|
class FetchUrlTool(ToolBase):
|
9
|
-
"""
|
10
|
-
|
11
|
-
"""
|
12
|
-
Fetch the content of a web page and extract its text.
|
10
|
+
"""
|
11
|
+
Fetch the content of a web page and extract its text.
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
Args:
|
14
|
+
url (str): The URL of the web page to fetch.
|
15
|
+
search_strings (list[str], optional): Strings to search for in the page content.
|
16
|
+
Returns:
|
17
|
+
str: Extracted text content from the web page, or a warning message. Example:
|
18
|
+
- "<main text content...>"
|
19
|
+
- "No lines found for the provided search strings."
|
20
|
+
- "Warning: Empty URL provided. Operation skipped."
|
21
|
+
"""
|
17
22
|
|
18
|
-
|
19
|
-
str: Extracted text content from the web page, or a warning message. Example:
|
20
|
-
- "<main text content...>"
|
21
|
-
- "No lines found for the provided search strings."
|
22
|
-
- "Warning: Empty URL provided. Operation skipped."
|
23
|
-
"""
|
23
|
+
def call(self, url: str, search_strings: list[str] = None) -> str:
|
24
24
|
if not url.strip():
|
25
25
|
self.report_warning("⚠️ Warning: Empty URL provided. Operation skipped.")
|
26
26
|
return "Warning: Empty URL provided. Operation skipped."
|
27
27
|
self.report_info(f"🌐 Fetching URL: {url} ... ")
|
28
28
|
response = requests.get(url, timeout=10)
|
29
29
|
response.raise_for_status()
|
30
|
-
self.update_progress(
|
31
|
-
|
32
|
-
|
30
|
+
self.update_progress(
|
31
|
+
{
|
32
|
+
"event": "progress",
|
33
|
+
"message": f"Fetched URL with status {response.status_code}",
|
34
|
+
}
|
35
|
+
)
|
36
|
+
soup = BeautifulSoup(response.text, "html.parser")
|
37
|
+
text = soup.get_text(separator="\n")
|
33
38
|
|
34
39
|
if search_strings:
|
35
40
|
filtered = []
|
@@ -41,10 +46,9 @@ class FetchUrlTool(ToolBase):
|
|
41
46
|
snippet = text[start:end]
|
42
47
|
filtered.append(snippet)
|
43
48
|
if filtered:
|
44
|
-
text =
|
49
|
+
text = "\n...\n".join(filtered)
|
45
50
|
else:
|
46
51
|
text = "No lines found for the provided search strings."
|
47
52
|
|
48
53
|
self.report_success("✅ Result")
|
49
54
|
return text
|
50
|
-
|