from __future__ import annotations import json import time from dataclasses import dataclass, field from pathlib import Path from typing import Any from yuxi.utils.logging_config import logger @dataclass class ConversationBinding: channel_chat_id: str agent_id: str = "main" label: str = "" created_at: float = field(default_factory=time.time) metadata: dict[str, Any] = field(default_factory=dict) class ConversationBindingManager: def __init__(self, storage_dir: str | None = None, account_id: str = "default"): self._bindings: dict[str, ConversationBinding] = {} self._storage_dir = storage_dir or str(Path.home() / ".yuxi" / "imessage") self._account_id = account_id def create( self, channel_chat_id: str, agent_id: str = "main", label: str = "", metadata: dict[str, Any] | None = None ) -> ConversationBinding: binding = ConversationBinding( channel_chat_id=channel_chat_id, agent_id=agent_id, label=label, metadata=metadata or {}, ) self._bindings[channel_chat_id] = binding logger.info(f"[iMessage/Bindings] Created binding: {channel_chat_id} -> agent:{agent_id}") return binding def get(self, channel_chat_id: str) -> ConversationBinding | None: return self._bindings.get(channel_chat_id) def list_all(self) -> list[ConversationBinding]: return list(self._bindings.values()) def delete(self, channel_chat_id: str) -> bool: if channel_chat_id in self._bindings: del self._bindings[channel_chat_id] logger.info(f"[iMessage/Bindings] Deleted binding: {channel_chat_id}") return True return False def get_agent_for_chat(self, channel_chat_id: str, default: str = "main") -> str: binding = self._bindings.get(channel_chat_id) return binding.agent_id if binding else default async def save_bindings(self) -> None: dir_path = Path(self._storage_dir) / self._account_id dir_path.mkdir(parents=True, exist_ok=True) data = { cid: { "agent_id": b.agent_id, "label": b.label, "created_at": b.created_at, "metadata": b.metadata, } for cid, b in self._bindings.items() } file_path = dir_path / "conversation-bindings.json" file_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8") async def load_bindings(self) -> None: file_path = Path(self._storage_dir) / self._account_id / "conversation-bindings.json" if not file_path.exists(): return try: data = json.loads(file_path.read_text(encoding="utf-8")) for cid, info in data.items(): if isinstance(info, dict): self._bindings[cid] = ConversationBinding( channel_chat_id=cid, agent_id=info.get("agent_id", "main"), label=info.get("label", ""), created_at=info.get("created_at", time.time()), metadata=info.get("metadata", {}), ) except (json.JSONDecodeError, OSError) as e: logger.warning(f"[iMessage/Bindings] Failed to load bindings: {e}")