from __future__ import annotations from dataclasses import dataclass from typing import TYPE_CHECKING from yuxi.utils.logging_config import logger if TYPE_CHECKING: from yuxi.channels.manager import ChannelManager @dataclass class DiagnosisIssue: severity: str channel_id: str category: str message: str auto_fixable: bool = False class ConfigDoctor: def __init__(self, channel_manager: ChannelManager): self._manager = channel_manager async def diagnose(self, channel_id: str | None = None) -> list[DiagnosisIssue]: issues: list[DiagnosisIssue] = [] channel_ids = [channel_id] if channel_id else self._manager._registry.list_channels() for cid in channel_ids: issues.extend(await self._diagnose_channel(cid)) return issues async def auto_fix(self, issue: DiagnosisIssue) -> bool: if not issue.auto_fixable: return False try: if issue.category == "connectivity": await self._manager.restart_channel(issue.channel_id) return True if issue.category == "credential": return False except Exception: logger.exception(f"Auto-fix failed for {issue.channel_id}: {issue.category}") return False async def suggest_migration(self) -> list[str]: return [] async def _diagnose_channel(self, channel_id: str) -> list[DiagnosisIssue]: issues: list[DiagnosisIssue] = [] channel_config = self._manager._channels_config.get(channel_id, {}) adapter = self._manager._adapters.get(channel_id) if adapter and hasattr(adapter, "diagnose_channel"): try: result = await adapter.diagnose_channel() for finding in result.get("findings", []): severity = finding.get("severity", "warning") if severity == "ok": continue issues.append( DiagnosisIssue( severity="error" if severity == "error" else "warning", channel_id=channel_id, category=finding.get("category", "connectivity"), message=finding.get("message", str(finding)), auto_fixable=finding.get("auto_fixable", False), ) ) except Exception: logger.exception(f"Adapter diagnose failed for {channel_id}") if not channel_config.get("enabled"): return issues required_fields = channel_config.get("required_fields", []) for field in required_fields: if not channel_config.get(field): issues.append( DiagnosisIssue( severity="error", channel_id=channel_id, category="credential", message=f"Missing required config field: {field}", auto_fixable=False, ) ) adapter_cls = self._manager._registry.get(channel_id) if adapter_cls is None: issues.append( DiagnosisIssue( severity="error", channel_id=channel_id, category="registry", message=f"No adapter registered for channel {channel_id}", auto_fixable=False, ) ) return issues