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,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
|