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,317 @@
|
|
|
1
|
+
"""
|
|
2
|
+
UI components for the setup wizard.
|
|
3
|
+
|
|
4
|
+
This module contains all UI and display methods for the setup wizard,
|
|
5
|
+
including welcome screens, provider details, and completion messages.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from rich.text import Text
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SetupWizardUI:
|
|
16
|
+
"""UI components for the setup wizard."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, console: Console, provider_info: Dict[str, Dict[str, Any]]):
|
|
19
|
+
"""
|
|
20
|
+
Initialize the UI component.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
console: Rich console instance
|
|
24
|
+
provider_info: Dictionary of provider information
|
|
25
|
+
"""
|
|
26
|
+
self.console = console
|
|
27
|
+
self.provider_info = provider_info
|
|
28
|
+
|
|
29
|
+
def show_welcome(self) -> None:
|
|
30
|
+
"""Show welcome message and setup introduction."""
|
|
31
|
+
welcome_text = Text()
|
|
32
|
+
welcome_text.append("Welcome to Omnimancer Setup!\n\n", style="bold blue")
|
|
33
|
+
welcome_text.append(
|
|
34
|
+
"This wizard will help you add AI providers to your configuration.\n"
|
|
35
|
+
)
|
|
36
|
+
welcome_text.append(
|
|
37
|
+
"You can run this anytime to add more providers without affecting existing ones.\n"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
panel = Panel(
|
|
41
|
+
welcome_text,
|
|
42
|
+
title="🚀 Omnimancer Setup Wizard",
|
|
43
|
+
border_style="blue",
|
|
44
|
+
padding=(1, 2),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
self.console.print(panel)
|
|
48
|
+
self.console.print()
|
|
49
|
+
|
|
50
|
+
def show_provider_details(self, provider_name: str) -> None:
|
|
51
|
+
"""
|
|
52
|
+
Show detailed information about a provider.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
provider_name: Name of the provider
|
|
56
|
+
"""
|
|
57
|
+
info = self.provider_info[provider_name]
|
|
58
|
+
|
|
59
|
+
details = Text()
|
|
60
|
+
details.append(f"{info['name']}\n", style="bold blue")
|
|
61
|
+
details.append(f"{info['description']}\n\n", style="dim")
|
|
62
|
+
details.append("Strengths:\n", style="bold")
|
|
63
|
+
for strength in info["strengths"]:
|
|
64
|
+
details.append(f" • {strength}\n", style="green")
|
|
65
|
+
details.append(
|
|
66
|
+
f"\nAvailable Models: {', '.join(info['models'][:3])}\n",
|
|
67
|
+
style="dim",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
panel = Panel(
|
|
71
|
+
details,
|
|
72
|
+
title=f"📋 {info['name']} Details",
|
|
73
|
+
border_style="blue",
|
|
74
|
+
padding=(1, 2),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
self.console.print(panel)
|
|
78
|
+
|
|
79
|
+
def show_completion(self, provider_name: str) -> None:
|
|
80
|
+
"""
|
|
81
|
+
Show setup completion message with next steps.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
provider_name: Name of the configured provider
|
|
85
|
+
"""
|
|
86
|
+
info = self.provider_info[provider_name]
|
|
87
|
+
|
|
88
|
+
completion_text = Text()
|
|
89
|
+
completion_text.append("🎉 Setup Complete!\n\n", style="bold green")
|
|
90
|
+
completion_text.append(f"You've successfully configured {info['name']}.\n\n")
|
|
91
|
+
completion_text.append("Next Steps:\n", style="bold")
|
|
92
|
+
completion_text.append(" • Start chatting by typing your message\n")
|
|
93
|
+
completion_text.append(" • Use /help to see available commands\n")
|
|
94
|
+
completion_text.append(" • Use /providers to add more AI providers\n")
|
|
95
|
+
completion_text.append(" • Use /models to see available models\n\n")
|
|
96
|
+
completion_text.append("Happy chatting! 🚀", style="bold blue")
|
|
97
|
+
|
|
98
|
+
panel = Panel(
|
|
99
|
+
completion_text,
|
|
100
|
+
title="✨ Welcome to Omnimancer!",
|
|
101
|
+
border_style="green",
|
|
102
|
+
padding=(1, 2),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
self.console.print(panel)
|
|
106
|
+
|
|
107
|
+
def show_troubleshooting_guidance(self, provider_name: str, error: str) -> None:
|
|
108
|
+
"""
|
|
109
|
+
Show provider-specific troubleshooting guidance.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
provider_name: Name of the provider
|
|
113
|
+
error: Error message
|
|
114
|
+
"""
|
|
115
|
+
info = self.provider_info[provider_name]
|
|
116
|
+
|
|
117
|
+
guidance = Text()
|
|
118
|
+
guidance.append("Troubleshooting Guide:\n\n", style="bold yellow")
|
|
119
|
+
|
|
120
|
+
# Show error-specific guidance
|
|
121
|
+
if (
|
|
122
|
+
"api key" in error.lower()
|
|
123
|
+
or "authentication" in error.lower()
|
|
124
|
+
or "format" in error.lower()
|
|
125
|
+
):
|
|
126
|
+
guidance.append("🔑 API Key Issues:\n", style="bold red")
|
|
127
|
+
guidance.append(f" • Get your API key from: {info['api_key_url']}\n")
|
|
128
|
+
if info["api_key_prefix"]:
|
|
129
|
+
guidance.append(
|
|
130
|
+
f" • Ensure the key starts with '{info['api_key_prefix']}'\n"
|
|
131
|
+
)
|
|
132
|
+
guidance.append(
|
|
133
|
+
" • Check that your account has sufficient credits/quota\n"
|
|
134
|
+
)
|
|
135
|
+
guidance.append(" • Verify the API key hasn't expired\n\n")
|
|
136
|
+
|
|
137
|
+
if (
|
|
138
|
+
"connection" in error.lower()
|
|
139
|
+
or "network" in error.lower()
|
|
140
|
+
or "timeout" in error.lower()
|
|
141
|
+
):
|
|
142
|
+
guidance.append("🌐 Connection Issues:\n", style="bold blue")
|
|
143
|
+
guidance.append(" • Check your internet connection\n")
|
|
144
|
+
guidance.append(" • Verify firewall/proxy settings\n")
|
|
145
|
+
guidance.append(" • Try again in a few moments (temporary outage)\n")
|
|
146
|
+
guidance.append(" • Check provider status page for known issues\n\n")
|
|
147
|
+
|
|
148
|
+
if "model" in error.lower():
|
|
149
|
+
guidance.append("🤖 Model Issues:\n", style="bold green")
|
|
150
|
+
guidance.append(" • Verify the model name is spelled correctly\n")
|
|
151
|
+
guidance.append(" • Check if you have access to the requested model\n")
|
|
152
|
+
guidance.append(" • Try a different model from the same provider\n\n")
|
|
153
|
+
|
|
154
|
+
# Provider-specific guidance
|
|
155
|
+
self._add_provider_specific_guidance(guidance, provider_name, error)
|
|
156
|
+
|
|
157
|
+
# Show common issues for this provider
|
|
158
|
+
from .setup_wizard_core import SetupWizardCore
|
|
159
|
+
|
|
160
|
+
# Create a temporary instance just to access the common issues method
|
|
161
|
+
temp_core = SetupWizardCore(None, None)
|
|
162
|
+
common_issues = temp_core.get_common_setup_issues(provider_name)
|
|
163
|
+
|
|
164
|
+
if common_issues:
|
|
165
|
+
guidance.append("💡 Common Issues:\n", style="bold magenta")
|
|
166
|
+
for issue in common_issues[:3]: # Show top 3 common issues
|
|
167
|
+
guidance.append(f" • {issue['issue']}\n", style="dim")
|
|
168
|
+
guidance.append(f" → {issue['solution']}\n")
|
|
169
|
+
guidance.append("\n")
|
|
170
|
+
|
|
171
|
+
# Add helpful links
|
|
172
|
+
guidance.append("📚 Helpful Links:\n", style="bold cyan")
|
|
173
|
+
guidance.append(f" • API Documentation: {info['api_key_url']}\n")
|
|
174
|
+
if provider_name == "claude":
|
|
175
|
+
guidance.append(" • Status Page: https://status.anthropic.com/\n")
|
|
176
|
+
guidance.append(" • Support: https://support.anthropic.com/\n")
|
|
177
|
+
elif provider_name == "openai":
|
|
178
|
+
guidance.append(" • Status Page: https://status.openai.com/\n")
|
|
179
|
+
guidance.append(" • Support: https://help.openai.com/\n")
|
|
180
|
+
elif provider_name == "gemini":
|
|
181
|
+
guidance.append(" • Status Page: https://status.cloud.google.com/\n")
|
|
182
|
+
guidance.append(" • Support: https://support.google.com/\n")
|
|
183
|
+
elif provider_name == "ollama":
|
|
184
|
+
guidance.append(" • Documentation: https://ollama.ai/docs\n")
|
|
185
|
+
guidance.append(" • GitHub: https://github.com/ollama/ollama\n")
|
|
186
|
+
|
|
187
|
+
panel = Panel(
|
|
188
|
+
guidance,
|
|
189
|
+
title=f"🔧 {info['name']} Troubleshooting",
|
|
190
|
+
border_style="yellow",
|
|
191
|
+
padding=(1, 2),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
self.console.print(panel)
|
|
195
|
+
|
|
196
|
+
def _add_provider_specific_guidance(
|
|
197
|
+
self, guidance: Text, provider_name: str, error: str
|
|
198
|
+
) -> None:
|
|
199
|
+
"""
|
|
200
|
+
Add provider-specific troubleshooting guidance.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
guidance: Text object to append guidance to
|
|
204
|
+
provider_name: Name of the provider
|
|
205
|
+
error: Error message
|
|
206
|
+
"""
|
|
207
|
+
if provider_name == "claude":
|
|
208
|
+
guidance.append("🎭 Claude-Specific:\n", style="bold")
|
|
209
|
+
guidance.append(
|
|
210
|
+
" • Ensure you're using the correct API version (2023-06-01)\n"
|
|
211
|
+
)
|
|
212
|
+
guidance.append(
|
|
213
|
+
" • Check message format (Claude uses specific message structure)\n"
|
|
214
|
+
)
|
|
215
|
+
guidance.append(
|
|
216
|
+
" • Verify your account tier supports the selected model\n"
|
|
217
|
+
)
|
|
218
|
+
guidance.append(" • Try reducing max_tokens if getting context errors\n\n")
|
|
219
|
+
|
|
220
|
+
elif provider_name == "openai":
|
|
221
|
+
guidance.append("🤖 OpenAI-Specific:\n", style="bold")
|
|
222
|
+
guidance.append(
|
|
223
|
+
" • Check if you have access to GPT-4 models (requires separate approval)\n"
|
|
224
|
+
)
|
|
225
|
+
guidance.append(" • Verify your organization ID if using team accounts\n")
|
|
226
|
+
guidance.append(" • Ensure billing is set up for API usage\n")
|
|
227
|
+
guidance.append(" • Try gpt-3.5-turbo if GPT-4 access is limited\n\n")
|
|
228
|
+
|
|
229
|
+
elif provider_name == "gemini":
|
|
230
|
+
guidance.append("🔮 Gemini-Specific:\n", style="bold")
|
|
231
|
+
guidance.append(
|
|
232
|
+
" • Enable the Generative AI API in Google Cloud Console\n"
|
|
233
|
+
)
|
|
234
|
+
guidance.append(" • Check safety settings if requests are being blocked\n")
|
|
235
|
+
guidance.append(
|
|
236
|
+
" • Verify your Google Cloud project has billing enabled\n"
|
|
237
|
+
)
|
|
238
|
+
guidance.append(" • Try gemini-1.5-flash for faster/cheaper responses\n\n")
|
|
239
|
+
|
|
240
|
+
elif provider_name == "perplexity":
|
|
241
|
+
guidance.append("🔍 Perplexity-Specific:\n", style="bold")
|
|
242
|
+
guidance.append(
|
|
243
|
+
" • Ensure you have a Perplexity Pro subscription for API access\n"
|
|
244
|
+
)
|
|
245
|
+
guidance.append(" • Check your monthly API usage limits\n")
|
|
246
|
+
guidance.append(
|
|
247
|
+
" • Verify search functionality is enabled for your account\n"
|
|
248
|
+
)
|
|
249
|
+
guidance.append(
|
|
250
|
+
" • Try 'sonar' model if 'sonar-pro' is not accessible\n\n"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
elif provider_name == "xai":
|
|
254
|
+
guidance.append("🚀 xAI-Specific:\n", style="bold")
|
|
255
|
+
guidance.append(
|
|
256
|
+
" • Ensure you have access to the xAI API (currently in beta)\n"
|
|
257
|
+
)
|
|
258
|
+
guidance.append(" • Check your account status and API limits\n")
|
|
259
|
+
guidance.append(" • Verify you're using the correct endpoint URL\n")
|
|
260
|
+
guidance.append(" • Try 'grok-3-fast' for quicker responses\n\n")
|
|
261
|
+
|
|
262
|
+
elif provider_name == "mistral":
|
|
263
|
+
guidance.append("🌪️ Mistral-Specific:\n", style="bold")
|
|
264
|
+
guidance.append(
|
|
265
|
+
" • Ensure you have a Mistral AI account with API access\n"
|
|
266
|
+
)
|
|
267
|
+
guidance.append(" • Check your usage quotas and billing status\n")
|
|
268
|
+
guidance.append(" • Verify the model name matches exactly\n")
|
|
269
|
+
guidance.append(" • Try 'mistral-small-3.1' for cost-effective usage\n\n")
|
|
270
|
+
|
|
271
|
+
elif provider_name == "cohere":
|
|
272
|
+
guidance.append("🧠 Cohere-Specific:\n", style="bold")
|
|
273
|
+
guidance.append(" • Ensure you have a Cohere account with API access\n")
|
|
274
|
+
guidance.append(" • Check your trial credits or subscription status\n")
|
|
275
|
+
guidance.append(" • Verify the model supports your use case\n")
|
|
276
|
+
guidance.append(" • Try 'command-light' for faster responses\n\n")
|
|
277
|
+
|
|
278
|
+
elif provider_name == "ollama":
|
|
279
|
+
guidance.append("🏠 Ollama-Specific:\n", style="bold")
|
|
280
|
+
guidance.append(" • Start Ollama server: 'ollama serve'\n")
|
|
281
|
+
guidance.append(" • Install required model: 'ollama pull <model-name>'\n")
|
|
282
|
+
guidance.append(" • Check server URL (default: http://localhost:11434)\n")
|
|
283
|
+
guidance.append(" • Ensure sufficient RAM for the model size\n")
|
|
284
|
+
guidance.append(" • List available models: 'ollama list'\n")
|
|
285
|
+
guidance.append(" • Check Ollama logs for detailed error messages\n\n")
|
|
286
|
+
|
|
287
|
+
def show_provider_help(self, provider_name: str) -> None:
|
|
288
|
+
"""
|
|
289
|
+
Show provider-specific setup help.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
provider_name: Name of the provider
|
|
293
|
+
"""
|
|
294
|
+
if provider_name not in self.provider_info:
|
|
295
|
+
self.console.print(f"[red]Unknown provider: {provider_name}[/red]")
|
|
296
|
+
return
|
|
297
|
+
|
|
298
|
+
info = self.provider_info[provider_name]
|
|
299
|
+
|
|
300
|
+
help_text = Text()
|
|
301
|
+
help_text.append(f"{info['name']} Setup Guide\n\n", style="bold blue")
|
|
302
|
+
help_text.append(f"{info['description']}\n\n")
|
|
303
|
+
help_text.append("Strengths:\n", style="bold")
|
|
304
|
+
for strength in info["strengths"]:
|
|
305
|
+
help_text.append(f" • {strength}\n", style="green")
|
|
306
|
+
help_text.append(f"\nAPI Key: {info['api_key_url']}\n", style="bold")
|
|
307
|
+
help_text.append(f"Setup Notes: {info['setup_notes']}\n")
|
|
308
|
+
help_text.append(f"Available Models: {', '.join(info['models'])}\n")
|
|
309
|
+
|
|
310
|
+
panel = Panel(
|
|
311
|
+
help_text,
|
|
312
|
+
title=f"📚 {info['name']} Help",
|
|
313
|
+
border_style="blue",
|
|
314
|
+
padding=(1, 2),
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
self.console.print(panel)
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Validation methods for the setup wizard.
|
|
3
|
+
|
|
4
|
+
This module contains all validation and testing methods for provider
|
|
5
|
+
configurations, including API key validation and credential testing.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
from typing import Any, Dict, Optional
|
|
10
|
+
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
13
|
+
from rich.prompt import Confirm
|
|
14
|
+
|
|
15
|
+
from .models import ProviderConfig
|
|
16
|
+
from .setup_wizard_ui import SetupWizardUI
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SetupWizardValidation:
|
|
20
|
+
"""Validation methods for the setup wizard."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
console: Console,
|
|
25
|
+
provider_info: Dict[str, Dict[str, Any]],
|
|
26
|
+
provider_factory,
|
|
27
|
+
cancellation_handler,
|
|
28
|
+
signal_handler,
|
|
29
|
+
):
|
|
30
|
+
"""
|
|
31
|
+
Initialize the validation component.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
console: Rich console instance
|
|
35
|
+
provider_info: Dictionary of provider information
|
|
36
|
+
provider_factory: Factory for creating provider instances
|
|
37
|
+
cancellation_handler: Handler for cancellation support
|
|
38
|
+
signal_handler: Handler for signal management
|
|
39
|
+
"""
|
|
40
|
+
self.console = console
|
|
41
|
+
self.provider_info = provider_info
|
|
42
|
+
self.provider_factory = provider_factory
|
|
43
|
+
self.cancellation_handler = cancellation_handler
|
|
44
|
+
self.signal_handler = signal_handler
|
|
45
|
+
self.ui = SetupWizardUI(console, provider_info)
|
|
46
|
+
|
|
47
|
+
async def test_configuration(
|
|
48
|
+
self, provider_name: str, provider_config: ProviderConfig
|
|
49
|
+
) -> bool:
|
|
50
|
+
"""
|
|
51
|
+
Test the configuration and provide feedback.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
provider_name: Name of the provider
|
|
55
|
+
provider_config: Provider configuration to test
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
True if configuration is valid, False otherwise
|
|
59
|
+
"""
|
|
60
|
+
self.console.print(
|
|
61
|
+
f"\n[bold]Testing {self.provider_info[provider_name]['name']} configuration...[/bold]"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
with Progress(
|
|
65
|
+
SpinnerColumn(),
|
|
66
|
+
TextColumn("[progress.description]{task.description}"),
|
|
67
|
+
console=self.console,
|
|
68
|
+
) as progress:
|
|
69
|
+
# Step 1: Validate API key format
|
|
70
|
+
task = progress.add_task("Validating API key format...", total=None)
|
|
71
|
+
|
|
72
|
+
if not self._validate_api_key_format(
|
|
73
|
+
provider_name, provider_config.api_key
|
|
74
|
+
):
|
|
75
|
+
progress.update(task, description="❌ Invalid API key format!")
|
|
76
|
+
await asyncio.sleep(0.5)
|
|
77
|
+
self.console.print("[red]❌ API key format validation failed![/red]")
|
|
78
|
+
self.ui.show_troubleshooting_guidance(
|
|
79
|
+
provider_name, "Invalid API key format"
|
|
80
|
+
)
|
|
81
|
+
return Confirm.ask("Continue with this configuration anyway?")
|
|
82
|
+
|
|
83
|
+
progress.update(task, description="✅ API key format valid!")
|
|
84
|
+
await asyncio.sleep(0.5)
|
|
85
|
+
|
|
86
|
+
# Step 2: Test API connection
|
|
87
|
+
progress.update(task, description="Testing API connection...")
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
# Create provider instance for testing
|
|
91
|
+
provider = self.provider_factory.create_provider(
|
|
92
|
+
provider_name, provider_config
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Test credentials
|
|
96
|
+
is_valid = await self._validate_provider_credentials(provider)
|
|
97
|
+
|
|
98
|
+
if is_valid:
|
|
99
|
+
progress.update(task, description="✅ API connection successful!")
|
|
100
|
+
await asyncio.sleep(0.5)
|
|
101
|
+
|
|
102
|
+
# Step 3: Test model availability
|
|
103
|
+
progress.update(task, description="Checking model availability...")
|
|
104
|
+
|
|
105
|
+
# Special handling for Bedrock to test model access
|
|
106
|
+
if provider_name == "bedrock" and hasattr(
|
|
107
|
+
provider, "validate_model_access"
|
|
108
|
+
):
|
|
109
|
+
model_test = await provider.validate_model_access()
|
|
110
|
+
|
|
111
|
+
if model_test["success"]:
|
|
112
|
+
progress.update(task, description="✅ Model accessible!")
|
|
113
|
+
await asyncio.sleep(0.5)
|
|
114
|
+
self.console.print(
|
|
115
|
+
"[green]✅ All configuration tests passed![/green]"
|
|
116
|
+
)
|
|
117
|
+
return True
|
|
118
|
+
else:
|
|
119
|
+
progress.update(task, description="⚠️ Model access issue!")
|
|
120
|
+
await asyncio.sleep(0.5)
|
|
121
|
+
|
|
122
|
+
# Show detailed model access warning
|
|
123
|
+
self.console.print(
|
|
124
|
+
f"[yellow]⚠️ Model Access Warning: {model_test['message']}[/yellow]"
|
|
125
|
+
)
|
|
126
|
+
if "suggestion" in model_test:
|
|
127
|
+
self.console.print(
|
|
128
|
+
f"[dim]💡 Suggestion: {model_test['suggestion']}[/dim]"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
self.console.print(
|
|
132
|
+
"\n[yellow]Note: Configuration will be saved, but you may need to:[/yellow]"
|
|
133
|
+
)
|
|
134
|
+
self.console.print(
|
|
135
|
+
" [yellow]• Request model access in AWS Console[/yellow]"
|
|
136
|
+
)
|
|
137
|
+
self.console.print(
|
|
138
|
+
" [yellow]• Check account permissions[/yellow]"
|
|
139
|
+
)
|
|
140
|
+
self.console.print(
|
|
141
|
+
" [yellow]• Try a different model[/yellow]"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return Confirm.ask(
|
|
145
|
+
"Continue with this configuration?",
|
|
146
|
+
default=True,
|
|
147
|
+
)
|
|
148
|
+
else:
|
|
149
|
+
# Standard model availability check for other providers
|
|
150
|
+
model_available = await self._check_model_availability(
|
|
151
|
+
provider, provider_config.model
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if model_available:
|
|
155
|
+
progress.update(task, description="✅ Model available!")
|
|
156
|
+
await asyncio.sleep(0.5)
|
|
157
|
+
self.console.print(
|
|
158
|
+
"[green]✅ All configuration tests passed![/green]"
|
|
159
|
+
)
|
|
160
|
+
return True
|
|
161
|
+
else:
|
|
162
|
+
progress.update(
|
|
163
|
+
task,
|
|
164
|
+
description="⚠️ Model may not be available!",
|
|
165
|
+
)
|
|
166
|
+
await asyncio.sleep(0.5)
|
|
167
|
+
self.console.print(
|
|
168
|
+
"[yellow]⚠️ Model availability could not be confirmed[/yellow]"
|
|
169
|
+
)
|
|
170
|
+
return Confirm.ask("Continue with this configuration?")
|
|
171
|
+
else:
|
|
172
|
+
progress.update(task, description="❌ API authentication failed!")
|
|
173
|
+
await asyncio.sleep(0.5)
|
|
174
|
+
self.console.print("[red]❌ API authentication failed![/red]")
|
|
175
|
+
self.ui.show_troubleshooting_guidance(
|
|
176
|
+
provider_name, "Authentication failed"
|
|
177
|
+
)
|
|
178
|
+
return Confirm.ask("Continue with this configuration anyway?")
|
|
179
|
+
|
|
180
|
+
except Exception as e:
|
|
181
|
+
progress.update(task, description="❌ Connection test failed!")
|
|
182
|
+
await asyncio.sleep(0.5)
|
|
183
|
+
|
|
184
|
+
self.console.print(f"[red]❌ Configuration test failed: {e}[/red]")
|
|
185
|
+
|
|
186
|
+
# Provide troubleshooting guidance
|
|
187
|
+
self.ui.show_troubleshooting_guidance(provider_name, str(e))
|
|
188
|
+
|
|
189
|
+
return Confirm.ask("Continue with this configuration anyway?")
|
|
190
|
+
|
|
191
|
+
def _validate_api_key_format(
|
|
192
|
+
self, provider_name: str, api_key: Optional[str]
|
|
193
|
+
) -> bool:
|
|
194
|
+
"""
|
|
195
|
+
Validate API key format for a specific provider.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
provider_name: Name of the provider
|
|
199
|
+
api_key: API key to validate
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
True if format is valid, False otherwise
|
|
203
|
+
"""
|
|
204
|
+
if provider_name in ["ollama", "claude-code"]:
|
|
205
|
+
return True # Ollama and Claude Code don't require API key
|
|
206
|
+
|
|
207
|
+
if not api_key:
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
info = self.provider_info.get(provider_name, {})
|
|
211
|
+
prefix = info.get("api_key_prefix", "")
|
|
212
|
+
|
|
213
|
+
# Basic format validation
|
|
214
|
+
if prefix and not api_key.startswith(prefix):
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
# Provider-specific validation
|
|
218
|
+
if provider_name == "claude":
|
|
219
|
+
return api_key.startswith("sk-ant-") and len(api_key) > 20
|
|
220
|
+
elif provider_name == "openai":
|
|
221
|
+
return api_key.startswith("sk-") and len(api_key) > 20
|
|
222
|
+
elif provider_name == "gemini":
|
|
223
|
+
return api_key.startswith("AIza") and len(api_key) > 20
|
|
224
|
+
elif provider_name == "perplexity":
|
|
225
|
+
return api_key.startswith("pplx-") and len(api_key) > 20
|
|
226
|
+
elif provider_name == "xai":
|
|
227
|
+
return api_key.startswith("xai-") and len(api_key) > 20
|
|
228
|
+
elif provider_name == "bedrock":
|
|
229
|
+
return api_key.startswith("ABSKQmVkcm9ja0FQSUtleS1") and len(api_key) > 50
|
|
230
|
+
elif provider_name == "mistral":
|
|
231
|
+
return len(api_key) > 20 # Mistral keys don't have consistent prefix
|
|
232
|
+
elif provider_name == "cohere":
|
|
233
|
+
return len(api_key) > 20 # Cohere keys don't have consistent prefix
|
|
234
|
+
|
|
235
|
+
return len(api_key) > 10 # Generic validation
|
|
236
|
+
|
|
237
|
+
async def _validate_provider_credentials(self, provider) -> bool:
|
|
238
|
+
"""
|
|
239
|
+
Validate provider credentials by making a test API call.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
provider: Provider instance to test
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
True if credentials are valid, False otherwise
|
|
246
|
+
"""
|
|
247
|
+
try:
|
|
248
|
+
# Use the provider's validate_credentials method with cancellation support
|
|
249
|
+
async def credential_validation():
|
|
250
|
+
return await provider.validate_credentials()
|
|
251
|
+
|
|
252
|
+
return await self.cancellation_handler.start_cancellable_operation(
|
|
253
|
+
operation=credential_validation,
|
|
254
|
+
status_message="Testing API credentials",
|
|
255
|
+
cancellation_message="Credential validation cancelled",
|
|
256
|
+
signal_handler=self.signal_handler,
|
|
257
|
+
)
|
|
258
|
+
except asyncio.CancelledError:
|
|
259
|
+
# Operation was cancelled by user
|
|
260
|
+
return False
|
|
261
|
+
except Exception:
|
|
262
|
+
# Log the error but don't raise it
|
|
263
|
+
return False
|
|
264
|
+
|
|
265
|
+
async def _check_model_availability(self, provider, model_name: str) -> bool:
|
|
266
|
+
"""
|
|
267
|
+
Check if a specific model is available for the provider.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
provider: Provider instance
|
|
271
|
+
model_name: Name of the model to check
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
True if model is available, False otherwise
|
|
275
|
+
"""
|
|
276
|
+
try:
|
|
277
|
+
# Try to get available models with cancellation support
|
|
278
|
+
async def model_availability_check():
|
|
279
|
+
available_models = provider.get_available_models()
|
|
280
|
+
model_names = [model.name for model in available_models]
|
|
281
|
+
return model_name in model_names
|
|
282
|
+
|
|
283
|
+
return await self.cancellation_handler.start_cancellable_operation(
|
|
284
|
+
operation=model_availability_check,
|
|
285
|
+
status_message="Checking model availability",
|
|
286
|
+
cancellation_message="Model availability check cancelled",
|
|
287
|
+
signal_handler=self.signal_handler,
|
|
288
|
+
)
|
|
289
|
+
except asyncio.CancelledError:
|
|
290
|
+
# Operation was cancelled by user
|
|
291
|
+
return True # Assume available to allow setup to continue
|
|
292
|
+
except Exception:
|
|
293
|
+
# If we can't check model availability, assume it's available
|
|
294
|
+
# This prevents blocking setup for providers that don't support model listing
|
|
295
|
+
return True
|
|
296
|
+
|
|
297
|
+
async def validate_existing_configuration(
|
|
298
|
+
self, provider_name: str, config_manager
|
|
299
|
+
) -> Dict[str, Any]:
|
|
300
|
+
"""
|
|
301
|
+
Validate an existing provider configuration.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
provider_name: Name of the provider to validate
|
|
305
|
+
config_manager: Configuration manager instance
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Dictionary with validation results
|
|
309
|
+
"""
|
|
310
|
+
result = {
|
|
311
|
+
"provider": provider_name,
|
|
312
|
+
"valid": False,
|
|
313
|
+
"errors": [],
|
|
314
|
+
"warnings": [],
|
|
315
|
+
"suggestions": [],
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
# Get existing configuration
|
|
320
|
+
provider_config = config_manager.get_provider_config(provider_name)
|
|
321
|
+
if not provider_config:
|
|
322
|
+
result["errors"].append(
|
|
323
|
+
f"No configuration found for provider '{provider_name}'"
|
|
324
|
+
)
|
|
325
|
+
return result
|
|
326
|
+
|
|
327
|
+
# Validate API key format
|
|
328
|
+
if not self._validate_api_key_format(
|
|
329
|
+
provider_name, provider_config.api_key
|
|
330
|
+
):
|
|
331
|
+
result["errors"].append("Invalid API key format")
|
|
332
|
+
result["suggestions"].append(
|
|
333
|
+
f"Check API key format for {self.provider_info[provider_name]['name']}"
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# Test credentials
|
|
337
|
+
try:
|
|
338
|
+
provider = self.provider_factory.create_provider(
|
|
339
|
+
provider_name, provider_config.model_dump(mode="json")
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
credentials_valid = await self._validate_provider_credentials(provider)
|
|
343
|
+
if not credentials_valid:
|
|
344
|
+
result["errors"].append("API credentials validation failed")
|
|
345
|
+
result["suggestions"].append("Verify API key and account status")
|
|
346
|
+
else:
|
|
347
|
+
# Check model availability
|
|
348
|
+
model_available = await self._check_model_availability(
|
|
349
|
+
provider, provider_config.model
|
|
350
|
+
)
|
|
351
|
+
if not model_available:
|
|
352
|
+
result["warnings"].append(
|
|
353
|
+
f"Model '{provider_config.model}' may not be available"
|
|
354
|
+
)
|
|
355
|
+
result["suggestions"].append(
|
|
356
|
+
"Consider updating to a newer model"
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
except Exception as e:
|
|
360
|
+
result["errors"].append(f"Provider initialization failed: {str(e)}")
|
|
361
|
+
result["suggestions"].append(
|
|
362
|
+
"Check provider configuration and network connectivity"
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Set overall validity
|
|
366
|
+
result["valid"] = len(result["errors"]) == 0
|
|
367
|
+
|
|
368
|
+
except Exception as e:
|
|
369
|
+
result["errors"].append(f"Configuration validation failed: {str(e)}")
|
|
370
|
+
|
|
371
|
+
return result
|