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.
Files changed (126) hide show
  1. omnimancer/__init__.py +42 -0
  2. omnimancer/__main__.py +9 -0
  3. omnimancer/cli/__init__.py +8 -0
  4. omnimancer/cli/agent_manager.py +826 -0
  5. omnimancer/cli/agent_wizard.py +944 -0
  6. omnimancer/cli/approval_formatter.py +734 -0
  7. omnimancer/cli/approval_integration.py +734 -0
  8. omnimancer/cli/approval_prompt.py +1286 -0
  9. omnimancer/cli/batch_approval_display.py +617 -0
  10. omnimancer/cli/batch_approval_filters.py +729 -0
  11. omnimancer/cli/batch_operation_monitor.py +775 -0
  12. omnimancer/cli/commands/performance_cmd.py +624 -0
  13. omnimancer/cli/commands.py +757 -0
  14. omnimancer/cli/completion_handler.py +281 -0
  15. omnimancer/cli/config_setup_wizard.py +783 -0
  16. omnimancer/cli/dynamic_commands.py +371 -0
  17. omnimancer/cli/handlers/__init__.py +18 -0
  18. omnimancer/cli/handlers/agent_handler.py +865 -0
  19. omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
  20. omnimancer/cli/handlers/custom_agent_handler.py +352 -0
  21. omnimancer/cli/handlers/permissions_handler.py +1609 -0
  22. omnimancer/cli/interface.py +6695 -0
  23. omnimancer/cli/performance_dashboard.py +612 -0
  24. omnimancer/core/__init__.py +34 -0
  25. omnimancer/core/agent/__init__.py +256 -0
  26. omnimancer/core/agent/agent_config.py +715 -0
  27. omnimancer/core/agent/agent_switcher.py +668 -0
  28. omnimancer/core/agent/approval_context.py +460 -0
  29. omnimancer/core/agent/approval_dialog.py +902 -0
  30. omnimancer/core/agent/approval_interface.py +628 -0
  31. omnimancer/core/agent/approval_manager.py +760 -0
  32. omnimancer/core/agent/cli_integration.py +470 -0
  33. omnimancer/core/agent/config.py +845 -0
  34. omnimancer/core/agent/custom_agent_integration.py +654 -0
  35. omnimancer/core/agent/diff_renderer.py +706 -0
  36. omnimancer/core/agent/file_content_display.py +503 -0
  37. omnimancer/core/agent/file_modification_workflow.py +658 -0
  38. omnimancer/core/agent/file_system_manager.py +1790 -0
  39. omnimancer/core/agent/input_handler.py +614 -0
  40. omnimancer/core/agent/metrics_collector.py +771 -0
  41. omnimancer/core/agent/optimization_engine.py +729 -0
  42. omnimancer/core/agent/performance_monitor.py +717 -0
  43. omnimancer/core/agent/persona.py +870 -0
  44. omnimancer/core/agent/persona_validator.py +672 -0
  45. omnimancer/core/agent/program_executor.py +748 -0
  46. omnimancer/core/agent/proposed_changes_integration.py +663 -0
  47. omnimancer/core/agent/read_before_write_errors.py +492 -0
  48. omnimancer/core/agent/read_before_write_ui.py +647 -0
  49. omnimancer/core/agent/rich_renderer.py +763 -0
  50. omnimancer/core/agent/status_config.py +238 -0
  51. omnimancer/core/agent/status_core.py +446 -0
  52. omnimancer/core/agent/status_integrated_engine.py +540 -0
  53. omnimancer/core/agent/status_manager.py +878 -0
  54. omnimancer/core/agent/status_ui_components.py +535 -0
  55. omnimancer/core/agent/template_applicator.py +548 -0
  56. omnimancer/core/agent/terminal_status_listener.py +394 -0
  57. omnimancer/core/agent/token_tracker.py +679 -0
  58. omnimancer/core/agent/types.py +60 -0
  59. omnimancer/core/agent/web_client.py +745 -0
  60. omnimancer/core/agent/workflow_orchestrator.py +826 -0
  61. omnimancer/core/agent_engine.py +1625 -0
  62. omnimancer/core/agent_mode_manager.py +706 -0
  63. omnimancer/core/agent_progress_ui.py +545 -0
  64. omnimancer/core/chat_manager.py +141 -0
  65. omnimancer/core/config_manager.py +2199 -0
  66. omnimancer/core/config_migration.py +772 -0
  67. omnimancer/core/config_provider.py +1974 -0
  68. omnimancer/core/config_security_validator.py +393 -0
  69. omnimancer/core/config_validator.py +768 -0
  70. omnimancer/core/conversation_manager.py +352 -0
  71. omnimancer/core/engine.py +787 -0
  72. omnimancer/core/enhanced_input.py +523 -0
  73. omnimancer/core/env_loader.py +83 -0
  74. omnimancer/core/fallback_manager.py +568 -0
  75. omnimancer/core/health_monitor.py +462 -0
  76. omnimancer/core/history_manager.py +462 -0
  77. omnimancer/core/mcp_integration_layer.py +878 -0
  78. omnimancer/core/models.py +1889 -0
  79. omnimancer/core/provider_capabilities.py +403 -0
  80. omnimancer/core/provider_initializer.py +450 -0
  81. omnimancer/core/provider_registry.py +432 -0
  82. omnimancer/core/security/__init__.py +16 -0
  83. omnimancer/core/security/approval_workflow.py +406 -0
  84. omnimancer/core/security/audit_logger.py +511 -0
  85. omnimancer/core/security/permission_controller.py +511 -0
  86. omnimancer/core/security/sandbox_manager.py +379 -0
  87. omnimancer/core/security/security_manager.py +530 -0
  88. omnimancer/core/setup_wizard.py +345 -0
  89. omnimancer/core/setup_wizard_core.py +449 -0
  90. omnimancer/core/setup_wizard_guide_generator.py +376 -0
  91. omnimancer/core/setup_wizard_provider_setup.py +454 -0
  92. omnimancer/core/setup_wizard_ui.py +317 -0
  93. omnimancer/core/setup_wizard_validation.py +371 -0
  94. omnimancer/core/signal_handler.py +174 -0
  95. omnimancer/mcp/__init__.py +11 -0
  96. omnimancer/mcp/client.py +725 -0
  97. omnimancer/mcp/manager.py +649 -0
  98. omnimancer/providers/__init__.py +24 -0
  99. omnimancer/providers/azure.py +608 -0
  100. omnimancer/providers/base.py +262 -0
  101. omnimancer/providers/bedrock.py +711 -0
  102. omnimancer/providers/claude.py +426 -0
  103. omnimancer/providers/claude_code.py +406 -0
  104. omnimancer/providers/cohere.py +412 -0
  105. omnimancer/providers/factory.py +988 -0
  106. omnimancer/providers/gemini.py +708 -0
  107. omnimancer/providers/mistral.py +554 -0
  108. omnimancer/providers/ollama.py +510 -0
  109. omnimancer/providers/openai.py +362 -0
  110. omnimancer/providers/openrouter.py +709 -0
  111. omnimancer/providers/perplexity.py +476 -0
  112. omnimancer/providers/vertex.py +617 -0
  113. omnimancer/providers/xai.py +484 -0
  114. omnimancer/ui/__init__.py +6 -0
  115. omnimancer/ui/cancellation_handler.py +235 -0
  116. omnimancer/ui/progress_indicator.py +318 -0
  117. omnimancer/utils/__init__.py +22 -0
  118. omnimancer/utils/error_handler.py +589 -0
  119. omnimancer/utils/errors.py +667 -0
  120. omnimancer/utils/retry.py +238 -0
  121. omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
  122. omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
  123. omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
  124. omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
  125. omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  126. 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