janito 2.5.1__py3-none-any.whl → 2.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/agent/setup_agent.py +231 -223
- janito/agent/templates/profiles/system_prompt_template_software_developer.txt.j2 +39 -0
- janito/cli/chat_mode/bindings.py +1 -26
- janito/cli/chat_mode/session.py +282 -294
- janito/cli/chat_mode/session_profile_select.py +125 -55
- janito/cli/chat_mode/shell/commands/tools.py +51 -48
- janito/cli/chat_mode/toolbar.py +42 -68
- janito/cli/cli_commands/list_tools.py +41 -56
- janito/cli/cli_commands/show_system_prompt.py +70 -49
- janito/cli/core/runner.py +6 -1
- janito/cli/core/setters.py +43 -34
- janito/cli/main_cli.py +25 -1
- janito/cli/prompt_core.py +76 -69
- janito/cli/rich_terminal_reporter.py +22 -1
- janito/cli/single_shot_mode/handler.py +95 -94
- janito/drivers/driver_registry.py +27 -29
- janito/drivers/openai/driver.py +436 -494
- janito/llm/agent.py +54 -68
- janito/provider_registry.py +178 -178
- janito/providers/anthropic/model_info.py +41 -22
- janito/providers/anthropic/provider.py +80 -67
- janito/providers/provider_static_info.py +18 -17
- janito/tools/adapters/local/__init__.py +66 -65
- janito/tools/adapters/local/adapter.py +79 -18
- janito/tools/adapters/local/create_directory.py +9 -9
- janito/tools/adapters/local/create_file.py +12 -12
- janito/tools/adapters/local/delete_text_in_file.py +16 -16
- janito/tools/adapters/local/find_files.py +2 -2
- janito/tools/adapters/local/get_file_outline/core.py +5 -5
- janito/tools/adapters/local/get_file_outline/search_outline.py +4 -4
- janito/tools/adapters/local/open_html_in_browser.py +15 -15
- janito/tools/adapters/local/python_file_run.py +4 -4
- janito/tools/adapters/local/read_files.py +40 -0
- janito/tools/adapters/local/remove_directory.py +5 -5
- janito/tools/adapters/local/remove_file.py +4 -4
- janito/tools/adapters/local/replace_text_in_file.py +21 -21
- janito/tools/adapters/local/run_bash_command.py +1 -1
- janito/tools/adapters/local/search_text/pattern_utils.py +2 -2
- janito/tools/adapters/local/search_text/traverse_directory.py +10 -10
- janito/tools/adapters/local/validate_file_syntax/core.py +7 -7
- janito/tools/adapters/local/validate_file_syntax/css_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/html_validator.py +7 -7
- janito/tools/adapters/local/validate_file_syntax/js_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/json_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/markdown_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/ps1_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/python_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/xml_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/yaml_validator.py +2 -2
- janito/tools/adapters/local/view_file.py +12 -12
- janito/tools/path_security.py +204 -0
- janito/tools/tool_use_tracker.py +12 -12
- janito/tools/tools_adapter.py +66 -34
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/METADATA +412 -412
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/RECORD +59 -58
- janito/drivers/anthropic/driver.py +0 -113
- janito/tools/adapters/local/get_file_outline/python_outline_v2.py +0 -156
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/WHEEL +0 -0
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/entry_points.txt +0 -0
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/licenses/LICENSE +0 -0
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/top_level.txt +0 -0
@@ -9,25 +9,10 @@ from janito.platform_discovery import PlatformDiscovery
|
|
9
9
|
from pathlib import Path
|
10
10
|
from jinja2 import Template
|
11
11
|
import importlib.resources
|
12
|
+
import re
|
12
13
|
|
13
14
|
|
14
|
-
def
|
15
|
-
# Collect modifiers as in JanitoCLI
|
16
|
-
from janito.cli.main_cli import MODIFIER_KEYS
|
17
|
-
|
18
|
-
modifiers = {
|
19
|
-
k: getattr(args, k) for k in MODIFIER_KEYS if getattr(args, k, None) is not None
|
20
|
-
}
|
21
|
-
provider, llm_driver_config, agent_role = prepare_llm_driver_config(args, modifiers)
|
22
|
-
if provider is None or llm_driver_config is None:
|
23
|
-
print("Error: Could not resolve provider or LLM driver config.")
|
24
|
-
return
|
25
|
-
|
26
|
-
# Prepare context for Jinja2 rendering
|
27
|
-
context = {}
|
28
|
-
context["role"] = agent_role or "developer"
|
29
|
-
context["profile"] = getattr(args, "profile", None)
|
30
|
-
# Compute allowed_permissions from CLI args (as in agent setup)
|
15
|
+
def _compute_permission_string(args):
|
31
16
|
from janito.tools.tool_base import ToolPermissions
|
32
17
|
read = getattr(args, "read", False)
|
33
18
|
write = getattr(args, "write", False)
|
@@ -40,63 +25,99 @@ def handle_show_system_prompt(args):
|
|
40
25
|
perm_str += "w"
|
41
26
|
if allowed.execute:
|
42
27
|
perm_str += "x"
|
43
|
-
|
28
|
+
return perm_str or None
|
29
|
+
|
30
|
+
|
31
|
+
def _prepare_context(args, agent_role, allowed_permissions):
|
32
|
+
context = {}
|
33
|
+
context["role"] = agent_role or "developer"
|
34
|
+
context["profile"] = getattr(args, "profile", None)
|
44
35
|
context["allowed_permissions"] = allowed_permissions
|
45
|
-
# DEBUG: Show permissions/context before rendering
|
46
|
-
from rich import print as rich_print
|
47
|
-
debug_flag = False
|
48
|
-
import sys
|
49
|
-
try:
|
50
|
-
debug_flag = (hasattr(sys, 'argv') and ('--debug' in sys.argv or '--verbose' in sys.argv or '-v' in sys.argv))
|
51
|
-
except Exception:
|
52
|
-
pass
|
53
|
-
if debug_flag:
|
54
|
-
rich_print(f"[bold magenta][DEBUG][/bold magenta] Rendering system prompt template '[cyan]{template_filename}[/cyan]' with allowed_permissions: [yellow]{allowed_permissions}[/yellow]")
|
55
|
-
rich_print(f"[bold magenta][DEBUG][/bold magenta] Template context: [green]{context}[/green]")
|
56
36
|
if allowed_permissions and 'x' in allowed_permissions:
|
57
37
|
pd = PlatformDiscovery()
|
58
38
|
context["platform"] = pd.get_platform_name()
|
59
39
|
context["python_version"] = pd.get_python_version()
|
60
40
|
context["shell_info"] = pd.detect_shell()
|
41
|
+
return context
|
61
42
|
|
62
|
-
|
63
|
-
|
64
|
-
Path(__file__).parent.parent.parent / "agent" / "templates" / "profiles"
|
65
|
-
)
|
66
|
-
profile = getattr(args, "profile", None)
|
43
|
+
|
44
|
+
def _load_template(profile, templates_dir):
|
67
45
|
if profile:
|
68
46
|
template_filename = f"system_prompt_template_{profile}.txt.j2"
|
69
47
|
template_path = templates_dir / template_filename
|
70
48
|
else:
|
71
|
-
|
72
|
-
print("[janito] No profile specified. The main agent runs without a system prompt template.\n"
|
73
|
-
"Use --profile PROFILE to view a profile-specific system prompt.")
|
74
|
-
return
|
49
|
+
return None, None
|
75
50
|
template_content = None
|
76
51
|
if template_path and template_path.exists():
|
77
52
|
with open(template_path, "r", encoding="utf-8") as file:
|
78
53
|
template_content = file.read()
|
79
54
|
else:
|
80
|
-
# Try package import fallback
|
81
55
|
try:
|
82
56
|
with importlib.resources.files("janito.agent.templates.profiles").joinpath(
|
83
57
|
template_filename
|
84
58
|
).open("r", encoding="utf-8") as file:
|
85
59
|
template_content = file.read()
|
86
60
|
except (FileNotFoundError, ModuleNotFoundError, AttributeError):
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
61
|
+
return template_filename, None
|
62
|
+
return template_filename, template_content
|
63
|
+
|
64
|
+
|
65
|
+
def _print_debug_info(debug_flag, template_filename, allowed_permissions, context):
|
66
|
+
if debug_flag:
|
67
|
+
from rich import print as rich_print
|
68
|
+
rich_print(f"[bold magenta][DEBUG][/bold magenta] Rendering system prompt template '[cyan]{template_filename}[/cyan]' with allowed_permissions: [yellow]{allowed_permissions}[/yellow]")
|
69
|
+
rich_print(f"[bold magenta][DEBUG][/bold magenta] Template context: [green]{context}[/green]")
|
70
|
+
|
71
|
+
|
72
|
+
def handle_show_system_prompt(args):
|
73
|
+
from janito.cli.main_cli import MODIFIER_KEYS
|
74
|
+
|
75
|
+
modifiers = {
|
76
|
+
k: getattr(args, k) for k in MODIFIER_KEYS if getattr(args, k, None) is not None
|
77
|
+
}
|
78
|
+
provider, llm_driver_config, agent_role = prepare_llm_driver_config(args, modifiers)
|
79
|
+
if provider is None or llm_driver_config is None:
|
80
|
+
print("Error: Could not resolve provider or LLM driver config.")
|
81
|
+
return
|
82
|
+
|
83
|
+
allowed_permissions = _compute_permission_string(args)
|
84
|
+
context = _prepare_context(args, agent_role, allowed_permissions)
|
85
|
+
|
86
|
+
# Debug flag detection
|
87
|
+
import sys
|
88
|
+
debug_flag = False
|
89
|
+
try:
|
90
|
+
debug_flag = (hasattr(sys, 'argv') and ('--debug' in sys.argv or '--verbose' in sys.argv or '-v' in sys.argv))
|
91
|
+
except Exception:
|
92
|
+
pass
|
93
|
+
|
94
|
+
templates_dir = (
|
95
|
+
Path(__file__).parent.parent.parent / "agent" / "templates" / "profiles"
|
96
|
+
)
|
97
|
+
profile = getattr(args, "profile", None)
|
98
|
+
if not profile:
|
99
|
+
print("[janito] No profile specified. The main agent runs without a system prompt template.\n"
|
100
|
+
"Use --profile PROFILE to view a profile-specific system prompt.")
|
101
|
+
return
|
102
|
+
|
103
|
+
template_filename, template_content = _load_template(profile, templates_dir)
|
104
|
+
_print_debug_info(debug_flag, template_filename, allowed_permissions, context)
|
105
|
+
|
106
|
+
if not template_content:
|
107
|
+
if profile:
|
108
|
+
raise FileNotFoundError(
|
109
|
+
f"[janito] Could not find profile-specific template '{template_filename}' in {templates_dir / template_filename} nor in janito.agent.templates.profiles package."
|
110
|
+
)
|
111
|
+
else:
|
112
|
+
print(
|
113
|
+
f"[janito] Could not find {template_filename} in {templates_dir / template_filename} nor in janito.agent.templates.profiles package."
|
114
|
+
)
|
115
|
+
print("No system prompt is set or resolved for this configuration.")
|
116
|
+
return
|
97
117
|
|
98
118
|
template = Template(template_content)
|
99
119
|
system_prompt = template.render(**context)
|
120
|
+
system_prompt = re.sub(r'\n{3,}', '\n\n', system_prompt)
|
100
121
|
|
101
122
|
print(f"\n--- System Prompt (resolved, profile: {getattr(args, 'profile', 'main')}) ---\n")
|
102
123
|
print(system_prompt)
|
janito/cli/core/runner.py
CHANGED
@@ -92,7 +92,8 @@ def prepare_llm_driver_config(args, modifiers):
|
|
92
92
|
llm_driver_config = LLMDriverConfig(**driver_config_data)
|
93
93
|
if getattr(llm_driver_config, "verbose_api", None):
|
94
94
|
pass
|
95
|
-
|
95
|
+
# If both --role and --profile are provided, --role takes precedence for agent_role
|
96
|
+
agent_role = modifiers.get("role") or modifiers.get("profile") or "developer"
|
96
97
|
return provider, llm_driver_config, agent_role
|
97
98
|
|
98
99
|
|
@@ -116,7 +117,11 @@ def handle_runner(args, provider, llm_driver_config, agent_role, verbose_tools=F
|
|
116
117
|
# Store the default permissions for later restoration (e.g., on /restart)
|
117
118
|
from janito.tools.permissions import set_default_allowed_permissions
|
118
119
|
set_default_allowed_permissions(allowed_permissions)
|
120
|
+
unrestricted_paths = getattr(args, "unrestricted_paths", False)
|
119
121
|
adapter = janito.tools.get_local_tools_adapter(workdir=getattr(args, "workdir", None))
|
122
|
+
if unrestricted_paths:
|
123
|
+
# Patch: disable path security enforcement for this adapter instance
|
124
|
+
setattr(adapter, "unrestricted_paths", True)
|
120
125
|
|
121
126
|
# Print allowed permissions in verbose mode
|
122
127
|
if getattr(args, "verbose", False):
|
janito/cli/core/setters.py
CHANGED
@@ -18,47 +18,56 @@ def handle_set(args, config_mgr=None):
|
|
18
18
|
if not set_arg:
|
19
19
|
return False
|
20
20
|
try:
|
21
|
-
if
|
22
|
-
print(
|
23
|
-
"Error: --set requires KEY=VALUE (e.g., --set provider=provider_name)."
|
24
|
-
)
|
21
|
+
if not _validate_set_arg_format(set_arg):
|
25
22
|
return True
|
26
|
-
key, value = set_arg
|
27
|
-
key, value = key.strip(), value.strip()
|
23
|
+
key, value = _parse_set_arg(set_arg)
|
28
24
|
key = key.replace("-", "_")
|
25
|
+
return _dispatch_set_key(key, value)
|
26
|
+
except Exception as e:
|
27
|
+
print(f"Error parsing --set value: {e}")
|
28
|
+
return True
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
if key == "model":
|
33
|
-
return _handle_set_global_model(value)
|
34
|
-
if "." in key and key.endswith(".model"):
|
35
|
-
return _handle_set_provider_model(key, value)
|
36
|
-
if key == "max_tokens":
|
37
|
-
return _handle_set_max_tokens(value)
|
38
|
-
if key == "base_url":
|
39
|
-
return _handle_set_base_url(value)
|
40
|
-
if key in ["azure_deployment_name", "azure-deployment-name"]:
|
41
|
-
global_config.file_set("azure_deployment_name", value)
|
42
|
-
print(f"Azure deployment name set to '{value}'.")
|
43
|
-
return True
|
44
|
-
if ".max_tokens" in key or ".base_url" in key:
|
45
|
-
return _handle_set_provider_level_setting(key, value)
|
46
|
-
# Tool permissions support: janito set tool_permissions=rwx
|
47
|
-
if key == "tool_permissions":
|
48
|
-
from janito.tools.permissions_parse import parse_permissions_string
|
49
|
-
from janito.tools.permissions import set_global_allowed_permissions
|
50
|
-
perms = parse_permissions_string(value)
|
51
|
-
global_config.file_set("tool_permissions", value)
|
52
|
-
set_global_allowed_permissions(perms)
|
53
|
-
print(f"Tool permissions set to '{value}' (parsed: {perms})")
|
54
|
-
return True
|
30
|
+
def _validate_set_arg_format(set_arg):
|
31
|
+
if "=" not in set_arg:
|
55
32
|
print(
|
56
|
-
|
33
|
+
"Error: --set requires KEY=VALUE (e.g., --set provider=provider_name)."
|
57
34
|
)
|
35
|
+
return False
|
36
|
+
return True
|
37
|
+
|
38
|
+
def _parse_set_arg(set_arg):
|
39
|
+
key, value = set_arg.split("=", 1)
|
40
|
+
return key.strip(), value.strip()
|
41
|
+
|
42
|
+
def _dispatch_set_key(key, value):
|
43
|
+
if key == "provider":
|
44
|
+
return _handle_set_config_provider(value)
|
45
|
+
if key == "model":
|
46
|
+
return _handle_set_global_model(value)
|
47
|
+
if "." in key and key.endswith(".model"):
|
48
|
+
return _handle_set_provider_model(key, value)
|
49
|
+
if key == "max_tokens":
|
50
|
+
return _handle_set_max_tokens(value)
|
51
|
+
if key == "base_url":
|
52
|
+
return _handle_set_base_url(value)
|
53
|
+
if key in ["azure_deployment_name", "azure-deployment-name"]:
|
54
|
+
global_config.file_set("azure_deployment_name", value)
|
55
|
+
print(f"Azure deployment name set to '{value}'.")
|
58
56
|
return True
|
59
|
-
|
60
|
-
|
57
|
+
if ".max_tokens" in key or ".base_url" in key:
|
58
|
+
return _handle_set_provider_level_setting(key, value)
|
59
|
+
if key == "tool_permissions":
|
60
|
+
from janito.tools.permissions_parse import parse_permissions_string
|
61
|
+
from janito.tools.permissions import set_global_allowed_permissions
|
62
|
+
perms = parse_permissions_string(value)
|
63
|
+
global_config.file_set("tool_permissions", value)
|
64
|
+
set_global_allowed_permissions(perms)
|
65
|
+
print(f"Tool permissions set to '{value}' (parsed: {perms})")
|
61
66
|
return True
|
67
|
+
print(
|
68
|
+
f"Error: Unknown config key '{key}'. Supported: provider, model, <provider>.model, max_tokens, base_url, azure_deployment_name, <provider>.max_tokens, <provider>.base_url, <provider>.<model>.max_tokens, <provider>.<model>.base_url, tool_permissions"
|
69
|
+
)
|
70
|
+
return True
|
62
71
|
|
63
72
|
|
64
73
|
def _handle_set_max_tokens(value):
|
janito/cli/main_cli.py
CHANGED
@@ -14,6 +14,14 @@ from janito.cli.core.event_logger import (
|
|
14
14
|
)
|
15
15
|
|
16
16
|
definition = [
|
17
|
+
(
|
18
|
+
["-u", "--unrestricted-paths"],
|
19
|
+
{
|
20
|
+
"action": "store_true",
|
21
|
+
"help": "Disable path security: allow tool arguments to use any file/directory path (DANGEROUS)",
|
22
|
+
},
|
23
|
+
),
|
24
|
+
|
17
25
|
(
|
18
26
|
["--profile"],
|
19
27
|
{
|
@@ -22,6 +30,14 @@ definition = [
|
|
22
30
|
"default": None,
|
23
31
|
},
|
24
32
|
),
|
33
|
+
(
|
34
|
+
["--role"],
|
35
|
+
{
|
36
|
+
"metavar": "ROLE",
|
37
|
+
"help": "Select the developer role name (overrides profile, e.g. 'python-expert').",
|
38
|
+
"default": None,
|
39
|
+
},
|
40
|
+
),
|
25
41
|
(
|
26
42
|
["-W", "--workdir"],
|
27
43
|
{
|
@@ -165,6 +181,7 @@ MODIFIER_KEYS = [
|
|
165
181
|
"provider",
|
166
182
|
"model",
|
167
183
|
"role",
|
184
|
+
"profile",
|
168
185
|
"system",
|
169
186
|
"temperature",
|
170
187
|
|
@@ -240,11 +257,15 @@ class JanitoCLI:
|
|
240
257
|
setattr(self.args, key, None)
|
241
258
|
|
242
259
|
def collect_modifiers(self):
|
243
|
-
|
260
|
+
modifiers = {
|
244
261
|
k: getattr(self.args, k)
|
245
262
|
for k in MODIFIER_KEYS
|
246
263
|
if getattr(self.args, k, None) is not None
|
247
264
|
}
|
265
|
+
# If --role is provided, override role in modifiers
|
266
|
+
if getattr(self.args, "role", None):
|
267
|
+
modifiers["role"] = getattr(self.args, "role")
|
268
|
+
return modifiers
|
248
269
|
|
249
270
|
def classify(self):
|
250
271
|
if any(getattr(self.args, k, None) for k in SETTER_KEYS):
|
@@ -273,6 +294,9 @@ class JanitoCLI:
|
|
273
294
|
self._maybe_print_verbose_provider_model()
|
274
295
|
handle_getter(self.args)
|
275
296
|
return
|
297
|
+
# If running in single shot mode and --profile is not provided, default to 'developer' profile
|
298
|
+
if get_prompt_mode(self.args) == "single_shot" and not getattr(self.args, "profile", None):
|
299
|
+
self.args.profile = "developer"
|
276
300
|
provider = self._get_provider_or_default()
|
277
301
|
if provider is None:
|
278
302
|
print(
|
janito/cli/prompt_core.py
CHANGED
@@ -49,87 +49,94 @@ class PromptHandler:
|
|
49
49
|
on_event(inner_event)
|
50
50
|
from janito.tools.tool_events import ToolCallFinished
|
51
51
|
|
52
|
-
# Print tool result if ToolCallFinished event is received
|
53
52
|
if isinstance(inner_event, ToolCallFinished):
|
54
|
-
|
55
|
-
if hasattr(self.args, "verbose_tools") and self.args.verbose_tools:
|
56
|
-
self.console.print(
|
57
|
-
f"[cyan][tools-adapter] Tool '{inner_event.tool_name}' result:[/cyan] {inner_event.result}"
|
58
|
-
)
|
59
|
-
else:
|
60
|
-
self.console.print(inner_event.result)
|
61
|
-
return None
|
53
|
+
return self._handle_tool_call_finished(inner_event)
|
62
54
|
if isinstance(inner_event, RateLimitRetry):
|
63
|
-
|
64
|
-
return None
|
55
|
+
return self._handle_rate_limit_retry(inner_event, status)
|
65
56
|
if isinstance(inner_event, RequestFinished):
|
57
|
+
if getattr(inner_event, "status", None) == "error":
|
58
|
+
return self._handle_request_finished_error(inner_event, status)
|
59
|
+
if getattr(inner_event, "status", None) in (RequestStatus.EMPTY_RESPONSE, RequestStatus.TIMEOUT):
|
60
|
+
return self._handle_empty_or_timeout(inner_event, status)
|
66
61
|
status.update("[bold green]Received response![bold green]")
|
67
62
|
return "break"
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
63
|
+
if isinstance(inner_event, ToolCallError):
|
64
|
+
return self._handle_tool_call_error(inner_event, status)
|
65
|
+
event_type = type(inner_event).__name__
|
66
|
+
self.console.print(
|
67
|
+
f"[yellow]Warning: Unknown event type encountered: {event_type}[yellow]"
|
68
|
+
)
|
69
|
+
return None
|
70
|
+
|
71
|
+
def _handle_tool_call_finished(self, inner_event):
|
72
|
+
if hasattr(self.args, "verbose_tools") and self.args.verbose_tools:
|
73
|
+
self.console.print(
|
74
|
+
f"[cyan][tools-adapter] Tool '{inner_event.tool_name}' result:[/cyan] {inner_event.result}"
|
75
|
+
)
|
76
|
+
else:
|
77
|
+
self.console.print(inner_event.result)
|
78
|
+
return None
|
79
|
+
|
80
|
+
def _handle_rate_limit_retry(self, inner_event, status):
|
81
|
+
status.update(f"[yellow]Rate limited. Waiting {inner_event.retry_delay:.0f}s before retry (attempt {inner_event.attempt}).[yellow]")
|
82
|
+
return None
|
83
|
+
|
84
|
+
def _handle_request_finished_error(self, inner_event, status):
|
85
|
+
error_msg = (
|
86
|
+
inner_event.error if hasattr(inner_event, "error") else "Unknown error"
|
87
|
+
)
|
88
|
+
if (
|
89
|
+
"Status 429" in error_msg
|
90
|
+
and "Service tier capacity exceeded for this model" in error_msg
|
91
|
+
):
|
92
|
+
status.update(
|
93
|
+
"[yellow]Service tier capacity exceeded, retrying...[yellow]"
|
74
94
|
)
|
75
|
-
if (
|
76
|
-
"Status 429" in error_msg
|
77
|
-
and "Service tier capacity exceeded for this model" in error_msg
|
78
|
-
):
|
79
|
-
status.update(
|
80
|
-
"[yellow]Service tier capacity exceeded, retrying...[yellow]"
|
81
|
-
)
|
82
|
-
return "break"
|
83
|
-
status.update(f"[bold red]Error: {error_msg}[bold red]")
|
84
|
-
self.console.print(f"[red]Error: {error_msg}[red]")
|
85
95
|
return "break"
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
96
|
+
status.update(f"[bold red]Error: {error_msg}[bold red]")
|
97
|
+
self.console.print(f"[red]Error: {error_msg}[red]")
|
98
|
+
return "break"
|
99
|
+
|
100
|
+
def _handle_tool_call_error(self, inner_event, status):
|
101
|
+
error_msg = (
|
102
|
+
inner_event.error
|
103
|
+
if hasattr(inner_event, "error")
|
104
|
+
else "Unknown tool error"
|
105
|
+
)
|
106
|
+
tool_name = (
|
107
|
+
inner_event.tool_name
|
108
|
+
if hasattr(inner_event, "tool_name")
|
109
|
+
else "unknown"
|
110
|
+
)
|
111
|
+
status.update(
|
112
|
+
f"[bold red]Tool Error in '{tool_name}': {error_msg}[bold red]"
|
113
|
+
)
|
114
|
+
self.console.print(f"[red]Tool Error in '{tool_name}': {error_msg}[red]")
|
115
|
+
return "break"
|
116
|
+
|
117
|
+
def _handle_empty_or_timeout(self, inner_event, status):
|
118
|
+
details = getattr(inner_event, "details", None) or {}
|
119
|
+
block_reason = details.get("block_reason")
|
120
|
+
block_msg = details.get("block_reason_message")
|
121
|
+
msg = details.get(
|
122
|
+
"message", "LLM returned an empty or incomplete response."
|
123
|
+
)
|
124
|
+
driver_name = getattr(inner_event, "driver_name", "unknown driver")
|
125
|
+
if block_reason or block_msg:
|
126
|
+
status.update(
|
127
|
+
f"[bold yellow]Blocked by driver: {driver_name} | {block_reason or ''} {block_msg or ''}[bold yellow]"
|
91
128
|
)
|
92
|
-
|
93
|
-
|
94
|
-
if hasattr(inner_event, "tool_name")
|
95
|
-
else "unknown"
|
129
|
+
self.console.print(
|
130
|
+
f"[yellow]Blocked by driver: {driver_name} (empty response): {block_reason or ''}\n{block_msg or ''}[/yellow]"
|
96
131
|
)
|
132
|
+
else:
|
97
133
|
status.update(
|
98
|
-
f"[
|
134
|
+
f"[yellow]LLM produced no output for this request (driver: {driver_name}).[/yellow]"
|
99
135
|
)
|
100
|
-
self.console.print(
|
101
|
-
|
102
|
-
elif isinstance(inner_event, RequestFinished) and getattr(
|
103
|
-
inner_event, "status", None
|
104
|
-
) in (RequestStatus.EMPTY_RESPONSE, RequestStatus.TIMEOUT):
|
105
|
-
details = getattr(inner_event, "details", None) or {}
|
106
|
-
block_reason = details.get("block_reason")
|
107
|
-
block_msg = details.get("block_reason_message")
|
108
|
-
msg = details.get(
|
109
|
-
"message", "LLM returned an empty or incomplete response."
|
136
|
+
self.console.print(
|
137
|
+
f"[yellow]Warning: {msg} (driver: {driver_name})[/yellow]"
|
110
138
|
)
|
111
|
-
|
112
|
-
if block_reason or block_msg:
|
113
|
-
status.update(
|
114
|
-
f"[bold yellow]Blocked by driver: {driver_name} | {block_reason or ''} {block_msg or ''}[bold yellow]"
|
115
|
-
)
|
116
|
-
self.console.print(
|
117
|
-
f"[yellow]Blocked by driver: {driver_name} (empty response): {block_reason or ''}\n{block_msg or ''}[/yellow]"
|
118
|
-
)
|
119
|
-
else:
|
120
|
-
status.update(
|
121
|
-
f"[yellow]LLM produced no output for this request (driver: {driver_name}).[/yellow]"
|
122
|
-
)
|
123
|
-
self.console.print(
|
124
|
-
f"[yellow]Warning: {msg} (driver: {driver_name})[/yellow]"
|
125
|
-
)
|
126
|
-
return "break"
|
127
|
-
# Report unknown event types
|
128
|
-
event_type = type(inner_event).__name__
|
129
|
-
self.console.print(
|
130
|
-
f"[yellow]Warning: Unknown event type encountered: {event_type}[yellow]"
|
131
|
-
)
|
132
|
-
return None
|
139
|
+
return "break"
|
133
140
|
|
134
141
|
def _process_event_iter(self, event_iter, on_event):
|
135
142
|
for event in event_iter:
|
@@ -30,7 +30,8 @@ class RichTerminalReporter(EventHandlerBase):
|
|
30
30
|
self.raw_mode = raw_mode
|
31
31
|
import janito.report_events as report_events
|
32
32
|
|
33
|
-
|
33
|
+
import janito.tools.tool_events as tool_events
|
34
|
+
super().__init__(driver_events, report_events, tool_events)
|
34
35
|
self._waiting_printed = False
|
35
36
|
|
36
37
|
def on_RequestStarted(self, event):
|
@@ -102,7 +103,27 @@ class RichTerminalReporter(EventHandlerBase):
|
|
102
103
|
self.console.file.flush()
|
103
104
|
# No output if not raw_mode or if response is None
|
104
105
|
|
106
|
+
def on_ToolCallError(self, event):
|
107
|
+
# Optionally handle tool call errors in a user-friendly way
|
108
|
+
error = getattr(event, "error", None)
|
109
|
+
tool = getattr(event, "tool_name", None)
|
110
|
+
if error and tool:
|
111
|
+
self.console.print(f"[bold red]Tool Error ({tool}):[/] {error}")
|
112
|
+
self.console.file.flush()
|
113
|
+
|
105
114
|
def on_ReportEvent(self, event):
|
115
|
+
# Special handling for security-related report events
|
116
|
+
subtype = getattr(event, "subtype", None)
|
117
|
+
msg = getattr(event, "message", None)
|
118
|
+
action = getattr(event, "action", None)
|
119
|
+
tool = getattr(event, "tool", None)
|
120
|
+
context = getattr(event, "context", None)
|
121
|
+
if subtype == ReportSubtype.ERROR and msg and "[SECURITY] Path access denied" in msg:
|
122
|
+
# Highlight security errors with a distinct style
|
123
|
+
self.console.print(Panel(f"{msg}", title="[red]SECURITY VIOLATION[/red]", style="bold red"))
|
124
|
+
self.console.file.flush()
|
125
|
+
return
|
126
|
+
|
106
127
|
msg = event.message if hasattr(event, "message") else None
|
107
128
|
subtype = event.subtype if hasattr(event, "subtype") else None
|
108
129
|
if not msg or not subtype:
|