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.
Files changed (116) hide show
  1. janito/__init__.py +0 -1
  2. janito/__main__.py +0 -1
  3. janito/_version.py +0 -3
  4. janito/agent/setup_agent.py +77 -10
  5. janito/agent/templates/profiles/{system_prompt_template_plain_software_developer.txt.j2 → system_prompt_template_Developer_with_Python_Tools.txt.j2} +5 -1
  6. janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +3 -12
  7. janito/cli/__init__.py +0 -1
  8. janito/cli/chat_mode/bindings.py +1 -1
  9. janito/cli/chat_mode/chat_entry.py +0 -2
  10. janito/cli/chat_mode/prompt_style.py +0 -3
  11. janito/cli/chat_mode/script_runner.py +9 -5
  12. janito/cli/chat_mode/session.py +80 -35
  13. janito/cli/chat_mode/session_profile_select.py +61 -52
  14. janito/cli/chat_mode/shell/commands/__init__.py +1 -5
  15. janito/cli/chat_mode/shell/commands/_priv_check.py +1 -0
  16. janito/cli/chat_mode/shell/commands/bang.py +10 -3
  17. janito/cli/chat_mode/shell/commands/conversation_restart.py +24 -7
  18. janito/cli/chat_mode/shell/commands/execute.py +22 -7
  19. janito/cli/chat_mode/shell/commands/help.py +4 -1
  20. janito/cli/chat_mode/shell/commands/model.py +13 -5
  21. janito/cli/chat_mode/shell/commands/privileges.py +21 -0
  22. janito/cli/chat_mode/shell/commands/prompt.py +0 -2
  23. janito/cli/chat_mode/shell/commands/read.py +22 -5
  24. janito/cli/chat_mode/shell/commands/tools.py +15 -4
  25. janito/cli/chat_mode/shell/commands/write.py +22 -5
  26. janito/cli/chat_mode/shell/input_history.py +3 -1
  27. janito/cli/chat_mode/shell/session/manager.py +0 -2
  28. janito/cli/chat_mode/toolbar.py +25 -19
  29. janito/cli/cli_commands/list_models.py +1 -1
  30. janito/cli/cli_commands/list_providers.py +1 -0
  31. janito/cli/cli_commands/list_tools.py +35 -7
  32. janito/cli/cli_commands/model_utils.py +5 -3
  33. janito/cli/cli_commands/show_config.py +12 -0
  34. janito/cli/cli_commands/show_system_prompt.py +23 -9
  35. janito/cli/config.py +0 -13
  36. janito/cli/core/getters.py +2 -0
  37. janito/cli/core/runner.py +25 -8
  38. janito/cli/core/setters.py +13 -76
  39. janito/cli/main_cli.py +9 -25
  40. janito/cli/prompt_core.py +19 -18
  41. janito/cli/prompt_setup.py +6 -3
  42. janito/cli/rich_terminal_reporter.py +19 -5
  43. janito/cli/single_shot_mode/handler.py +14 -5
  44. janito/cli/verbose_output.py +5 -1
  45. janito/config_manager.py +4 -0
  46. janito/drivers/azure_openai/driver.py +27 -30
  47. janito/drivers/openai/driver.py +52 -36
  48. janito/formatting_token.py +12 -4
  49. janito/llm/agent.py +15 -6
  50. janito/llm/driver.py +1 -0
  51. janito/provider_registry.py +31 -70
  52. janito/providers/__init__.py +1 -0
  53. janito/providers/anthropic/model_info.py +0 -1
  54. janito/providers/anthropic/provider.py +9 -14
  55. janito/providers/azure_openai/provider.py +9 -4
  56. janito/providers/deepseek/provider.py +5 -4
  57. janito/providers/google/model_info.py +4 -2
  58. janito/providers/google/provider.py +11 -5
  59. janito/providers/groq/__init__.py +1 -0
  60. janito/providers/groq/model_info.py +46 -0
  61. janito/providers/groq/provider.py +76 -0
  62. janito/providers/moonshotai/provider.py +11 -4
  63. janito/providers/openai/model_info.py +0 -1
  64. janito/providers/openai/provider.py +6 -7
  65. janito/tools/__init__.py +2 -0
  66. janito/tools/adapters/local/__init__.py +2 -1
  67. janito/tools/adapters/local/adapter.py +21 -4
  68. janito/tools/adapters/local/ask_user.py +1 -0
  69. janito/tools/adapters/local/copy_file.py +1 -0
  70. janito/tools/adapters/local/create_directory.py +1 -0
  71. janito/tools/adapters/local/create_file.py +1 -0
  72. janito/tools/adapters/local/delete_text_in_file.py +2 -1
  73. janito/tools/adapters/local/fetch_url.py +1 -0
  74. janito/tools/adapters/local/find_files.py +7 -6
  75. janito/tools/adapters/local/get_file_outline/core.py +1 -0
  76. janito/tools/adapters/local/get_file_outline/java_outline.py +22 -15
  77. janito/tools/adapters/local/get_file_outline/search_outline.py +1 -0
  78. janito/tools/adapters/local/move_file.py +1 -0
  79. janito/tools/adapters/local/open_html_in_browser.py +15 -5
  80. janito/tools/adapters/local/open_url.py +1 -0
  81. janito/tools/adapters/local/python_code_run.py +1 -0
  82. janito/tools/adapters/local/python_command_run.py +1 -0
  83. janito/tools/adapters/local/python_file_run.py +1 -0
  84. janito/tools/adapters/local/read_files.py +19 -4
  85. janito/tools/adapters/local/remove_directory.py +1 -0
  86. janito/tools/adapters/local/remove_file.py +1 -0
  87. janito/tools/adapters/local/replace_text_in_file.py +4 -3
  88. janito/tools/adapters/local/run_bash_command.py +1 -0
  89. janito/tools/adapters/local/run_powershell_command.py +1 -0
  90. janito/tools/adapters/local/search_text/core.py +18 -17
  91. janito/tools/adapters/local/search_text/match_lines.py +5 -5
  92. janito/tools/adapters/local/search_text/pattern_utils.py +1 -1
  93. janito/tools/adapters/local/search_text/traverse_directory.py +7 -7
  94. janito/tools/adapters/local/validate_file_syntax/core.py +1 -1
  95. janito/tools/adapters/local/validate_file_syntax/html_validator.py +8 -1
  96. janito/tools/disabled_tools.py +68 -0
  97. janito/tools/path_security.py +18 -11
  98. janito/tools/permissions.py +6 -0
  99. janito/tools/permissions_parse.py +4 -3
  100. janito/tools/tool_base.py +11 -5
  101. janito/tools/tool_use_tracker.py +1 -4
  102. janito/tools/tool_utils.py +1 -1
  103. janito/tools/tools_adapter.py +57 -25
  104. {janito-2.7.0.dist-info → janito-2.8.0.dist-info}/METADATA +4 -12
  105. janito-2.8.0.dist-info/RECORD +202 -0
  106. janito/cli/chat_mode/shell/commands/livelogs.py +0 -49
  107. janito/drivers/mistralai/driver.py +0 -41
  108. janito/providers/mistralai/model_info.py +0 -37
  109. janito/providers/mistralai/provider.py +0 -72
  110. janito/providers/provider_static_info.py +0 -21
  111. janito-2.7.0.dist-info/RECORD +0 -202
  112. /janito/agent/templates/profiles/{system_prompt_template_assistant.txt.j2 → system_prompt_template_model_conversation_without_tools_or_context.txt.j2} +0 -0
  113. {janito-2.7.0.dist-info → janito-2.8.0.dist-info}/WHEEL +0 -0
  114. {janito-2.7.0.dist-info → janito-2.8.0.dist-info}/entry_points.txt +0 -0
  115. {janito-2.7.0.dist-info → janito-2.8.0.dist-info}/licenses/LICENSE +0 -0
  116. {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": "[PROVIDER_NAME.]KEY=VALUE", "help": "Set a config key"}),
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
- ["--web"],
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 = ' '.join(self.args.user_prompt) + ' ' + stdin_input
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(self.args, "profile", None):
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 RequestStarted, RequestFinished, RequestStatus, RateLimitRetry
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 (RequestStatus.EMPTY_RESPONSE, RequestStatus.TIMEOUT):
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(f"[yellow]Rate limited. Waiting {inner_event.retry_delay:.0f}s before retry (attempt {inner_event.attempt}).[yellow]")
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
@@ -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(f"[bold cyan]Waiting for {provider} (model: {model})...[/bold cyan]", end="")
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('\033[2K\r')
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 subtype == ReportSubtype.ERROR and msg and "[SECURITY] Path access denied" in msg:
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(Panel(f"{msg}", title="[red]SECURITY VIOLATION[/red]", style="bold red"))
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 = "orange1" if getattr(event, "action", None) in modification_actions else "cyan"
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__(self, args, provider_instance, llm_driver_config, role=None, allowed_permissions=None):
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
- # Instantiate agent together with prompt handler using the shared helper
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(shared_console, msg_count=1, usage=usage, elapsed=elapsed)
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 (
@@ -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
- (f"Model: {agent.llm_provider.driver_config.extra.get('azure_deployment_name', agent.llm_provider.driver_config.model)}{role_part}" if agent.llm_provider.__class__.__name__ == 'AzureOpenAIProvider' else f"Model: {getattr(agent.llm_provider.driver_config, 'model', '-')}{role_part}"),
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
- # Safe import of AzureOpenAI SDK
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, 'config', None)
11
+ config = getattr(self, "config", None)
20
12
  deployment_name = None
21
- if config and hasattr(config, 'extra'):
22
- deployment_name = config.extra.get('azure_deployment_name')
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("AzureOpenAIModelDriver requires 'azure_deployment_name' to be set in config.extra['azure_deployment_name'] before starting.")
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(), 'start'):
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
- if not self.available:
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 = config.extra.get("azure_deployment_name") if hasattr(config, "extra") else None
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 generate_tool_schemas
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(f"[AzureOpenAIModelDriver] Tool schema generation failed: {e}")
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(f"[ERROR] Exception during AzureOpenAI client instantiation: {e}", flush=True)
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
-
@@ -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
- # Safe import of openai SDK
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(driver_input.conversation_history)
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(e, config, api_kwargs, attempt, max_retries, request_id):
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 = type(self.tools_adapter).__name__ if self.tools_adapter else None
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(self, e, config, api_kwargs, attempt, max_retries, request_id):
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 or "exceeded your current 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
- (status_code == 429 or "error code: 429" in lower_err or "resource_exhausted" in lower_err)
168
- and not is_insufficient_quota
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(getattr(config, "cancel_event", None), request_id, before_call=False):
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(r"retryDelay['\"]?\s*[:=]\s*['\"]?(\d+(?:\.\d+)?)(s)?", payload)
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("[OpenAIModelDriver] HTTP debug enabled via OPENAI_DEBUG_HTTP=1", flush=True)
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
- "role": "tool",
382
- "content": result.get("content", ""),
383
- "name": result.get("name", ""),
384
- "tool_call_id": result.get("tool_call_id", ""),
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
- "role": "tool",
389
- "content": str(result),
390
- "name": "",
391
- "tool_call_id": "",
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({"role": "assistant", "content": "", "tool_calls": tool_calls})
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