janito 1.14.2__py3-none-any.whl → 2.0.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 +6 -1
- janito/__main__.py +1 -1
- janito/agent/setup_agent.py +139 -0
- janito/agent/templates/profiles/{system_prompt_template_base.txt.j2 → system_prompt_template_main.txt.j2} +1 -1
- janito/cli/__init__.py +9 -0
- janito/cli/chat_mode/bindings.py +37 -0
- janito/cli/chat_mode/chat_entry.py +23 -0
- janito/cli/chat_mode/prompt_style.py +19 -0
- janito/cli/chat_mode/session.py +272 -0
- janito/{shell/prompt/completer.py → cli/chat_mode/shell/autocomplete.py} +7 -6
- janito/cli/chat_mode/shell/commands/__init__.py +55 -0
- janito/cli/chat_mode/shell/commands/base.py +9 -0
- janito/cli/chat_mode/shell/commands/clear.py +12 -0
- janito/{shell → cli/chat_mode/shell}/commands/conversation_restart.py +34 -30
- janito/cli/chat_mode/shell/commands/edit.py +25 -0
- janito/cli/chat_mode/shell/commands/help.py +16 -0
- janito/cli/chat_mode/shell/commands/history_view.py +93 -0
- janito/cli/chat_mode/shell/commands/lang.py +25 -0
- janito/cli/chat_mode/shell/commands/last.py +137 -0
- janito/cli/chat_mode/shell/commands/livelogs.py +49 -0
- janito/cli/chat_mode/shell/commands/multi.py +51 -0
- janito/cli/chat_mode/shell/commands/prompt.py +64 -0
- janito/cli/chat_mode/shell/commands/role.py +36 -0
- janito/cli/chat_mode/shell/commands/session.py +40 -0
- janito/{shell → cli/chat_mode/shell}/commands/session_control.py +2 -2
- janito/cli/chat_mode/shell/commands/termweb_log.py +92 -0
- janito/cli/chat_mode/shell/commands/tools.py +32 -0
- janito/{shell → cli/chat_mode/shell}/commands/utility.py +4 -7
- janito/{shell → cli/chat_mode/shell}/commands/verbose.py +5 -5
- janito/cli/chat_mode/shell/session/__init__.py +1 -0
- janito/{shell → cli/chat_mode/shell}/session/manager.py +9 -1
- janito/cli/chat_mode/toolbar.py +90 -0
- janito/cli/cli_commands/list_models.py +35 -0
- janito/cli/cli_commands/list_providers.py +9 -0
- janito/cli/cli_commands/list_tools.py +53 -0
- janito/cli/cli_commands/model_selection.py +50 -0
- janito/cli/cli_commands/model_utils.py +84 -0
- janito/cli/cli_commands/set_api_key.py +19 -0
- janito/cli/cli_commands/show_config.py +51 -0
- janito/cli/cli_commands/show_system_prompt.py +62 -0
- janito/cli/config.py +28 -0
- janito/cli/console.py +3 -0
- janito/cli/core/__init__.py +4 -0
- janito/cli/core/event_logger.py +59 -0
- janito/cli/core/getters.py +31 -0
- janito/cli/core/runner.py +141 -0
- janito/cli/core/setters.py +174 -0
- janito/cli/core/unsetters.py +54 -0
- janito/cli/main.py +8 -196
- janito/cli/main_cli.py +312 -0
- janito/cli/prompt_core.py +230 -0
- janito/cli/prompt_handler.py +6 -0
- janito/cli/rich_terminal_reporter.py +101 -0
- janito/cli/single_shot_mode/__init__.py +6 -0
- janito/cli/single_shot_mode/handler.py +137 -0
- janito/cli/termweb_starter.py +73 -24
- janito/cli/utils.py +25 -0
- janito/cli/verbose_output.py +196 -0
- janito/config.py +5 -0
- janito/config_manager.py +110 -0
- janito/conversation_history.py +30 -0
- janito/{agent/tools_utils/dir_walk_utils.py → dir_walk_utils.py} +3 -2
- janito/driver_events.py +98 -0
- janito/drivers/anthropic/driver.py +113 -0
- janito/drivers/azure_openai/driver.py +36 -0
- janito/drivers/driver_registry.py +33 -0
- janito/drivers/google_genai/driver.py +54 -0
- janito/drivers/google_genai/schema_generator.py +67 -0
- janito/drivers/mistralai/driver.py +41 -0
- janito/drivers/openai/driver.py +334 -0
- janito/event_bus/__init__.py +2 -0
- janito/event_bus/bus.py +68 -0
- janito/event_bus/event.py +15 -0
- janito/event_bus/handler.py +31 -0
- janito/event_bus/queue_bus.py +57 -0
- janito/exceptions.py +23 -0
- janito/formatting_token.py +54 -0
- janito/i18n/pt.py +1 -0
- janito/llm/__init__.py +5 -0
- janito/llm/agent.py +443 -0
- janito/llm/auth.py +62 -0
- janito/llm/driver.py +239 -0
- janito/llm/driver_config.py +34 -0
- janito/llm/driver_config_builder.py +34 -0
- janito/llm/driver_input.py +12 -0
- janito/llm/message_parts.py +60 -0
- janito/llm/model.py +38 -0
- janito/llm/provider.py +187 -0
- janito/perf_singleton.py +3 -0
- janito/performance_collector.py +167 -0
- janito/provider_config.py +98 -0
- janito/provider_registry.py +152 -0
- janito/providers/__init__.py +7 -0
- janito/providers/anthropic/model_info.py +22 -0
- janito/providers/anthropic/provider.py +65 -0
- janito/providers/azure_openai/model_info.py +15 -0
- janito/providers/azure_openai/provider.py +72 -0
- janito/providers/deepseek/__init__.py +1 -0
- janito/providers/deepseek/model_info.py +16 -0
- janito/providers/deepseek/provider.py +91 -0
- janito/providers/google/__init__.py +1 -0
- janito/providers/google/model_info.py +40 -0
- janito/providers/google/provider.py +69 -0
- janito/providers/mistralai/model_info.py +37 -0
- janito/providers/mistralai/provider.py +69 -0
- janito/providers/openai/__init__.py +1 -0
- janito/providers/openai/model_info.py +137 -0
- janito/providers/openai/provider.py +107 -0
- janito/providers/openai/schema_generator.py +63 -0
- janito/providers/provider_static_info.py +21 -0
- janito/providers/registry.py +26 -0
- janito/report_events.py +38 -0
- janito/termweb/app.py +1 -1
- janito/tools/__init__.py +16 -0
- janito/tools/adapters/__init__.py +1 -0
- janito/tools/adapters/local/__init__.py +54 -0
- janito/tools/adapters/local/adapter.py +92 -0
- janito/{agent/tools → tools/adapters/local}/ask_user.py +30 -13
- janito/tools/adapters/local/copy_file.py +84 -0
- janito/{agent/tools → tools/adapters/local}/create_directory.py +11 -10
- janito/tools/adapters/local/create_file.py +82 -0
- janito/tools/adapters/local/delete_text_in_file.py +136 -0
- janito/{agent/tools → tools/adapters/local}/fetch_url.py +18 -19
- janito/tools/adapters/local/find_files.py +140 -0
- janito/tools/adapters/local/get_file_outline/core.py +151 -0
- janito/{agent/tools → tools/adapters/local}/get_file_outline/python_outline.py +125 -0
- janito/tools/adapters/local/get_file_outline/python_outline_v2.py +156 -0
- janito/{agent/tools → tools/adapters/local}/get_file_outline/search_outline.py +12 -7
- janito/{agent/tools → tools/adapters/local}/move_file.py +13 -9
- janito/{agent/tools → tools/adapters/local}/open_url.py +7 -5
- janito/tools/adapters/local/python_code_run.py +165 -0
- janito/tools/adapters/local/python_command_run.py +163 -0
- janito/tools/adapters/local/python_file_run.py +162 -0
- janito/{agent/tools → tools/adapters/local}/remove_directory.py +15 -9
- janito/{agent/tools → tools/adapters/local}/remove_file.py +17 -14
- janito/{agent/tools → tools/adapters/local}/replace_text_in_file.py +27 -22
- janito/tools/adapters/local/run_bash_command.py +176 -0
- janito/tools/adapters/local/run_powershell_command.py +219 -0
- janito/{agent/tools → tools/adapters/local}/search_text/core.py +32 -12
- janito/{agent/tools → tools/adapters/local}/search_text/match_lines.py +13 -4
- janito/{agent/tools → tools/adapters/local}/search_text/pattern_utils.py +12 -4
- janito/{agent/tools → tools/adapters/local}/search_text/traverse_directory.py +15 -2
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/core.py +12 -11
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/css_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/html_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/js_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/json_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/markdown_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/ps1_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/python_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/xml_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/yaml_validator.py +1 -1
- janito/{agent/tools/get_lines.py → tools/adapters/local/view_file.py} +45 -27
- janito/tools/inspect_registry.py +17 -0
- janito/tools/tool_base.py +105 -0
- janito/tools/tool_events.py +58 -0
- janito/tools/tool_run_exception.py +12 -0
- janito/{agent → tools}/tool_use_tracker.py +2 -4
- janito/{agent/tools_utils/utils.py → tools/tool_utils.py} +18 -9
- janito/tools/tools_adapter.py +207 -0
- janito/tools/tools_schema.py +104 -0
- janito/utils.py +11 -0
- janito/version.py +4 -0
- janito-2.0.0.dist-info/METADATA +232 -0
- janito-2.0.0.dist-info/RECORD +180 -0
- janito/agent/__init__.py +0 -0
- janito/agent/api_exceptions.py +0 -4
- janito/agent/config.py +0 -147
- janito/agent/config_defaults.py +0 -12
- janito/agent/config_utils.py +0 -0
- janito/agent/content_handler.py +0 -0
- janito/agent/conversation.py +0 -238
- janito/agent/conversation_api.py +0 -306
- janito/agent/conversation_exceptions.py +0 -18
- janito/agent/conversation_tool_calls.py +0 -39
- janito/agent/conversation_ui.py +0 -17
- janito/agent/event.py +0 -24
- janito/agent/event_dispatcher.py +0 -24
- janito/agent/event_handler_protocol.py +0 -5
- janito/agent/event_system.py +0 -15
- janito/agent/llm_conversation_history.py +0 -82
- janito/agent/message_handler.py +0 -20
- janito/agent/message_handler_protocol.py +0 -5
- janito/agent/openai_client.py +0 -149
- janito/agent/openai_schema_generator.py +0 -187
- janito/agent/profile_manager.py +0 -96
- janito/agent/queued_message_handler.py +0 -50
- janito/agent/rich_live.py +0 -32
- janito/agent/rich_message_handler.py +0 -115
- janito/agent/runtime_config.py +0 -36
- janito/agent/test_handler_protocols.py +0 -47
- janito/agent/test_openai_schema_generator.py +0 -93
- janito/agent/tests/__init__.py +0 -1
- janito/agent/tool_base.py +0 -63
- janito/agent/tool_executor.py +0 -122
- janito/agent/tool_registry.py +0 -49
- janito/agent/tools/__init__.py +0 -47
- janito/agent/tools/create_file.py +0 -59
- janito/agent/tools/delete_text_in_file.py +0 -97
- janito/agent/tools/find_files.py +0 -106
- janito/agent/tools/get_file_outline/core.py +0 -81
- janito/agent/tools/present_choices.py +0 -64
- janito/agent/tools/python_command_runner.py +0 -201
- janito/agent/tools/python_file_runner.py +0 -199
- janito/agent/tools/python_stdin_runner.py +0 -208
- janito/agent/tools/replace_file.py +0 -72
- janito/agent/tools/run_bash_command.py +0 -218
- janito/agent/tools/run_powershell_command.py +0 -251
- janito/agent/tools_utils/__init__.py +0 -1
- janito/agent/tools_utils/action_type.py +0 -7
- janito/agent/tools_utils/test_gitignore_utils.py +0 -46
- janito/cli/_livereload_log_utils.py +0 -13
- janito/cli/_print_config.py +0 -96
- janito/cli/_termweb_log_utils.py +0 -17
- janito/cli/_utils.py +0 -9
- janito/cli/arg_parser.py +0 -272
- janito/cli/cli_main.py +0 -281
- janito/cli/config_commands.py +0 -211
- janito/cli/config_runner.py +0 -35
- janito/cli/formatting_runner.py +0 -12
- janito/cli/livereload_starter.py +0 -60
- janito/cli/logging_setup.py +0 -38
- janito/cli/one_shot.py +0 -80
- janito/livereload/app.py +0 -25
- janito/rich_utils.py +0 -59
- janito/shell/__init__.py +0 -0
- janito/shell/commands/__init__.py +0 -61
- janito/shell/commands/config.py +0 -22
- janito/shell/commands/edit.py +0 -24
- janito/shell/commands/history_view.py +0 -18
- janito/shell/commands/lang.py +0 -19
- janito/shell/commands/livelogs.py +0 -42
- janito/shell/commands/prompt.py +0 -62
- janito/shell/commands/termweb_log.py +0 -94
- janito/shell/commands/tools.py +0 -26
- janito/shell/commands/track.py +0 -36
- janito/shell/main.py +0 -326
- janito/shell/prompt/load_prompt.py +0 -57
- janito/shell/prompt/session_setup.py +0 -57
- janito/shell/session/config.py +0 -109
- janito/shell/session/history.py +0 -0
- janito/shell/ui/interactive.py +0 -226
- janito/termweb/static/editor.css +0 -158
- janito/termweb/static/editor.css.bak +0 -145
- janito/termweb/static/editor.html +0 -46
- janito/termweb/static/editor.html.bak +0 -46
- janito/termweb/static/editor.js +0 -265
- janito/termweb/static/editor.js.bak +0 -259
- janito/termweb/static/explorer.html.bak +0 -59
- janito/termweb/static/favicon.ico +0 -0
- janito/termweb/static/favicon.ico.bak +0 -0
- janito/termweb/static/index.html +0 -53
- janito/termweb/static/index.html.bak +0 -54
- janito/termweb/static/index.html.bak.bak +0 -175
- janito/termweb/static/landing.html.bak +0 -36
- janito/termweb/static/termicon.svg +0 -1
- janito/termweb/static/termweb.css +0 -214
- janito/termweb/static/termweb.css.bak +0 -237
- janito/termweb/static/termweb.js +0 -162
- janito/termweb/static/termweb.js.bak +0 -168
- janito/termweb/static/termweb.js.bak.bak +0 -157
- janito/termweb/static/termweb_quickopen.js +0 -135
- janito/termweb/static/termweb_quickopen.js.bak +0 -125
- janito/tests/test_rich_utils.py +0 -44
- janito/web/__init__.py +0 -0
- janito/web/__main__.py +0 -25
- janito/web/app.py +0 -145
- janito-1.14.2.dist-info/METADATA +0 -306
- janito-1.14.2.dist-info/RECORD +0 -162
- janito-1.14.2.dist-info/licenses/LICENSE +0 -21
- /janito/{shell → cli/chat_mode/shell}/input_history.py +0 -0
- /janito/{shell/commands/session.py → cli/chat_mode/shell/session/history.py} +0 -0
- /janito/{agent/tools_utils/formatting.py → formatting.py} +0 -0
- /janito/{agent/tools_utils/gitignore_utils.py → gitignore_utils.py} +0 -0
- /janito/{agent/platform_discovery.py → platform_discovery.py} +0 -0
- /janito/{agent/tools → tools/adapters/local}/get_file_outline/__init__.py +0 -0
- /janito/{agent/tools → tools/adapters/local}/get_file_outline/markdown_outline.py +0 -0
- /janito/{agent/tools → tools/adapters/local}/search_text/__init__.py +0 -0
- /janito/{agent/tools → tools/adapters/local}/validate_file_syntax/__init__.py +0 -0
- {janito-1.14.2.dist-info → janito-2.0.0.dist-info}/WHEEL +0 -0
- {janito-1.14.2.dist-info → janito-2.0.0.dist-info}/entry_points.txt +0 -0
- {janito-1.14.2.dist-info → janito-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
from janito.llm.provider import LLMProvider
|
2
|
+
from janito.llm.model import LLMModelInfo
|
3
|
+
from janito.llm.auth import LLMAuthManager
|
4
|
+
from janito.llm.driver_config import LLMDriverConfig
|
5
|
+
from janito.drivers.openai.driver import OpenAIModelDriver
|
6
|
+
from janito.tools import get_local_tools_adapter
|
7
|
+
from janito.providers.registry import LLMProviderRegistry
|
8
|
+
from .model_info import MODEL_SPECS
|
9
|
+
from queue import Queue
|
10
|
+
|
11
|
+
available = OpenAIModelDriver.available
|
12
|
+
unavailable_reason = OpenAIModelDriver.unavailable_reason
|
13
|
+
|
14
|
+
|
15
|
+
class DeepseekProvider(LLMProvider):
|
16
|
+
name = "deepseek"
|
17
|
+
maintainer = "Needs maintainer"
|
18
|
+
MODEL_SPECS = MODEL_SPECS
|
19
|
+
DEFAULT_MODEL = "deepseek-chat" # Options: deepseek-chat, deepseek-coder
|
20
|
+
|
21
|
+
def __init__(
|
22
|
+
self, auth_manager: LLMAuthManager = None, config: LLMDriverConfig = None
|
23
|
+
):
|
24
|
+
if not self.available:
|
25
|
+
self._driver = None
|
26
|
+
else:
|
27
|
+
self.auth_manager = auth_manager or LLMAuthManager()
|
28
|
+
self._api_key = self.auth_manager.get_credentials(type(self).name)
|
29
|
+
self._tools_adapter = get_local_tools_adapter()
|
30
|
+
self._driver_config = config or LLMDriverConfig(model=None)
|
31
|
+
if not self._driver_config.model:
|
32
|
+
self._driver_config.model = self.DEFAULT_MODEL
|
33
|
+
if not self._driver_config.api_key:
|
34
|
+
self._driver_config.api_key = self._api_key
|
35
|
+
# Set DeepSeek public endpoint as default base_url if not provided
|
36
|
+
if not getattr(self._driver_config, "base_url", None):
|
37
|
+
self._driver_config.base_url = "https://api.deepseek.com/v1"
|
38
|
+
self.fill_missing_device_info(self._driver_config)
|
39
|
+
self._driver = None # to be provided by factory/agent
|
40
|
+
|
41
|
+
@property
|
42
|
+
def driver(self) -> OpenAIModelDriver:
|
43
|
+
if not self.available:
|
44
|
+
raise ImportError(f"OpenAIProvider unavailable: {self.unavailable_reason}")
|
45
|
+
return self._driver
|
46
|
+
|
47
|
+
@property
|
48
|
+
def available(self):
|
49
|
+
return available
|
50
|
+
|
51
|
+
@property
|
52
|
+
def unavailable_reason(self):
|
53
|
+
return unavailable_reason
|
54
|
+
|
55
|
+
def create_driver(self):
|
56
|
+
"""
|
57
|
+
Creates and returns a new OpenAIModelDriver instance with input/output queues.
|
58
|
+
"""
|
59
|
+
driver = OpenAIModelDriver(
|
60
|
+
tools_adapter=self._tools_adapter, provider_name=self.name
|
61
|
+
)
|
62
|
+
driver.config = self._driver_config
|
63
|
+
# NOTE: The caller is responsible for calling driver.start() if background processing is needed.
|
64
|
+
return driver
|
65
|
+
|
66
|
+
def create_agent(self, tools_adapter=None, agent_name: str = None, **kwargs):
|
67
|
+
from janito.llm.agent import LLMAgent
|
68
|
+
|
69
|
+
# Always create a new driver with the passed-in tools_adapter
|
70
|
+
if tools_adapter is None:
|
71
|
+
tools_adapter = get_local_tools_adapter()
|
72
|
+
# Should use new-style driver construction via queues/factory (handled elsewhere)
|
73
|
+
raise NotImplementedError(
|
74
|
+
"create_agent must be constructed via new factory using input/output queues and config."
|
75
|
+
)
|
76
|
+
|
77
|
+
@property
|
78
|
+
def model_name(self):
|
79
|
+
return self._driver_config.model
|
80
|
+
|
81
|
+
@property
|
82
|
+
def driver_config(self):
|
83
|
+
"""Public, read-only access to the provider's LLMDriverConfig object."""
|
84
|
+
return self._driver_config
|
85
|
+
|
86
|
+
def execute_tool(self, tool_name: str, event_bus, *args, **kwargs):
|
87
|
+
self._tools_adapter.event_bus = event_bus
|
88
|
+
return self._tools_adapter.execute_by_name(tool_name, *args, **kwargs)
|
89
|
+
|
90
|
+
|
91
|
+
LLMProviderRegistry.register(DeepseekProvider.name, DeepseekProvider)
|
@@ -0,0 +1 @@
|
|
1
|
+
from .provider import GoogleProvider
|
@@ -0,0 +1,40 @@
|
|
1
|
+
from janito.llm.model import LLMModelInfo
|
2
|
+
|
3
|
+
MODEL_SPECS = {
|
4
|
+
"gemini-2.5-pro-preview-05-06": LLMModelInfo(
|
5
|
+
name="gemini-2.5-pro-preview-05-06",
|
6
|
+
context=131072,
|
7
|
+
max_input=131072,
|
8
|
+
max_cot="N/A",
|
9
|
+
max_response=32768,
|
10
|
+
thinking_supported=True,
|
11
|
+
default_temp=0.2,
|
12
|
+
open="google",
|
13
|
+
driver="GoogleGenaiModelDriver",
|
14
|
+
other={"preview": True},
|
15
|
+
),
|
16
|
+
"gemini-2.5-flash-preview-05-20": LLMModelInfo(
|
17
|
+
name="gemini-2.5-flash-preview-05-20",
|
18
|
+
context=1000000,
|
19
|
+
max_input=1000000,
|
20
|
+
max_cot="N/A",
|
21
|
+
max_response=65536,
|
22
|
+
thinking_supported=True,
|
23
|
+
default_temp=0.2,
|
24
|
+
open="google",
|
25
|
+
driver="GoogleGenaiModelDriver",
|
26
|
+
other={"preview": True, "flash": True},
|
27
|
+
),
|
28
|
+
"gemini-2.5-flash-preview-04-17": LLMModelInfo(
|
29
|
+
name="gemini-2.5-flash-preview-04-17",
|
30
|
+
context=1000000,
|
31
|
+
max_input=1000000,
|
32
|
+
max_cot="N/A",
|
33
|
+
max_response=65536,
|
34
|
+
thinking_supported=True,
|
35
|
+
default_temp=0.2,
|
36
|
+
open="google",
|
37
|
+
driver="GoogleGenaiModelDriver",
|
38
|
+
other={"preview": True, "flash": True},
|
39
|
+
),
|
40
|
+
}
|
@@ -0,0 +1,69 @@
|
|
1
|
+
from janito.llm.provider import LLMProvider
|
2
|
+
from janito.llm.model import LLMModelInfo
|
3
|
+
from janito.llm.auth import LLMAuthManager
|
4
|
+
from janito.llm.driver_config import LLMDriverConfig
|
5
|
+
from janito.drivers.google_genai.driver import GoogleGenaiModelDriver
|
6
|
+
from janito.tools.adapters.local.adapter import LocalToolsAdapter
|
7
|
+
from janito.providers.registry import LLMProviderRegistry
|
8
|
+
|
9
|
+
from .model_info import MODEL_SPECS
|
10
|
+
|
11
|
+
from janito.drivers.google_genai.driver import GoogleGenaiModelDriver
|
12
|
+
|
13
|
+
available = GoogleGenaiModelDriver.available
|
14
|
+
unavailable_reason = GoogleGenaiModelDriver.unavailable_reason
|
15
|
+
maintainer = "Needs maintainer"
|
16
|
+
|
17
|
+
|
18
|
+
class GoogleProvider(LLMProvider):
|
19
|
+
MODEL_SPECS = MODEL_SPECS
|
20
|
+
maintainer = "Needs maintainer"
|
21
|
+
"""
|
22
|
+
Provider for Google LLMs via google-google.
|
23
|
+
Default model: 'gemini-2.5-pro-preview-05-06'.
|
24
|
+
"""
|
25
|
+
name = "google"
|
26
|
+
DEFAULT_MODEL = "gemini-2.5-flash-preview-04-17"
|
27
|
+
|
28
|
+
def __init__(self, config: LLMDriverConfig = None):
|
29
|
+
if not self.available:
|
30
|
+
self._driver = None
|
31
|
+
return
|
32
|
+
self._auth_manager = LLMAuthManager()
|
33
|
+
self._api_key = self._auth_manager.get_credentials(type(self).name)
|
34
|
+
self._tools_adapter = LocalToolsAdapter()
|
35
|
+
self._info = config or LLMDriverConfig(model=None)
|
36
|
+
if not self._info.model:
|
37
|
+
self._info.model = self.DEFAULT_MODEL
|
38
|
+
if not self._info.api_key:
|
39
|
+
self._info.api_key = self._api_key
|
40
|
+
self.fill_missing_device_info(self._info)
|
41
|
+
self._driver = GoogleGenaiModelDriver(tools_adapter=self._tools_adapter)
|
42
|
+
|
43
|
+
@property
|
44
|
+
def driver(self) -> GoogleGenaiModelDriver:
|
45
|
+
if not self.available:
|
46
|
+
raise ImportError(f"GoogleProvider unavailable: {self.unavailable_reason}")
|
47
|
+
return self._driver
|
48
|
+
|
49
|
+
@property
|
50
|
+
def available(self):
|
51
|
+
return available
|
52
|
+
|
53
|
+
@property
|
54
|
+
def unavailable_reason(self):
|
55
|
+
return unavailable_reason
|
56
|
+
|
57
|
+
def create_agent(self, tools_adapter=None, agent_name: str = None, **kwargs):
|
58
|
+
from janito.llm.agent import LLMAgent
|
59
|
+
|
60
|
+
# Always create a new driver with the passed-in tools_adapter
|
61
|
+
driver = GoogleGenaiModelDriver(tools_adapter=tools_adapter)
|
62
|
+
return LLMAgent(self, tools_adapter, agent_name=agent_name, **kwargs)
|
63
|
+
|
64
|
+
def execute_tool(self, tool_name: str, event_bus, *args, **kwargs):
|
65
|
+
self._tools_adapter.event_bus = event_bus
|
66
|
+
return self._tools_adapter.execute_by_name(tool_name, *args, **kwargs)
|
67
|
+
|
68
|
+
|
69
|
+
LLMProviderRegistry.register(GoogleProvider.name, GoogleProvider)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
from janito.llm.model import LLMModelInfo
|
2
|
+
|
3
|
+
MODEL_SPECS = {
|
4
|
+
"mistral-medium-latest": LLMModelInfo(
|
5
|
+
name="mistral-medium-latest",
|
6
|
+
context=32000,
|
7
|
+
max_input=32000,
|
8
|
+
max_cot="N/A",
|
9
|
+
max_response=8192,
|
10
|
+
thinking_supported=True,
|
11
|
+
default_temp=0.2,
|
12
|
+
open="mistralai",
|
13
|
+
driver="MistralAIModelDriver",
|
14
|
+
),
|
15
|
+
"mistral-large-latest": LLMModelInfo(
|
16
|
+
name="mistral-large-latest",
|
17
|
+
context=64000,
|
18
|
+
max_input=64000,
|
19
|
+
max_cot="N/A",
|
20
|
+
max_response=16384,
|
21
|
+
thinking_supported=True,
|
22
|
+
default_temp=0.2,
|
23
|
+
open="mistralai",
|
24
|
+
driver="MistralAIModelDriver",
|
25
|
+
),
|
26
|
+
"mistral-small-latest": LLMModelInfo(
|
27
|
+
name="mistral-small-latest",
|
28
|
+
context=16000,
|
29
|
+
max_input=16000,
|
30
|
+
max_cot="N/A",
|
31
|
+
max_response=4096,
|
32
|
+
thinking_supported=False,
|
33
|
+
default_temp=0.2,
|
34
|
+
open="mistralai",
|
35
|
+
driver="MistralAIModelDriver",
|
36
|
+
),
|
37
|
+
}
|
@@ -0,0 +1,69 @@
|
|
1
|
+
from janito.llm.provider import LLMProvider
|
2
|
+
from janito.llm.model import LLMModelInfo
|
3
|
+
from janito.llm.auth import LLMAuthManager
|
4
|
+
from janito.llm.driver_config import LLMDriverConfig
|
5
|
+
from janito.drivers.mistralai.driver import MistralAIModelDriver
|
6
|
+
from janito.tools.adapters.local.adapter import LocalToolsAdapter
|
7
|
+
from janito.providers.registry import LLMProviderRegistry
|
8
|
+
|
9
|
+
from .model_info import MODEL_SPECS
|
10
|
+
|
11
|
+
from janito.drivers.mistralai.driver import MistralAIModelDriver
|
12
|
+
|
13
|
+
available = MistralAIModelDriver.available
|
14
|
+
unavailable_reason = MistralAIModelDriver.unavailable_reason
|
15
|
+
|
16
|
+
|
17
|
+
class MistralAIProvider(LLMProvider):
|
18
|
+
MODEL_SPECS = MODEL_SPECS
|
19
|
+
name = "mistralai"
|
20
|
+
maintainer = "Needs maintainer"
|
21
|
+
|
22
|
+
DEFAULT_MODEL = "mistral-medium-latest"
|
23
|
+
|
24
|
+
def __init__(
|
25
|
+
self, config: LLMDriverConfig = None, auth_manager: LLMAuthManager = None
|
26
|
+
):
|
27
|
+
if not self.available:
|
28
|
+
self._driver = None
|
29
|
+
return
|
30
|
+
self.auth_manager = auth_manager or LLMAuthManager()
|
31
|
+
self._api_key = self.auth_manager.get_credentials(type(self).name)
|
32
|
+
self._tools_adapter = LocalToolsAdapter()
|
33
|
+
self._info = config or LLMDriverConfig(model=None)
|
34
|
+
if not self._info.model:
|
35
|
+
self._info.model = self.DEFAULT_MODEL
|
36
|
+
if not self._info.api_key:
|
37
|
+
self._info.api_key = self._api_key
|
38
|
+
self.fill_missing_device_info(self._info)
|
39
|
+
self._driver = MistralAIModelDriver(tools_adapter=self._tools_adapter)
|
40
|
+
|
41
|
+
@property
|
42
|
+
def driver(self):
|
43
|
+
if not self.available:
|
44
|
+
raise ImportError(
|
45
|
+
f"MistralAIProvider unavailable: {self.unavailable_reason}"
|
46
|
+
)
|
47
|
+
return self._driver
|
48
|
+
|
49
|
+
@property
|
50
|
+
def available(self):
|
51
|
+
return available
|
52
|
+
|
53
|
+
@property
|
54
|
+
def unavailable_reason(self):
|
55
|
+
return unavailable_reason
|
56
|
+
|
57
|
+
def create_agent(self, tools_adapter=None, agent_name: str = None, **kwargs):
|
58
|
+
from janito.llm.agent import LLMAgent
|
59
|
+
|
60
|
+
# Always create a new driver with the passed-in tools_adapter
|
61
|
+
driver = MistralAIModelDriver(tools_adapter=tools_adapter)
|
62
|
+
return LLMAgent(self, tools_adapter, agent_name=agent_name, **kwargs)
|
63
|
+
|
64
|
+
def execute_tool(self, tool_name: str, event_bus, *args, **kwargs):
|
65
|
+
self._tools_adapter.event_bus = event_bus
|
66
|
+
return self._tools_adapter.execute_by_name(tool_name, *args, **kwargs)
|
67
|
+
|
68
|
+
|
69
|
+
LLMProviderRegistry.register(MistralAIProvider.name, MistralAIProvider)
|
@@ -0,0 +1 @@
|
|
1
|
+
from .provider import OpenAIProvider
|
@@ -0,0 +1,137 @@
|
|
1
|
+
from janito.llm.model import LLMModelInfo
|
2
|
+
|
3
|
+
MODEL_SPECS = {
|
4
|
+
"gpt-3.5-turbo": LLMModelInfo(
|
5
|
+
name="gpt-3.5-turbo",
|
6
|
+
context=16385,
|
7
|
+
max_input=12289,
|
8
|
+
max_cot="N/A",
|
9
|
+
max_response=4096,
|
10
|
+
thinking_supported=False,
|
11
|
+
default_temp=0.2,
|
12
|
+
open="openai",
|
13
|
+
driver="OpenAIModelDriver",
|
14
|
+
),
|
15
|
+
"gpt-4.1": LLMModelInfo(
|
16
|
+
name="gpt-4.1",
|
17
|
+
context=1047576,
|
18
|
+
max_input=1014808,
|
19
|
+
max_cot="N/A",
|
20
|
+
max_response=32768,
|
21
|
+
thinking_supported=False,
|
22
|
+
default_temp=0.2,
|
23
|
+
open="openai",
|
24
|
+
driver="OpenAIModelDriver",
|
25
|
+
),
|
26
|
+
"gpt-4.1-mini": LLMModelInfo(
|
27
|
+
name="gpt-4.1-mini",
|
28
|
+
context=1047576,
|
29
|
+
max_input=1014808,
|
30
|
+
max_cot="N/A",
|
31
|
+
max_response=32768,
|
32
|
+
thinking_supported=False,
|
33
|
+
default_temp=0.2,
|
34
|
+
open="openai",
|
35
|
+
driver="OpenAIModelDriver",
|
36
|
+
),
|
37
|
+
"gpt-4.1-nano": LLMModelInfo(
|
38
|
+
name="gpt-4.1-nano",
|
39
|
+
context=1047576,
|
40
|
+
max_input=1014808,
|
41
|
+
max_cot="N/A",
|
42
|
+
max_response=32768,
|
43
|
+
thinking_supported=False,
|
44
|
+
default_temp=0.2,
|
45
|
+
open="openai",
|
46
|
+
driver="OpenAIModelDriver",
|
47
|
+
),
|
48
|
+
"gpt-4-turbo": LLMModelInfo(
|
49
|
+
name="gpt-4-turbo",
|
50
|
+
context=128000,
|
51
|
+
max_input="N/A",
|
52
|
+
max_cot="N/A",
|
53
|
+
max_response="N/A",
|
54
|
+
thinking_supported=False,
|
55
|
+
default_temp=0.2,
|
56
|
+
open="openai",
|
57
|
+
driver="OpenAIModelDriver",
|
58
|
+
),
|
59
|
+
"gpt-4o": LLMModelInfo(
|
60
|
+
name="gpt-4o",
|
61
|
+
context=128000,
|
62
|
+
max_input=123904,
|
63
|
+
max_cot="N/A",
|
64
|
+
max_response=4096,
|
65
|
+
thinking_supported=False,
|
66
|
+
default_temp=0.2,
|
67
|
+
open="openai",
|
68
|
+
driver="OpenAIModelDriver",
|
69
|
+
),
|
70
|
+
"gpt-4o-mini": LLMModelInfo(
|
71
|
+
name="gpt-4o-mini",
|
72
|
+
context=128000,
|
73
|
+
max_input=111616,
|
74
|
+
max_cot="N/A",
|
75
|
+
max_response=16384,
|
76
|
+
thinking_supported=False,
|
77
|
+
default_temp=0.2,
|
78
|
+
open="openai",
|
79
|
+
driver="OpenAIModelDriver",
|
80
|
+
),
|
81
|
+
"o3-mini": LLMModelInfo(
|
82
|
+
name="o3-mini",
|
83
|
+
context=200000,
|
84
|
+
max_input=100000,
|
85
|
+
max_cot="N/A",
|
86
|
+
max_response=100000,
|
87
|
+
thinking_supported=True,
|
88
|
+
default_temp=1.0,
|
89
|
+
open="openai",
|
90
|
+
driver="OpenAIModelDriver",
|
91
|
+
),
|
92
|
+
"o3": LLMModelInfo(
|
93
|
+
name="o3",
|
94
|
+
context=200000,
|
95
|
+
max_input=100000,
|
96
|
+
max_cot="N/A",
|
97
|
+
max_response=100000,
|
98
|
+
thinking_supported=True,
|
99
|
+
default_temp=1.0,
|
100
|
+
open="openai",
|
101
|
+
driver="OpenAIModelDriver",
|
102
|
+
),
|
103
|
+
"o4-mini": LLMModelInfo(
|
104
|
+
name="o4-mini",
|
105
|
+
context=200000,
|
106
|
+
max_input=100000,
|
107
|
+
max_cot="N/A",
|
108
|
+
max_response=100000,
|
109
|
+
thinking_supported=True,
|
110
|
+
default_temp=1.0,
|
111
|
+
open="openai",
|
112
|
+
driver="OpenAIModelDriver",
|
113
|
+
),
|
114
|
+
"o4-mini-high": LLMModelInfo(
|
115
|
+
name="o4-mini-high",
|
116
|
+
context=200000,
|
117
|
+
max_input=100000,
|
118
|
+
max_cot="N/A",
|
119
|
+
max_response=100000,
|
120
|
+
thinking_supported=True,
|
121
|
+
default_temp=0.2,
|
122
|
+
open="openai",
|
123
|
+
driver="OpenAIModelDriver",
|
124
|
+
),
|
125
|
+
# duplicated gpt-4-turbo with minimal properties for distinction
|
126
|
+
"gpt-4-turbo-alt": LLMModelInfo(
|
127
|
+
name="gpt-4-turbo",
|
128
|
+
context=128000,
|
129
|
+
max_input="N/A",
|
130
|
+
max_cot="N/A",
|
131
|
+
max_response="N/A",
|
132
|
+
thinking_supported=False,
|
133
|
+
default_temp=0.2,
|
134
|
+
open="openai",
|
135
|
+
driver="OpenAIModelDriver",
|
136
|
+
),
|
137
|
+
}
|
@@ -0,0 +1,107 @@
|
|
1
|
+
from janito.llm.provider import LLMProvider
|
2
|
+
from janito.llm.model import LLMModelInfo
|
3
|
+
from janito.llm.auth import LLMAuthManager
|
4
|
+
from janito.llm.driver_config import LLMDriverConfig
|
5
|
+
from janito.drivers.openai.driver import OpenAIModelDriver
|
6
|
+
from janito.tools import get_local_tools_adapter
|
7
|
+
from janito.providers.registry import LLMProviderRegistry
|
8
|
+
from .model_info import MODEL_SPECS
|
9
|
+
from queue import Queue
|
10
|
+
|
11
|
+
available = OpenAIModelDriver.available
|
12
|
+
unavailable_reason = OpenAIModelDriver.unavailable_reason
|
13
|
+
|
14
|
+
|
15
|
+
class OpenAIProvider(LLMProvider):
|
16
|
+
name = "openai"
|
17
|
+
maintainer = "João Pinto <lamego.pinto@gmail.com>"
|
18
|
+
MODEL_SPECS = MODEL_SPECS
|
19
|
+
DEFAULT_MODEL = (
|
20
|
+
"gpt-4.1" # Options: gpt-4.1, gpt-4o, o3-mini, o4-mini, o4-mini-high
|
21
|
+
)
|
22
|
+
|
23
|
+
def __init__(
|
24
|
+
self, auth_manager: LLMAuthManager = None, config: LLMDriverConfig = None
|
25
|
+
):
|
26
|
+
if not self.available:
|
27
|
+
self._driver = None
|
28
|
+
else:
|
29
|
+
self.auth_manager = auth_manager or LLMAuthManager()
|
30
|
+
self._api_key = self.auth_manager.get_credentials(type(self).name)
|
31
|
+
self._tools_adapter = get_local_tools_adapter()
|
32
|
+
self._driver_config = config or LLMDriverConfig(model=None)
|
33
|
+
if not self._driver_config.model:
|
34
|
+
self._driver_config.model = self.DEFAULT_MODEL
|
35
|
+
if not self._driver_config.api_key:
|
36
|
+
self._driver_config.api_key = self._api_key
|
37
|
+
# Set only the correct token parameter for the model
|
38
|
+
model_name = self._driver_config.model
|
39
|
+
model_spec = self.MODEL_SPECS.get(model_name)
|
40
|
+
# Remove both to avoid stale values
|
41
|
+
if hasattr(self._driver_config, "max_tokens"):
|
42
|
+
self._driver_config.max_tokens = None
|
43
|
+
if hasattr(self._driver_config, "max_completion_tokens"):
|
44
|
+
self._driver_config.max_completion_tokens = None
|
45
|
+
if model_spec:
|
46
|
+
if getattr(model_spec, "thinking_supported", False):
|
47
|
+
max_cot = getattr(model_spec, "max_cot", None)
|
48
|
+
if max_cot and max_cot != "N/A":
|
49
|
+
self._driver_config.max_completion_tokens = int(max_cot)
|
50
|
+
else:
|
51
|
+
max_response = getattr(model_spec, "max_response", None)
|
52
|
+
if max_response and max_response != "N/A":
|
53
|
+
self._driver_config.max_tokens = int(max_response)
|
54
|
+
self.fill_missing_device_info(self._driver_config)
|
55
|
+
self._driver = None # to be provided by factory/agent
|
56
|
+
|
57
|
+
@property
|
58
|
+
def driver(self) -> OpenAIModelDriver:
|
59
|
+
if not self.available:
|
60
|
+
raise ImportError(f"OpenAIProvider unavailable: {self.unavailable_reason}")
|
61
|
+
return self._driver
|
62
|
+
|
63
|
+
@property
|
64
|
+
def available(self):
|
65
|
+
return available
|
66
|
+
|
67
|
+
@property
|
68
|
+
def unavailable_reason(self):
|
69
|
+
return unavailable_reason
|
70
|
+
|
71
|
+
def create_driver(self):
|
72
|
+
"""
|
73
|
+
Creates and returns a new OpenAIModelDriver instance with input/output queues.
|
74
|
+
"""
|
75
|
+
driver = OpenAIModelDriver(
|
76
|
+
tools_adapter=self._tools_adapter, provider_name=self.name
|
77
|
+
)
|
78
|
+
driver.config = self._driver_config
|
79
|
+
# NOTE: The caller is responsible for calling driver.start() if background processing is needed.
|
80
|
+
return driver
|
81
|
+
|
82
|
+
def create_agent(self, tools_adapter=None, agent_name: str = None, **kwargs):
|
83
|
+
from janito.llm.agent import LLMAgent
|
84
|
+
|
85
|
+
# Always create a new driver with the passed-in tools_adapter
|
86
|
+
if tools_adapter is None:
|
87
|
+
tools_adapter = get_local_tools_adapter()
|
88
|
+
# Should use new-style driver construction via queues/factory (handled elsewhere)
|
89
|
+
raise NotImplementedError(
|
90
|
+
"create_agent must be constructed via new factory using input/output queues and config."
|
91
|
+
)
|
92
|
+
|
93
|
+
@property
|
94
|
+
def model_name(self):
|
95
|
+
return self._driver_config.model
|
96
|
+
|
97
|
+
@property
|
98
|
+
def driver_config(self):
|
99
|
+
"""Public, read-only access to the provider's LLMDriverConfig object."""
|
100
|
+
return self._driver_config
|
101
|
+
|
102
|
+
def execute_tool(self, tool_name: str, event_bus, *args, **kwargs):
|
103
|
+
self._tools_adapter.event_bus = event_bus
|
104
|
+
return self._tools_adapter.execute_by_name(tool_name, *args, **kwargs)
|
105
|
+
|
106
|
+
|
107
|
+
LLMProviderRegistry.register(OpenAIProvider.name, OpenAIProvider)
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import inspect
|
2
|
+
import typing
|
3
|
+
from collections import OrderedDict
|
4
|
+
from typing import List
|
5
|
+
from janito.tools.tools_schema import ToolSchemaBase
|
6
|
+
|
7
|
+
|
8
|
+
class OpenAISchemaGenerator(ToolSchemaBase):
|
9
|
+
PYTHON_TYPE_TO_JSON = {
|
10
|
+
str: "string",
|
11
|
+
int: "integer",
|
12
|
+
float: "number",
|
13
|
+
bool: "boolean",
|
14
|
+
list: "array",
|
15
|
+
dict: "object",
|
16
|
+
}
|
17
|
+
|
18
|
+
def type_to_json_schema(self, annotation):
|
19
|
+
if hasattr(annotation, "__origin__"):
|
20
|
+
if annotation.__origin__ is list or annotation.__origin__ is typing.List:
|
21
|
+
return {
|
22
|
+
"type": "array",
|
23
|
+
"items": self.type_to_json_schema(annotation.__args__[0]),
|
24
|
+
}
|
25
|
+
if annotation.__origin__ is dict or annotation.__origin__ is typing.Dict:
|
26
|
+
return {"type": "object"}
|
27
|
+
return {"type": self.PYTHON_TYPE_TO_JSON.get(annotation, "string")}
|
28
|
+
|
29
|
+
def generate_schema(self, tool_class):
|
30
|
+
# DEBUG: Print class and .name for trace
|
31
|
+
func, tool_name, sig, summary, param_descs, return_desc, description = (
|
32
|
+
self.validate_tool_class(tool_class)
|
33
|
+
)
|
34
|
+
properties = OrderedDict()
|
35
|
+
required = []
|
36
|
+
# Removed tool_call_reason from properties and required
|
37
|
+
for name, param in sig.parameters.items():
|
38
|
+
if name == "self":
|
39
|
+
continue
|
40
|
+
annotation = param.annotation
|
41
|
+
pdesc = param_descs.get(name, "")
|
42
|
+
schema = self.type_to_json_schema(annotation)
|
43
|
+
schema["description"] = pdesc
|
44
|
+
properties[name] = schema
|
45
|
+
if param.default == inspect._empty:
|
46
|
+
required.append(name)
|
47
|
+
return {
|
48
|
+
"name": tool_name,
|
49
|
+
"description": description,
|
50
|
+
"parameters": {
|
51
|
+
"type": "object",
|
52
|
+
"properties": properties,
|
53
|
+
"required": required,
|
54
|
+
},
|
55
|
+
}
|
56
|
+
|
57
|
+
|
58
|
+
def generate_tool_schemas(tool_classes: List[type]):
|
59
|
+
generator = OpenAISchemaGenerator()
|
60
|
+
return [
|
61
|
+
{"type": "function", "function": generator.generate_schema(tool_class)}
|
62
|
+
for tool_class in tool_classes
|
63
|
+
]
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Provider static metadata registry for listing purposes (name, maintainer, and future fields)
|
2
|
+
STATIC_PROVIDER_METADATA = {
|
3
|
+
"openai": {
|
4
|
+
"maintainer": "João Pinto <lamego.pinto@gmail.com>",
|
5
|
+
},
|
6
|
+
"azure_openai": {
|
7
|
+
"maintainer": "João Pinto <lamego.pinto@gmail.com>",
|
8
|
+
},
|
9
|
+
"google": {
|
10
|
+
"maintainer": "Needs maintainer",
|
11
|
+
},
|
12
|
+
"mistralai": {
|
13
|
+
"maintainer": "Needs maintainer",
|
14
|
+
},
|
15
|
+
"anthropic": {
|
16
|
+
"maintainer": "Needs maintainer",
|
17
|
+
},
|
18
|
+
"deepseek": {
|
19
|
+
"maintainer": "João Pinto <lamego.pinto@gmail.com>",
|
20
|
+
},
|
21
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from typing import Type, Dict
|
2
|
+
from janito.llm.provider import LLMProvider
|
3
|
+
|
4
|
+
|
5
|
+
class LLMProviderRegistry:
|
6
|
+
"""
|
7
|
+
Registry for LLM provider classes.
|
8
|
+
"""
|
9
|
+
|
10
|
+
_providers: Dict[str, Type[LLMProvider]] = {}
|
11
|
+
|
12
|
+
@classmethod
|
13
|
+
def register(cls, name: str, provider_cls: Type[LLMProvider]):
|
14
|
+
if name in cls._providers:
|
15
|
+
raise ValueError(f"Provider '{name}' is already registered.")
|
16
|
+
cls._providers[name] = provider_cls
|
17
|
+
|
18
|
+
@classmethod
|
19
|
+
def get(cls, name: str) -> Type[LLMProvider]:
|
20
|
+
if name not in cls._providers:
|
21
|
+
raise KeyError(f"Provider '{name}' is not registered.")
|
22
|
+
return cls._providers[name]
|
23
|
+
|
24
|
+
@classmethod
|
25
|
+
def list_providers(cls):
|
26
|
+
return list(cls._providers.keys())
|