janito 2.7.0__py3-none-any.whl → 2.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 +0 -1
- janito/__main__.py +0 -1
- janito/_version.py +0 -3
- janito/agent/setup_agent.py +77 -10
- janito/agent/templates/profiles/{system_prompt_template_plain_software_developer.txt.j2 → system_prompt_template_Developer_with_Python_Tools.txt.j2} +5 -1
- janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +3 -12
- janito/cli/__init__.py +0 -1
- janito/cli/chat_mode/bindings.py +1 -1
- janito/cli/chat_mode/chat_entry.py +0 -2
- janito/cli/chat_mode/prompt_style.py +0 -3
- janito/cli/chat_mode/script_runner.py +9 -5
- janito/cli/chat_mode/session.py +80 -35
- janito/cli/chat_mode/session_profile_select.py +61 -52
- janito/cli/chat_mode/shell/commands/__init__.py +1 -5
- janito/cli/chat_mode/shell/commands/_priv_check.py +1 -0
- janito/cli/chat_mode/shell/commands/bang.py +10 -3
- janito/cli/chat_mode/shell/commands/conversation_restart.py +24 -7
- janito/cli/chat_mode/shell/commands/execute.py +22 -7
- janito/cli/chat_mode/shell/commands/help.py +4 -1
- janito/cli/chat_mode/shell/commands/model.py +13 -5
- janito/cli/chat_mode/shell/commands/privileges.py +21 -0
- janito/cli/chat_mode/shell/commands/prompt.py +0 -2
- janito/cli/chat_mode/shell/commands/read.py +22 -5
- janito/cli/chat_mode/shell/commands/tools.py +15 -4
- janito/cli/chat_mode/shell/commands/write.py +22 -5
- janito/cli/chat_mode/shell/input_history.py +3 -1
- janito/cli/chat_mode/shell/session/manager.py +0 -2
- janito/cli/chat_mode/toolbar.py +25 -19
- janito/cli/cli_commands/list_models.py +1 -1
- janito/cli/cli_commands/list_providers.py +1 -0
- janito/cli/cli_commands/list_tools.py +35 -7
- janito/cli/cli_commands/model_utils.py +5 -3
- janito/cli/cli_commands/show_config.py +12 -0
- janito/cli/cli_commands/show_system_prompt.py +23 -9
- janito/cli/config.py +0 -13
- janito/cli/core/getters.py +2 -0
- janito/cli/core/runner.py +25 -8
- janito/cli/core/setters.py +13 -76
- janito/cli/main_cli.py +9 -25
- janito/cli/prompt_core.py +19 -18
- janito/cli/prompt_setup.py +6 -3
- janito/cli/rich_terminal_reporter.py +19 -5
- janito/cli/single_shot_mode/handler.py +14 -5
- janito/cli/verbose_output.py +5 -1
- janito/config_manager.py +4 -0
- janito/drivers/azure_openai/driver.py +27 -30
- janito/drivers/openai/driver.py +52 -36
- janito/formatting_token.py +12 -4
- janito/llm/agent.py +15 -6
- janito/llm/driver.py +1 -0
- janito/provider_registry.py +31 -70
- janito/providers/__init__.py +1 -0
- janito/providers/anthropic/model_info.py +0 -1
- janito/providers/anthropic/provider.py +9 -14
- janito/providers/azure_openai/provider.py +9 -4
- janito/providers/deepseek/provider.py +5 -4
- janito/providers/google/model_info.py +4 -2
- janito/providers/google/provider.py +11 -5
- janito/providers/groq/__init__.py +1 -0
- janito/providers/groq/model_info.py +46 -0
- janito/providers/groq/provider.py +76 -0
- janito/providers/moonshotai/provider.py +11 -4
- janito/providers/openai/model_info.py +0 -1
- janito/providers/openai/provider.py +6 -7
- janito/tools/__init__.py +2 -0
- janito/tools/adapters/local/__init__.py +2 -1
- janito/tools/adapters/local/adapter.py +21 -4
- janito/tools/adapters/local/ask_user.py +1 -0
- janito/tools/adapters/local/copy_file.py +1 -0
- janito/tools/adapters/local/create_directory.py +1 -0
- janito/tools/adapters/local/create_file.py +1 -0
- janito/tools/adapters/local/delete_text_in_file.py +2 -1
- janito/tools/adapters/local/fetch_url.py +1 -0
- janito/tools/adapters/local/find_files.py +7 -6
- janito/tools/adapters/local/get_file_outline/core.py +1 -0
- janito/tools/adapters/local/get_file_outline/java_outline.py +22 -15
- janito/tools/adapters/local/get_file_outline/search_outline.py +1 -0
- janito/tools/adapters/local/move_file.py +1 -0
- janito/tools/adapters/local/open_html_in_browser.py +15 -5
- janito/tools/adapters/local/open_url.py +1 -0
- janito/tools/adapters/local/python_code_run.py +1 -0
- janito/tools/adapters/local/python_command_run.py +1 -0
- janito/tools/adapters/local/python_file_run.py +1 -0
- janito/tools/adapters/local/read_files.py +19 -4
- janito/tools/adapters/local/remove_directory.py +1 -0
- janito/tools/adapters/local/remove_file.py +1 -0
- janito/tools/adapters/local/replace_text_in_file.py +4 -3
- janito/tools/adapters/local/run_bash_command.py +1 -0
- janito/tools/adapters/local/run_powershell_command.py +1 -0
- janito/tools/adapters/local/search_text/core.py +18 -17
- janito/tools/adapters/local/search_text/match_lines.py +5 -5
- janito/tools/adapters/local/search_text/pattern_utils.py +1 -1
- janito/tools/adapters/local/search_text/traverse_directory.py +7 -7
- janito/tools/adapters/local/validate_file_syntax/core.py +1 -1
- janito/tools/adapters/local/validate_file_syntax/html_validator.py +8 -1
- janito/tools/disabled_tools.py +68 -0
- janito/tools/path_security.py +18 -11
- janito/tools/permissions.py +6 -0
- janito/tools/permissions_parse.py +4 -3
- janito/tools/tool_base.py +11 -5
- janito/tools/tool_use_tracker.py +1 -4
- janito/tools/tool_utils.py +1 -1
- janito/tools/tools_adapter.py +57 -25
- {janito-2.7.0.dist-info → janito-2.8.0.dist-info}/METADATA +4 -12
- janito-2.8.0.dist-info/RECORD +202 -0
- janito/cli/chat_mode/shell/commands/livelogs.py +0 -49
- janito/drivers/mistralai/driver.py +0 -41
- janito/providers/mistralai/model_info.py +0 -37
- janito/providers/mistralai/provider.py +0 -72
- janito/providers/provider_static_info.py +0 -21
- janito-2.7.0.dist-info/RECORD +0 -202
- /janito/agent/templates/profiles/{system_prompt_template_assistant.txt.j2 → system_prompt_template_model_conversation_without_tools_or_context.txt.j2} +0 -0
- {janito-2.7.0.dist-info → janito-2.8.0.dist-info}/WHEEL +0 -0
- {janito-2.7.0.dist-info → janito-2.8.0.dist-info}/entry_points.txt +0 -0
- {janito-2.7.0.dist-info → janito-2.8.0.dist-info}/licenses/LICENSE +0 -0
- {janito-2.7.0.dist-info → janito-2.8.0.dist-info}/top_level.txt +0 -0
janito/cli/main_cli.py
CHANGED
@@ -21,7 +21,6 @@ definition = [
|
|
21
21
|
"help": "Disable path security: allow tool arguments to use any file/directory path (DANGEROUS)",
|
22
22
|
},
|
23
23
|
),
|
24
|
-
|
25
24
|
(
|
26
25
|
["--profile"],
|
27
26
|
{
|
@@ -114,7 +113,7 @@ definition = [
|
|
114
113
|
"help": "Set API key for the provider (requires -p PROVIDER)",
|
115
114
|
},
|
116
115
|
),
|
117
|
-
(["--set"], {"metavar": "
|
116
|
+
(["--set"], {"metavar": "KEY=VALUE", "help": "Set a config key"}),
|
118
117
|
(["-s", "--system"], {"metavar": "SYSTEM_PROMPT", "help": "Set a system prompt"}),
|
119
118
|
(
|
120
119
|
["-S", "--show-system"],
|
@@ -141,22 +140,7 @@ definition = [
|
|
141
140
|
},
|
142
141
|
),
|
143
142
|
(
|
144
|
-
["--
|
145
|
-
{
|
146
|
-
"action": "store_true",
|
147
|
-
"default": False,
|
148
|
-
"help": "Enable the builtin lightweight web file viewer for terminal links (disabled by default)",
|
149
|
-
},
|
150
|
-
),
|
151
|
-
(
|
152
|
-
["---port"],
|
153
|
-
{
|
154
|
-
"type": int,
|
155
|
-
"default": 8088,
|
156
|
-
"help": "Port for the server (default: 8088)",
|
157
|
-
},
|
158
|
-
),
|
159
|
-
(["--effort"],
|
143
|
+
["--effort"],
|
160
144
|
{
|
161
145
|
"choices": ["low", "medium", "high", "none"],
|
162
146
|
"default": None,
|
@@ -184,11 +168,8 @@ MODIFIER_KEYS = [
|
|
184
168
|
"profile",
|
185
169
|
"system",
|
186
170
|
"temperature",
|
187
|
-
|
188
171
|
"verbose",
|
189
172
|
"raw",
|
190
|
-
"web",
|
191
|
-
"_port",
|
192
173
|
"verbose_api",
|
193
174
|
"verbose_tools",
|
194
175
|
"exec",
|
@@ -219,12 +200,13 @@ class JanitoCLI:
|
|
219
200
|
self._set_all_arg_defaults()
|
220
201
|
# Support reading prompt from stdin if no user_prompt is given
|
221
202
|
import sys
|
203
|
+
|
222
204
|
if not sys.stdin.isatty():
|
223
205
|
stdin_input = sys.stdin.read().strip()
|
224
206
|
if stdin_input:
|
225
207
|
if self.args.user_prompt and len(self.args.user_prompt) > 0:
|
226
208
|
# Prefix the prompt argument to the stdin input
|
227
|
-
combined =
|
209
|
+
combined = " ".join(self.args.user_prompt) + " " + stdin_input
|
228
210
|
self.args.user_prompt = [combined]
|
229
211
|
else:
|
230
212
|
self.args.user_prompt = [stdin_input]
|
@@ -295,7 +277,9 @@ class JanitoCLI:
|
|
295
277
|
handle_getter(self.args)
|
296
278
|
return
|
297
279
|
# 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(
|
280
|
+
if get_prompt_mode(self.args) == "single_shot" and not getattr(
|
281
|
+
self.args, "profile", None
|
282
|
+
):
|
299
283
|
self.args.profile = "developer"
|
300
284
|
provider = self._get_provider_or_default()
|
301
285
|
if provider is None:
|
@@ -316,14 +300,14 @@ class JanitoCLI:
|
|
316
300
|
if run_mode == RunMode.RUN:
|
317
301
|
self._maybe_print_verbose_run_mode()
|
318
302
|
# DEBUG: Print exec_enabled propagation at main_cli
|
319
|
-
|
303
|
+
|
320
304
|
handle_runner(
|
321
305
|
self.args,
|
322
306
|
provider,
|
323
307
|
llm_driver_config,
|
324
308
|
agent_role,
|
325
309
|
verbose_tools=self.args.verbose_tools,
|
326
|
-
|
310
|
+
)
|
327
311
|
elif run_mode == RunMode.GET:
|
328
312
|
handle_getter(self.args)
|
329
313
|
|
janito/cli/prompt_core.py
CHANGED
@@ -8,7 +8,12 @@ from janito.performance_collector import PerformanceCollector
|
|
8
8
|
from rich.status import Status
|
9
9
|
from rich.console import Console
|
10
10
|
from typing import Any, Optional, Callable
|
11
|
-
from janito.driver_events import
|
11
|
+
from janito.driver_events import (
|
12
|
+
RequestStarted,
|
13
|
+
RequestFinished,
|
14
|
+
RequestStatus,
|
15
|
+
RateLimitRetry,
|
16
|
+
)
|
12
17
|
from janito.tools.tool_events import ToolCallError
|
13
18
|
import threading
|
14
19
|
from janito.cli.verbose_output import print_verbose_header
|
@@ -56,7 +61,10 @@ class PromptHandler:
|
|
56
61
|
if isinstance(inner_event, RequestFinished):
|
57
62
|
if getattr(inner_event, "status", None) == "error":
|
58
63
|
return self._handle_request_finished_error(inner_event, status)
|
59
|
-
if getattr(inner_event, "status", None) in (
|
64
|
+
if getattr(inner_event, "status", None) in (
|
65
|
+
RequestStatus.EMPTY_RESPONSE,
|
66
|
+
RequestStatus.TIMEOUT,
|
67
|
+
):
|
60
68
|
return self._handle_empty_or_timeout(inner_event, status)
|
61
69
|
status.update("[bold green]Received response![bold green]")
|
62
70
|
return "break"
|
@@ -78,7 +86,9 @@ class PromptHandler:
|
|
78
86
|
return None
|
79
87
|
|
80
88
|
def _handle_rate_limit_retry(self, inner_event, status):
|
81
|
-
status.update(
|
89
|
+
status.update(
|
90
|
+
f"[yellow]Rate limited. Waiting {inner_event.retry_delay:.0f}s before retry (attempt {inner_event.attempt}).[yellow]"
|
91
|
+
)
|
82
92
|
return None
|
83
93
|
|
84
94
|
def _handle_request_finished_error(self, inner_event, status):
|
@@ -89,9 +99,7 @@ class PromptHandler:
|
|
89
99
|
"Status 429" in error_msg
|
90
100
|
and "Service tier capacity exceeded for this model" in error_msg
|
91
101
|
):
|
92
|
-
status.update(
|
93
|
-
"[yellow]Service tier capacity exceeded, retrying...[yellow]"
|
94
|
-
)
|
102
|
+
status.update("[yellow]Service tier capacity exceeded, retrying...[yellow]")
|
95
103
|
return "break"
|
96
104
|
status.update(f"[bold red]Error: {error_msg}[bold red]")
|
97
105
|
self.console.print(f"[red]Error: {error_msg}[red]")
|
@@ -99,18 +107,12 @@ class PromptHandler:
|
|
99
107
|
|
100
108
|
def _handle_tool_call_error(self, inner_event, status):
|
101
109
|
error_msg = (
|
102
|
-
inner_event.error
|
103
|
-
if hasattr(inner_event, "error")
|
104
|
-
else "Unknown tool error"
|
110
|
+
inner_event.error if hasattr(inner_event, "error") else "Unknown tool error"
|
105
111
|
)
|
106
112
|
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
|
+
inner_event.tool_name if hasattr(inner_event, "tool_name") else "unknown"
|
113
114
|
)
|
115
|
+
status.update(f"[bold red]Tool Error in '{tool_name}': {error_msg}[bold red]")
|
114
116
|
self.console.print(f"[red]Tool Error in '{tool_name}': {error_msg}[red]")
|
115
117
|
return "break"
|
116
118
|
|
@@ -118,9 +120,7 @@ class PromptHandler:
|
|
118
120
|
details = getattr(inner_event, "details", None) or {}
|
119
121
|
block_reason = details.get("block_reason")
|
120
122
|
block_msg = details.get("block_reason_message")
|
121
|
-
msg = details.get(
|
122
|
-
"message", "LLM returned an empty or incomplete response."
|
123
|
-
)
|
123
|
+
msg = details.get("message", "LLM returned an empty or incomplete response.")
|
124
124
|
driver_name = getattr(inner_event, "driver_name", "unknown driver")
|
125
125
|
if block_reason or block_msg:
|
126
126
|
status.update(
|
@@ -221,6 +221,7 @@ class PromptHandler:
|
|
221
221
|
self.console.print("[red]Interrupted by the user.[/red]")
|
222
222
|
try:
|
223
223
|
from janito.driver_events import RequestFinished, RequestStatus
|
224
|
+
|
224
225
|
# Record a synthetic "cancelled" final event so that downstream
|
225
226
|
# handlers (e.g. single_shot_mode.handler._post_prompt_actions)
|
226
227
|
# can reliably detect that the prompt was interrupted by the
|
janito/cli/prompt_setup.py
CHANGED
@@ -4,6 +4,7 @@ both single–shot and chat modes can reuse. Having one central place avoids th
|
|
4
4
|
code duplication that previously existed in `chat_mode.session.ChatSession` and
|
5
5
|
`single_shot_mode.handler.PromptHandler`.
|
6
6
|
"""
|
7
|
+
|
7
8
|
from __future__ import annotations
|
8
9
|
|
9
10
|
from janito.agent.setup_agent import create_configured_agent
|
@@ -21,7 +22,6 @@ def setup_agent_and_prompt_handler(
|
|
21
22
|
role: Optional[str] = None,
|
22
23
|
verbose_tools: bool = False,
|
23
24
|
verbose_agent: bool = False,
|
24
|
-
|
25
25
|
allowed_permissions: Optional[list[str]] = None,
|
26
26
|
profile: Optional[str] = None,
|
27
27
|
profile_system_prompt: Optional[str] = None,
|
@@ -34,16 +34,19 @@ def setup_agent_and_prompt_handler(
|
|
34
34
|
across *single-shot* and *chat* modes – both of which need an agent plus a
|
35
35
|
prompt handler that points to that agent.
|
36
36
|
"""
|
37
|
+
no_tools_mode = False
|
38
|
+
if hasattr(args, 'no_tools_mode'):
|
39
|
+
no_tools_mode = getattr(args, 'no_tools_mode', False)
|
37
40
|
agent = create_configured_agent(
|
38
41
|
provider_instance=provider_instance,
|
39
42
|
llm_driver_config=llm_driver_config,
|
40
43
|
role=role,
|
41
44
|
verbose_tools=verbose_tools,
|
42
45
|
verbose_agent=verbose_agent,
|
43
|
-
|
44
46
|
allowed_permissions=allowed_permissions,
|
45
47
|
profile=profile,
|
46
48
|
profile_system_prompt=profile_system_prompt,
|
49
|
+
no_tools_mode=no_tools_mode,
|
47
50
|
)
|
48
51
|
|
49
52
|
prompt_handler = GenericPromptHandler(
|
@@ -53,4 +56,4 @@ def setup_agent_and_prompt_handler(
|
|
53
56
|
)
|
54
57
|
prompt_handler.agent = agent
|
55
58
|
|
56
|
-
return agent, prompt_handler
|
59
|
+
return agent, prompt_handler
|
@@ -12,6 +12,7 @@ from janito.llm import message_parts
|
|
12
12
|
|
13
13
|
import sys
|
14
14
|
|
15
|
+
|
15
16
|
class RichTerminalReporter(EventHandlerBase):
|
16
17
|
"""
|
17
18
|
Handles UI rendering for janito events using Rich.
|
@@ -31,6 +32,7 @@ class RichTerminalReporter(EventHandlerBase):
|
|
31
32
|
import janito.report_events as report_events
|
32
33
|
|
33
34
|
import janito.tools.tool_events as tool_events
|
35
|
+
|
34
36
|
super().__init__(driver_events, report_events, tool_events)
|
35
37
|
self._waiting_printed = False
|
36
38
|
|
@@ -53,7 +55,9 @@ class RichTerminalReporter(EventHandlerBase):
|
|
53
55
|
model = getattr(event, "model_name", None)
|
54
56
|
if not model:
|
55
57
|
model = "?"
|
56
|
-
self.console.print(
|
58
|
+
self.console.print(
|
59
|
+
f"[bold cyan]Waiting for {provider} (model: {model})...[/bold cyan]", end=""
|
60
|
+
)
|
57
61
|
|
58
62
|
def on_ResponseReceived(self, event):
|
59
63
|
parts = event.parts if hasattr(event, "parts") else None
|
@@ -70,7 +74,7 @@ class RichTerminalReporter(EventHandlerBase):
|
|
70
74
|
"""
|
71
75
|
Clears the entire current line in the terminal and returns the cursor to column 1.
|
72
76
|
"""
|
73
|
-
sys.stdout.write(
|
77
|
+
sys.stdout.write("\033[2K\r")
|
74
78
|
sys.stdout.flush()
|
75
79
|
|
76
80
|
def on_RequestFinished(self, event):
|
@@ -118,9 +122,15 @@ class RichTerminalReporter(EventHandlerBase):
|
|
118
122
|
action = getattr(event, "action", None)
|
119
123
|
tool = getattr(event, "tool", None)
|
120
124
|
context = getattr(event, "context", None)
|
121
|
-
if
|
125
|
+
if (
|
126
|
+
subtype == ReportSubtype.ERROR
|
127
|
+
and msg
|
128
|
+
and "[SECURITY] Path access denied" in msg
|
129
|
+
):
|
122
130
|
# Highlight security errors with a distinct style
|
123
|
-
self.console.print(
|
131
|
+
self.console.print(
|
132
|
+
Panel(f"{msg}", title="[red]SECURITY VIOLATION[/red]", style="bold red")
|
133
|
+
)
|
124
134
|
self.console.file.flush()
|
125
135
|
return
|
126
136
|
|
@@ -135,7 +145,11 @@ class RichTerminalReporter(EventHandlerBase):
|
|
135
145
|
getattr(ReportAction, "WRITE", None),
|
136
146
|
getattr(ReportAction, "DELETE", None),
|
137
147
|
)
|
138
|
-
style =
|
148
|
+
style = (
|
149
|
+
"orange1"
|
150
|
+
if getattr(event, "action", None) in modification_actions
|
151
|
+
else "cyan"
|
152
|
+
)
|
139
153
|
self.console.print(Text(msg, style=style), end="")
|
140
154
|
self.console.file.flush()
|
141
155
|
elif subtype in (
|
@@ -11,12 +11,19 @@ import time
|
|
11
11
|
|
12
12
|
|
13
13
|
class PromptHandler:
|
14
|
-
def __init__(
|
14
|
+
def __init__(
|
15
|
+
self,
|
16
|
+
args,
|
17
|
+
provider_instance,
|
18
|
+
llm_driver_config,
|
19
|
+
role=None,
|
20
|
+
allowed_permissions=None,
|
21
|
+
):
|
15
22
|
self.args = args
|
16
23
|
self.provider_instance = provider_instance
|
17
24
|
self.llm_driver_config = llm_driver_config
|
18
25
|
self.role = role
|
19
|
-
|
26
|
+
# Instantiate agent together with prompt handler using the shared helper
|
20
27
|
self.agent, self.generic_handler = setup_agent_and_prompt_handler(
|
21
28
|
args=args,
|
22
29
|
provider_instance=provider_instance,
|
@@ -24,7 +31,6 @@ class PromptHandler:
|
|
24
31
|
role=role,
|
25
32
|
verbose_tools=getattr(args, "verbose_tools", False),
|
26
33
|
verbose_agent=getattr(args, "verbose_agent", False),
|
27
|
-
|
28
34
|
allowed_permissions=allowed_permissions,
|
29
35
|
profile=getattr(args, "profile", None),
|
30
36
|
)
|
@@ -43,6 +49,7 @@ class PromptHandler:
|
|
43
49
|
"[yellow]Warning: Some characters in your input were not valid UTF-8 and have been replaced.[/yellow]"
|
44
50
|
)
|
45
51
|
import time
|
52
|
+
|
46
53
|
try:
|
47
54
|
start_time = time.time()
|
48
55
|
self.generic_handler.handle_prompt(
|
@@ -67,13 +74,15 @@ class PromptHandler:
|
|
67
74
|
import sys
|
68
75
|
from janito.formatting_token import print_token_message_summary
|
69
76
|
from janito.perf_singleton import performance_collector
|
77
|
+
|
70
78
|
usage = performance_collector.get_last_request_usage()
|
71
79
|
# If running in stdin mode, do not print token usage
|
72
80
|
if sys.stdin.isatty():
|
73
|
-
print_token_message_summary(
|
81
|
+
print_token_message_summary(
|
82
|
+
shared_console, msg_count=1, usage=usage, elapsed=elapsed
|
83
|
+
)
|
74
84
|
self._cleanup_driver_and_console()
|
75
85
|
|
76
|
-
|
77
86
|
def _cleanup_driver_and_console(self):
|
78
87
|
if hasattr(self.agent, "join_driver"):
|
79
88
|
if (
|
janito/cli/verbose_output.py
CHANGED
@@ -19,7 +19,11 @@ def print_verbose_header(agent, args):
|
|
19
19
|
parts = [
|
20
20
|
f"Janito {VERSION}",
|
21
21
|
f"Provider: {agent.llm_provider.__class__.__name__}",
|
22
|
-
(
|
22
|
+
(
|
23
|
+
f"Model: {agent.llm_provider.driver_config.extra.get('azure_deployment_name', agent.llm_provider.driver_config.model)}{role_part}"
|
24
|
+
if agent.llm_provider.__class__.__name__ == "AzureOpenAIProvider"
|
25
|
+
else f"Model: {getattr(agent.llm_provider.driver_config, 'model', '-')}{role_part}"
|
26
|
+
),
|
23
27
|
f"Driver: {agent.llm_provider.__class__.__module__.split('.')[-2] if len(agent.llm_provider.__class__.__module__.split('.')) > 1 else agent.llm_provider.__class__.__name__}",
|
24
28
|
]
|
25
29
|
if hasattr(args, "think") and args.think:
|
janito/config_manager.py
CHANGED
@@ -40,11 +40,15 @@ class ConfigManager:
|
|
40
40
|
try:
|
41
41
|
from janito.tools.permissions_parse import parse_permissions_string
|
42
42
|
from janito.tools.permissions import set_global_allowed_permissions
|
43
|
+
|
43
44
|
perms = parse_permissions_string(perm_str)
|
44
45
|
set_global_allowed_permissions(perms)
|
45
46
|
except Exception as e:
|
46
47
|
print(f"Warning: Failed to apply tool_permissions from config: {e}")
|
47
48
|
|
49
|
+
# Load disabled tools from config - skip during startup to avoid circular imports
|
50
|
+
# This will be handled by the CLI when needed
|
51
|
+
|
48
52
|
def _load_file_config(self):
|
49
53
|
if self.config_path.exists():
|
50
54
|
with open(self.config_path, "r", encoding="utf-8") as f:
|
@@ -1,14 +1,6 @@
|
|
1
1
|
from janito.drivers.openai.driver import OpenAIModelDriver
|
2
2
|
|
3
|
-
|
4
|
-
try:
|
5
|
-
from openai import AzureOpenAI
|
6
|
-
|
7
|
-
DRIVER_AVAILABLE = True
|
8
|
-
DRIVER_UNAVAILABLE_REASON = None
|
9
|
-
except ImportError:
|
10
|
-
DRIVER_AVAILABLE = False
|
11
|
-
DRIVER_UNAVAILABLE_REASON = "Missing dependency: openai (pip install openai)"
|
3
|
+
from openai import AzureOpenAI
|
12
4
|
|
13
5
|
from janito.llm.driver_config import LLMDriverConfig
|
14
6
|
|
@@ -16,30 +8,22 @@ from janito.llm.driver_config import LLMDriverConfig
|
|
16
8
|
class AzureOpenAIModelDriver(OpenAIModelDriver):
|
17
9
|
def start(self, *args, **kwargs):
|
18
10
|
# Ensure azure_deployment_name is set before starting
|
19
|
-
config = getattr(self,
|
11
|
+
config = getattr(self, "config", None)
|
20
12
|
deployment_name = None
|
21
|
-
if config and hasattr(config,
|
22
|
-
deployment_name = config.extra.get(
|
13
|
+
if config and hasattr(config, "extra"):
|
14
|
+
deployment_name = config.extra.get("azure_deployment_name")
|
23
15
|
if not deployment_name:
|
24
|
-
raise RuntimeError(
|
16
|
+
raise RuntimeError(
|
17
|
+
"AzureOpenAIModelDriver requires 'azure_deployment_name' to be set in config.extra['azure_deployment_name'] before starting."
|
18
|
+
)
|
25
19
|
# Call parent start if exists
|
26
|
-
if hasattr(super(),
|
20
|
+
if hasattr(super(), "start"):
|
27
21
|
return super().start(*args, **kwargs)
|
28
22
|
|
29
|
-
available = DRIVER_AVAILABLE
|
30
|
-
unavailable_reason = DRIVER_UNAVAILABLE_REASON
|
31
|
-
|
32
|
-
@classmethod
|
33
|
-
def is_available(cls):
|
34
|
-
return cls.available
|
35
|
-
|
36
23
|
required_config = {"base_url"} # Update key as used in your config logic
|
37
24
|
|
38
25
|
def __init__(self, tools_adapter=None, provider_name=None):
|
39
|
-
|
40
|
-
raise ImportError(
|
41
|
-
f"AzureOpenAIModelDriver unavailable: {self.unavailable_reason}"
|
42
|
-
)
|
26
|
+
|
43
27
|
# Ensure proper parent initialization
|
44
28
|
super().__init__(tools_adapter=tools_adapter, provider_name=provider_name)
|
45
29
|
self.azure_endpoint = None
|
@@ -52,25 +36,35 @@ class AzureOpenAIModelDriver(OpenAIModelDriver):
|
|
52
36
|
Also ensures tool schemas are included if tools_adapter is present.
|
53
37
|
"""
|
54
38
|
api_kwargs = super()._prepare_api_kwargs(config, conversation)
|
55
|
-
deployment_name =
|
39
|
+
deployment_name = (
|
40
|
+
config.extra.get("azure_deployment_name")
|
41
|
+
if hasattr(config, "extra")
|
42
|
+
else None
|
43
|
+
)
|
56
44
|
if deployment_name:
|
57
45
|
api_kwargs["model"] = deployment_name
|
58
46
|
# Patch: Ensure tools are included for Azure as for OpenAI
|
59
47
|
if self.tools_adapter:
|
60
48
|
try:
|
61
|
-
from janito.providers.openai.schema_generator import
|
49
|
+
from janito.providers.openai.schema_generator import (
|
50
|
+
generate_tool_schemas,
|
51
|
+
)
|
52
|
+
|
62
53
|
tool_classes = self.tools_adapter.get_tool_classes()
|
63
54
|
tool_schemas = generate_tool_schemas(tool_classes)
|
64
55
|
api_kwargs["tools"] = tool_schemas
|
65
56
|
except Exception as e:
|
66
57
|
api_kwargs["tools"] = []
|
67
58
|
if hasattr(config, "verbose_api") and config.verbose_api:
|
68
|
-
print(
|
59
|
+
print(
|
60
|
+
f"[AzureOpenAIModelDriver] Tool schema generation failed: {e}"
|
61
|
+
)
|
69
62
|
return api_kwargs
|
70
63
|
|
71
64
|
def _instantiate_openai_client(self, config):
|
72
65
|
try:
|
73
66
|
from openai import AzureOpenAI
|
67
|
+
|
74
68
|
api_key_display = str(config.api_key)
|
75
69
|
if api_key_display and len(api_key_display) > 8:
|
76
70
|
api_key_display = api_key_display[:4] + "..." + api_key_display[-4:]
|
@@ -83,8 +77,11 @@ class AzureOpenAIModelDriver(OpenAIModelDriver):
|
|
83
77
|
client = AzureOpenAI(**client_kwargs)
|
84
78
|
return client
|
85
79
|
except Exception as e:
|
86
|
-
print(
|
80
|
+
print(
|
81
|
+
f"[ERROR] Exception during AzureOpenAI client instantiation: {e}",
|
82
|
+
flush=True,
|
83
|
+
)
|
87
84
|
import traceback
|
85
|
+
|
88
86
|
print(traceback.format_exc(), flush=True)
|
89
87
|
raise
|
90
|
-
|
janito/drivers/openai/driver.py
CHANGED
@@ -12,15 +12,7 @@ from janito.llm.driver_input import DriverInput
|
|
12
12
|
from janito.driver_events import RequestFinished, RequestStatus, RateLimitRetry
|
13
13
|
from janito.llm.message_parts import TextMessagePart, FunctionCallMessagePart
|
14
14
|
|
15
|
-
|
16
|
-
try:
|
17
|
-
import openai
|
18
|
-
|
19
|
-
DRIVER_AVAILABLE = True
|
20
|
-
DRIVER_UNAVAILABLE_REASON = None
|
21
|
-
except ImportError:
|
22
|
-
DRIVER_AVAILABLE = False
|
23
|
-
DRIVER_UNAVAILABLE_REASON = "Missing dependency: openai (pip install openai)"
|
15
|
+
import openai
|
24
16
|
|
25
17
|
|
26
18
|
class OpenAIModelDriver(LLMDriver):
|
@@ -33,8 +25,6 @@ class OpenAIModelDriver(LLMDriver):
|
|
33
25
|
"""
|
34
26
|
OpenAI LLM driver (threaded, queue-based, stateless). Uses input/output queues accessible via instance attributes.
|
35
27
|
"""
|
36
|
-
available = DRIVER_AVAILABLE
|
37
|
-
unavailable_reason = DRIVER_UNAVAILABLE_REASON
|
38
28
|
|
39
29
|
def __init__(self, tools_adapter=None, provider_name=None):
|
40
30
|
super().__init__(tools_adapter=tools_adapter, provider_name=provider_name)
|
@@ -84,13 +74,17 @@ class OpenAIModelDriver(LLMDriver):
|
|
84
74
|
api_kwargs[p] = v
|
85
75
|
api_kwargs["messages"] = conversation
|
86
76
|
api_kwargs["stream"] = False
|
77
|
+
if self.tools_adapter and self.tools_adapter.get_tool_classes():
|
78
|
+
api_kwargs["parallel_tool_calls"] = True
|
87
79
|
return api_kwargs
|
88
80
|
|
89
81
|
def _call_api(self, driver_input: DriverInput):
|
90
82
|
"""Call the OpenAI-compatible chat completion endpoint with retry and error handling."""
|
91
83
|
cancel_event = getattr(driver_input, "cancel_event", None)
|
92
84
|
config = driver_input.config
|
93
|
-
conversation = self.convert_history_to_api_messages(
|
85
|
+
conversation = self.convert_history_to_api_messages(
|
86
|
+
driver_input.conversation_history
|
87
|
+
)
|
94
88
|
request_id = getattr(config, "request_id", None)
|
95
89
|
self._print_api_call_start(config)
|
96
90
|
client = self._instantiate_openai_client(config)
|
@@ -108,14 +102,18 @@ class OpenAIModelDriver(LLMDriver):
|
|
108
102
|
self._handle_api_success(config, result, request_id)
|
109
103
|
return result
|
110
104
|
except Exception as e:
|
111
|
-
if self._handle_api_exception(
|
105
|
+
if self._handle_api_exception(
|
106
|
+
e, config, api_kwargs, attempt, max_retries, request_id
|
107
|
+
):
|
112
108
|
attempt += 1
|
113
109
|
continue
|
114
110
|
raise
|
115
111
|
|
116
112
|
def _print_api_call_start(self, config):
|
117
113
|
if getattr(config, "verbose_api", False):
|
118
|
-
tool_adapter_name =
|
114
|
+
tool_adapter_name = (
|
115
|
+
type(self.tools_adapter).__name__ if self.tools_adapter else None
|
116
|
+
)
|
119
117
|
tool_names = []
|
120
118
|
if self.tools_adapter and hasattr(self.tools_adapter, "list_tools"):
|
121
119
|
try:
|
@@ -156,17 +154,21 @@ class OpenAIModelDriver(LLMDriver):
|
|
156
154
|
print("[OpenAI] API RESPONSE:", flush=True)
|
157
155
|
pretty.pprint(result)
|
158
156
|
|
159
|
-
def _handle_api_exception(
|
157
|
+
def _handle_api_exception(
|
158
|
+
self, e, config, api_kwargs, attempt, max_retries, request_id
|
159
|
+
):
|
160
160
|
status_code = getattr(e, "status_code", None)
|
161
161
|
err_str = str(e)
|
162
162
|
lower_err = err_str.lower()
|
163
163
|
is_insufficient_quota = (
|
164
|
-
"insufficient_quota" in lower_err
|
164
|
+
"insufficient_quota" in lower_err
|
165
|
+
or "exceeded your current quota" in lower_err
|
165
166
|
)
|
166
167
|
is_rate_limit = (
|
167
|
-
|
168
|
-
|
169
|
-
|
168
|
+
status_code == 429
|
169
|
+
or "error code: 429" in lower_err
|
170
|
+
or "resource_exhausted" in lower_err
|
171
|
+
) and not is_insufficient_quota
|
170
172
|
if not is_rate_limit or attempt > max_retries:
|
171
173
|
self._handle_fatal_exception(e, config, api_kwargs)
|
172
174
|
retry_delay = self._extract_retry_delay_seconds(e)
|
@@ -189,7 +191,9 @@ class OpenAIModelDriver(LLMDriver):
|
|
189
191
|
)
|
190
192
|
start_wait = time.time()
|
191
193
|
while time.time() - start_wait < retry_delay:
|
192
|
-
if self._check_cancel(
|
194
|
+
if self._check_cancel(
|
195
|
+
getattr(config, "cancel_event", None), request_id, before_call=False
|
196
|
+
):
|
193
197
|
return False
|
194
198
|
time.sleep(0.1)
|
195
199
|
return True
|
@@ -208,7 +212,9 @@ class OpenAIModelDriver(LLMDriver):
|
|
208
212
|
else:
|
209
213
|
payload = str(exception)
|
210
214
|
# Look for 'retryDelay': '41s' or similar
|
211
|
-
m = re.search(
|
215
|
+
m = re.search(
|
216
|
+
r"retryDelay['\"]?\s*[:=]\s*['\"]?(\d+(?:\.\d+)?)(s)?", payload
|
217
|
+
)
|
212
218
|
if m:
|
213
219
|
return float(m.group(1))
|
214
220
|
# Fallback: generic number of seconds in the message
|
@@ -249,13 +255,17 @@ class OpenAIModelDriver(LLMDriver):
|
|
249
255
|
# HTTP debug wrapper
|
250
256
|
if os.environ.get("OPENAI_DEBUG_HTTP", "0") == "1":
|
251
257
|
from http.client import HTTPConnection
|
258
|
+
|
252
259
|
HTTPConnection.debuglevel = 1
|
253
260
|
logging.basicConfig()
|
254
261
|
logging.getLogger().setLevel(logging.DEBUG)
|
255
262
|
requests_log = logging.getLogger("http.client")
|
256
263
|
requests_log.setLevel(logging.DEBUG)
|
257
264
|
requests_log.propagate = True
|
258
|
-
print(
|
265
|
+
print(
|
266
|
+
"[OpenAIModelDriver] HTTP debug enabled via OPENAI_DEBUG_HTTP=1",
|
267
|
+
flush=True,
|
268
|
+
)
|
259
269
|
|
260
270
|
client = openai.OpenAI(**client_kwargs)
|
261
271
|
return client
|
@@ -377,26 +387,32 @@ class OpenAIModelDriver(LLMDriver):
|
|
377
387
|
results = [content]
|
378
388
|
for result in results:
|
379
389
|
if isinstance(result, dict):
|
380
|
-
api_messages.append(
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
390
|
+
api_messages.append(
|
391
|
+
{
|
392
|
+
"role": "tool",
|
393
|
+
"content": result.get("content", ""),
|
394
|
+
"name": result.get("name", ""),
|
395
|
+
"tool_call_id": result.get("tool_call_id", ""),
|
396
|
+
}
|
397
|
+
)
|
386
398
|
else:
|
387
|
-
api_messages.append(
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
399
|
+
api_messages.append(
|
400
|
+
{
|
401
|
+
"role": "tool",
|
402
|
+
"content": str(result),
|
403
|
+
"name": "",
|
404
|
+
"tool_call_id": "",
|
405
|
+
}
|
406
|
+
)
|
393
407
|
|
394
408
|
def _handle_tool_calls(self, api_messages, content):
|
395
409
|
try:
|
396
410
|
tool_calls = json.loads(content) if isinstance(content, str) else content
|
397
411
|
except Exception:
|
398
412
|
tool_calls = []
|
399
|
-
api_messages.append(
|
413
|
+
api_messages.append(
|
414
|
+
{"role": "assistant", "content": "", "tool_calls": tool_calls}
|
415
|
+
)
|
400
416
|
|
401
417
|
def _handle_other_roles(self, api_messages, msg, role, content):
|
402
418
|
if role == "function":
|
@@ -433,4 +449,4 @@ class OpenAIModelDriver(LLMDriver):
|
|
433
449
|
)
|
434
450
|
)
|
435
451
|
# Extend here for other message part types if needed
|
436
|
-
return parts
|
452
|
+
return parts
|