janito 1.7.0__py3-none-any.whl → 1.8.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/agent/config.py +1 -1
- janito/agent/config_defaults.py +2 -2
- janito/agent/conversation.py +70 -27
- janito/agent/conversation_api.py +104 -4
- janito/agent/conversation_exceptions.py +6 -0
- janito/agent/conversation_tool_calls.py +17 -3
- janito/agent/event.py +24 -0
- janito/agent/event_dispatcher.py +24 -0
- janito/agent/event_handler_protocol.py +5 -0
- janito/agent/event_system.py +15 -0
- janito/agent/message_handler.py +4 -1
- janito/agent/message_handler_protocol.py +5 -0
- janito/agent/openai_client.py +5 -8
- janito/agent/openai_schema_generator.py +23 -4
- janito/agent/profile_manager.py +15 -83
- janito/agent/queued_message_handler.py +22 -3
- janito/agent/rich_message_handler.py +66 -72
- janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +14 -0
- janito/agent/templates/profiles/system_prompt_template_base_pt.txt.j2 +13 -0
- janito/agent/test_handler_protocols.py +47 -0
- janito/agent/tests/__init__.py +1 -0
- janito/agent/tool_base.py +1 -1
- janito/agent/tool_executor.py +109 -0
- janito/agent/tool_registry.py +3 -75
- janito/agent/tool_use_tracker.py +46 -0
- janito/agent/tools/__init__.py +8 -9
- janito/agent/tools/ask_user.py +19 -11
- janito/agent/tools/create_directory.py +43 -28
- janito/agent/tools/create_file.py +60 -29
- janito/agent/tools/dir_walk_utils.py +16 -0
- janito/agent/tools/fetch_url.py +10 -11
- janito/agent/tools/find_files.py +49 -32
- janito/agent/tools/get_lines.py +54 -18
- janito/agent/tools/memory.py +32 -52
- janito/agent/tools/move_file.py +72 -23
- janito/agent/tools/outline_file/__init__.py +85 -0
- janito/agent/tools/outline_file/formatting.py +20 -0
- janito/agent/tools/outline_file/markdown_outline.py +14 -0
- janito/agent/tools/outline_file/python_outline.py +71 -0
- janito/agent/tools/present_choices.py +62 -0
- janito/agent/tools/present_choices_test.py +18 -0
- janito/agent/tools/remove_directory.py +31 -26
- janito/agent/tools/remove_file.py +31 -13
- janito/agent/tools/replace_text_in_file.py +135 -36
- janito/agent/tools/run_bash_command.py +47 -50
- janito/agent/tools/run_powershell_command.py +52 -36
- janito/agent/tools/run_python_command.py +49 -29
- janito/agent/tools/search_outline.py +17 -0
- janito/agent/tools/search_text.py +208 -0
- janito/agent/tools/tools_utils.py +47 -4
- janito/agent/tools/utils.py +14 -15
- janito/agent/tools/validate_file_syntax.py +163 -0
- janito/cli/arg_parser.py +36 -4
- janito/cli/logging_setup.py +7 -2
- janito/cli/main.py +96 -2
- janito/cli/runner/_termweb_log_utils.py +17 -0
- janito/cli/runner/cli_main.py +119 -77
- janito/cli/runner/config.py +2 -2
- janito/cli/termweb_starter.py +73 -0
- janito/cli_chat_shell/chat_loop.py +42 -7
- janito/cli_chat_shell/chat_state.py +1 -1
- janito/cli_chat_shell/chat_ui.py +0 -1
- janito/cli_chat_shell/commands/__init__.py +15 -6
- janito/cli_chat_shell/commands/{history_reset.py → history_start.py} +13 -5
- janito/cli_chat_shell/commands/lang.py +16 -0
- janito/cli_chat_shell/commands/prompt.py +42 -0
- janito/cli_chat_shell/commands/session_control.py +36 -1
- janito/cli_chat_shell/commands/termweb_log.py +86 -0
- janito/cli_chat_shell/commands/utility.py +5 -2
- janito/cli_chat_shell/commands/verbose.py +29 -0
- janito/cli_chat_shell/session_manager.py +9 -1
- janito/cli_chat_shell/shell_command_completer.py +20 -0
- janito/cli_chat_shell/ui.py +110 -99
- janito/i18n/__init__.py +35 -0
- janito/i18n/messages.py +23 -0
- janito/i18n/pt.py +46 -0
- janito/rich_utils.py +43 -43
- janito/termweb/app.py +95 -0
- janito/termweb/static/editor.html +238 -0
- janito/termweb/static/editor.html.bak +238 -0
- janito/termweb/static/explorer.html.bak +59 -0
- janito/termweb/static/favicon.ico +0 -0
- janito/termweb/static/favicon.ico.bak +0 -0
- janito/termweb/static/index.html +55 -0
- janito/termweb/static/index.html.bak +55 -0
- janito/termweb/static/index.html.bak.bak +175 -0
- janito/termweb/static/landing.html.bak +36 -0
- janito/termweb/static/termicon.svg +1 -0
- janito/termweb/static/termweb.css +235 -0
- janito/termweb/static/termweb.css.bak +286 -0
- janito/termweb/static/termweb.js +187 -0
- janito/termweb/static/termweb.js.bak +187 -0
- janito/termweb/static/termweb.js.bak.bak +157 -0
- janito/termweb/static/termweb_quickopen.js +135 -0
- janito/termweb/static/termweb_quickopen.js.bak +125 -0
- janito/web/app.py +4 -4
- {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/METADATA +58 -25
- janito-1.8.0.dist-info/RECORD +127 -0
- {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/WHEEL +1 -1
- janito/agent/templates/profiles/system_prompt_template_base.toml +0 -76
- janito/agent/templates/profiles/system_prompt_template_default.toml +0 -3
- janito/agent/templates/profiles/system_prompt_template_technical.toml +0 -13
- janito/agent/tests/test_prompt_toml.py +0 -61
- janito/agent/tool_registry_core.py +0 -2
- janito/agent/tools/get_file_outline.py +0 -146
- janito/agent/tools/py_compile_file.py +0 -40
- janito/agent/tools/replace_file.py +0 -51
- janito/agent/tools/search_files.py +0 -65
- janito/cli/runner/scan.py +0 -57
- janito/cli_chat_shell/commands/system.py +0 -73
- janito-1.7.0.dist-info/RECORD +0 -89
- {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/entry_points.txt +0 -0
- {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/top_level.txt +0 -0
janito/agent/profile_manager.py
CHANGED
@@ -1,21 +1,11 @@
|
|
1
1
|
from openai import OpenAI
|
2
|
-
import
|
3
|
-
from string import Template
|
2
|
+
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
4
3
|
from pathlib import Path
|
5
4
|
from janito.agent.platform_discovery import get_platform_name, get_python_version
|
6
5
|
from janito.agent.platform_discovery import detect_shell
|
7
6
|
|
8
7
|
|
9
8
|
class AgentProfileManager:
|
10
|
-
def _report_template_not_found(self, template_name, search_dirs):
|
11
|
-
import sys
|
12
|
-
|
13
|
-
search_dirs_str = ", ".join(str(d) for d in search_dirs)
|
14
|
-
print(
|
15
|
-
f"❗ TemplateNotFound: '{template_name}'\n Searched paths: {search_dirs_str}",
|
16
|
-
file=sys.stderr,
|
17
|
-
)
|
18
|
-
|
19
9
|
REFERER = "www.janito.dev"
|
20
10
|
TITLE = "Janito"
|
21
11
|
|
@@ -24,90 +14,30 @@ class AgentProfileManager:
|
|
24
14
|
self.role = new_role
|
25
15
|
self.refresh_prompt()
|
26
16
|
|
27
|
-
def parse_style_string(self, style: str):
|
28
|
-
if "-" in style:
|
29
|
-
parts = style.split("-")
|
30
|
-
return parts[0], parts[1:]
|
31
|
-
return style, []
|
32
|
-
|
33
17
|
def render_prompt(self):
|
34
|
-
main_style, features = self.parse_style_string(self.interaction_style)
|
35
18
|
base_dir = Path(__file__).parent / "templates"
|
36
19
|
profiles_dir = base_dir / "profiles"
|
37
|
-
|
38
|
-
|
39
|
-
main_template = profiles_dir / "system_prompt_template_technical.toml"
|
20
|
+
if getattr(self, "lang", "en") == "pt":
|
21
|
+
main_template_name = "system_prompt_template_base_pt.txt.j2"
|
40
22
|
else:
|
41
|
-
|
42
|
-
# Gather context variables
|
23
|
+
main_template_name = "system_prompt_template_base.txt.j2"
|
43
24
|
platform_name = get_platform_name()
|
44
25
|
python_version = get_python_version()
|
45
26
|
shell_info = detect_shell()
|
46
|
-
|
47
|
-
tech_txt_exists = tech_txt_path.exists()
|
48
|
-
tech_txt_content = ""
|
49
|
-
if tech_txt_exists:
|
50
|
-
try:
|
51
|
-
tech_txt_content = tech_txt_path.read_text(encoding="utf-8")
|
52
|
-
except Exception:
|
53
|
-
tech_txt_content = "⚠️ Error reading janito/tech.txt."
|
27
|
+
|
54
28
|
context = {
|
55
29
|
"role": self.role,
|
56
30
|
"interaction_mode": self.interaction_mode,
|
57
31
|
"platform": platform_name,
|
58
32
|
"python_version": python_version,
|
59
33
|
"shell_info": shell_info,
|
60
|
-
"tech_txt_exists": str(tech_txt_exists),
|
61
|
-
"tech_txt_content": tech_txt_content,
|
62
34
|
}
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
base_data = toml.load(base_path)
|
70
|
-
base_data.update({k: v for k, v in data.items() if k != "extends"})
|
71
|
-
return base_data
|
72
|
-
return data
|
73
|
-
|
74
|
-
toml_data = load_toml_with_inheritance(main_template)
|
75
|
-
# Merge in feature-specific TOML if any
|
76
|
-
for feature in features:
|
77
|
-
feature_template = profiles_dir / f"system_prompt_template_{feature}.toml"
|
78
|
-
if feature_template.exists():
|
79
|
-
feature_data = toml.load(feature_template)
|
80
|
-
toml_data.update(
|
81
|
-
{k: v for k, v in feature_data.items() if k != "extends"}
|
82
|
-
)
|
83
|
-
|
84
|
-
# Render the TOML structure as a prompt string
|
85
|
-
def render_section(section):
|
86
|
-
if isinstance(section, dict):
|
87
|
-
out = []
|
88
|
-
for k, v in section.items():
|
89
|
-
if isinstance(v, list):
|
90
|
-
out.append(f"{k}:")
|
91
|
-
for item in v:
|
92
|
-
out.append(f" - {item}")
|
93
|
-
else:
|
94
|
-
out.append(f"{k}: {v}")
|
95
|
-
return "\n".join(out)
|
96
|
-
elif isinstance(section, list):
|
97
|
-
return "\n".join(f"- {item}" for item in section)
|
98
|
-
else:
|
99
|
-
return str(section)
|
100
|
-
|
101
|
-
prompt_sections = []
|
102
|
-
for section, value in toml_data.items():
|
103
|
-
if section == "extends":
|
104
|
-
continue
|
105
|
-
prompt_sections.append(f"[{section}]")
|
106
|
-
prompt_sections.append(render_section(value))
|
107
|
-
prompt_sections.append("")
|
108
|
-
prompt_template = "\n".join(prompt_sections)
|
109
|
-
# Substitute variables
|
110
|
-
prompt = Template(prompt_template).safe_substitute(context)
|
35
|
+
env = Environment(
|
36
|
+
loader=FileSystemLoader(str(profiles_dir)),
|
37
|
+
autoescape=select_autoescape(["txt", "j2"]),
|
38
|
+
)
|
39
|
+
template = env.get_template(main_template_name)
|
40
|
+
prompt = template.render(**context)
|
111
41
|
return prompt
|
112
42
|
|
113
43
|
def __init__(
|
@@ -115,22 +45,24 @@ class AgentProfileManager:
|
|
115
45
|
api_key,
|
116
46
|
model,
|
117
47
|
role,
|
118
|
-
|
48
|
+
profile_name,
|
119
49
|
interaction_mode,
|
120
50
|
verbose_tools,
|
121
51
|
base_url,
|
122
52
|
azure_openai_api_version,
|
123
53
|
use_azure_openai,
|
54
|
+
lang="en",
|
124
55
|
):
|
125
56
|
self.api_key = api_key
|
126
57
|
self.model = model
|
127
58
|
self.role = role
|
128
|
-
self.
|
59
|
+
self.profile_name = "base"
|
129
60
|
self.interaction_mode = interaction_mode
|
130
61
|
self.verbose_tools = verbose_tools
|
131
62
|
self.base_url = base_url
|
132
63
|
self.azure_openai_api_version = azure_openai_api_version
|
133
64
|
self.use_azure_openai = use_azure_openai
|
65
|
+
self.lang = lang
|
134
66
|
if use_azure_openai:
|
135
67
|
from openai import AzureOpenAI
|
136
68
|
|
@@ -1,3 +1,6 @@
|
|
1
|
+
from janito.i18n import tr
|
2
|
+
|
3
|
+
|
1
4
|
class QueuedMessageHandler:
|
2
5
|
def __init__(self, queue, *args, **kwargs):
|
3
6
|
self._queue = queue
|
@@ -6,17 +9,33 @@ class QueuedMessageHandler:
|
|
6
9
|
# Unified: send content (agent/LLM) messages to the frontend via queue
|
7
10
|
if not isinstance(msg, dict):
|
8
11
|
raise TypeError(
|
9
|
-
|
12
|
+
tr(
|
13
|
+
"QueuedMessageHandler.handle_message expects a dict with 'type' and 'message', got {msg_type}: {msg!r}",
|
14
|
+
msg_type=type(msg),
|
15
|
+
msg=msg,
|
16
|
+
)
|
10
17
|
)
|
11
18
|
msg_type = msg.get("type", "info")
|
12
19
|
# For tool_call and tool_result, print and forward the full dict
|
13
20
|
if msg_type in ("tool_call", "tool_result"):
|
14
|
-
print(
|
21
|
+
print(
|
22
|
+
tr(
|
23
|
+
"[QueuedMessageHandler] {msg_type}: {msg}",
|
24
|
+
msg_type=msg_type,
|
25
|
+
msg=msg,
|
26
|
+
)
|
27
|
+
)
|
15
28
|
self._queue.put(msg)
|
16
29
|
return
|
17
30
|
message = msg.get("message", "")
|
18
31
|
# For normal agent/user/info messages, emit type 'content' for frontend compatibility
|
19
|
-
print(
|
32
|
+
print(
|
33
|
+
tr(
|
34
|
+
"[QueuedMessageHandler] {msg_type}: {message}",
|
35
|
+
msg_type=msg_type,
|
36
|
+
message=message,
|
37
|
+
)
|
38
|
+
)
|
20
39
|
if msg_type == "content":
|
21
40
|
self._queue.put({"type": "content", "content": message})
|
22
41
|
elif msg_type == "info":
|
@@ -1,72 +1,66 @@
|
|
1
|
-
from rich.console import Console
|
2
|
-
from janito.agent.runtime_config import runtime_config, unified_config
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
trust
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
self.console.print(
|
46
|
-
elif msg_type == "
|
47
|
-
self.
|
48
|
-
elif msg_type == "
|
49
|
-
self.console.print(
|
50
|
-
elif msg_type == "
|
51
|
-
|
52
|
-
|
53
|
-
self.
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
Text(safe_message, style="on #330000", no_wrap=True, overflow=None),
|
68
|
-
end="",
|
69
|
-
)
|
70
|
-
else:
|
71
|
-
# Ignore unsupported message types silently
|
72
|
-
return
|
1
|
+
from rich.console import Console
|
2
|
+
from janito.agent.runtime_config import runtime_config, unified_config
|
3
|
+
from janito.agent.message_handler_protocol import MessageHandlerProtocol
|
4
|
+
|
5
|
+
console = Console()
|
6
|
+
|
7
|
+
|
8
|
+
class RichMessageHandler(MessageHandlerProtocol):
|
9
|
+
"""
|
10
|
+
Unified message handler for all output (tool, agent, system) using Rich for styled output.
|
11
|
+
"""
|
12
|
+
|
13
|
+
def __init__(self):
|
14
|
+
self.console = console
|
15
|
+
|
16
|
+
def handle_message(self, msg, msg_type=None):
|
17
|
+
"""
|
18
|
+
Handles a dict with 'type' and 'message'.
|
19
|
+
All messages must be dicts. Raises if not.
|
20
|
+
"""
|
21
|
+
# Check trust config: suppress all output except 'content' if enabled
|
22
|
+
trust = runtime_config.get("trust")
|
23
|
+
if trust is None:
|
24
|
+
trust = unified_config.get("trust", False)
|
25
|
+
|
26
|
+
from rich.markdown import Markdown
|
27
|
+
|
28
|
+
if not isinstance(msg, dict):
|
29
|
+
raise TypeError(
|
30
|
+
f"RichMessageHandler.handle_message expects a dict with 'type' and 'message', got {type(msg)}: {msg!r}"
|
31
|
+
)
|
32
|
+
|
33
|
+
msg_type = msg.get("type", "info")
|
34
|
+
message = msg.get("message", "")
|
35
|
+
|
36
|
+
if trust and msg_type != "content":
|
37
|
+
return # Suppress all except content
|
38
|
+
if msg_type == "content":
|
39
|
+
self.console.print(Markdown(message))
|
40
|
+
elif msg_type == "info":
|
41
|
+
self.console.print(message, style="cyan", end="")
|
42
|
+
elif msg_type == "success":
|
43
|
+
self.console.print(message, style="bold green", end="\n")
|
44
|
+
elif msg_type == "error":
|
45
|
+
self.console.print(message, style="bold red", end="\n")
|
46
|
+
elif msg_type == "progress":
|
47
|
+
self._handle_progress(message)
|
48
|
+
elif msg_type == "warning":
|
49
|
+
self.console.print(message, style="bold yellow", end="\n")
|
50
|
+
elif msg_type == "stdout":
|
51
|
+
from rich.text import Text
|
52
|
+
|
53
|
+
self.console.print(
|
54
|
+
Text(message, style="on #003300", no_wrap=True, overflow=None),
|
55
|
+
end="",
|
56
|
+
)
|
57
|
+
elif msg_type == "stderr":
|
58
|
+
from rich.text import Text
|
59
|
+
|
60
|
+
self.console.print(
|
61
|
+
Text(message, style="on #330000", no_wrap=True, overflow=None),
|
62
|
+
end="",
|
63
|
+
)
|
64
|
+
else:
|
65
|
+
# Ignore unsupported message types silently
|
66
|
+
return
|
@@ -0,0 +1,14 @@
|
|
1
|
+
You are acting as: {{ role }}
|
2
|
+
|
3
|
+
You have access for development purposes to the following environment:
|
4
|
+
Platform: {{ platform }}
|
5
|
+
Python version: {{ python_version }}
|
6
|
+
Shell/Environment: {{ shell_info }}
|
7
|
+
|
8
|
+
Answer according to the following guidelines:
|
9
|
+
- Start by searching for text/files in the project.
|
10
|
+
- Before using your namespace functions, provide a concise explanation.
|
11
|
+
- Prefer making localized edits using string replacements. If the required change is extensive, replace the entire file instead.
|
12
|
+
- When locating code lines to change, consider the previous lines content for indentation precise replacement.
|
13
|
+
- After edting files, validate them.
|
14
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Você está atuando como: {{ role }}
|
2
|
+
|
3
|
+
Você tem acesso, para fins de desenvolvimento, ao seguinte ambiente:
|
4
|
+
Plataforma: {{ platform }}
|
5
|
+
Versão do Python: {{ python_version }}
|
6
|
+
Shell/Ambiente: {{ shell_info }}
|
7
|
+
|
8
|
+
Responda seguindo estas diretrizes:
|
9
|
+
- Antes de usar suas funções de namespace, forneça uma explicação concisa.
|
10
|
+
- Na primeira mensagem faça uma pesquisa no projeto.
|
11
|
+
- Prefira fazer edições localizadas usando substituições de string. Se a alteração necessária for extensa, substitua o arquivo inteiro.
|
12
|
+
- Ao localizar linhas de código para alterar, consider o conteúdo das linhas anteriores para uma substituição precisa de indentação.
|
13
|
+
- Após editar arquivos, valide-os.
|
@@ -0,0 +1,47 @@
|
|
1
|
+
from janito.agent.message_handler_protocol import MessageHandlerProtocol
|
2
|
+
from janito.agent.event_handler_protocol import EventHandlerProtocol
|
3
|
+
from janito.agent.event_dispatcher import EventDispatcher
|
4
|
+
|
5
|
+
|
6
|
+
class DummyMessageHandler(MessageHandlerProtocol):
|
7
|
+
def __init__(self):
|
8
|
+
self.last_message = None
|
9
|
+
|
10
|
+
def handle_message(self, msg, msg_type=None):
|
11
|
+
self.last_message = (msg, msg_type)
|
12
|
+
|
13
|
+
|
14
|
+
class DummyEvent:
|
15
|
+
def __init__(self, type_, payload):
|
16
|
+
self.type = type_
|
17
|
+
self.payload = payload
|
18
|
+
|
19
|
+
|
20
|
+
class DummyEventHandler(EventHandlerProtocol):
|
21
|
+
def __init__(self):
|
22
|
+
self.last_event = None
|
23
|
+
|
24
|
+
def handle_event(self, event):
|
25
|
+
self.last_event = event
|
26
|
+
|
27
|
+
|
28
|
+
def test_message_handler():
|
29
|
+
handler = DummyMessageHandler()
|
30
|
+
handler.handle_message({"type": "info", "message": "hello"}, "info")
|
31
|
+
assert handler.last_message[0]["message"] == "hello"
|
32
|
+
print("MessageHandlerProtocol test passed")
|
33
|
+
|
34
|
+
|
35
|
+
def test_event_dispatcher():
|
36
|
+
dispatcher = EventDispatcher()
|
37
|
+
handler = DummyEventHandler()
|
38
|
+
dispatcher.register("test", handler)
|
39
|
+
event = DummyEvent("test", {"foo": "bar"})
|
40
|
+
dispatcher.dispatch(event)
|
41
|
+
assert handler.last_event.payload["foo"] == "bar"
|
42
|
+
print("EventHandlerProtocol/EventDispatcher test passed")
|
43
|
+
|
44
|
+
|
45
|
+
if __name__ == "__main__":
|
46
|
+
test_message_handler()
|
47
|
+
test_event_dispatcher()
|
@@ -0,0 +1 @@
|
|
1
|
+
# Makes this directory a package for test discovery
|
janito/agent/tool_base.py
CHANGED
@@ -0,0 +1,109 @@
|
|
1
|
+
# janito/agent/tool_executor.py
|
2
|
+
"""
|
3
|
+
ToolExecutor: Responsible for executing tools, validating arguments, handling errors, and reporting progress.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import json
|
7
|
+
from janito.i18n import tr
|
8
|
+
import inspect
|
9
|
+
from janito.agent.tool_base import ToolBase
|
10
|
+
from janito.agent.runtime_config import runtime_config
|
11
|
+
|
12
|
+
|
13
|
+
class ToolExecutor:
|
14
|
+
def __init__(self, message_handler=None):
|
15
|
+
self.message_handler = message_handler
|
16
|
+
|
17
|
+
def execute(self, tool_entry, tool_call):
|
18
|
+
import uuid
|
19
|
+
|
20
|
+
call_id = getattr(tool_call, "id", None) or str(uuid.uuid4())
|
21
|
+
func = tool_entry["function"]
|
22
|
+
args = json.loads(tool_call.function.arguments)
|
23
|
+
tool_call_reason = args.pop(
|
24
|
+
"tool_call_reason", None
|
25
|
+
) # Extract and remove 'tool_call_reason' if present
|
26
|
+
# Record tool usage
|
27
|
+
try:
|
28
|
+
from janito.agent.tool_use_tracker import ToolUseTracker
|
29
|
+
|
30
|
+
ToolUseTracker().record(tool_call.function.name, dict(args))
|
31
|
+
except Exception as e:
|
32
|
+
if runtime_config.get("verbose", False):
|
33
|
+
print(f"[ToolExecutor] ToolUseTracker record failed: {e}")
|
34
|
+
|
35
|
+
verbose = runtime_config.get("verbose", False)
|
36
|
+
if verbose:
|
37
|
+
print(
|
38
|
+
tr(
|
39
|
+
"[ToolExecutor] {tool_name} called with arguments: {args}",
|
40
|
+
tool_name=tool_call.function.name,
|
41
|
+
args=args,
|
42
|
+
)
|
43
|
+
)
|
44
|
+
if runtime_config.get("verbose_reason", False) and tool_call_reason:
|
45
|
+
print(
|
46
|
+
tr(
|
47
|
+
"[ToolExecutor] Reason for call: {tool_call_reason}",
|
48
|
+
tool_call_reason=tool_call_reason,
|
49
|
+
)
|
50
|
+
)
|
51
|
+
instance = None
|
52
|
+
if hasattr(func, "__self__") and isinstance(func.__self__, ToolBase):
|
53
|
+
instance = func.__self__
|
54
|
+
if self.message_handler:
|
55
|
+
instance._progress_callback = self.message_handler.handle_message
|
56
|
+
# Emit tool_call event before calling the tool
|
57
|
+
if self.message_handler:
|
58
|
+
event = {
|
59
|
+
"type": "tool_call",
|
60
|
+
"tool": tool_call.function.name,
|
61
|
+
"call_id": call_id,
|
62
|
+
"arguments": args,
|
63
|
+
}
|
64
|
+
if tool_call_reason:
|
65
|
+
event["tool_call_reason"] = tool_call_reason
|
66
|
+
self.message_handler.handle_message(event)
|
67
|
+
# Argument validation
|
68
|
+
sig = inspect.signature(func)
|
69
|
+
try:
|
70
|
+
sig.bind(**args)
|
71
|
+
except TypeError as e:
|
72
|
+
error_msg = f"Argument validation error for tool '{tool_call.function.name}': {str(e)}"
|
73
|
+
if self.message_handler:
|
74
|
+
error_event = {
|
75
|
+
"type": "tool_error",
|
76
|
+
"tool": tool_call.function.name,
|
77
|
+
"call_id": call_id,
|
78
|
+
"error": error_msg,
|
79
|
+
}
|
80
|
+
if tool_call_reason:
|
81
|
+
error_event["tool_call_reason"] = tool_call_reason
|
82
|
+
self.message_handler.handle_message(error_event)
|
83
|
+
raise TypeError(error_msg)
|
84
|
+
# Execute tool
|
85
|
+
try:
|
86
|
+
result = func(**args)
|
87
|
+
if self.message_handler:
|
88
|
+
result_event = {
|
89
|
+
"type": "tool_result",
|
90
|
+
"tool": tool_call.function.name,
|
91
|
+
"call_id": call_id,
|
92
|
+
"result": result,
|
93
|
+
}
|
94
|
+
if tool_call_reason:
|
95
|
+
result_event["tool_call_reason"] = tool_call_reason
|
96
|
+
self.message_handler.handle_message(result_event)
|
97
|
+
return result
|
98
|
+
except Exception as e:
|
99
|
+
if self.message_handler:
|
100
|
+
error_event = {
|
101
|
+
"type": "tool_error",
|
102
|
+
"tool": tool_call.function.name,
|
103
|
+
"call_id": call_id,
|
104
|
+
"error": str(e),
|
105
|
+
}
|
106
|
+
if tool_call_reason:
|
107
|
+
error_event["tool_call_reason"] = tool_call_reason
|
108
|
+
self.message_handler.handle_message(error_event)
|
109
|
+
raise
|
janito/agent/tool_registry.py
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# janito/agent/tool_registry.py
|
2
|
-
import json
|
3
2
|
from janito.agent.tool_base import ToolBase
|
4
3
|
from janito.agent.openai_schema_generator import generate_openai_function_schema
|
5
|
-
import inspect
|
6
4
|
|
7
5
|
_tool_registry = {}
|
8
6
|
|
@@ -14,16 +12,16 @@ def register_tool(tool=None, *, name: str = None):
|
|
14
12
|
if not (isinstance(tool, type) and issubclass(tool, ToolBase)):
|
15
13
|
raise TypeError("Tool must be a class derived from ToolBase.")
|
16
14
|
instance = tool()
|
17
|
-
if not hasattr(instance, "
|
15
|
+
if not hasattr(instance, "run") or not callable(instance.run):
|
18
16
|
raise TypeError(
|
19
17
|
f"Tool '{tool.__name__}' must implement a callable 'call' method."
|
20
18
|
)
|
21
19
|
tool_name = override_name or instance.name
|
22
20
|
if tool_name in _tool_registry:
|
23
21
|
raise ValueError(f"Tool '{tool_name}' is already registered.")
|
24
|
-
schema = generate_openai_function_schema(instance.
|
22
|
+
schema = generate_openai_function_schema(instance.run, tool_name, tool_class=tool)
|
25
23
|
_tool_registry[tool_name] = {
|
26
|
-
"function": instance.
|
24
|
+
"function": instance.run,
|
27
25
|
"description": schema["description"],
|
28
26
|
"parameters": schema["parameters"],
|
29
27
|
"class": tool,
|
@@ -46,73 +44,3 @@ def get_tool_schemas():
|
|
46
44
|
}
|
47
45
|
)
|
48
46
|
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
|