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.
Files changed (115) hide show
  1. janito/__init__.py +1 -1
  2. janito/agent/config.py +1 -1
  3. janito/agent/config_defaults.py +2 -2
  4. janito/agent/conversation.py +70 -27
  5. janito/agent/conversation_api.py +104 -4
  6. janito/agent/conversation_exceptions.py +6 -0
  7. janito/agent/conversation_tool_calls.py +17 -3
  8. janito/agent/event.py +24 -0
  9. janito/agent/event_dispatcher.py +24 -0
  10. janito/agent/event_handler_protocol.py +5 -0
  11. janito/agent/event_system.py +15 -0
  12. janito/agent/message_handler.py +4 -1
  13. janito/agent/message_handler_protocol.py +5 -0
  14. janito/agent/openai_client.py +5 -8
  15. janito/agent/openai_schema_generator.py +23 -4
  16. janito/agent/profile_manager.py +15 -83
  17. janito/agent/queued_message_handler.py +22 -3
  18. janito/agent/rich_message_handler.py +66 -72
  19. janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +14 -0
  20. janito/agent/templates/profiles/system_prompt_template_base_pt.txt.j2 +13 -0
  21. janito/agent/test_handler_protocols.py +47 -0
  22. janito/agent/tests/__init__.py +1 -0
  23. janito/agent/tool_base.py +1 -1
  24. janito/agent/tool_executor.py +109 -0
  25. janito/agent/tool_registry.py +3 -75
  26. janito/agent/tool_use_tracker.py +46 -0
  27. janito/agent/tools/__init__.py +8 -9
  28. janito/agent/tools/ask_user.py +19 -11
  29. janito/agent/tools/create_directory.py +43 -28
  30. janito/agent/tools/create_file.py +60 -29
  31. janito/agent/tools/dir_walk_utils.py +16 -0
  32. janito/agent/tools/fetch_url.py +10 -11
  33. janito/agent/tools/find_files.py +49 -32
  34. janito/agent/tools/get_lines.py +54 -18
  35. janito/agent/tools/memory.py +32 -52
  36. janito/agent/tools/move_file.py +72 -23
  37. janito/agent/tools/outline_file/__init__.py +85 -0
  38. janito/agent/tools/outline_file/formatting.py +20 -0
  39. janito/agent/tools/outline_file/markdown_outline.py +14 -0
  40. janito/agent/tools/outline_file/python_outline.py +71 -0
  41. janito/agent/tools/present_choices.py +62 -0
  42. janito/agent/tools/present_choices_test.py +18 -0
  43. janito/agent/tools/remove_directory.py +31 -26
  44. janito/agent/tools/remove_file.py +31 -13
  45. janito/agent/tools/replace_text_in_file.py +135 -36
  46. janito/agent/tools/run_bash_command.py +47 -50
  47. janito/agent/tools/run_powershell_command.py +52 -36
  48. janito/agent/tools/run_python_command.py +49 -29
  49. janito/agent/tools/search_outline.py +17 -0
  50. janito/agent/tools/search_text.py +208 -0
  51. janito/agent/tools/tools_utils.py +47 -4
  52. janito/agent/tools/utils.py +14 -15
  53. janito/agent/tools/validate_file_syntax.py +163 -0
  54. janito/cli/arg_parser.py +36 -4
  55. janito/cli/logging_setup.py +7 -2
  56. janito/cli/main.py +96 -2
  57. janito/cli/runner/_termweb_log_utils.py +17 -0
  58. janito/cli/runner/cli_main.py +119 -77
  59. janito/cli/runner/config.py +2 -2
  60. janito/cli/termweb_starter.py +73 -0
  61. janito/cli_chat_shell/chat_loop.py +42 -7
  62. janito/cli_chat_shell/chat_state.py +1 -1
  63. janito/cli_chat_shell/chat_ui.py +0 -1
  64. janito/cli_chat_shell/commands/__init__.py +15 -6
  65. janito/cli_chat_shell/commands/{history_reset.py → history_start.py} +13 -5
  66. janito/cli_chat_shell/commands/lang.py +16 -0
  67. janito/cli_chat_shell/commands/prompt.py +42 -0
  68. janito/cli_chat_shell/commands/session_control.py +36 -1
  69. janito/cli_chat_shell/commands/termweb_log.py +86 -0
  70. janito/cli_chat_shell/commands/utility.py +5 -2
  71. janito/cli_chat_shell/commands/verbose.py +29 -0
  72. janito/cli_chat_shell/session_manager.py +9 -1
  73. janito/cli_chat_shell/shell_command_completer.py +20 -0
  74. janito/cli_chat_shell/ui.py +110 -99
  75. janito/i18n/__init__.py +35 -0
  76. janito/i18n/messages.py +23 -0
  77. janito/i18n/pt.py +46 -0
  78. janito/rich_utils.py +43 -43
  79. janito/termweb/app.py +95 -0
  80. janito/termweb/static/editor.html +238 -0
  81. janito/termweb/static/editor.html.bak +238 -0
  82. janito/termweb/static/explorer.html.bak +59 -0
  83. janito/termweb/static/favicon.ico +0 -0
  84. janito/termweb/static/favicon.ico.bak +0 -0
  85. janito/termweb/static/index.html +55 -0
  86. janito/termweb/static/index.html.bak +55 -0
  87. janito/termweb/static/index.html.bak.bak +175 -0
  88. janito/termweb/static/landing.html.bak +36 -0
  89. janito/termweb/static/termicon.svg +1 -0
  90. janito/termweb/static/termweb.css +235 -0
  91. janito/termweb/static/termweb.css.bak +286 -0
  92. janito/termweb/static/termweb.js +187 -0
  93. janito/termweb/static/termweb.js.bak +187 -0
  94. janito/termweb/static/termweb.js.bak.bak +157 -0
  95. janito/termweb/static/termweb_quickopen.js +135 -0
  96. janito/termweb/static/termweb_quickopen.js.bak +125 -0
  97. janito/web/app.py +4 -4
  98. {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/METADATA +58 -25
  99. janito-1.8.0.dist-info/RECORD +127 -0
  100. {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/WHEEL +1 -1
  101. janito/agent/templates/profiles/system_prompt_template_base.toml +0 -76
  102. janito/agent/templates/profiles/system_prompt_template_default.toml +0 -3
  103. janito/agent/templates/profiles/system_prompt_template_technical.toml +0 -13
  104. janito/agent/tests/test_prompt_toml.py +0 -61
  105. janito/agent/tool_registry_core.py +0 -2
  106. janito/agent/tools/get_file_outline.py +0 -146
  107. janito/agent/tools/py_compile_file.py +0 -40
  108. janito/agent/tools/replace_file.py +0 -51
  109. janito/agent/tools/search_files.py +0 -65
  110. janito/cli/runner/scan.py +0 -57
  111. janito/cli_chat_shell/commands/system.py +0 -73
  112. janito-1.7.0.dist-info/RECORD +0 -89
  113. {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/entry_points.txt +0 -0
  114. {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/licenses/LICENSE +0 -0
  115. {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/top_level.txt +0 -0
@@ -1,21 +1,11 @@
1
1
  from openai import OpenAI
2
- import toml
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
- # Determine which TOML profile to use
38
- if main_style == "technical":
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
- main_template = profiles_dir / "system_prompt_template_default.toml"
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
- tech_txt_path = Path(".janito") / "tech.txt"
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
- # Load and merge TOML templates (handle inheritance)
65
- def load_toml_with_inheritance(path):
66
- data = toml.load(path)
67
- if "extends" in data:
68
- base_path = profiles_dir / data["extends"]
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
- interaction_style,
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.interaction_style = interaction_style
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
- f"QueuedMessageHandler.handle_message expects a dict with 'type' and 'message', got {type(msg)}: {msg!r}"
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(f"[QueuedMessageHandler] {msg_type}: {msg}")
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(f"[QueuedMessageHandler] {msg_type}: {message}")
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
- console = Console()
5
-
6
-
7
- class RichMessageHandler:
8
- """
9
- Unified message handler for all output (tool, agent, system) using Rich for styled output.
10
- """
11
-
12
- def __init__(self):
13
- self.console = console
14
-
15
- def handle_message(self, msg, msg_type=None):
16
- """
17
- Handles a dict with 'type' and 'message'.
18
- All messages must be dicts. Raises if not.
19
- """
20
- # Check trust config: suppress all output except 'content' if enabled
21
- trust = runtime_config.get("trust")
22
- if trust is None:
23
- trust = unified_config.get("trust", False)
24
-
25
- from rich.markdown import Markdown
26
-
27
- if not isinstance(msg, dict):
28
- raise TypeError(
29
- f"RichMessageHandler.handle_message expects a dict with 'type' and 'message', got {type(msg)}: {msg!r}"
30
- )
31
-
32
- msg_type = msg.get("type", "info")
33
- message = msg.get("message", "")
34
-
35
- def _remove_surrogates(text):
36
- return "".join(c for c in text if not 0xD800 <= ord(c) <= 0xDFFF)
37
-
38
- safe_message = (
39
- _remove_surrogates(message) if isinstance(message, str) else message
40
- )
41
-
42
- if trust and msg_type != "content":
43
- return # Suppress all except content
44
- if msg_type == "content":
45
- self.console.print(Markdown(safe_message))
46
- elif msg_type == "info":
47
- self.console.print(safe_message, style="cyan", end="")
48
- elif msg_type == "success":
49
- self.console.print(safe_message, style="bold green", end="\n")
50
- elif msg_type == "error":
51
- self.console.print(safe_message, style="bold red", end="\n")
52
- elif msg_type == "progress":
53
- self._handle_progress(safe_message)
54
- elif msg_type == "warning":
55
- self.console.print(safe_message, style="bold yellow", end="\n")
56
- elif msg_type == "stdout":
57
- from rich.text import Text
58
-
59
- self.console.print(
60
- Text(safe_message, style="on #003300", no_wrap=True, overflow=None),
61
- end="",
62
- )
63
- elif msg_type == "stderr":
64
- from rich.text import Text
65
-
66
- self.console.print(
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
@@ -17,7 +17,7 @@ class ToolBase(ABC):
17
17
  self.update_progress({"type": "stderr", "message": message})
18
18
 
19
19
  @abstractmethod
20
- def call(self, **kwargs):
20
+ def run(self, **kwargs):
21
21
  """
22
22
  Abstract call method for tool execution. Should be overridden by subclasses.
23
23
 
@@ -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
@@ -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, "call") or not callable(instance.call):
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.call, tool_name, tool_class=tool)
22
+ schema = generate_openai_function_schema(instance.run, tool_name, tool_class=tool)
25
23
  _tool_registry[tool_name] = {
26
- "function": instance.call,
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