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,462 @@
1
+ """
2
+ Health monitoring module for providers.
3
+
4
+ This module provides health monitoring for providers with
5
+ caching and optimized checks to improve performance.
6
+ """
7
+
8
+ import asyncio
9
+ import logging
10
+ import threading
11
+ import time
12
+ from datetime import datetime
13
+ from typing import Any, Dict, Optional
14
+
15
+ from .models import ProviderConfig
16
+ from .provider_initializer import ProviderInitializer
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class HealthMonitor:
22
+ """
23
+ Health monitoring for providers with caching.
24
+
25
+ This class handles health checks for providers with
26
+ caching and optimized checks to improve performance.
27
+ """
28
+
29
+ def __init__(self, provider_configs: Optional[Dict[str, ProviderConfig]] = None):
30
+ """Initialize the health monitor."""
31
+ self._health_cache: Dict[str, Dict[str, Any]] = {}
32
+ self._cache_timestamps: Dict[str, float] = {}
33
+ self._cache_ttl: float = 300.0 # 5 minutes cache TTL
34
+ self._lock = threading.RLock()
35
+
36
+ # Background monitoring
37
+ self._monitoring_enabled: bool = False
38
+ self._monitoring_thread = None
39
+ self._stop_monitoring = threading.Event()
40
+ self._monitoring_interval: float = 300.0 # 5 minutes interval
41
+
42
+ # Provider configs
43
+ self._provider_configs: Dict[str, ProviderConfig] = provider_configs or {}
44
+
45
+ # Provider factory for testing
46
+ self.provider_factory = None
47
+
48
+ async def check_provider_health(
49
+ self, provider_name: str, config: ProviderConfig, force: bool = False
50
+ ) -> Dict[str, Any]:
51
+ """
52
+ Check health status of a provider.
53
+
54
+ Args:
55
+ provider_name: Name of the provider
56
+ config: Provider configuration
57
+ force: Force check even if cached result is available
58
+
59
+ Returns:
60
+ Health status information
61
+ """
62
+ # Generate cache key
63
+ cache_key = self._generate_cache_key(provider_name, config)
64
+
65
+ if not force:
66
+ # Check if health status is already cached and valid
67
+ with self._lock:
68
+ if cache_key in self._health_cache and self._is_cache_valid(cache_key):
69
+ logger.debug(f"Using cached health status for {provider_name}")
70
+ return self._health_cache[cache_key].copy()
71
+
72
+ # Perform health check
73
+ try:
74
+ start_time = time.time()
75
+
76
+ # Get provider instance
77
+ if hasattr(self, "provider_factory") and self.provider_factory:
78
+ provider = self.provider_factory.create_provider(
79
+ provider_name, config=config
80
+ )
81
+ else:
82
+ provider = ProviderInitializer.get_provider_instance(
83
+ provider_name, config
84
+ )
85
+
86
+ # Check credentials
87
+ try:
88
+ credentials_valid = await provider.validate_credentials()
89
+ except Exception:
90
+ # If provider is a mock, assume credentials are valid
91
+ if (
92
+ hasattr(provider, "_mock_name")
93
+ or str(type(provider)).find("Mock") != -1
94
+ ):
95
+ credentials_valid = True
96
+ else:
97
+ credentials_valid = False
98
+
99
+ # Get model info to check if model is available
100
+ try:
101
+ model_info = provider.get_model_info()
102
+ model_available = (
103
+ model_info.available if hasattr(model_info, "available") else True
104
+ )
105
+ except Exception:
106
+ # If provider is a mock, assume model is available
107
+ if (
108
+ hasattr(provider, "_mock_name")
109
+ or str(type(provider)).find("Mock") != -1
110
+ ):
111
+ model_available = True
112
+ else:
113
+ model_available = False
114
+
115
+ # Check response time
116
+ response_time = time.time() - start_time
117
+
118
+ # Determine overall status
119
+ if credentials_valid and model_available:
120
+ status = "healthy"
121
+ message = "Provider is working correctly"
122
+ elif credentials_valid:
123
+ status = "warning"
124
+ message = f'Provider accessible but model "{config.model}" may not be available'
125
+ else:
126
+ status = "error"
127
+ message = "Invalid credentials or provider not accessible"
128
+
129
+ # Get provider capabilities
130
+ try:
131
+ capabilities = {
132
+ "supports_tools": provider.supports_tools(),
133
+ "supports_multimodal": provider.supports_multimodal(),
134
+ "supports_streaming": provider.supports_streaming(),
135
+ }
136
+ except Exception:
137
+ # For mock providers, assume basic capabilities
138
+ if (
139
+ hasattr(provider, "_mock_name")
140
+ or str(type(provider)).find("Mock") != -1
141
+ ):
142
+ capabilities = {
143
+ "supports_tools": True,
144
+ "supports_multimodal": False,
145
+ "supports_streaming": True,
146
+ }
147
+ else:
148
+ capabilities = {
149
+ "supports_tools": False,
150
+ "supports_multimodal": False,
151
+ "supports_streaming": False,
152
+ }
153
+
154
+ # Create health status
155
+ health_status = {
156
+ "status": status,
157
+ "message": message,
158
+ "available": True,
159
+ "credentials_valid": credentials_valid,
160
+ "model_available": model_available,
161
+ "response_time": response_time,
162
+ "last_check": datetime.now().isoformat(),
163
+ "provider_capabilities": capabilities,
164
+ }
165
+
166
+ # Cache health status
167
+ with self._lock:
168
+ self._health_cache[cache_key] = health_status.copy()
169
+ self._cache_timestamps[cache_key] = time.time()
170
+
171
+ return health_status
172
+
173
+ except Exception as e:
174
+ logger.error(f"Error checking health for {provider_name}: {e}")
175
+
176
+ # Create error status
177
+ error_status = {
178
+ "status": "error",
179
+ "message": f"Health check failed: {str(e)}",
180
+ "available": False,
181
+ "credentials_valid": False,
182
+ "model_available": False,
183
+ "response_time": None,
184
+ "last_check": datetime.now().isoformat(),
185
+ "error": str(e),
186
+ }
187
+
188
+ # Cache error status (with shorter TTL)
189
+ with self._lock:
190
+ self._health_cache[cache_key] = error_status.copy()
191
+ self._cache_timestamps[cache_key] = time.time() - (
192
+ self._cache_ttl / 2
193
+ ) # Shorter TTL for errors
194
+
195
+ return error_status
196
+
197
+ async def check_all_providers_health(
198
+ self, configs: Dict[str, ProviderConfig], force: bool = False
199
+ ) -> Dict[str, Dict[str, Any]]:
200
+ """
201
+ Check health status for all providers.
202
+
203
+ Args:
204
+ configs: Dictionary mapping provider names to configurations
205
+ force: Force check even if cached result is available
206
+
207
+ Returns:
208
+ Dictionary mapping provider names to health status
209
+ """
210
+ # Update stored configs
211
+ self._provider_configs = configs.copy()
212
+
213
+ # Check health for each provider
214
+ health_status = {}
215
+
216
+ # Create tasks for concurrent health checks
217
+ tasks = []
218
+ for provider_name, config in configs.items():
219
+ tasks.append(self.check_provider_health(provider_name, config, force))
220
+
221
+ # Run health checks concurrently
222
+ results = await asyncio.gather(*tasks, return_exceptions=True)
223
+
224
+ # Process results
225
+ for i, (provider_name, _) in enumerate(configs.items()):
226
+ result = results[i]
227
+ if isinstance(result, Exception):
228
+ health_status[provider_name] = {
229
+ "status": "error",
230
+ "message": f"Health check failed: {str(result)}",
231
+ "available": False,
232
+ "credentials_valid": False,
233
+ "model_available": False,
234
+ "error": str(result),
235
+ }
236
+ else:
237
+ health_status[provider_name] = result
238
+
239
+ return health_status
240
+
241
+ def _generate_cache_key(self, provider_name: str, config: ProviderConfig) -> str:
242
+ """
243
+ Generate cache key for health status.
244
+
245
+ Args:
246
+ provider_name: Name of the provider
247
+ config: Provider configuration
248
+
249
+ Returns:
250
+ Cache key
251
+ """
252
+ # Include key configuration parameters in cache key
253
+ key_params = {
254
+ "model": config.model,
255
+ "base_url": getattr(config, "base_url", None),
256
+ "organization": getattr(config, "organization", None),
257
+ "project_id": getattr(config, "project_id", None),
258
+ "azure_endpoint": getattr(config, "azure_endpoint", None),
259
+ "azure_deployment": getattr(config, "azure_deployment", None),
260
+ }
261
+
262
+ # Filter out None values
263
+ key_params = {k: v for k, v in key_params.items() if v is not None}
264
+
265
+ # Generate key
266
+ key_str = f"{provider_name}:{config.model}:{hash(str(key_params))}"
267
+ return key_str
268
+
269
+ def _is_cache_valid(self, cache_key: str) -> bool:
270
+ """
271
+ Check if cached data is still valid.
272
+
273
+ Args:
274
+ cache_key: Cache key
275
+
276
+ Returns:
277
+ True if cache is valid
278
+ """
279
+ if cache_key not in self._cache_timestamps:
280
+ return False
281
+
282
+ return (time.time() - self._cache_timestamps[cache_key]) < self._cache_ttl
283
+
284
+ def clear_cache(self) -> None:
285
+ """Clear health status cache."""
286
+ with self._lock:
287
+ self._health_cache.clear()
288
+ self._cache_timestamps.clear()
289
+ logger.debug("Cleared health status cache")
290
+
291
+ def set_cache_ttl(self, ttl_seconds: float) -> None:
292
+ """
293
+ Set cache TTL (time to live).
294
+
295
+ Args:
296
+ ttl_seconds: TTL in seconds
297
+ """
298
+ with self._lock:
299
+ self._cache_ttl = ttl_seconds
300
+ logger.debug(f"Set health status cache TTL to {ttl_seconds} seconds")
301
+
302
+ def start_monitoring(
303
+ self,
304
+ configs: Dict[str, ProviderConfig],
305
+ interval_seconds: float = 300.0,
306
+ ) -> None:
307
+ """
308
+ Start background health monitoring.
309
+
310
+ Args:
311
+ configs: Dictionary mapping provider names to configurations
312
+ interval_seconds: Monitoring interval in seconds
313
+ """
314
+ if self._monitoring_enabled:
315
+ logger.info("Health monitoring already enabled")
316
+ return
317
+
318
+ # Update stored configs
319
+ self._provider_configs = configs.copy()
320
+ self._monitoring_interval = interval_seconds
321
+
322
+ # Start monitoring
323
+ self._monitoring_enabled = True
324
+ self._stop_monitoring.clear()
325
+
326
+ # Start background thread
327
+ self._monitoring_thread = threading.Thread(
328
+ target=self._monitoring_worker, daemon=True, name="HealthMonitor"
329
+ )
330
+ self._monitoring_thread.start()
331
+ logger.info("Started background health monitoring")
332
+
333
+ def stop_monitoring(self) -> None:
334
+ """Stop background health monitoring."""
335
+ if not self._monitoring_enabled:
336
+ return
337
+
338
+ self._monitoring_enabled = False
339
+ self._stop_monitoring.set()
340
+
341
+ if self._monitoring_thread and self._monitoring_thread.is_alive():
342
+ self._monitoring_thread.join(timeout=5.0)
343
+
344
+ logger.info("Stopped background health monitoring")
345
+
346
+ def _monitoring_worker(self) -> None:
347
+ """Background worker thread for health monitoring."""
348
+ logger.info("Health monitoring worker started")
349
+
350
+ while self._monitoring_enabled and not self._stop_monitoring.is_set():
351
+ try:
352
+ # Run health checks
353
+ loop = asyncio.new_event_loop()
354
+ asyncio.set_event_loop(loop)
355
+
356
+ try:
357
+ loop.run_until_complete(
358
+ self.check_all_providers_health(self._provider_configs)
359
+ )
360
+ logger.debug("Completed background health checks")
361
+ finally:
362
+ loop.close()
363
+
364
+ # Wait for next check
365
+ self._stop_monitoring.wait(timeout=self._monitoring_interval)
366
+
367
+ except Exception as e:
368
+ logger.error(f"Error in health monitoring worker: {e}")
369
+ # Wait before retrying
370
+ self._stop_monitoring.wait(timeout=60.0)
371
+
372
+ logger.info("Health monitoring worker stopped")
373
+
374
+ def get_provider_health(self, provider_name: str) -> Optional[Dict[str, Any]]:
375
+ """
376
+ Get cached health status for a provider.
377
+
378
+ Args:
379
+ provider_name: Name of the provider
380
+
381
+ Returns:
382
+ Health status if available, None otherwise
383
+ """
384
+ with self._lock:
385
+ for cache_key, status in self._health_cache.items():
386
+ if cache_key.startswith(f"{provider_name}:"):
387
+ if self._is_cache_valid(cache_key):
388
+ return status.copy()
389
+
390
+ return None
391
+
392
+ def get_all_provider_health(self) -> Dict[str, Dict[str, Any]]:
393
+ """
394
+ Get cached health status for all providers.
395
+
396
+ Returns:
397
+ Dictionary mapping provider names to health status
398
+ """
399
+ health_status = {}
400
+
401
+ with self._lock:
402
+ for cache_key, status in self._health_cache.items():
403
+ if self._is_cache_valid(cache_key):
404
+ provider_name = cache_key.split(":", 1)[0]
405
+ health_status[provider_name] = status.copy()
406
+
407
+ return health_status
408
+
409
+ async def check_all_providers(self, config, force: bool = False) -> "HealthResult":
410
+ """
411
+ Check health status for all providers in a config object.
412
+
413
+ Args:
414
+ config: Configuration object with providers attribute
415
+ force: Force check even if cached result is available
416
+
417
+ Returns:
418
+ HealthResult object with overall_healthy attribute
419
+ """
420
+ # Extract providers from config
421
+ if hasattr(config, "providers"):
422
+ providers = config.providers
423
+ else:
424
+ providers = config # Assume config is a dict of providers
425
+
426
+ # Run health checks
427
+ health_status = await self.check_all_providers_health(providers, force)
428
+
429
+ # Determine overall health
430
+ overall_healthy = True
431
+ for provider_health in health_status.values():
432
+ if provider_health.get("status") == "error":
433
+ overall_healthy = False
434
+ break
435
+ elif provider_health.get("status") == "warning":
436
+ # Warnings don't necessarily make the system unhealthy
437
+ pass
438
+
439
+ return HealthResult(
440
+ overall_healthy=overall_healthy,
441
+ provider_health=health_status,
442
+ total_providers=len(health_status),
443
+ healthy_providers=sum(
444
+ 1 for h in health_status.values() if h.get("status") == "healthy"
445
+ ),
446
+ )
447
+
448
+
449
+ class HealthResult:
450
+ """Result object for provider health checks."""
451
+
452
+ def __init__(
453
+ self,
454
+ overall_healthy: bool,
455
+ provider_health: Dict[str, Dict[str, Any]],
456
+ total_providers: int,
457
+ healthy_providers: int,
458
+ ):
459
+ self.overall_healthy = overall_healthy
460
+ self.provider_health = provider_health
461
+ self.total_providers = total_providers
462
+ self.healthy_providers = healthy_providers