from __future__ import annotations from collections.abc import AsyncIterator from typing import TYPE_CHECKING, Any if TYPE_CHECKING: from yuxi.channels.base import BaseChannelAdapter from yuxi.channels.models import ChannelMessage, ChannelResponse, DeliveryResult, HealthStatus class BridgeAdapter: def __init__(self, legacy: BaseChannelAdapter): self._legacy = legacy @property def channel_id(self) -> str: return self._legacy.channel_id @property def channel_type(self) -> str: return self._legacy.channel_type.value def is_enabled(self) -> bool: return bool(self._legacy.config.get("enabled", False)) def is_configured(self) -> bool: return bool(self._legacy.config) def list_account_ids(self) -> list[str]: return [self.channel_id] def resolve_account(self, account_id: str) -> dict | None: return self._legacy.config if account_id == self.channel_id else None async def connect(self) -> None: await self._legacy.connect() async def disconnect(self) -> None: await self._legacy.disconnect() async def health_check(self) -> HealthStatus: return await self._legacy.health_check() async def pre_connect(self) -> dict: return await self._legacy.pre_connect() async def send(self, response: ChannelResponse) -> DeliveryResult: return await self._legacy.send(response) async def send_media(self, chat_id: str, media_type: str, data: Any) -> DeliveryResult: return await self._legacy.send_media(chat_id, media_type, data) async def edit_message(self, chat_id: str, msg_id: str, content: str) -> DeliveryResult: return await self._legacy.edit_message(chat_id, msg_id, content) async def delete_message(self, chat_id: str, msg_id: str) -> DeliveryResult: return await self._legacy.delete_message(chat_id, msg_id) async def send_reaction(self, chat_id: str, msg_id: str, emoji: str) -> DeliveryResult: return await self._legacy.send_reaction(chat_id, msg_id, emoji) def on_message(self, handler) -> None: self._legacy.on_message(handler) def normalize_inbound(self, raw: Any) -> ChannelMessage: return self._legacy.normalize_inbound(raw) def format_outbound(self, response: ChannelResponse) -> Any: return self._legacy.format_outbound(response) async def receive(self) -> AsyncIterator[ChannelMessage]: async for msg in self._legacy.receive(): yield msg async def download_media(self, file_id: str) -> bytes: return await self._legacy.download_media(file_id) async def get_user_info(self, channel_user_id: str) -> dict: return await self._legacy.get_user_info(channel_user_id) def resolve_reply_mode(self, config: dict[str, Any], chat_type: str) -> str: return "off" def resolve_thread_id(self, message: ChannelMessage) -> str | None: return None async def send_typing_indicator(self, chat_id: str) -> None: pass async def send_stream_chunk(self, chat_id: str, msg_id: str, chunk: str, finished: bool) -> DeliveryResult: return await self._legacy.send_stream_chunk(chat_id, msg_id, chunk, finished) @property def status(self) -> str: return getattr(self._legacy, "_status", "unknown") def snapshot(self) -> dict[str, Any]: from yuxi.channels.models import build_snapshot_from_adapter snap = build_snapshot_from_adapter(self._legacy) return snap.model_dump() def default_account_id(self) -> str: return self.channel_id def disabled_reason(self) -> str: return "disabled" if not self.is_enabled() else "" def unconfigured_reason(self) -> str: return "not_configured" if not self.is_configured() else "" def describe_account(self) -> dict: return {"channel_id": self.channel_id, "channel_type": self.channel_type} def resolve_allow_from(self) -> list[str]: return ["*"] def has_configured_state(self) -> bool: return self.is_configured() async def logout_account(self, ctx) -> None: await self._legacy.disconnect() async def login_with_qr_start(self, force: bool, timeout_ms: int) -> str: return "" def chunker(self, text: str, limit: int) -> list[str]: return [text] if len(text) <= limit else [text[i : i + limit] for i in range(0, len(text), limit)] @property def chunker_mode(self) -> str: return "text" @property def text_chunk_limit(self) -> int: return getattr(self._legacy, "text_chunk_limit", 4096) @property def delivery_mode(self) -> str: return "direct" def sanitize_text(self, text: str) -> str: return text async def send_text(self, ctx) -> DeliveryResult: return await self._legacy.send(ctx) async def send_media_url(self, ctx) -> DeliveryResult: return await self._legacy.send(ctx) async def send_poll(self, ctx) -> DeliveryResult: return await self._legacy.send(ctx) async def pin_message(self, chat_id: str, msg_id: str) -> DeliveryResult: raise NotImplementedError async def unpin_message(self, chat_id: str, msg_id: str) -> DeliveryResult: raise NotImplementedError async def probe_account(self, account: dict, timeout_ms: int) -> dict: try: hs = await self.health_check() return {"status": hs.status} except Exception: return {"status": "error"} async def build_account_snapshot(self, account: dict, probe: dict): from yuxi.channels.models import ChannelAccountSnapshot return ChannelAccountSnapshot( account_id=account.get("account_id", self.channel_id), channel_id=self.channel_id, channel_type=self._legacy.channel_type, ) def build_channel_summary(self, account: dict, snapshot: dict) -> dict: return {"channel_id": self.channel_id, "status": self.status} async def audit_account(self, account: dict, timeout_ms: int) -> dict: return {"channel_id": self.channel_id, "audited": True} def resolve_account_state(self, configured: bool, enabled: bool) -> str: if not configured: return "not_configured" if not enabled: return "disabled" return "active" def collect_status_issues(self, accounts: list) -> list[str]: issues = [] if not self.is_configured(): issues.append("not_configured") if not self.is_enabled(): issues.append("disabled") return issues def on_config_changed(self, prev_cfg: dict, next_cfg: dict) -> None: pass async def run_startup_maintenance(self) -> None: await self._legacy._refresh_token_if_needed() def normalize_target(self, raw: Any) -> str: return str(raw) def resolve_inbound_conversation(self, from_: str, to: str, thread_id: str) -> dict: return {"from": from_, "to": to, "thread_id": thread_id} def resolve_delivery_target(self, conversation_id: str) -> dict: return {"chat_id": conversation_id} def infer_target_chat_type(self, to: str) -> str: return "direct" def parse_explicit_target(self, raw: str) -> dict: return {"target": raw} def transform_reply_payload(self, payload: dict) -> dict: return payload def resolve_outbound_session_route(self, target: dict) -> dict: return {"channel_id": self.channel_id} def build_tool_context(self, context: dict) -> dict: return context def resolve_auto_thread_id(self, to: str, reply_to_id: str) -> str | None: return None def resolve_reply_transport(self, thread_id: str, reply_to_id: str) -> dict: return {"thread_id": thread_id, "reply_to_id": reply_to_id} async def check_ready(self) -> bool: return self.is_enabled() and self.is_configured() async def clear_typing_indicator(self, chat_id: str) -> None: pass