janito 1.6.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 +3 -3
- janito/agent/config_defaults.py +3 -2
- janito/agent/conversation.py +73 -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 -6
- janito/agent/openai_schema_generator.py +23 -4
- janito/agent/platform_discovery.py +90 -0
- janito/agent/profile_manager.py +34 -110
- janito/agent/queued_message_handler.py +22 -3
- janito/agent/rich_message_handler.py +3 -1
- 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 +11 -8
- janito/agent/tools/ask_user.py +26 -12
- janito/agent/tools/create_directory.py +50 -18
- 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 -40
- janito/agent/tools/get_lines.py +60 -25
- janito/agent/tools/memory.py +48 -0
- 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 +113 -97
- janito/agent/tools/run_powershell_command.py +169 -0
- janito/agent/tools/run_python_command.py +53 -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/_print_config.py +1 -1
- janito/cli/arg_parser.py +36 -4
- janito/cli/config_commands.py +1 -1
- janito/cli/logging_setup.py +7 -2
- janito/cli/main.py +97 -3
- janito/cli/runner/__init__.py +0 -2
- janito/cli/runner/_termweb_log_utils.py +17 -0
- janito/cli/runner/cli_main.py +121 -89
- janito/cli/runner/config.py +6 -4
- janito/cli/termweb_starter.py +73 -0
- janito/cli_chat_shell/chat_loop.py +52 -13
- janito/cli_chat_shell/chat_state.py +1 -1
- janito/cli_chat_shell/chat_ui.py +2 -3
- janito/cli_chat_shell/commands/__init__.py +17 -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/sum.py +49 -0
- 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/load_prompt.py +47 -8
- 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 -93
- 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 +10 -13
- {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/METADATA +73 -32
- janito-1.8.0.dist-info/RECORD +127 -0
- {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/WHEEL +1 -1
- janito/agent/tool_registry_core.py +0 -2
- janito/agent/tools/get_file_outline.py +0 -117
- janito/agent/tools/py_compile_file.py +0 -40
- janito/agent/tools/replace_file.py +0 -51
- janito/agent/tools/search_files.py +0 -71
- janito/cli/runner/scan.py +0 -44
- janito/cli_chat_shell/commands/system.py +0 -73
- janito-1.6.0.dist-info/RECORD +0 -81
- {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/entry_points.txt +0 -0
- {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/top_level.txt +0 -0
janito/agent/profile_manager.py
CHANGED
@@ -1,152 +1,68 @@
|
|
1
|
-
from janito.agent.conversation import ConversationHandler
|
2
1
|
from openai import OpenAI
|
3
|
-
import
|
2
|
+
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
4
3
|
from pathlib import Path
|
5
|
-
import
|
6
|
-
import
|
4
|
+
from janito.agent.platform_discovery import get_platform_name, get_python_version
|
5
|
+
from janito.agent.platform_discovery import detect_shell
|
7
6
|
|
8
7
|
|
9
8
|
class AgentProfileManager:
|
10
9
|
REFERER = "www.janito.dev"
|
11
10
|
TITLE = "Janito"
|
12
11
|
|
13
|
-
def
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
return style, []
|
18
|
-
|
19
|
-
def get_platform_name(self):
|
20
|
-
sys_platform = platform.system().lower()
|
21
|
-
if sys_platform.startswith("win"):
|
22
|
-
return "windows"
|
23
|
-
elif sys_platform.startswith("linux"):
|
24
|
-
return "linux"
|
25
|
-
elif sys_platform.startswith("darwin"):
|
26
|
-
return "darwin"
|
27
|
-
return sys_platform
|
28
|
-
|
29
|
-
def get_python_version(self):
|
30
|
-
return platform.python_version()
|
31
|
-
|
32
|
-
def get_shell_info(self):
|
33
|
-
shell = os.environ.get("SHELL")
|
34
|
-
term = os.environ.get("TERM")
|
35
|
-
term_program = os.environ.get("TERM_PROGRAM")
|
36
|
-
if shell:
|
37
|
-
info = shell
|
38
|
-
elif os.environ.get("MSYSTEM"):
|
39
|
-
info = f"Git Bash ({os.environ.get('MSYSTEM')})"
|
40
|
-
elif os.environ.get("WSL_DISTRO_NAME"):
|
41
|
-
info = f"WSL ({os.environ.get('WSL_DISTRO_NAME')})"
|
42
|
-
else:
|
43
|
-
comspec = os.environ.get("COMSPEC")
|
44
|
-
if comspec:
|
45
|
-
if "powershell" in comspec.lower():
|
46
|
-
info = "PowerShell"
|
47
|
-
elif "cmd" in comspec.lower():
|
48
|
-
info = "cmd.exe"
|
49
|
-
else:
|
50
|
-
info = "Unknown shell"
|
51
|
-
else:
|
52
|
-
info = "Unknown shell"
|
53
|
-
if term:
|
54
|
-
info += f", TERM={term}"
|
55
|
-
if term_program and term_program.lower() == "vscode":
|
56
|
-
info += ", running in VSCode"
|
57
|
-
home_dir = os.path.expanduser("~")
|
58
|
-
if home_dir:
|
59
|
-
info += f", HOME={home_dir}"
|
60
|
-
return info
|
61
|
-
return "unknown"
|
12
|
+
def set_role(self, new_role):
|
13
|
+
"""Set the agent's role and force prompt re-rendering."""
|
14
|
+
self.role = new_role
|
15
|
+
self.refresh_prompt()
|
62
16
|
|
63
17
|
def render_prompt(self):
|
64
|
-
main_style, features = self.parse_style_string(self.interaction_style)
|
65
18
|
base_dir = Path(__file__).parent / "templates"
|
66
19
|
profiles_dir = base_dir / "profiles"
|
67
|
-
|
68
|
-
|
69
|
-
[
|
70
|
-
jinja2.FileSystemLoader(str(profiles_dir)),
|
71
|
-
jinja2.FileSystemLoader(str(features_dir)),
|
72
|
-
]
|
73
|
-
)
|
74
|
-
env = jinja2.Environment(loader=loader)
|
75
|
-
if main_style == "technical":
|
76
|
-
main_template = "system_prompt_template_technical.j2"
|
20
|
+
if getattr(self, "lang", "en") == "pt":
|
21
|
+
main_template_name = "system_prompt_template_base_pt.txt.j2"
|
77
22
|
else:
|
78
|
-
|
79
|
-
platform_name =
|
80
|
-
python_version =
|
81
|
-
shell_info =
|
82
|
-
|
83
|
-
# Inject tech.txt existence and content
|
84
|
-
tech_txt_path = Path(".janito") / "tech.txt"
|
85
|
-
tech_txt_exists = tech_txt_path.exists()
|
86
|
-
tech_txt_content = ""
|
87
|
-
if tech_txt_exists:
|
88
|
-
try:
|
89
|
-
tech_txt_content = tech_txt_path.read_text(encoding="utf-8")
|
90
|
-
except Exception:
|
91
|
-
tech_txt_content = "⚠️ Error reading janito/tech.txt."
|
92
|
-
template = env.get_template(main_template)
|
93
|
-
return template.render(
|
94
|
-
role=self.role,
|
95
|
-
interaction_mode=self.interaction_mode,
|
96
|
-
platform=platform_name,
|
97
|
-
python_version=python_version,
|
98
|
-
shell_info=shell_info,
|
99
|
-
tech_txt_exists=tech_txt_exists,
|
100
|
-
tech_txt_content=tech_txt_content,
|
101
|
-
)
|
102
|
-
parent_template = main_template
|
103
|
-
# Inject tech.txt existence and content for feature templates as well
|
104
|
-
tech_txt_path = Path(".janito") / "tech.txt"
|
105
|
-
tech_txt_exists = tech_txt_path.exists()
|
106
|
-
tech_txt_content = ""
|
107
|
-
if tech_txt_exists:
|
108
|
-
try:
|
109
|
-
tech_txt_content = tech_txt_path.read_text(encoding="utf-8")
|
110
|
-
except Exception:
|
111
|
-
tech_txt_content = "⚠️ Error reading janito/tech.txt."
|
23
|
+
main_template_name = "system_prompt_template_base.txt.j2"
|
24
|
+
platform_name = get_platform_name()
|
25
|
+
python_version = get_python_version()
|
26
|
+
shell_info = detect_shell()
|
27
|
+
|
112
28
|
context = {
|
113
29
|
"role": self.role,
|
114
30
|
"interaction_mode": self.interaction_mode,
|
115
31
|
"platform": platform_name,
|
116
32
|
"python_version": python_version,
|
117
33
|
"shell_info": shell_info,
|
118
|
-
"tech_txt_exists": tech_txt_exists,
|
119
|
-
"tech_txt_content": tech_txt_content,
|
120
34
|
}
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
return
|
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)
|
41
|
+
return prompt
|
128
42
|
|
129
43
|
def __init__(
|
130
44
|
self,
|
131
45
|
api_key,
|
132
46
|
model,
|
133
47
|
role,
|
134
|
-
|
48
|
+
profile_name,
|
135
49
|
interaction_mode,
|
136
50
|
verbose_tools,
|
137
51
|
base_url,
|
138
52
|
azure_openai_api_version,
|
139
53
|
use_azure_openai,
|
54
|
+
lang="en",
|
140
55
|
):
|
141
56
|
self.api_key = api_key
|
142
57
|
self.model = model
|
143
58
|
self.role = role
|
144
|
-
self.
|
59
|
+
self.profile_name = "base"
|
145
60
|
self.interaction_mode = interaction_mode
|
146
61
|
self.verbose_tools = verbose_tools
|
147
62
|
self.base_url = base_url
|
148
63
|
self.azure_openai_api_version = azure_openai_api_version
|
149
64
|
self.use_azure_openai = use_azure_openai
|
65
|
+
self.lang = lang
|
150
66
|
if use_azure_openai:
|
151
67
|
from openai import AzureOpenAI
|
152
68
|
|
@@ -161,7 +77,15 @@ class AgentProfileManager:
|
|
161
77
|
api_key=api_key,
|
162
78
|
default_headers={"HTTP-Referer": self.REFERER, "X-Title": self.TITLE},
|
163
79
|
)
|
164
|
-
|
80
|
+
from janito.agent.openai_client import Agent
|
81
|
+
|
82
|
+
self.agent = Agent(
|
83
|
+
api_key=api_key,
|
84
|
+
model=model,
|
85
|
+
base_url=base_url,
|
86
|
+
use_azure_openai=use_azure_openai,
|
87
|
+
azure_openai_api_version=azure_openai_api_version,
|
88
|
+
)
|
165
89
|
self.system_prompt_template = None
|
166
90
|
|
167
91
|
def refresh_prompt(self):
|
@@ -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,10 +1,11 @@
|
|
1
1
|
from rich.console import Console
|
2
2
|
from janito.agent.runtime_config import runtime_config, unified_config
|
3
|
+
from janito.agent.message_handler_protocol import MessageHandlerProtocol
|
3
4
|
|
4
5
|
console = Console()
|
5
6
|
|
6
7
|
|
7
|
-
class RichMessageHandler:
|
8
|
+
class RichMessageHandler(MessageHandlerProtocol):
|
8
9
|
"""
|
9
10
|
Unified message handler for all output (tool, agent, system) using Rich for styled output.
|
10
11
|
"""
|
@@ -31,6 +32,7 @@ class RichMessageHandler:
|
|
31
32
|
|
32
33
|
msg_type = msg.get("type", "info")
|
33
34
|
message = msg.get("message", "")
|
35
|
+
|
34
36
|
if trust and msg_type != "content":
|
35
37
|
return # Suppress all except content
|
36
38
|
if msg_type == "content":
|
@@ -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
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import threading
|
2
|
+
from typing import Any, Dict, List
|
3
|
+
|
4
|
+
|
5
|
+
class ToolUseTracker:
|
6
|
+
_instance = None
|
7
|
+
_lock = threading.Lock()
|
8
|
+
|
9
|
+
def __new__(cls):
|
10
|
+
if not cls._instance:
|
11
|
+
with cls._lock:
|
12
|
+
if not cls._instance:
|
13
|
+
cls._instance = super().__new__(cls)
|
14
|
+
cls._instance._history = []
|
15
|
+
return cls._instance
|
16
|
+
|
17
|
+
def record(self, tool_name: str, params: Dict[str, Any]):
|
18
|
+
self._history.append({"tool": tool_name, "params": params})
|
19
|
+
|
20
|
+
def get_history(self) -> List[Dict[str, Any]]:
|
21
|
+
return list(self._history)
|
22
|
+
|
23
|
+
def get_operations_on_file(self, file_path: str) -> List[Dict[str, Any]]:
|
24
|
+
ops = []
|
25
|
+
for entry in self._history:
|
26
|
+
params = entry["params"]
|
27
|
+
if any(isinstance(v, str) and file_path in v for v in params.values()):
|
28
|
+
ops.append(entry)
|
29
|
+
return ops
|
30
|
+
|
31
|
+
def file_fully_read(self, file_path: str) -> bool:
|
32
|
+
for entry in self._history:
|
33
|
+
if entry["tool"] == "get_lines":
|
34
|
+
params = entry["params"]
|
35
|
+
if params.get("file_path") == file_path:
|
36
|
+
# If both from_line and to_line are None, full file was read
|
37
|
+
if (
|
38
|
+
params.get("from_line") is None
|
39
|
+
and params.get("to_line") is None
|
40
|
+
):
|
41
|
+
return True
|
42
|
+
return False
|
43
|
+
|
44
|
+
@classmethod
|
45
|
+
def instance(cls):
|
46
|
+
return cls()
|
janito/agent/tools/__init__.py
CHANGED
@@ -3,20 +3,20 @@ from . import create_directory
|
|
3
3
|
from . import create_file
|
4
4
|
from . import fetch_url
|
5
5
|
from . import find_files
|
6
|
-
from . import get_file_outline
|
7
6
|
from . import get_lines
|
7
|
+
from . import outline_file
|
8
8
|
from . import gitignore_utils
|
9
9
|
from . import move_file
|
10
|
-
from . import
|
10
|
+
from . import validate_file_syntax
|
11
11
|
from . import remove_directory
|
12
12
|
from . import remove_file
|
13
13
|
from . import replace_text_in_file
|
14
14
|
from . import rich_live
|
15
15
|
from . import run_bash_command
|
16
|
+
from . import run_powershell_command
|
16
17
|
from . import run_python_command
|
17
|
-
from . import
|
18
|
-
from . import
|
19
|
-
from . import replace_file
|
18
|
+
from . import present_choices
|
19
|
+
from . import search_text
|
20
20
|
|
21
21
|
__all__ = [
|
22
22
|
"ask_user",
|
@@ -24,18 +24,21 @@ __all__ = [
|
|
24
24
|
"create_file",
|
25
25
|
"fetch_url",
|
26
26
|
"find_files",
|
27
|
-
"
|
27
|
+
"outline_file",
|
28
28
|
"get_lines",
|
29
29
|
"gitignore_utils",
|
30
30
|
"move_file",
|
31
|
-
"
|
31
|
+
"validate_file_syntax",
|
32
32
|
"remove_directory",
|
33
33
|
"remove_file",
|
34
34
|
"replace_text_in_file",
|
35
35
|
"rich_live",
|
36
36
|
"run_bash_command",
|
37
|
+
"run_powershell_command",
|
37
38
|
"run_python_command",
|
38
39
|
"search_files",
|
39
40
|
"tools_utils",
|
40
|
-
"
|
41
|
+
"memory",
|
42
|
+
"present_choices",
|
43
|
+
"search_text",
|
41
44
|
]
|