janito 2.3.1__py3-none-any.whl → 2.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- janito/__init__.py +1 -1
- janito/_version.py +57 -0
- janito/agent/setup_agent.py +95 -21
- janito/agent/templates/profiles/system_prompt_template_assistant.txt.j2 +1 -0
- janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +44 -0
- janito/cli/chat_mode/bindings.py +21 -2
- janito/cli/chat_mode/chat_entry.py +2 -3
- janito/cli/chat_mode/prompt_style.py +5 -0
- janito/cli/chat_mode/script_runner.py +153 -0
- janito/cli/chat_mode/session.py +128 -122
- janito/cli/chat_mode/session_profile_select.py +80 -0
- janito/cli/chat_mode/shell/commands/__init__.py +19 -9
- janito/cli/chat_mode/shell/commands/_priv_check.py +5 -0
- janito/cli/chat_mode/shell/commands/bang.py +36 -0
- janito/cli/chat_mode/shell/commands/conversation_restart.py +31 -24
- janito/cli/chat_mode/shell/commands/execute.py +42 -0
- janito/cli/chat_mode/shell/commands/help.py +7 -4
- janito/cli/chat_mode/shell/commands/model.py +28 -0
- janito/cli/chat_mode/shell/commands/prompt.py +0 -8
- janito/cli/chat_mode/shell/commands/read.py +37 -0
- janito/cli/chat_mode/shell/commands/tools.py +45 -18
- janito/cli/chat_mode/shell/commands/write.py +37 -0
- janito/cli/chat_mode/shell/commands.bak.zip +0 -0
- janito/cli/chat_mode/shell/input_history.py +1 -1
- janito/cli/chat_mode/shell/session/manager.py +0 -68
- janito/cli/chat_mode/shell/session.bak.zip +0 -0
- janito/cli/chat_mode/toolbar.py +44 -27
- janito/cli/cli_commands/list_tools.py +44 -11
- janito/cli/cli_commands/model_utils.py +95 -95
- janito/cli/cli_commands/show_system_prompt.py +57 -14
- janito/cli/config.py +5 -6
- janito/cli/core/getters.py +33 -33
- janito/cli/core/runner.py +27 -20
- janito/cli/core/setters.py +10 -1
- janito/cli/main_cli.py +40 -10
- janito/cli/prompt_core.py +18 -2
- janito/cli/prompt_setup.py +56 -0
- janito/cli/rich_terminal_reporter.py +21 -6
- janito/cli/single_shot_mode/handler.py +24 -77
- janito/cli/verbose_output.py +1 -1
- janito/config_manager.py +125 -112
- janito/drivers/dashscope.bak.zip +0 -0
- janito/drivers/driver_registry.py +0 -2
- janito/drivers/openai/README.md +20 -0
- janito/drivers/openai_responses.bak.zip +0 -0
- janito/event_bus/event.py +2 -2
- janito/formatting_token.py +7 -6
- janito/i18n/pt.py +0 -1
- janito/llm/README.md +23 -0
- janito/llm/agent.py +80 -16
- janito/llm/auth.py +63 -63
- janito/llm/driver.py +8 -0
- janito/provider_registry.py +178 -176
- janito/providers/__init__.py +0 -2
- janito/providers/azure_openai/model_info.py +16 -16
- janito/providers/dashscope.bak.zip +0 -0
- janito/providers/provider_static_info.py +0 -3
- janito/providers/registry.py +26 -26
- janito/shell.bak.zip +0 -0
- janito/tools/DOCSTRING_STANDARD.txt +33 -0
- janito/tools/README.md +3 -0
- janito/tools/__init__.py +20 -6
- janito/tools/adapters/local/__init__.py +65 -62
- janito/tools/adapters/local/adapter.py +18 -35
- janito/tools/adapters/local/ask_user.py +3 -4
- janito/tools/adapters/local/copy_file.py +2 -2
- janito/tools/adapters/local/create_directory.py +2 -2
- janito/tools/adapters/local/create_file.py +2 -2
- janito/tools/adapters/local/delete_text_in_file.py +2 -2
- janito/tools/adapters/local/fetch_url.py +2 -2
- janito/tools/adapters/local/find_files.py +2 -1
- janito/tools/adapters/local/get_file_outline/core.py +2 -2
- janito/tools/adapters/local/get_file_outline/search_outline.py +2 -2
- janito/tools/adapters/local/move_file.py +2 -2
- janito/tools/adapters/local/open_html_in_browser.py +2 -1
- janito/tools/adapters/local/open_url.py +2 -2
- janito/tools/adapters/local/python_code_run.py +3 -3
- janito/tools/adapters/local/python_command_run.py +3 -3
- janito/tools/adapters/local/python_file_run.py +3 -3
- janito/tools/adapters/local/remove_directory.py +2 -2
- janito/tools/adapters/local/remove_file.py +2 -2
- janito/tools/adapters/local/replace_text_in_file.py +2 -2
- janito/tools/adapters/local/run_bash_command.py +3 -3
- janito/tools/adapters/local/run_powershell_command.py +3 -3
- janito/tools/adapters/local/search_text/core.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/core.py +3 -3
- janito/tools/adapters/local/view_file.py +2 -1
- janito/tools/outline_file.bak.zip +0 -0
- janito/tools/permissions.py +45 -0
- janito/tools/permissions_parse.py +12 -0
- janito/tools/tool_base.py +14 -11
- janito/tools/tool_utils.py +4 -6
- janito/tools/tools_adapter.py +25 -20
- {janito-2.3.1.dist-info → janito-2.5.0.dist-info}/METADATA +46 -24
- {janito-2.3.1.dist-info → janito-2.5.0.dist-info}/RECORD +99 -82
- janito/agent/templates/profiles/system_prompt_template_base_pt.txt.j2 +0 -13
- janito/agent/templates/profiles/system_prompt_template_main.txt.j2 +0 -37
- janito/cli/chat_mode/shell/commands/edit.py +0 -25
- janito/cli/chat_mode/shell/commands/exec.py +0 -27
- janito/cli/chat_mode/shell/commands/termweb_log.py +0 -92
- janito/cli/termweb_starter.py +0 -122
- janito/termweb/app.py +0 -95
- janito/version.py +0 -4
- {janito-2.3.1.dist-info → janito-2.5.0.dist-info}/WHEEL +0 -0
- {janito-2.3.1.dist-info → janito-2.5.0.dist-info}/entry_points.txt +0 -0
- {janito-2.3.1.dist-info → janito-2.5.0.dist-info}/licenses/LICENSE +0 -0
- {janito-2.3.1.dist-info → janito-2.5.0.dist-info}/top_level.txt +0 -0
janito/llm/agent.py
CHANGED
@@ -94,6 +94,21 @@ class LLMAgent:
|
|
94
94
|
autoescape=select_autoescape(),
|
95
95
|
)
|
96
96
|
template = env.get_template(Path(self.system_prompt_template).name)
|
97
|
+
# Refresh allowed_permissions in context before rendering
|
98
|
+
from janito.tools.permissions import get_global_allowed_permissions
|
99
|
+
from janito.tools.tool_base import ToolPermissions
|
100
|
+
perms = get_global_allowed_permissions()
|
101
|
+
if isinstance(perms, ToolPermissions):
|
102
|
+
perm_str = ""
|
103
|
+
if perms.read:
|
104
|
+
perm_str += "r"
|
105
|
+
if perms.write:
|
106
|
+
perm_str += "w"
|
107
|
+
if perms.execute:
|
108
|
+
perm_str += "x"
|
109
|
+
self._template_vars["allowed_permissions"] = perm_str or None
|
110
|
+
else:
|
111
|
+
self._template_vars["allowed_permissions"] = perms
|
97
112
|
self.system_prompt = template.render(**self._template_vars)
|
98
113
|
|
99
114
|
def get_system_prompt(self) -> str:
|
@@ -165,13 +180,10 @@ class LLMAgent:
|
|
165
180
|
if getattr(self, "verbose_agent", False):
|
166
181
|
print("[agent] [DEBUG] Entered _process_next_response")
|
167
182
|
elapsed = 0.0
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
except KeyboardInterrupt:
|
173
|
-
self._handle_keyboard_interrupt()
|
174
|
-
return None, False
|
183
|
+
if getattr(self, "verbose_agent", False):
|
184
|
+
print("[agent] [DEBUG] Waiting for event from output_queue...")
|
185
|
+
# Let KeyboardInterrupt propagate to caller
|
186
|
+
return self._poll_for_event(poll_timeout, max_wait_time)
|
175
187
|
|
176
188
|
def _poll_for_event(self, poll_timeout, max_wait_time):
|
177
189
|
elapsed = 0.0
|
@@ -201,15 +213,6 @@ class LLMAgent:
|
|
201
213
|
]:
|
202
214
|
return (event, False)
|
203
215
|
|
204
|
-
def _handle_keyboard_interrupt(self):
|
205
|
-
if hasattr(self, "input_queue") and self.input_queue is not None:
|
206
|
-
from janito.driver_events import RequestFinished
|
207
|
-
|
208
|
-
cancel_event = RequestFinished(
|
209
|
-
status=RequestStatus.CANCELLED,
|
210
|
-
reason="User interrupted (KeyboardInterrupt)",
|
211
|
-
)
|
212
|
-
self.input_queue.put(cancel_event)
|
213
216
|
|
214
217
|
def _get_event_from_output_queue(self, poll_timeout):
|
215
218
|
try:
|
@@ -302,6 +305,13 @@ class LLMAgent:
|
|
302
305
|
and hasattr(self.driver, "clear_output_queue")
|
303
306
|
):
|
304
307
|
self.driver.clear_output_queue()
|
308
|
+
# Drain input queue before sending new messages
|
309
|
+
if (
|
310
|
+
hasattr(self, "driver")
|
311
|
+
and self.driver
|
312
|
+
and hasattr(self.driver, "clear_input_queue")
|
313
|
+
):
|
314
|
+
self.driver.clear_input_queue()
|
305
315
|
"""
|
306
316
|
Main agent conversation loop supporting function/tool calls and conversation history extension, now as a blocking event-driven loop with event publishing.
|
307
317
|
|
@@ -329,6 +339,7 @@ class LLMAgent:
|
|
329
339
|
try:
|
330
340
|
result, added_tool_results = self._process_next_response()
|
331
341
|
except KeyboardInterrupt:
|
342
|
+
# Propagate the interrupt to the caller, but signal the driver to cancel first
|
332
343
|
cancel_event.set()
|
333
344
|
raise
|
334
345
|
if getattr(self, "verbose_agent", False):
|
@@ -420,6 +431,59 @@ class LLMAgent:
|
|
420
431
|
return self.llm_provider.model_name
|
421
432
|
return "?"
|
422
433
|
|
434
|
+
def reset_driver_config_to_model_defaults(self, model_name: str):
|
435
|
+
"""
|
436
|
+
Reset all driver config fields to the model's defaults for the current provider (overwriting any user customizations).
|
437
|
+
"""
|
438
|
+
provider = self.llm_provider
|
439
|
+
# Find model spec
|
440
|
+
model_spec = None
|
441
|
+
if hasattr(provider, "MODEL_SPECS"):
|
442
|
+
model_spec = provider.MODEL_SPECS.get(model_name)
|
443
|
+
if not model_spec:
|
444
|
+
raise ValueError(f"Model '{model_name}' not found in provider MODEL_SPECS.")
|
445
|
+
# Overwrite all config fields with model defaults
|
446
|
+
config = getattr(provider, "driver_config", None)
|
447
|
+
if config is None:
|
448
|
+
return
|
449
|
+
config.model = model_name
|
450
|
+
# Standard fields, with safe conversion for int fields
|
451
|
+
def safe_int(val):
|
452
|
+
try:
|
453
|
+
if val is None or val == "N/A":
|
454
|
+
return None
|
455
|
+
return int(val)
|
456
|
+
except Exception:
|
457
|
+
return None
|
458
|
+
def safe_float(val):
|
459
|
+
try:
|
460
|
+
if val is None or val == "N/A":
|
461
|
+
return None
|
462
|
+
return float(val)
|
463
|
+
except Exception:
|
464
|
+
return None
|
465
|
+
config.temperature = safe_float(getattr(model_spec, "default_temp", None))
|
466
|
+
config.max_tokens = safe_int(getattr(model_spec, "max_response", None))
|
467
|
+
config.max_completion_tokens = safe_int(getattr(model_spec, "max_cot", None))
|
468
|
+
# Optionally reset other fields to None/defaults
|
469
|
+
config.top_p = None
|
470
|
+
config.presence_penalty = None
|
471
|
+
config.frequency_penalty = None
|
472
|
+
config.stop = None
|
473
|
+
config.reasoning_effort = None
|
474
|
+
# Update driver if present
|
475
|
+
if self.driver is not None:
|
476
|
+
if hasattr(self.driver, "model_name"):
|
477
|
+
self.driver.model_name = model_name
|
478
|
+
if hasattr(self.driver, "config"):
|
479
|
+
self.driver.config = config
|
480
|
+
|
481
|
+
def change_model(self, model_name: str):
|
482
|
+
"""
|
483
|
+
Change the model for the agent's provider and driver config, and update the driver if present.
|
484
|
+
"""
|
485
|
+
self.reset_driver_config_to_model_defaults(model_name)
|
486
|
+
|
423
487
|
def join_driver(self, timeout=None):
|
424
488
|
"""
|
425
489
|
Wait for the driver's background thread to finish. Call this before exiting to avoid daemon thread shutdown errors.
|
janito/llm/auth.py
CHANGED
@@ -1,63 +1,63 @@
|
|
1
|
-
"""
|
2
|
-
LLMAuthManager: Handles authentication credentials for LLM providers, persisted in ~/.janito/auth.json or a custom path.
|
3
|
-
"""
|
4
|
-
|
5
|
-
import os
|
6
|
-
import json
|
7
|
-
from typing import Dict, Optional
|
8
|
-
|
9
|
-
|
10
|
-
class LLMAuthManager:
|
11
|
-
"""
|
12
|
-
Manages authentication tokens, API keys, or credentials for LLM providers.
|
13
|
-
Persists credentials in ~/.janito/auth.json or a custom path.
|
14
|
-
"""
|
15
|
-
|
16
|
-
def __init__(self, auth_file: Optional[str] = None):
|
17
|
-
if auth_file is not None:
|
18
|
-
self._auth_file = os.path.expanduser(auth_file)
|
19
|
-
else:
|
20
|
-
self._auth_file = os.path.expanduser("~/.janito/auth.json")
|
21
|
-
self._credentials: Dict[str, str] = {}
|
22
|
-
self._load_credentials()
|
23
|
-
|
24
|
-
def _load_credentials(self):
|
25
|
-
if os.path.exists(self._auth_file):
|
26
|
-
try:
|
27
|
-
with open(self._auth_file, "r") as f:
|
28
|
-
self._credentials = json.load(f)
|
29
|
-
except Exception:
|
30
|
-
self._credentials = {}
|
31
|
-
else:
|
32
|
-
self._credentials = {}
|
33
|
-
|
34
|
-
def _save_credentials(self):
|
35
|
-
os.makedirs(os.path.dirname(self._auth_file), exist_ok=True)
|
36
|
-
with open(self._auth_file, "w") as f:
|
37
|
-
json.dump(self._credentials, f, indent=2)
|
38
|
-
f.write("\n")
|
39
|
-
|
40
|
-
def set_credentials(self, provider_name: str, credentials: str) -> None:
|
41
|
-
"""
|
42
|
-
Store credentials for a given provider and persist to disk. Raises ValueError if provider is unknown.
|
43
|
-
"""
|
44
|
-
from janito.providers.registry import LLMProviderRegistry
|
45
|
-
|
46
|
-
if provider_name not in LLMProviderRegistry.list_providers():
|
47
|
-
raise ValueError(f"Unknown provider: {provider_name}")
|
48
|
-
self._credentials[provider_name] = credentials
|
49
|
-
self._save_credentials()
|
50
|
-
|
51
|
-
def get_credentials(self, provider_name: str) -> Optional[str]:
|
52
|
-
"""
|
53
|
-
Retrieve credentials for a given provider.
|
54
|
-
"""
|
55
|
-
return self._credentials.get(provider_name)
|
56
|
-
|
57
|
-
def remove_credentials(self, provider_name: str) -> None:
|
58
|
-
"""
|
59
|
-
Remove credentials for a given provider and update disk.
|
60
|
-
"""
|
61
|
-
if provider_name in self._credentials:
|
62
|
-
del self._credentials[provider_name]
|
63
|
-
self._save_credentials()
|
1
|
+
"""
|
2
|
+
LLMAuthManager: Handles authentication credentials for LLM providers, persisted in ~/.janito/auth.json or a custom path.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import os
|
6
|
+
import json
|
7
|
+
from typing import Dict, Optional
|
8
|
+
|
9
|
+
|
10
|
+
class LLMAuthManager:
|
11
|
+
"""
|
12
|
+
Manages authentication tokens, API keys, or credentials for LLM providers.
|
13
|
+
Persists credentials in ~/.janito/auth.json or a custom path.
|
14
|
+
"""
|
15
|
+
|
16
|
+
def __init__(self, auth_file: Optional[str] = None):
|
17
|
+
if auth_file is not None:
|
18
|
+
self._auth_file = os.path.expanduser(auth_file)
|
19
|
+
else:
|
20
|
+
self._auth_file = os.path.expanduser("~/.janito/auth.json")
|
21
|
+
self._credentials: Dict[str, str] = {}
|
22
|
+
self._load_credentials()
|
23
|
+
|
24
|
+
def _load_credentials(self):
|
25
|
+
if os.path.exists(self._auth_file):
|
26
|
+
try:
|
27
|
+
with open(self._auth_file, "r") as f:
|
28
|
+
self._credentials = json.load(f)
|
29
|
+
except Exception:
|
30
|
+
self._credentials = {}
|
31
|
+
else:
|
32
|
+
self._credentials = {}
|
33
|
+
|
34
|
+
def _save_credentials(self):
|
35
|
+
os.makedirs(os.path.dirname(self._auth_file), exist_ok=True)
|
36
|
+
with open(self._auth_file, "w") as f:
|
37
|
+
json.dump(self._credentials, f, indent=2)
|
38
|
+
f.write("\n")
|
39
|
+
|
40
|
+
def set_credentials(self, provider_name: str, credentials: str) -> None:
|
41
|
+
"""
|
42
|
+
Store credentials for a given provider and persist to disk. Raises ValueError if provider is unknown.
|
43
|
+
"""
|
44
|
+
from janito.providers.registry import LLMProviderRegistry
|
45
|
+
|
46
|
+
if provider_name not in LLMProviderRegistry.list_providers():
|
47
|
+
raise ValueError(f"Unknown provider: {provider_name}")
|
48
|
+
self._credentials[provider_name] = credentials
|
49
|
+
self._save_credentials()
|
50
|
+
|
51
|
+
def get_credentials(self, provider_name: str) -> Optional[str]:
|
52
|
+
"""
|
53
|
+
Retrieve credentials for a given provider.
|
54
|
+
"""
|
55
|
+
return self._credentials.get(provider_name)
|
56
|
+
|
57
|
+
def remove_credentials(self, provider_name: str) -> None:
|
58
|
+
"""
|
59
|
+
Remove credentials for a given provider and update disk.
|
60
|
+
"""
|
61
|
+
if provider_name in self._credentials:
|
62
|
+
del self._credentials[provider_name]
|
63
|
+
self._save_credentials()
|
janito/llm/driver.py
CHANGED
@@ -19,6 +19,14 @@ class LLMDriver(ABC):
|
|
19
19
|
except Exception:
|
20
20
|
pass
|
21
21
|
|
22
|
+
def clear_input_queue(self):
|
23
|
+
"""Remove all items from the input queue."""
|
24
|
+
try:
|
25
|
+
while True:
|
26
|
+
self.input_queue.get_nowait()
|
27
|
+
except Exception:
|
28
|
+
pass
|
29
|
+
|
22
30
|
"""
|
23
31
|
Abstract base class for LLM drivers (threaded, queue-based).
|
24
32
|
Subclasses must implement:
|
janito/provider_registry.py
CHANGED
@@ -1,176 +1,178 @@
|
|
1
|
-
"""
|
2
|
-
ProviderRegistry: Handles provider listing and selection logic for janito CLI.
|
3
|
-
"""
|
4
|
-
|
5
|
-
from rich.table import Table
|
6
|
-
from janito.cli.console import shared_console
|
7
|
-
from janito.providers.registry import LLMProviderRegistry
|
8
|
-
from janito.providers.provider_static_info import STATIC_PROVIDER_METADATA
|
9
|
-
from janito.llm.auth import LLMAuthManager
|
10
|
-
import sys
|
11
|
-
from janito.exceptions import MissingProviderSelectionException
|
12
|
-
|
13
|
-
|
14
|
-
class ProviderRegistry:
|
15
|
-
def list_providers(self):
|
16
|
-
"""List all supported LLM providers as a table using rich, showing if auth is configured and supported model names."""
|
17
|
-
providers = self._get_provider_names()
|
18
|
-
table = self._create_table()
|
19
|
-
rows = self._get_all_provider_rows(providers)
|
20
|
-
self._add_rows_to_table(table, rows)
|
21
|
-
self._print_table(table)
|
22
|
-
|
23
|
-
def _get_provider_names(self):
|
24
|
-
return list(STATIC_PROVIDER_METADATA.keys())
|
25
|
-
|
26
|
-
def _create_table(self):
|
27
|
-
table = Table(title="Supported LLM Providers")
|
28
|
-
table.add_column("Provider", style="cyan")
|
29
|
-
table.add_column("Maintainer", style="yellow", justify="center")
|
30
|
-
table.add_column("Model Names", style="magenta")
|
31
|
-
return table
|
32
|
-
|
33
|
-
def _get_all_provider_rows(self, providers):
|
34
|
-
rows = []
|
35
|
-
for p in providers:
|
36
|
-
info = self._get_provider_info(p)
|
37
|
-
# info is (provider_name, maintainer, model_names, skip)
|
38
|
-
if len(info) == 4 and info[3]:
|
39
|
-
continue # skip providers flagged as not implemented
|
40
|
-
rows.append(info[:3])
|
41
|
-
rows.sort(key=self._maintainer_sort_key)
|
42
|
-
return rows
|
43
|
-
|
44
|
-
def _add_rows_to_table(self, table, rows):
|
45
|
-
for idx, (p, maintainer, model_names) in enumerate(rows):
|
46
|
-
table.add_row(p, maintainer, model_names)
|
47
|
-
if idx != len(rows) - 1:
|
48
|
-
table.add_section()
|
49
|
-
|
50
|
-
def _print_table(self, table):
|
51
|
-
"""Print the table using rich when running in a terminal; otherwise fall back to a plain ASCII listing.
|
52
|
-
This avoids UnicodeDecodeError when the parent process captures the output with a non-UTF8 encoding.
|
53
|
-
"""
|
54
|
-
import sys
|
55
|
-
|
56
|
-
if sys.stdout.isatty():
|
57
|
-
# Safe to use rich's unicode output when attached to an interactive terminal.
|
58
|
-
shared_console.print(table)
|
59
|
-
return
|
60
|
-
|
61
|
-
# Fallback: plain ASCII output
|
62
|
-
print("Supported LLM Providers")
|
63
|
-
|
64
|
-
for
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
model_names =
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
"
|
131
|
-
"
|
132
|
-
"
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
return "(
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
if
|
169
|
-
return
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
1
|
+
"""
|
2
|
+
ProviderRegistry: Handles provider listing and selection logic for janito CLI.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from rich.table import Table
|
6
|
+
from janito.cli.console import shared_console
|
7
|
+
from janito.providers.registry import LLMProviderRegistry
|
8
|
+
from janito.providers.provider_static_info import STATIC_PROVIDER_METADATA
|
9
|
+
from janito.llm.auth import LLMAuthManager
|
10
|
+
import sys
|
11
|
+
from janito.exceptions import MissingProviderSelectionException
|
12
|
+
|
13
|
+
|
14
|
+
class ProviderRegistry:
|
15
|
+
def list_providers(self):
|
16
|
+
"""List all supported LLM providers as a table using rich, showing if auth is configured and supported model names."""
|
17
|
+
providers = self._get_provider_names()
|
18
|
+
table = self._create_table()
|
19
|
+
rows = self._get_all_provider_rows(providers)
|
20
|
+
self._add_rows_to_table(table, rows)
|
21
|
+
self._print_table(table)
|
22
|
+
|
23
|
+
def _get_provider_names(self):
|
24
|
+
return list(STATIC_PROVIDER_METADATA.keys())
|
25
|
+
|
26
|
+
def _create_table(self):
|
27
|
+
table = Table(title="Supported LLM Providers")
|
28
|
+
table.add_column("Provider", style="cyan")
|
29
|
+
table.add_column("Maintainer", style="yellow", justify="center")
|
30
|
+
table.add_column("Model Names", style="magenta")
|
31
|
+
return table
|
32
|
+
|
33
|
+
def _get_all_provider_rows(self, providers):
|
34
|
+
rows = []
|
35
|
+
for p in providers:
|
36
|
+
info = self._get_provider_info(p)
|
37
|
+
# info is (provider_name, maintainer, model_names, skip)
|
38
|
+
if len(info) == 4 and info[3]:
|
39
|
+
continue # skip providers flagged as not implemented
|
40
|
+
rows.append(info[:3])
|
41
|
+
rows.sort(key=self._maintainer_sort_key)
|
42
|
+
return rows
|
43
|
+
|
44
|
+
def _add_rows_to_table(self, table, rows):
|
45
|
+
for idx, (p, maintainer, model_names) in enumerate(rows):
|
46
|
+
table.add_row(p, maintainer, model_names)
|
47
|
+
if idx != len(rows) - 1:
|
48
|
+
table.add_section()
|
49
|
+
|
50
|
+
def _print_table(self, table):
|
51
|
+
"""Print the table using rich when running in a terminal; otherwise fall back to a plain ASCII listing.
|
52
|
+
This avoids UnicodeDecodeError when the parent process captures the output with a non-UTF8 encoding.
|
53
|
+
"""
|
54
|
+
import sys
|
55
|
+
|
56
|
+
if sys.stdout.isatty():
|
57
|
+
# Safe to use rich's unicode output when attached to an interactive terminal.
|
58
|
+
shared_console.print(table)
|
59
|
+
return
|
60
|
+
|
61
|
+
# Fallback: plain ASCII output (render without rich formatting)
|
62
|
+
print("Supported LLM Providers")
|
63
|
+
# Build header from column titles
|
64
|
+
header_titles = [column.header or "" for column in table.columns]
|
65
|
+
print(" | ".join(header_titles))
|
66
|
+
# rich.table.Row objects in recent Rich versions don't expose a public `.cells` attribute.
|
67
|
+
# Instead, cell content is stored in each column's private `_cells` list.
|
68
|
+
for row_index, _ in enumerate(table.rows):
|
69
|
+
cells_text = [str(column._cells[row_index]) for column in table.columns]
|
70
|
+
ascii_row = " | ".join(cells_text).encode("ascii", "ignore").decode("ascii")
|
71
|
+
print(ascii_row)
|
72
|
+
|
73
|
+
def _get_provider_info(self, provider_name):
|
74
|
+
static_info = STATIC_PROVIDER_METADATA.get(provider_name, {})
|
75
|
+
maintainer_val = static_info.get("maintainer", "-")
|
76
|
+
maintainer = (
|
77
|
+
"[red]🚨 Needs maintainer[/red]"
|
78
|
+
if maintainer_val == "Needs maintainer"
|
79
|
+
else f"👤 {maintainer_val}"
|
80
|
+
)
|
81
|
+
model_names = "-"
|
82
|
+
unavailable_reason = None
|
83
|
+
skip = False
|
84
|
+
try:
|
85
|
+
provider_class = LLMProviderRegistry.get(provider_name)
|
86
|
+
creds = LLMAuthManager().get_credentials(provider_name)
|
87
|
+
provider_instance = None
|
88
|
+
instantiation_failed = False
|
89
|
+
try:
|
90
|
+
provider_instance = provider_class()
|
91
|
+
except NotImplementedError:
|
92
|
+
skip = True
|
93
|
+
unavailable_reason = "Not implemented"
|
94
|
+
model_names = f"[red]❌ Not implemented[/red]"
|
95
|
+
except Exception as e:
|
96
|
+
instantiation_failed = True
|
97
|
+
unavailable_reason = (
|
98
|
+
f"Unavailable (import error or missing dependency): {str(e)}"
|
99
|
+
)
|
100
|
+
model_names = f"[red]❌ {unavailable_reason}[/red]"
|
101
|
+
if not instantiation_failed and provider_instance is not None:
|
102
|
+
available, unavailable_reason = self._get_availability(
|
103
|
+
provider_instance
|
104
|
+
)
|
105
|
+
if (
|
106
|
+
not available
|
107
|
+
and unavailable_reason
|
108
|
+
and "not implemented" in str(unavailable_reason).lower()
|
109
|
+
):
|
110
|
+
skip = True
|
111
|
+
if available:
|
112
|
+
model_names = self._get_model_names(provider_name)
|
113
|
+
else:
|
114
|
+
model_names = f"[red]❌ {unavailable_reason}[/red]"
|
115
|
+
except Exception as import_error:
|
116
|
+
model_names = f"[red]❌ Unavailable (cannot import provider module): {str(import_error)}[/red]"
|
117
|
+
return (provider_name, maintainer, model_names, skip)
|
118
|
+
|
119
|
+
def _get_availability(self, provider_instance):
|
120
|
+
try:
|
121
|
+
available = getattr(provider_instance, "available", True)
|
122
|
+
unavailable_reason = getattr(provider_instance, "unavailable_reason", None)
|
123
|
+
except Exception as e:
|
124
|
+
available = False
|
125
|
+
unavailable_reason = f"Error reading runtime availability: {str(e)}"
|
126
|
+
return available, unavailable_reason
|
127
|
+
|
128
|
+
def _get_model_names(self, provider_name):
|
129
|
+
provider_to_specs = {
|
130
|
+
"openai": "janito.providers.openai.model_info",
|
131
|
+
"azure_openai": "janito.providers.azure_openai.model_info",
|
132
|
+
"google": "janito.providers.google.model_info",
|
133
|
+
|
134
|
+
"deepseek": "janito.providers.deepseek.model_info",
|
135
|
+
}
|
136
|
+
if provider_name in provider_to_specs:
|
137
|
+
try:
|
138
|
+
mod = __import__(
|
139
|
+
provider_to_specs[provider_name], fromlist=["MODEL_SPECS"]
|
140
|
+
)
|
141
|
+
return ", ".join(mod.MODEL_SPECS.keys())
|
142
|
+
except Exception:
|
143
|
+
return "(Error)"
|
144
|
+
return "-"
|
145
|
+
|
146
|
+
def _maintainer_sort_key(self, row):
|
147
|
+
maint = row[1]
|
148
|
+
is_needs_maint = "Needs maintainer" in maint
|
149
|
+
return (is_needs_maint, row[2] != "✅ Auth")
|
150
|
+
|
151
|
+
def get_provider(self, provider_name):
|
152
|
+
"""Return the provider class for the given provider name. Returns None if not found."""
|
153
|
+
from janito.providers.registry import LLMProviderRegistry
|
154
|
+
|
155
|
+
if not provider_name:
|
156
|
+
print("Error: Provider name must be specified.")
|
157
|
+
return None
|
158
|
+
provider_class = LLMProviderRegistry.get(provider_name)
|
159
|
+
if provider_class is None:
|
160
|
+
available = ', '.join(LLMProviderRegistry.list_providers())
|
161
|
+
print(f"Error: Provider '{provider_name}' is not recognized. Available providers: {available}.")
|
162
|
+
return None
|
163
|
+
return provider_class
|
164
|
+
|
165
|
+
def get_instance(self, provider_name, config=None):
|
166
|
+
"""Return an instance of the provider for the given provider name, optionally passing a config object. Returns None if not found."""
|
167
|
+
provider_class = self.get_provider(provider_name)
|
168
|
+
if provider_class is None:
|
169
|
+
return None
|
170
|
+
if config is not None:
|
171
|
+
return provider_class(config=config)
|
172
|
+
return provider_class()
|
173
|
+
|
174
|
+
|
175
|
+
# For backward compatibility
|
176
|
+
def list_providers():
|
177
|
+
"""Legacy function for listing providers, now uses ProviderRegistry class."""
|
178
|
+
ProviderRegistry().list_providers()
|
janito/providers/__init__.py
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# Ensure all providers are registered by importing their modules
|
2
2
|
import janito.providers.openai.provider
|
3
3
|
import janito.providers.google.provider
|
4
|
-
import janito.providers.mistralai.provider
|
5
|
-
import janito.providers.google.provider
|
6
4
|
import janito.providers.azure_openai.provider
|
7
5
|
import janito.providers.anthropic.provider
|
8
6
|
import janito.providers.deepseek.provider
|