omnimancer-cli 0.1.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.
- omnimancer/__init__.py +42 -0
- omnimancer/__main__.py +9 -0
- omnimancer/cli/__init__.py +8 -0
- omnimancer/cli/agent_manager.py +826 -0
- omnimancer/cli/agent_wizard.py +944 -0
- omnimancer/cli/approval_formatter.py +734 -0
- omnimancer/cli/approval_integration.py +734 -0
- omnimancer/cli/approval_prompt.py +1286 -0
- omnimancer/cli/batch_approval_display.py +617 -0
- omnimancer/cli/batch_approval_filters.py +729 -0
- omnimancer/cli/batch_operation_monitor.py +775 -0
- omnimancer/cli/commands/performance_cmd.py +624 -0
- omnimancer/cli/commands.py +757 -0
- omnimancer/cli/completion_handler.py +281 -0
- omnimancer/cli/config_setup_wizard.py +783 -0
- omnimancer/cli/dynamic_commands.py +371 -0
- omnimancer/cli/handlers/__init__.py +18 -0
- omnimancer/cli/handlers/agent_handler.py +865 -0
- omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
- omnimancer/cli/handlers/custom_agent_handler.py +352 -0
- omnimancer/cli/handlers/permissions_handler.py +1609 -0
- omnimancer/cli/interface.py +6695 -0
- omnimancer/cli/performance_dashboard.py +612 -0
- omnimancer/core/__init__.py +34 -0
- omnimancer/core/agent/__init__.py +256 -0
- omnimancer/core/agent/agent_config.py +715 -0
- omnimancer/core/agent/agent_switcher.py +668 -0
- omnimancer/core/agent/approval_context.py +460 -0
- omnimancer/core/agent/approval_dialog.py +902 -0
- omnimancer/core/agent/approval_interface.py +628 -0
- omnimancer/core/agent/approval_manager.py +760 -0
- omnimancer/core/agent/cli_integration.py +470 -0
- omnimancer/core/agent/config.py +845 -0
- omnimancer/core/agent/custom_agent_integration.py +654 -0
- omnimancer/core/agent/diff_renderer.py +706 -0
- omnimancer/core/agent/file_content_display.py +503 -0
- omnimancer/core/agent/file_modification_workflow.py +658 -0
- omnimancer/core/agent/file_system_manager.py +1790 -0
- omnimancer/core/agent/input_handler.py +614 -0
- omnimancer/core/agent/metrics_collector.py +771 -0
- omnimancer/core/agent/optimization_engine.py +729 -0
- omnimancer/core/agent/performance_monitor.py +717 -0
- omnimancer/core/agent/persona.py +870 -0
- omnimancer/core/agent/persona_validator.py +672 -0
- omnimancer/core/agent/program_executor.py +748 -0
- omnimancer/core/agent/proposed_changes_integration.py +663 -0
- omnimancer/core/agent/read_before_write_errors.py +492 -0
- omnimancer/core/agent/read_before_write_ui.py +647 -0
- omnimancer/core/agent/rich_renderer.py +763 -0
- omnimancer/core/agent/status_config.py +238 -0
- omnimancer/core/agent/status_core.py +446 -0
- omnimancer/core/agent/status_integrated_engine.py +540 -0
- omnimancer/core/agent/status_manager.py +878 -0
- omnimancer/core/agent/status_ui_components.py +535 -0
- omnimancer/core/agent/template_applicator.py +548 -0
- omnimancer/core/agent/terminal_status_listener.py +394 -0
- omnimancer/core/agent/token_tracker.py +679 -0
- omnimancer/core/agent/types.py +60 -0
- omnimancer/core/agent/web_client.py +745 -0
- omnimancer/core/agent/workflow_orchestrator.py +826 -0
- omnimancer/core/agent_engine.py +1625 -0
- omnimancer/core/agent_mode_manager.py +706 -0
- omnimancer/core/agent_progress_ui.py +545 -0
- omnimancer/core/chat_manager.py +141 -0
- omnimancer/core/config_manager.py +2199 -0
- omnimancer/core/config_migration.py +772 -0
- omnimancer/core/config_provider.py +1974 -0
- omnimancer/core/config_security_validator.py +393 -0
- omnimancer/core/config_validator.py +768 -0
- omnimancer/core/conversation_manager.py +352 -0
- omnimancer/core/engine.py +787 -0
- omnimancer/core/enhanced_input.py +523 -0
- omnimancer/core/env_loader.py +83 -0
- omnimancer/core/fallback_manager.py +568 -0
- omnimancer/core/health_monitor.py +462 -0
- omnimancer/core/history_manager.py +462 -0
- omnimancer/core/mcp_integration_layer.py +878 -0
- omnimancer/core/models.py +1889 -0
- omnimancer/core/provider_capabilities.py +403 -0
- omnimancer/core/provider_initializer.py +450 -0
- omnimancer/core/provider_registry.py +432 -0
- omnimancer/core/security/__init__.py +16 -0
- omnimancer/core/security/approval_workflow.py +406 -0
- omnimancer/core/security/audit_logger.py +511 -0
- omnimancer/core/security/permission_controller.py +511 -0
- omnimancer/core/security/sandbox_manager.py +379 -0
- omnimancer/core/security/security_manager.py +530 -0
- omnimancer/core/setup_wizard.py +345 -0
- omnimancer/core/setup_wizard_core.py +449 -0
- omnimancer/core/setup_wizard_guide_generator.py +376 -0
- omnimancer/core/setup_wizard_provider_setup.py +454 -0
- omnimancer/core/setup_wizard_ui.py +317 -0
- omnimancer/core/setup_wizard_validation.py +371 -0
- omnimancer/core/signal_handler.py +174 -0
- omnimancer/mcp/__init__.py +11 -0
- omnimancer/mcp/client.py +725 -0
- omnimancer/mcp/manager.py +649 -0
- omnimancer/providers/__init__.py +24 -0
- omnimancer/providers/azure.py +608 -0
- omnimancer/providers/base.py +262 -0
- omnimancer/providers/bedrock.py +711 -0
- omnimancer/providers/claude.py +426 -0
- omnimancer/providers/claude_code.py +406 -0
- omnimancer/providers/cohere.py +412 -0
- omnimancer/providers/factory.py +988 -0
- omnimancer/providers/gemini.py +708 -0
- omnimancer/providers/mistral.py +554 -0
- omnimancer/providers/ollama.py +510 -0
- omnimancer/providers/openai.py +362 -0
- omnimancer/providers/openrouter.py +709 -0
- omnimancer/providers/perplexity.py +476 -0
- omnimancer/providers/vertex.py +617 -0
- omnimancer/providers/xai.py +484 -0
- omnimancer/ui/__init__.py +6 -0
- omnimancer/ui/cancellation_handler.py +235 -0
- omnimancer/ui/progress_indicator.py +318 -0
- omnimancer/utils/__init__.py +22 -0
- omnimancer/utils/error_handler.py +589 -0
- omnimancer/utils/errors.py +667 -0
- omnimancer/utils/retry.py +238 -0
- omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
- omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
- omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
- omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
- omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- omnimancer_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,988 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provider factory for Omnimancer.
|
|
3
|
+
|
|
4
|
+
This module provides a factory for creating AI provider instances
|
|
5
|
+
based on configuration and provider type.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, List, Type
|
|
9
|
+
|
|
10
|
+
from ..core.models import EnhancedModelInfo, ModelInfo, ProviderConfig
|
|
11
|
+
from ..utils.errors import ConfigurationError
|
|
12
|
+
from .azure import AzureProvider
|
|
13
|
+
from .base import BaseProvider
|
|
14
|
+
from .bedrock import BedrockProvider
|
|
15
|
+
from .claude import ClaudeProvider
|
|
16
|
+
from .claude_code import ClaudeCodeProvider
|
|
17
|
+
from .cohere import CohereProvider
|
|
18
|
+
from .gemini import GeminiProvider
|
|
19
|
+
from .mistral import MistralProvider
|
|
20
|
+
from .ollama import OllamaProvider
|
|
21
|
+
from .openai import OpenAIProvider
|
|
22
|
+
from .openrouter import OpenRouterProvider
|
|
23
|
+
from .perplexity import PerplexityProvider
|
|
24
|
+
from .vertex import VertexAIProvider
|
|
25
|
+
from .xai import XAIProvider
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ProviderFactory:
|
|
29
|
+
"""
|
|
30
|
+
Factory for creating AI provider instances.
|
|
31
|
+
|
|
32
|
+
This class manages the registration and creation of different
|
|
33
|
+
AI provider implementations with lazy loading and caching.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
_providers: Dict[str, Type[BaseProvider]] = {}
|
|
37
|
+
_provider_instances: Dict[str, BaseProvider] = {}
|
|
38
|
+
_model_cache: Dict[str, List[ModelInfo]] = {}
|
|
39
|
+
_enhanced_model_cache: Dict[str, List[EnhancedModelInfo]] = {}
|
|
40
|
+
_cache_timestamps: Dict[str, float] = {}
|
|
41
|
+
_cache_ttl: float = 3600.0 # 1 hour cache TTL
|
|
42
|
+
_lazy_loading_enabled: bool = True
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def register_provider(cls, name: str, provider_class: Type[BaseProvider]) -> None:
|
|
46
|
+
"""
|
|
47
|
+
Register a provider class.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
name: Provider name (e.g., "claude", "openai")
|
|
51
|
+
provider_class: Provider class to register
|
|
52
|
+
"""
|
|
53
|
+
cls._providers[name] = provider_class
|
|
54
|
+
# Clear cached data for this provider when re-registering
|
|
55
|
+
cls._clear_provider_cache(name)
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def _clear_provider_cache(cls, provider_name: str) -> None:
|
|
59
|
+
"""Clear cached data for a specific provider."""
|
|
60
|
+
|
|
61
|
+
if provider_name in cls._model_cache:
|
|
62
|
+
del cls._model_cache[provider_name]
|
|
63
|
+
if provider_name in cls._enhanced_model_cache:
|
|
64
|
+
del cls._enhanced_model_cache[provider_name]
|
|
65
|
+
if provider_name in cls._cache_timestamps:
|
|
66
|
+
del cls._cache_timestamps[provider_name]
|
|
67
|
+
if provider_name in cls._provider_instances:
|
|
68
|
+
del cls._provider_instances[provider_name]
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def _is_cache_valid(cls, provider_name: str) -> bool:
|
|
72
|
+
"""Check if cached data for a provider is still valid."""
|
|
73
|
+
import time
|
|
74
|
+
|
|
75
|
+
if provider_name not in cls._cache_timestamps:
|
|
76
|
+
return False
|
|
77
|
+
return (time.time() - cls._cache_timestamps[provider_name]) < cls._cache_ttl
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def _get_cached_provider_instance(
|
|
81
|
+
cls, provider_name: str, config: ProviderConfig
|
|
82
|
+
) -> BaseProvider:
|
|
83
|
+
"""Get or create a cached provider instance."""
|
|
84
|
+
cache_key = f"{provider_name}:{config.model}:{hash(str(config.dict()))}"
|
|
85
|
+
|
|
86
|
+
if cache_key in cls._provider_instances and cls._is_cache_valid(cache_key):
|
|
87
|
+
return cls._provider_instances[cache_key]
|
|
88
|
+
|
|
89
|
+
# Create new instance
|
|
90
|
+
if provider_name not in cls._providers:
|
|
91
|
+
raise ConfigurationError(f"Unknown provider: {provider_name}")
|
|
92
|
+
|
|
93
|
+
provider_class = cls._providers[provider_name]
|
|
94
|
+
instance = provider_class(
|
|
95
|
+
api_key=config.api_key,
|
|
96
|
+
model=config.model,
|
|
97
|
+
**config.dict(exclude={"api_key", "model"}),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Cache the instance
|
|
101
|
+
import time
|
|
102
|
+
|
|
103
|
+
cls._provider_instances[cache_key] = instance
|
|
104
|
+
cls._cache_timestamps[cache_key] = time.time()
|
|
105
|
+
|
|
106
|
+
return instance
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def _get_cached_models(cls, provider_name: str, enhanced: bool = False) -> List:
|
|
110
|
+
"""Get cached model information for a provider."""
|
|
111
|
+
import time
|
|
112
|
+
|
|
113
|
+
cache_dict = cls._enhanced_model_cache if enhanced else cls._model_cache
|
|
114
|
+
|
|
115
|
+
if provider_name in cache_dict and cls._is_cache_valid(provider_name):
|
|
116
|
+
return cache_dict[provider_name]
|
|
117
|
+
|
|
118
|
+
# Cache miss - need to fetch models
|
|
119
|
+
if provider_name not in cls._providers:
|
|
120
|
+
return []
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
provider_class = cls._providers[provider_name]
|
|
124
|
+
temp_provider = provider_class(api_key="dummy", model="dummy")
|
|
125
|
+
models = temp_provider.get_available_models()
|
|
126
|
+
|
|
127
|
+
if enhanced:
|
|
128
|
+
# Convert to EnhancedModelInfo if needed
|
|
129
|
+
if models and isinstance(models[0], ModelInfo):
|
|
130
|
+
models = [
|
|
131
|
+
EnhancedModelInfo.from_model_info(model) for model in models
|
|
132
|
+
]
|
|
133
|
+
cls._enhanced_model_cache[provider_name] = models
|
|
134
|
+
else:
|
|
135
|
+
# Convert to ModelInfo if needed
|
|
136
|
+
if models and isinstance(models[0], EnhancedModelInfo):
|
|
137
|
+
models = [model.to_model_info() for model in models]
|
|
138
|
+
cls._model_cache[provider_name] = models
|
|
139
|
+
|
|
140
|
+
cls._cache_timestamps[provider_name] = time.time()
|
|
141
|
+
return models
|
|
142
|
+
|
|
143
|
+
except Exception:
|
|
144
|
+
# Return empty list on error
|
|
145
|
+
cache_dict[provider_name] = []
|
|
146
|
+
cls._cache_timestamps[provider_name] = time.time()
|
|
147
|
+
return []
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def clear_all_caches(cls) -> None:
|
|
151
|
+
"""Clear all cached data."""
|
|
152
|
+
cls._model_cache.clear()
|
|
153
|
+
cls._enhanced_model_cache.clear()
|
|
154
|
+
cls._cache_timestamps.clear()
|
|
155
|
+
cls._provider_instances.clear()
|
|
156
|
+
|
|
157
|
+
@classmethod
|
|
158
|
+
def set_cache_ttl(cls, ttl_seconds: float) -> None:
|
|
159
|
+
"""Set the cache TTL (time to live) in seconds."""
|
|
160
|
+
cls._cache_ttl = ttl_seconds
|
|
161
|
+
|
|
162
|
+
@classmethod
|
|
163
|
+
def enable_lazy_loading(cls, enabled: bool = True) -> None:
|
|
164
|
+
"""Enable or disable lazy loading."""
|
|
165
|
+
cls._lazy_loading_enabled = enabled
|
|
166
|
+
if not enabled:
|
|
167
|
+
# If disabling lazy loading, clear caches to force immediate loading
|
|
168
|
+
cls.clear_all_caches()
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def create_provider(
|
|
172
|
+
cls,
|
|
173
|
+
name: str,
|
|
174
|
+
config: ProviderConfig,
|
|
175
|
+
config_manager: "ConfigManager" = None,
|
|
176
|
+
) -> BaseProvider:
|
|
177
|
+
"""
|
|
178
|
+
Create a provider instance.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
name: Provider name
|
|
182
|
+
config: Provider configuration
|
|
183
|
+
config_manager: Optional config manager for API key decryption
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Provider instance
|
|
187
|
+
|
|
188
|
+
Raises:
|
|
189
|
+
ConfigurationError: If provider is not registered
|
|
190
|
+
"""
|
|
191
|
+
if name not in cls._providers:
|
|
192
|
+
raise ConfigurationError(f"Unknown provider: {name}")
|
|
193
|
+
|
|
194
|
+
# Use the optimized provider initializer for lazy loading and caching
|
|
195
|
+
from ..core.provider_initializer import ProviderInitializer
|
|
196
|
+
|
|
197
|
+
return ProviderInitializer.get_provider_instance(name, config, config_manager)
|
|
198
|
+
|
|
199
|
+
@classmethod
|
|
200
|
+
def get_available_providers(cls) -> list[str]:
|
|
201
|
+
"""
|
|
202
|
+
Get list of available provider names.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
List of registered provider names
|
|
206
|
+
"""
|
|
207
|
+
return list(cls._providers.keys())
|
|
208
|
+
|
|
209
|
+
@classmethod
|
|
210
|
+
def is_provider_available(cls, name: str) -> bool:
|
|
211
|
+
"""
|
|
212
|
+
Check if a provider is available.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
name: Provider name
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
True if provider is registered, False otherwise
|
|
219
|
+
"""
|
|
220
|
+
return name in cls._providers
|
|
221
|
+
|
|
222
|
+
@classmethod
|
|
223
|
+
def get_all_models(cls) -> Dict[str, List[ModelInfo]]:
|
|
224
|
+
"""
|
|
225
|
+
Get all available models from all registered providers.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Dictionary mapping provider names to their available models (legacy format)
|
|
229
|
+
"""
|
|
230
|
+
all_models = {}
|
|
231
|
+
|
|
232
|
+
for provider_name, provider_class in cls._providers.items():
|
|
233
|
+
try:
|
|
234
|
+
# Create a temporary instance to get model info
|
|
235
|
+
# We'll use a dummy config since we only need model info
|
|
236
|
+
temp_provider = provider_class(api_key="dummy", model="dummy")
|
|
237
|
+
models = temp_provider.get_available_models()
|
|
238
|
+
|
|
239
|
+
# Convert EnhancedModelInfo to ModelInfo for backward compatibility
|
|
240
|
+
if models and isinstance(models[0], EnhancedModelInfo):
|
|
241
|
+
all_models[provider_name] = [
|
|
242
|
+
model.to_model_info() for model in models
|
|
243
|
+
]
|
|
244
|
+
else:
|
|
245
|
+
all_models[provider_name] = models
|
|
246
|
+
except Exception:
|
|
247
|
+
# If we can't create the provider, skip it
|
|
248
|
+
all_models[provider_name] = []
|
|
249
|
+
|
|
250
|
+
return all_models
|
|
251
|
+
|
|
252
|
+
@classmethod
|
|
253
|
+
def get_all_enhanced_models(cls) -> Dict[str, List[EnhancedModelInfo]]:
|
|
254
|
+
"""
|
|
255
|
+
Get all available models with enhanced information from all registered providers.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Dictionary mapping provider names to their enhanced model information
|
|
259
|
+
"""
|
|
260
|
+
all_models = {}
|
|
261
|
+
|
|
262
|
+
for provider_name, provider_class in cls._providers.items():
|
|
263
|
+
try:
|
|
264
|
+
# Create a temporary instance to get model info
|
|
265
|
+
temp_provider = provider_class(api_key="dummy", model="dummy")
|
|
266
|
+
models = temp_provider.get_available_models()
|
|
267
|
+
|
|
268
|
+
# Convert ModelInfo to EnhancedModelInfo if needed
|
|
269
|
+
if models and isinstance(models[0], ModelInfo):
|
|
270
|
+
all_models[provider_name] = [
|
|
271
|
+
EnhancedModelInfo.from_model_info(model) for model in models
|
|
272
|
+
]
|
|
273
|
+
else:
|
|
274
|
+
all_models[provider_name] = models
|
|
275
|
+
except Exception:
|
|
276
|
+
# If we can't create the provider, skip it
|
|
277
|
+
all_models[provider_name] = []
|
|
278
|
+
|
|
279
|
+
return all_models
|
|
280
|
+
|
|
281
|
+
@classmethod
|
|
282
|
+
def get_all_models_with_capabilities(cls) -> Dict[str, Dict[str, any]]:
|
|
283
|
+
"""
|
|
284
|
+
Get all available models with their capabilities and provider info.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Dictionary with provider info and models with capabilities
|
|
288
|
+
"""
|
|
289
|
+
result = {}
|
|
290
|
+
|
|
291
|
+
for provider_name, provider_class in cls._providers.items():
|
|
292
|
+
try:
|
|
293
|
+
# Create a temporary instance to get model info
|
|
294
|
+
temp_provider = provider_class(api_key="dummy", model="dummy")
|
|
295
|
+
models = temp_provider.get_available_models()
|
|
296
|
+
|
|
297
|
+
# Get provider capabilities
|
|
298
|
+
provider_supports_tools = temp_provider.supports_tools()
|
|
299
|
+
provider_supports_multimodal = temp_provider.supports_multimodal()
|
|
300
|
+
|
|
301
|
+
result[provider_name] = {
|
|
302
|
+
"models": models,
|
|
303
|
+
"provider_capabilities": {
|
|
304
|
+
"supports_tools": provider_supports_tools,
|
|
305
|
+
"supports_multimodal": provider_supports_multimodal,
|
|
306
|
+
"supports_streaming": temp_provider.supports_streaming(),
|
|
307
|
+
},
|
|
308
|
+
"latest_models": [
|
|
309
|
+
m for m in models if getattr(m, "latest_version", False)
|
|
310
|
+
],
|
|
311
|
+
"tool_capable_models": [
|
|
312
|
+
m for m in models if getattr(m, "supports_tools", False)
|
|
313
|
+
],
|
|
314
|
+
"multimodal_models": [
|
|
315
|
+
m for m in models if getattr(m, "supports_multimodal", False)
|
|
316
|
+
],
|
|
317
|
+
}
|
|
318
|
+
except Exception:
|
|
319
|
+
# If we can't create the provider, provide minimal info
|
|
320
|
+
result[provider_name] = {
|
|
321
|
+
"models": [],
|
|
322
|
+
"provider_capabilities": {
|
|
323
|
+
"supports_tools": False,
|
|
324
|
+
"supports_multimodal": False,
|
|
325
|
+
"supports_streaming": False,
|
|
326
|
+
},
|
|
327
|
+
"latest_models": [],
|
|
328
|
+
"tool_capable_models": [],
|
|
329
|
+
"multimodal_models": [],
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return result
|
|
333
|
+
|
|
334
|
+
@classmethod
|
|
335
|
+
def get_models_for_provider(cls, provider_name: str) -> List[ModelInfo]:
|
|
336
|
+
"""
|
|
337
|
+
Get available models for a specific provider.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
provider_name: Name of the provider
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
List of ModelInfo objects for the provider
|
|
344
|
+
|
|
345
|
+
Raises:
|
|
346
|
+
ConfigurationError: If provider is not registered
|
|
347
|
+
"""
|
|
348
|
+
if provider_name not in cls._providers:
|
|
349
|
+
raise ConfigurationError(f"Unknown provider: {provider_name}")
|
|
350
|
+
|
|
351
|
+
provider_class = cls._providers[provider_name]
|
|
352
|
+
try:
|
|
353
|
+
# Create a temporary instance to get model info
|
|
354
|
+
temp_provider = provider_class(api_key="dummy", model="dummy")
|
|
355
|
+
return temp_provider.get_available_models()
|
|
356
|
+
except Exception as e:
|
|
357
|
+
raise ConfigurationError(
|
|
358
|
+
f"Failed to get models for provider {provider_name}: {e}"
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
@classmethod
|
|
362
|
+
def validate_model_for_provider(cls, provider_name: str, model_name: str) -> bool:
|
|
363
|
+
"""
|
|
364
|
+
Validate that a model is available for a specific provider.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
provider_name: Name of the provider
|
|
368
|
+
model_name: Name of the model
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
True if model is available for the provider
|
|
372
|
+
"""
|
|
373
|
+
try:
|
|
374
|
+
models = cls.get_models_for_provider(provider_name)
|
|
375
|
+
return any(model.name == model_name for model in models)
|
|
376
|
+
except ConfigurationError:
|
|
377
|
+
return False
|
|
378
|
+
|
|
379
|
+
@classmethod
|
|
380
|
+
def get_latest_models(cls) -> Dict[str, List[ModelInfo]]:
|
|
381
|
+
"""
|
|
382
|
+
Get the latest models from all providers.
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
Dictionary mapping provider names to their latest models
|
|
386
|
+
"""
|
|
387
|
+
latest_models = {}
|
|
388
|
+
|
|
389
|
+
for provider_name, provider_class in cls._providers.items():
|
|
390
|
+
try:
|
|
391
|
+
temp_provider = provider_class(api_key="dummy", model="dummy")
|
|
392
|
+
all_models = temp_provider.get_available_models()
|
|
393
|
+
latest_models[provider_name] = [
|
|
394
|
+
model
|
|
395
|
+
for model in all_models
|
|
396
|
+
if getattr(model, "latest_version", False)
|
|
397
|
+
]
|
|
398
|
+
except Exception:
|
|
399
|
+
latest_models[provider_name] = []
|
|
400
|
+
|
|
401
|
+
return latest_models
|
|
402
|
+
|
|
403
|
+
@classmethod
|
|
404
|
+
def get_provider_capabilities(cls, provider_name: str) -> Dict[str, bool]:
|
|
405
|
+
"""
|
|
406
|
+
Get capabilities for a specific provider.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
provider_name: Name of the provider
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
Dictionary with provider capabilities
|
|
413
|
+
|
|
414
|
+
Raises:
|
|
415
|
+
ConfigurationError: If provider is not registered
|
|
416
|
+
"""
|
|
417
|
+
if provider_name not in cls._providers:
|
|
418
|
+
raise ConfigurationError(f"Unknown provider: {provider_name}")
|
|
419
|
+
|
|
420
|
+
try:
|
|
421
|
+
provider_class = cls._providers[provider_name]
|
|
422
|
+
temp_provider = provider_class(api_key="dummy", model="dummy")
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
"supports_tools": temp_provider.supports_tools(),
|
|
426
|
+
"supports_multimodal": temp_provider.supports_multimodal(),
|
|
427
|
+
"supports_streaming": temp_provider.supports_streaming(),
|
|
428
|
+
}
|
|
429
|
+
except Exception as e:
|
|
430
|
+
raise ConfigurationError(
|
|
431
|
+
f"Failed to get capabilities for provider {provider_name}: {e}"
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
@classmethod
|
|
435
|
+
async def check_provider_health(
|
|
436
|
+
cls, provider_name: str, config: ProviderConfig
|
|
437
|
+
) -> Dict[str, any]:
|
|
438
|
+
"""
|
|
439
|
+
Check the health status of a provider.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
provider_name: Name of the provider
|
|
443
|
+
config: Provider configuration
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
Dictionary with health status information
|
|
447
|
+
"""
|
|
448
|
+
if provider_name not in cls._providers:
|
|
449
|
+
return {
|
|
450
|
+
"status": "error",
|
|
451
|
+
"message": f"Unknown provider: {provider_name}",
|
|
452
|
+
"available": False,
|
|
453
|
+
"credentials_valid": False,
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
try:
|
|
457
|
+
# Create provider instance
|
|
458
|
+
provider = cls.create_provider(provider_name, config)
|
|
459
|
+
|
|
460
|
+
# Check credentials
|
|
461
|
+
credentials_valid = await provider.validate_credentials()
|
|
462
|
+
|
|
463
|
+
# Get model info to check if model is available
|
|
464
|
+
try:
|
|
465
|
+
model_info = provider.get_model_info()
|
|
466
|
+
model_available = model_info.available
|
|
467
|
+
except Exception:
|
|
468
|
+
model_available = False
|
|
469
|
+
|
|
470
|
+
# Determine overall status
|
|
471
|
+
if credentials_valid and model_available:
|
|
472
|
+
status = "healthy"
|
|
473
|
+
message = "Provider is working correctly"
|
|
474
|
+
elif credentials_valid:
|
|
475
|
+
status = "warning"
|
|
476
|
+
message = f'Provider accessible but model "{config.model}" may not be available'
|
|
477
|
+
else:
|
|
478
|
+
status = "error"
|
|
479
|
+
message = "Invalid credentials or provider not accessible"
|
|
480
|
+
|
|
481
|
+
return {
|
|
482
|
+
"status": status,
|
|
483
|
+
"message": message,
|
|
484
|
+
"available": True,
|
|
485
|
+
"credentials_valid": credentials_valid,
|
|
486
|
+
"model_available": model_available,
|
|
487
|
+
"provider_capabilities": cls.get_provider_capabilities(provider_name),
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
except Exception as e:
|
|
491
|
+
return {
|
|
492
|
+
"status": "error",
|
|
493
|
+
"message": f"Health check failed: {str(e)}",
|
|
494
|
+
"available": False,
|
|
495
|
+
"credentials_valid": False,
|
|
496
|
+
"model_available": False,
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
@classmethod
|
|
500
|
+
async def get_all_provider_health(
|
|
501
|
+
cls, configs: Dict[str, ProviderConfig]
|
|
502
|
+
) -> Dict[str, Dict[str, any]]:
|
|
503
|
+
"""
|
|
504
|
+
Check health status for all configured providers.
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
configs: Dictionary mapping provider names to their configurations
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
Dictionary mapping provider names to their health status
|
|
511
|
+
"""
|
|
512
|
+
health_status = {}
|
|
513
|
+
|
|
514
|
+
for provider_name in cls._providers.keys():
|
|
515
|
+
if provider_name in configs:
|
|
516
|
+
health_status[provider_name] = await cls.check_provider_health(
|
|
517
|
+
provider_name, configs[provider_name]
|
|
518
|
+
)
|
|
519
|
+
else:
|
|
520
|
+
health_status[provider_name] = {
|
|
521
|
+
"status": "not_configured",
|
|
522
|
+
"message": "Provider not configured",
|
|
523
|
+
"available": True, # Provider class is available
|
|
524
|
+
"credentials_valid": False,
|
|
525
|
+
"configured": False,
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return health_status
|
|
529
|
+
|
|
530
|
+
@classmethod
|
|
531
|
+
def get_models_by_capability(cls, capability: str) -> Dict[str, List[ModelInfo]]:
|
|
532
|
+
"""
|
|
533
|
+
Get models that support a specific capability.
|
|
534
|
+
|
|
535
|
+
Args:
|
|
536
|
+
capability: Capability to filter by ('tools', 'multimodal', 'streaming')
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
Dictionary mapping provider names to models with the capability
|
|
540
|
+
"""
|
|
541
|
+
capability_models = {}
|
|
542
|
+
|
|
543
|
+
for provider_name, provider_class in cls._providers.items():
|
|
544
|
+
try:
|
|
545
|
+
temp_provider = provider_class(api_key="dummy", model="dummy")
|
|
546
|
+
all_models = temp_provider.get_available_models()
|
|
547
|
+
|
|
548
|
+
if capability == "tools":
|
|
549
|
+
filtered_models = [
|
|
550
|
+
m for m in all_models if getattr(m, "supports_tools", False)
|
|
551
|
+
]
|
|
552
|
+
elif capability == "multimodal":
|
|
553
|
+
filtered_models = [
|
|
554
|
+
m
|
|
555
|
+
for m in all_models
|
|
556
|
+
if getattr(m, "supports_multimodal", False)
|
|
557
|
+
]
|
|
558
|
+
elif capability == "streaming":
|
|
559
|
+
# For streaming, check provider capability since it's not model-specific
|
|
560
|
+
if temp_provider.supports_streaming():
|
|
561
|
+
filtered_models = all_models
|
|
562
|
+
else:
|
|
563
|
+
filtered_models = []
|
|
564
|
+
else:
|
|
565
|
+
filtered_models = []
|
|
566
|
+
|
|
567
|
+
# Convert to legacy format if needed
|
|
568
|
+
if filtered_models and isinstance(
|
|
569
|
+
filtered_models[0], EnhancedModelInfo
|
|
570
|
+
):
|
|
571
|
+
capability_models[provider_name] = [
|
|
572
|
+
model.to_model_info() for model in filtered_models
|
|
573
|
+
]
|
|
574
|
+
else:
|
|
575
|
+
capability_models[provider_name] = filtered_models
|
|
576
|
+
except Exception:
|
|
577
|
+
capability_models[provider_name] = []
|
|
578
|
+
|
|
579
|
+
return capability_models
|
|
580
|
+
|
|
581
|
+
@classmethod
|
|
582
|
+
def get_enhanced_models_by_capability(
|
|
583
|
+
cls, capability: str
|
|
584
|
+
) -> Dict[str, List[EnhancedModelInfo]]:
|
|
585
|
+
"""
|
|
586
|
+
Get enhanced models that support a specific capability.
|
|
587
|
+
|
|
588
|
+
Args:
|
|
589
|
+
capability: Capability to filter by ('tools', 'multimodal', 'streaming', 'latest', 'free')
|
|
590
|
+
|
|
591
|
+
Returns:
|
|
592
|
+
Dictionary mapping provider names to enhanced models with the capability
|
|
593
|
+
"""
|
|
594
|
+
capability_models = {}
|
|
595
|
+
|
|
596
|
+
for provider_name, provider_class in cls._providers.items():
|
|
597
|
+
try:
|
|
598
|
+
temp_provider = provider_class(api_key="dummy", model="dummy")
|
|
599
|
+
all_models = temp_provider.get_available_models()
|
|
600
|
+
|
|
601
|
+
# Convert to EnhancedModelInfo if needed
|
|
602
|
+
if all_models and isinstance(all_models[0], ModelInfo):
|
|
603
|
+
all_models = [
|
|
604
|
+
EnhancedModelInfo.from_model_info(model) for model in all_models
|
|
605
|
+
]
|
|
606
|
+
|
|
607
|
+
if capability == "tools":
|
|
608
|
+
filtered_models = [m for m in all_models if m.supports_tools]
|
|
609
|
+
elif capability == "multimodal":
|
|
610
|
+
filtered_models = [m for m in all_models if m.supports_multimodal]
|
|
611
|
+
elif capability == "streaming":
|
|
612
|
+
# For streaming, check provider capability since it's not model-specific
|
|
613
|
+
if temp_provider.supports_streaming():
|
|
614
|
+
filtered_models = all_models
|
|
615
|
+
else:
|
|
616
|
+
filtered_models = []
|
|
617
|
+
elif capability == "latest":
|
|
618
|
+
filtered_models = [m for m in all_models if m.latest_version]
|
|
619
|
+
elif capability == "free":
|
|
620
|
+
filtered_models = [m for m in all_models if m.is_free]
|
|
621
|
+
elif capability == "available":
|
|
622
|
+
filtered_models = [
|
|
623
|
+
m for m in all_models if m.available and not m.deprecated
|
|
624
|
+
]
|
|
625
|
+
else:
|
|
626
|
+
filtered_models = []
|
|
627
|
+
|
|
628
|
+
if filtered_models:
|
|
629
|
+
capability_models[provider_name] = filtered_models
|
|
630
|
+
except Exception:
|
|
631
|
+
capability_models[provider_name] = []
|
|
632
|
+
|
|
633
|
+
return capability_models
|
|
634
|
+
|
|
635
|
+
@classmethod
|
|
636
|
+
def get_latest_models_enhanced(cls) -> Dict[str, List[EnhancedModelInfo]]:
|
|
637
|
+
"""
|
|
638
|
+
Get the latest models from all providers with enhanced information.
|
|
639
|
+
|
|
640
|
+
Returns:
|
|
641
|
+
Dictionary mapping provider names to their latest enhanced models
|
|
642
|
+
"""
|
|
643
|
+
return cls.get_enhanced_models_by_capability("latest")
|
|
644
|
+
|
|
645
|
+
@classmethod
|
|
646
|
+
def get_models_by_performance(
|
|
647
|
+
cls, min_swe_score: float
|
|
648
|
+
) -> Dict[str, List[EnhancedModelInfo]]:
|
|
649
|
+
"""
|
|
650
|
+
Get models filtered by minimum SWE score.
|
|
651
|
+
|
|
652
|
+
Args:
|
|
653
|
+
min_swe_score: Minimum SWE score (0-100)
|
|
654
|
+
|
|
655
|
+
Returns:
|
|
656
|
+
Dictionary mapping provider names to high-performance models
|
|
657
|
+
"""
|
|
658
|
+
performance_models = {}
|
|
659
|
+
|
|
660
|
+
for provider_name, provider_class in cls._providers.items():
|
|
661
|
+
try:
|
|
662
|
+
temp_provider = provider_class(api_key="dummy", model="dummy")
|
|
663
|
+
all_models = temp_provider.get_available_models()
|
|
664
|
+
|
|
665
|
+
# Convert to EnhancedModelInfo if needed
|
|
666
|
+
if all_models and isinstance(all_models[0], ModelInfo):
|
|
667
|
+
all_models = [
|
|
668
|
+
EnhancedModelInfo.from_model_info(model) for model in all_models
|
|
669
|
+
]
|
|
670
|
+
|
|
671
|
+
high_perf_models = [
|
|
672
|
+
m
|
|
673
|
+
for m in all_models
|
|
674
|
+
if m.swe_score is not None and m.swe_score >= min_swe_score
|
|
675
|
+
]
|
|
676
|
+
|
|
677
|
+
if high_perf_models:
|
|
678
|
+
performance_models[provider_name] = high_perf_models
|
|
679
|
+
except Exception:
|
|
680
|
+
performance_models[provider_name] = []
|
|
681
|
+
|
|
682
|
+
return performance_models
|
|
683
|
+
|
|
684
|
+
@classmethod
|
|
685
|
+
def get_models_by_pricing(
|
|
686
|
+
cls, max_cost_per_million: float
|
|
687
|
+
) -> Dict[str, List[EnhancedModelInfo]]:
|
|
688
|
+
"""
|
|
689
|
+
Get models filtered by maximum cost per million tokens.
|
|
690
|
+
|
|
691
|
+
Args:
|
|
692
|
+
max_cost_per_million: Maximum average cost per million tokens
|
|
693
|
+
|
|
694
|
+
Returns:
|
|
695
|
+
Dictionary mapping provider names to models within budget
|
|
696
|
+
"""
|
|
697
|
+
budget_models = {}
|
|
698
|
+
|
|
699
|
+
for provider_name, provider_class in cls._providers.items():
|
|
700
|
+
try:
|
|
701
|
+
temp_provider = provider_class(api_key="dummy", model="dummy")
|
|
702
|
+
all_models = temp_provider.get_available_models()
|
|
703
|
+
|
|
704
|
+
# Convert to EnhancedModelInfo if needed
|
|
705
|
+
if all_models and isinstance(all_models[0], ModelInfo):
|
|
706
|
+
all_models = [
|
|
707
|
+
EnhancedModelInfo.from_model_info(model) for model in all_models
|
|
708
|
+
]
|
|
709
|
+
|
|
710
|
+
affordable_models = []
|
|
711
|
+
for model in all_models:
|
|
712
|
+
if model.is_free:
|
|
713
|
+
affordable_models.append(model)
|
|
714
|
+
else:
|
|
715
|
+
avg_cost = (
|
|
716
|
+
model.cost_per_million_input + model.cost_per_million_output
|
|
717
|
+
) / 2
|
|
718
|
+
if avg_cost <= max_cost_per_million:
|
|
719
|
+
affordable_models.append(model)
|
|
720
|
+
|
|
721
|
+
if affordable_models:
|
|
722
|
+
budget_models[provider_name] = affordable_models
|
|
723
|
+
except Exception:
|
|
724
|
+
budget_models[provider_name] = []
|
|
725
|
+
|
|
726
|
+
return budget_models
|
|
727
|
+
|
|
728
|
+
@classmethod
|
|
729
|
+
def get_provider_model_summary(cls, provider_name: str) -> Dict[str, any]:
|
|
730
|
+
"""
|
|
731
|
+
Get comprehensive model summary for a specific provider.
|
|
732
|
+
|
|
733
|
+
Args:
|
|
734
|
+
provider_name: Name of the provider
|
|
735
|
+
|
|
736
|
+
Returns:
|
|
737
|
+
Dictionary with provider model summary and capabilities
|
|
738
|
+
"""
|
|
739
|
+
if provider_name not in cls._providers:
|
|
740
|
+
return {"error": f"Provider {provider_name} not registered"}
|
|
741
|
+
|
|
742
|
+
try:
|
|
743
|
+
provider_class = cls._providers[provider_name]
|
|
744
|
+
temp_provider = provider_class(api_key="dummy", model="dummy")
|
|
745
|
+
models = temp_provider.get_available_models()
|
|
746
|
+
|
|
747
|
+
# Convert to EnhancedModelInfo if needed
|
|
748
|
+
if models and isinstance(models[0], ModelInfo):
|
|
749
|
+
models = [EnhancedModelInfo.from_model_info(model) for model in models]
|
|
750
|
+
|
|
751
|
+
# Calculate statistics
|
|
752
|
+
total_models = len(models)
|
|
753
|
+
available_models = len(
|
|
754
|
+
[m for m in models if m.available and not m.deprecated]
|
|
755
|
+
)
|
|
756
|
+
tool_capable = len([m for m in models if m.supports_tools])
|
|
757
|
+
multimodal = len([m for m in models if m.supports_multimodal])
|
|
758
|
+
free_models = len([m for m in models if m.is_free])
|
|
759
|
+
latest_models = len([m for m in models if m.latest_version])
|
|
760
|
+
deprecated_models = len([m for m in models if m.deprecated])
|
|
761
|
+
|
|
762
|
+
# Get provider capabilities
|
|
763
|
+
provider_caps = cls.get_provider_capabilities(provider_name)
|
|
764
|
+
|
|
765
|
+
# Find best performing model
|
|
766
|
+
best_model = None
|
|
767
|
+
if models:
|
|
768
|
+
scored_models = [m for m in models if m.swe_score is not None]
|
|
769
|
+
if scored_models:
|
|
770
|
+
best_model = max(scored_models, key=lambda x: x.swe_score)
|
|
771
|
+
|
|
772
|
+
# Find cheapest model
|
|
773
|
+
cheapest_model = None
|
|
774
|
+
if models:
|
|
775
|
+
paid_models = [m for m in models if not m.is_free]
|
|
776
|
+
if paid_models:
|
|
777
|
+
cheapest_model = min(
|
|
778
|
+
paid_models,
|
|
779
|
+
key=lambda x: (
|
|
780
|
+
x.cost_per_million_input + x.cost_per_million_output
|
|
781
|
+
)
|
|
782
|
+
/ 2,
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
return {
|
|
786
|
+
"provider_name": provider_name,
|
|
787
|
+
"total_models": total_models,
|
|
788
|
+
"available_models": available_models,
|
|
789
|
+
"tool_capable_models": tool_capable,
|
|
790
|
+
"multimodal_models": multimodal,
|
|
791
|
+
"free_models": free_models,
|
|
792
|
+
"latest_models": latest_models,
|
|
793
|
+
"deprecated_models": deprecated_models,
|
|
794
|
+
"provider_capabilities": provider_caps,
|
|
795
|
+
"best_performing_model": (
|
|
796
|
+
{
|
|
797
|
+
"name": best_model.name,
|
|
798
|
+
"swe_score": best_model.swe_score,
|
|
799
|
+
"swe_rating": best_model.swe_rating,
|
|
800
|
+
}
|
|
801
|
+
if best_model
|
|
802
|
+
else None
|
|
803
|
+
),
|
|
804
|
+
"cheapest_model": (
|
|
805
|
+
{
|
|
806
|
+
"name": cheapest_model.name,
|
|
807
|
+
"cost_display": cheapest_model.get_cost_display(),
|
|
808
|
+
}
|
|
809
|
+
if cheapest_model
|
|
810
|
+
else None
|
|
811
|
+
),
|
|
812
|
+
"model_names": [
|
|
813
|
+
m.name for m in models if m.available and not m.deprecated
|
|
814
|
+
],
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
except Exception as e:
|
|
818
|
+
return {
|
|
819
|
+
"error": f"Failed to get summary for provider {provider_name}: {str(e)}"
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
@classmethod
|
|
823
|
+
def get_all_provider_summaries(cls) -> Dict[str, Dict[str, any]]:
|
|
824
|
+
"""
|
|
825
|
+
Get comprehensive summaries for all registered providers.
|
|
826
|
+
|
|
827
|
+
Returns:
|
|
828
|
+
Dictionary mapping provider names to their summaries
|
|
829
|
+
"""
|
|
830
|
+
summaries = {}
|
|
831
|
+
|
|
832
|
+
for provider_name in cls._providers.keys():
|
|
833
|
+
summaries[provider_name] = cls.get_provider_model_summary(provider_name)
|
|
834
|
+
|
|
835
|
+
return summaries
|
|
836
|
+
|
|
837
|
+
@classmethod
|
|
838
|
+
def detect_latest_models(cls) -> Dict[str, List[str]]:
|
|
839
|
+
"""
|
|
840
|
+
Detect and flag latest models across all providers.
|
|
841
|
+
|
|
842
|
+
Returns:
|
|
843
|
+
Dictionary mapping provider names to their latest model names
|
|
844
|
+
"""
|
|
845
|
+
latest_models = {}
|
|
846
|
+
|
|
847
|
+
for provider_name, provider_class in cls._providers.items():
|
|
848
|
+
try:
|
|
849
|
+
temp_provider = provider_class(api_key="dummy", model="dummy")
|
|
850
|
+
models = temp_provider.get_available_models()
|
|
851
|
+
|
|
852
|
+
# Convert to EnhancedModelInfo if needed
|
|
853
|
+
if models and isinstance(models[0], ModelInfo):
|
|
854
|
+
models = [
|
|
855
|
+
EnhancedModelInfo.from_model_info(model) for model in models
|
|
856
|
+
]
|
|
857
|
+
|
|
858
|
+
# Find latest models
|
|
859
|
+
latest = [m.name for m in models if m.latest_version]
|
|
860
|
+
if latest:
|
|
861
|
+
latest_models[provider_name] = latest
|
|
862
|
+
|
|
863
|
+
except Exception:
|
|
864
|
+
latest_models[provider_name] = []
|
|
865
|
+
|
|
866
|
+
return latest_models
|
|
867
|
+
|
|
868
|
+
@classmethod
|
|
869
|
+
def get_capability_matrix(cls) -> Dict[str, Dict[str, bool]]:
|
|
870
|
+
"""
|
|
871
|
+
Get capability matrix showing which providers support which features.
|
|
872
|
+
|
|
873
|
+
Returns:
|
|
874
|
+
Dictionary with provider capabilities matrix
|
|
875
|
+
"""
|
|
876
|
+
matrix = {}
|
|
877
|
+
|
|
878
|
+
for provider_name in cls._providers.keys():
|
|
879
|
+
try:
|
|
880
|
+
caps = cls.get_provider_capabilities(provider_name)
|
|
881
|
+
matrix[provider_name] = caps
|
|
882
|
+
except Exception:
|
|
883
|
+
matrix[provider_name] = {
|
|
884
|
+
"supports_tools": False,
|
|
885
|
+
"supports_multimodal": False,
|
|
886
|
+
"supports_streaming": False,
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
return matrix
|
|
890
|
+
|
|
891
|
+
@classmethod
|
|
892
|
+
async def batch_health_check(
|
|
893
|
+
cls, configs: Dict[str, ProviderConfig], timeout: float = 30.0
|
|
894
|
+
) -> Dict[str, Dict[str, any]]:
|
|
895
|
+
"""
|
|
896
|
+
Perform batch health checks on multiple providers with timeout.
|
|
897
|
+
|
|
898
|
+
Args:
|
|
899
|
+
configs: Dictionary mapping provider names to their configurations
|
|
900
|
+
timeout: Timeout for each health check in seconds
|
|
901
|
+
|
|
902
|
+
Returns:
|
|
903
|
+
Dictionary mapping provider names to their health status
|
|
904
|
+
"""
|
|
905
|
+
import asyncio
|
|
906
|
+
|
|
907
|
+
async def check_single_provider(
|
|
908
|
+
provider_name: str, config: ProviderConfig
|
|
909
|
+
) -> tuple[str, Dict[str, any]]:
|
|
910
|
+
try:
|
|
911
|
+
# Add timeout to individual health check
|
|
912
|
+
health_result = await asyncio.wait_for(
|
|
913
|
+
cls.check_provider_health(provider_name, config),
|
|
914
|
+
timeout=timeout,
|
|
915
|
+
)
|
|
916
|
+
return provider_name, health_result
|
|
917
|
+
except asyncio.TimeoutError:
|
|
918
|
+
return provider_name, {
|
|
919
|
+
"status": "timeout",
|
|
920
|
+
"message": f"Health check timed out after {timeout} seconds",
|
|
921
|
+
"available": True,
|
|
922
|
+
"credentials_valid": False,
|
|
923
|
+
"model_available": False,
|
|
924
|
+
}
|
|
925
|
+
except Exception as e:
|
|
926
|
+
return provider_name, {
|
|
927
|
+
"status": "error",
|
|
928
|
+
"message": f"Health check failed: {str(e)}",
|
|
929
|
+
"available": False,
|
|
930
|
+
"credentials_valid": False,
|
|
931
|
+
"model_available": False,
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
# Create tasks for all configured providers
|
|
935
|
+
tasks = []
|
|
936
|
+
for provider_name, config in configs.items():
|
|
937
|
+
if provider_name in cls._providers:
|
|
938
|
+
tasks.append(check_single_provider(provider_name, config))
|
|
939
|
+
|
|
940
|
+
# Execute all health checks concurrently
|
|
941
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
942
|
+
|
|
943
|
+
# Process results
|
|
944
|
+
health_status = {}
|
|
945
|
+
for result in results:
|
|
946
|
+
if isinstance(result, tuple):
|
|
947
|
+
provider_name, status = result
|
|
948
|
+
health_status[provider_name] = status
|
|
949
|
+
else:
|
|
950
|
+
# Handle exceptions
|
|
951
|
+
health_status["unknown"] = {
|
|
952
|
+
"status": "error",
|
|
953
|
+
"message": f"Unexpected error: {str(result)}",
|
|
954
|
+
"available": False,
|
|
955
|
+
"credentials_valid": False,
|
|
956
|
+
"model_available": False,
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
# Add unconfigured providers
|
|
960
|
+
for provider_name in cls._providers.keys():
|
|
961
|
+
if provider_name not in health_status:
|
|
962
|
+
health_status[provider_name] = {
|
|
963
|
+
"status": "not_configured",
|
|
964
|
+
"message": "Provider not configured",
|
|
965
|
+
"available": True,
|
|
966
|
+
"credentials_valid": False,
|
|
967
|
+
"configured": False,
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
return health_status
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
# Register built-in providers
|
|
974
|
+
ProviderFactory.register_provider("claude", ClaudeProvider)
|
|
975
|
+
ProviderFactory.register_provider("openai", OpenAIProvider)
|
|
976
|
+
ProviderFactory.register_provider("gemini", GeminiProvider)
|
|
977
|
+
ProviderFactory.register_provider("cohere", CohereProvider)
|
|
978
|
+
ProviderFactory.register_provider("ollama", OllamaProvider)
|
|
979
|
+
|
|
980
|
+
# Register new providers
|
|
981
|
+
ProviderFactory.register_provider("perplexity", PerplexityProvider)
|
|
982
|
+
ProviderFactory.register_provider("xai", XAIProvider)
|
|
983
|
+
ProviderFactory.register_provider("mistral", MistralProvider)
|
|
984
|
+
ProviderFactory.register_provider("azure", AzureProvider)
|
|
985
|
+
ProviderFactory.register_provider("vertex", VertexAIProvider)
|
|
986
|
+
ProviderFactory.register_provider("bedrock", BedrockProvider)
|
|
987
|
+
ProviderFactory.register_provider("openrouter", OpenRouterProvider)
|
|
988
|
+
ProviderFactory.register_provider("claude-code", ClaudeCodeProvider)
|