from __future__ import annotations from typing import Any, TYPE_CHECKING from yuxi.utils.logging_config import logger if TYPE_CHECKING: from .adapter import UrbitAdapter class UrbitSetupPlugin: def __init__(self, config: dict[str, Any] | None = None): self._config = config or {} self._legacy_migrations_detected = False @property def config(self) -> dict[str, Any]: return self._config def detect_legacy_state_migrations(self) -> list[dict[str, Any]]: from .doctor import create_legacy_private_network_doctor_contract migrations = create_legacy_private_network_doctor_contract(self._config) if migrations: self._legacy_migrations_detected = True logger.info(f"[Urbit SetupPlugin] Detected {len(migrations)} legacy config migrations") return migrations def check_ship_url_reachability(self) -> dict[str, Any]: ship_url = self._config.get("ship_url", "") if not ship_url: return {"reachable": False, "error": "No ship_url configured"} from .probe import validate_urbit_url is_valid, warnings = validate_urbit_url(ship_url) return { "reachable": is_valid, "url": ship_url, "warnings": warnings, } def validate_setup(self) -> list[str]: errors: list[str] = [] ship_url = self._config.get("ship_url", "") if not ship_url: errors.append("ship_url is missing") elif not ship_url.startswith(("http://", "https://")): errors.append("ship_url must use http:// or https:// protocol") ship_name = self._config.get("ship_name", "") if not ship_name: errors.append("ship_name is missing") ship_code = self._config.get("ship_code", "") if not ship_code: errors.append("ship_code is missing") dm_policy = self._config.get("dm_policy", "pairing") if dm_policy not in ("open", "disabled", "pairing", "allowlist"): errors.append(f"dm_policy '{dm_policy}' is invalid") group_policy = self._config.get("group_policy", "open") if group_policy not in ("open", "disabled", "allowlist"): errors.append(f"group_policy '{group_policy}' is invalid") return errors def get_setup_schema(self) -> dict[str, Any]: from .setup import get_setup_schema return get_setup_schema() def get_config_ui_hints(self) -> list[dict[str, Any]]: from .config_ui_hints import get_config_ui_hints return get_config_ui_hints() @property def has_legacy_migrations(self) -> bool: return self._legacy_migrations_detected class UrbitAccountPlugin: def __init__(self, adapter: UrbitAdapter): self._adapter = adapter @property def adapter(self) -> UrbitAdapter: return self._adapter async def handle_inbound(self, raw: dict[str, Any]) -> Any: return self._adapter.normalize_inbound(raw) def format_outbound(self, response) -> dict[str, Any]: return self._adapter.format_outbound(response) def build_stream_identity(self, chat_id: str, msg_id: str): return self._adapter._build_stream_identity(chat_id, msg_id) async def fetch_thread_history(self, resource_path: str, parent_id: str) -> list[dict[str, Any]]: return await self._adapter.fetch_thread_history(resource_path, parent_id) def build_thread_context(self, history: list[dict[str, Any]]) -> str: return self._adapter.build_thread_context(history) async def get_user_info(self, channel_user_id: str) -> dict[str, Any]: return await self._adapter.get_user_info(channel_user_id) async def download_media(self, file_id: str) -> bytes: return await self._adapter.download_media(file_id) class DualPluginRegistry: def __init__(self): self._setup_plugin: UrbitSetupPlugin | None = None self._account_plugins: dict[str, UrbitAccountPlugin] = {} def register_setup_plugin(self, plugin: UrbitSetupPlugin) -> None: self._setup_plugin = plugin logger.debug("[Urbit] SetupPlugin registered") def register_account_plugin(self, name: str, plugin: UrbitAccountPlugin) -> None: self._account_plugins[name] = plugin logger.debug(f"[Urbit] AccountPlugin '{name}' registered") def unregister_account_plugin(self, name: str) -> None: self._account_plugins.pop(name, None) logger.debug(f"[Urbit] AccountPlugin '{name}' unregistered") def get_setup_plugin(self) -> UrbitSetupPlugin | None: return self._setup_plugin def get_account_plugin(self, name: str = "default") -> UrbitAccountPlugin | None: return self._account_plugins.get(name) def list_account_plugins(self) -> list[str]: return list(self._account_plugins.keys()) def clear(self) -> None: self._setup_plugin = None self._account_plugins.clear() logger.debug("[Urbit] DualPluginRegistry cleared") _global_registry: DualPluginRegistry | None = None def get_global_registry() -> DualPluginRegistry: global _global_registry if _global_registry is None: _global_registry = DualPluginRegistry() return _global_registry