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,362 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OpenAI provider implementation for Omnimancer.
|
|
3
|
+
|
|
4
|
+
This module provides the OpenAI API provider implementation using OpenAI's API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Dict, List
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from ..core.models import ChatContext, ChatResponse, ModelInfo
|
|
13
|
+
from ..utils.errors import (
|
|
14
|
+
AuthenticationError,
|
|
15
|
+
ModelNotFoundError,
|
|
16
|
+
NetworkError,
|
|
17
|
+
ProviderError,
|
|
18
|
+
RateLimitError,
|
|
19
|
+
)
|
|
20
|
+
from .base import BaseProvider
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class OpenAIProvider(BaseProvider):
|
|
24
|
+
"""
|
|
25
|
+
OpenAI API provider implementation using OpenAI's API.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
BASE_URL = "https://api.openai.com/v1"
|
|
29
|
+
|
|
30
|
+
def __init__(self, api_key: str, model: str = "", **kwargs):
|
|
31
|
+
"""
|
|
32
|
+
Initialize OpenAI provider.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
api_key: OpenAI API key
|
|
36
|
+
model: OpenAI model to use (e.g., 'gpt-4', 'gpt-4o', 'gpt-3.5-turbo')
|
|
37
|
+
**kwargs: Additional configuration
|
|
38
|
+
"""
|
|
39
|
+
super().__init__(api_key, model or "gpt-4", **kwargs)
|
|
40
|
+
self.max_tokens = kwargs.get("max_tokens", 4096)
|
|
41
|
+
self.temperature = kwargs.get("temperature", 0.7)
|
|
42
|
+
|
|
43
|
+
async def send_message(self, message: str, context: ChatContext) -> ChatResponse:
|
|
44
|
+
"""
|
|
45
|
+
Send a message to OpenAI API.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
message: User message
|
|
49
|
+
context: Conversation context
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
ChatResponse with OpenAI's reply
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
# Prepare messages for OpenAI API
|
|
56
|
+
messages = self._prepare_messages(message, context)
|
|
57
|
+
|
|
58
|
+
# Make API request
|
|
59
|
+
async with httpx.AsyncClient() as client:
|
|
60
|
+
response = await client.post(
|
|
61
|
+
f"{self.BASE_URL}/chat/completions",
|
|
62
|
+
headers={
|
|
63
|
+
"Content-Type": "application/json",
|
|
64
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
65
|
+
},
|
|
66
|
+
json={
|
|
67
|
+
"model": self.model,
|
|
68
|
+
"messages": messages,
|
|
69
|
+
"max_tokens": self.max_tokens,
|
|
70
|
+
"temperature": self.temperature,
|
|
71
|
+
},
|
|
72
|
+
timeout=30.0,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Handle response
|
|
76
|
+
return self._handle_response(response)
|
|
77
|
+
|
|
78
|
+
except httpx.TimeoutException:
|
|
79
|
+
raise NetworkError("Request to OpenAI API timed out")
|
|
80
|
+
except httpx.RequestError as e:
|
|
81
|
+
raise NetworkError(f"Network error: {e}")
|
|
82
|
+
except Exception as e:
|
|
83
|
+
raise ProviderError(f"Unexpected error: {e}")
|
|
84
|
+
|
|
85
|
+
async def validate_credentials(self) -> bool:
|
|
86
|
+
"""
|
|
87
|
+
Validate OpenAI API credentials by making a test request.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
True if credentials are valid
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
async with httpx.AsyncClient() as client:
|
|
94
|
+
response = await client.post(
|
|
95
|
+
f"{self.BASE_URL}/chat/completions",
|
|
96
|
+
headers={
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
99
|
+
},
|
|
100
|
+
json={
|
|
101
|
+
"model": self.model,
|
|
102
|
+
"messages": [{"role": "user", "content": "Hi"}],
|
|
103
|
+
"max_tokens": 10,
|
|
104
|
+
},
|
|
105
|
+
timeout=10.0,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return response.status_code == 200
|
|
109
|
+
|
|
110
|
+
except Exception:
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
def _prepare_messages(
|
|
114
|
+
self, message: str, context: ChatContext
|
|
115
|
+
) -> List[Dict[str, str]]:
|
|
116
|
+
"""
|
|
117
|
+
Prepare messages for OpenAI API format.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
message: Current user message
|
|
121
|
+
context: Conversation context
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
List of messages formatted for OpenAI API
|
|
125
|
+
"""
|
|
126
|
+
messages = []
|
|
127
|
+
|
|
128
|
+
# Add context messages
|
|
129
|
+
for msg in context.messages:
|
|
130
|
+
messages.append({"role": msg.role.value, "content": msg.content})
|
|
131
|
+
|
|
132
|
+
# Add current message
|
|
133
|
+
messages.append({"role": "user", "content": message})
|
|
134
|
+
|
|
135
|
+
return messages
|
|
136
|
+
|
|
137
|
+
def _handle_response(self, response: httpx.Response) -> ChatResponse:
|
|
138
|
+
"""
|
|
139
|
+
Handle OpenAI API response.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
response: HTTP response from OpenAI API
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
ChatResponse object
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
Various provider errors based on response status
|
|
149
|
+
"""
|
|
150
|
+
if response.status_code == 200:
|
|
151
|
+
data = response.json()
|
|
152
|
+
choices = data.get("choices", [])
|
|
153
|
+
|
|
154
|
+
if choices and len(choices) > 0:
|
|
155
|
+
message = choices[0].get("message", {})
|
|
156
|
+
content = message.get("content", "")
|
|
157
|
+
usage = data.get("usage", {})
|
|
158
|
+
|
|
159
|
+
return ChatResponse(
|
|
160
|
+
content=content,
|
|
161
|
+
model_used=self.model,
|
|
162
|
+
tokens_used=usage.get("total_tokens", 0),
|
|
163
|
+
timestamp=datetime.now(),
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
raise ProviderError("Empty response from OpenAI API")
|
|
167
|
+
|
|
168
|
+
elif response.status_code == 401:
|
|
169
|
+
raise AuthenticationError("Invalid OpenAI API key")
|
|
170
|
+
elif response.status_code == 429:
|
|
171
|
+
raise RateLimitError("OpenAI API rate limit exceeded")
|
|
172
|
+
elif response.status_code == 404:
|
|
173
|
+
raise ModelNotFoundError(f"OpenAI model '{self.model}' not found")
|
|
174
|
+
else:
|
|
175
|
+
try:
|
|
176
|
+
error_data = response.json()
|
|
177
|
+
error_msg = error_data.get("error", {}).get("message", "Unknown error")
|
|
178
|
+
except:
|
|
179
|
+
error_msg = f"HTTP {response.status_code}"
|
|
180
|
+
|
|
181
|
+
raise ProviderError(f"OpenAI API error: {error_msg}")
|
|
182
|
+
|
|
183
|
+
def get_model_info(self) -> ModelInfo:
|
|
184
|
+
"""
|
|
185
|
+
Get information about the current OpenAI model.
|
|
186
|
+
"""
|
|
187
|
+
model_configs = {
|
|
188
|
+
"gpt-4": {
|
|
189
|
+
"description": "GPT-4 - Most capable model",
|
|
190
|
+
"max_tokens": 8192,
|
|
191
|
+
"cost_per_token": 0.00003,
|
|
192
|
+
},
|
|
193
|
+
"gpt-4-turbo": {
|
|
194
|
+
"description": "GPT-4 Turbo - Enhanced performance",
|
|
195
|
+
"max_tokens": 128000,
|
|
196
|
+
"cost_per_token": 0.00001,
|
|
197
|
+
},
|
|
198
|
+
"gpt-3.5-turbo": {
|
|
199
|
+
"description": "GPT-3.5 Turbo - Fast and efficient",
|
|
200
|
+
"max_tokens": 4096,
|
|
201
|
+
"cost_per_token": 0.000002,
|
|
202
|
+
},
|
|
203
|
+
"gpt-3.5-turbo-16k": {
|
|
204
|
+
"description": "GPT-3.5 Turbo with 16K context",
|
|
205
|
+
"max_tokens": 16384,
|
|
206
|
+
"cost_per_token": 0.000004,
|
|
207
|
+
},
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
config = model_configs.get(
|
|
211
|
+
self.model,
|
|
212
|
+
{
|
|
213
|
+
"description": f"OpenAI model {self.model}",
|
|
214
|
+
"max_tokens": 4096,
|
|
215
|
+
"cost_per_token": 0.00002,
|
|
216
|
+
},
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
return ModelInfo(
|
|
220
|
+
name=self.model,
|
|
221
|
+
provider="openai",
|
|
222
|
+
description=config["description"],
|
|
223
|
+
max_tokens=config["max_tokens"],
|
|
224
|
+
cost_per_token=config["cost_per_token"],
|
|
225
|
+
available=True,
|
|
226
|
+
supports_tools=self.supports_tools(),
|
|
227
|
+
supports_multimodal=self.supports_multimodal(),
|
|
228
|
+
latest_version=self.model == "gpt-4-turbo",
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
def _get_static_models(self) -> List[ModelInfo]:
|
|
232
|
+
"""
|
|
233
|
+
Get static list of available OpenAI models.
|
|
234
|
+
"""
|
|
235
|
+
return [
|
|
236
|
+
ModelInfo(
|
|
237
|
+
name="gpt-4",
|
|
238
|
+
provider="openai",
|
|
239
|
+
description="GPT-4 - Most capable model",
|
|
240
|
+
max_tokens=8192,
|
|
241
|
+
cost_per_token=0.00003,
|
|
242
|
+
available=True,
|
|
243
|
+
supports_tools=True,
|
|
244
|
+
supports_multimodal=False,
|
|
245
|
+
),
|
|
246
|
+
ModelInfo(
|
|
247
|
+
name="gpt-4-turbo",
|
|
248
|
+
provider="openai",
|
|
249
|
+
description="GPT-4 Turbo - Enhanced performance",
|
|
250
|
+
max_tokens=128000,
|
|
251
|
+
cost_per_token=0.00001,
|
|
252
|
+
available=True,
|
|
253
|
+
supports_tools=True,
|
|
254
|
+
supports_multimodal=True,
|
|
255
|
+
latest_version=True,
|
|
256
|
+
),
|
|
257
|
+
ModelInfo(
|
|
258
|
+
name="gpt-3.5-turbo",
|
|
259
|
+
provider="openai",
|
|
260
|
+
description="GPT-3.5 Turbo - Fast and efficient",
|
|
261
|
+
max_tokens=4096,
|
|
262
|
+
cost_per_token=0.000002,
|
|
263
|
+
available=True,
|
|
264
|
+
supports_tools=True,
|
|
265
|
+
supports_multimodal=False,
|
|
266
|
+
),
|
|
267
|
+
ModelInfo(
|
|
268
|
+
name="gpt-3.5-turbo-16k",
|
|
269
|
+
provider="openai",
|
|
270
|
+
description="GPT-3.5 Turbo with 16K context",
|
|
271
|
+
max_tokens=16384,
|
|
272
|
+
cost_per_token=0.000004,
|
|
273
|
+
available=True,
|
|
274
|
+
supports_tools=True,
|
|
275
|
+
supports_multimodal=False,
|
|
276
|
+
),
|
|
277
|
+
]
|
|
278
|
+
|
|
279
|
+
def supports_tools(self) -> bool:
|
|
280
|
+
"""
|
|
281
|
+
Check if OpenAI provider supports tool calling.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
True - OpenAI supports function calling/tools
|
|
285
|
+
"""
|
|
286
|
+
return True
|
|
287
|
+
|
|
288
|
+
def supports_multimodal(self) -> bool:
|
|
289
|
+
"""
|
|
290
|
+
Check if OpenAI provider supports multimodal inputs.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
True for GPT-4 models that support vision, False for others
|
|
294
|
+
"""
|
|
295
|
+
# GPT-4 models with vision support
|
|
296
|
+
vision_models = ["gpt-4-vision-preview", "gpt-4-turbo", "gpt-4o"]
|
|
297
|
+
return any(model in self.model for model in vision_models)
|
|
298
|
+
|
|
299
|
+
async def fetch_live_models(self) -> List[ModelInfo]:
|
|
300
|
+
"""
|
|
301
|
+
Fetch live model list from OpenAI API.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
List of ModelInfo objects from OpenAI API
|
|
305
|
+
"""
|
|
306
|
+
try:
|
|
307
|
+
headers = {
|
|
308
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
309
|
+
"Content-Type": "application/json",
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async with httpx.AsyncClient() as client:
|
|
313
|
+
response = await client.get(
|
|
314
|
+
f"{self.BASE_URL}/models", headers=headers, timeout=30.0
|
|
315
|
+
)
|
|
316
|
+
response.raise_for_status()
|
|
317
|
+
|
|
318
|
+
data = response.json()
|
|
319
|
+
models = []
|
|
320
|
+
|
|
321
|
+
# Filter and convert to ModelInfo objects
|
|
322
|
+
for model_data in data.get("data", []):
|
|
323
|
+
model_id = model_data.get("id", "")
|
|
324
|
+
|
|
325
|
+
# Filter for chat models (exclude fine-tuned and other model types)
|
|
326
|
+
if any(prefix in model_id for prefix in ["gpt-3.5", "gpt-4"]):
|
|
327
|
+
# Determine model capabilities
|
|
328
|
+
supports_tools = (
|
|
329
|
+
"gpt-3.5-turbo" in model_id or "gpt-4" in model_id
|
|
330
|
+
)
|
|
331
|
+
supports_multimodal = any(
|
|
332
|
+
vision in model_id
|
|
333
|
+
for vision in ["gpt-4-turbo", "gpt-4o", "vision"]
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# Estimate max tokens based on model
|
|
337
|
+
max_tokens = 4096 # default
|
|
338
|
+
if "gpt-4-turbo" in model_id or "gpt-4o" in model_id:
|
|
339
|
+
max_tokens = 128000
|
|
340
|
+
elif "gpt-4" in model_id:
|
|
341
|
+
max_tokens = 8192
|
|
342
|
+
elif "gpt-3.5-turbo" in model_id:
|
|
343
|
+
max_tokens = 16384
|
|
344
|
+
|
|
345
|
+
models.append(
|
|
346
|
+
ModelInfo(
|
|
347
|
+
name=model_id,
|
|
348
|
+
provider="openai",
|
|
349
|
+
description=f"OpenAI {model_id}",
|
|
350
|
+
max_tokens=max_tokens,
|
|
351
|
+
cost_per_token=0.00001, # Approximate
|
|
352
|
+
available=True,
|
|
353
|
+
supports_tools=supports_tools,
|
|
354
|
+
supports_multimodal=supports_multimodal,
|
|
355
|
+
)
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
return models
|
|
359
|
+
|
|
360
|
+
except Exception:
|
|
361
|
+
# Fall back to static model list if API call fails
|
|
362
|
+
return self.get_available_models()
|