from __future__ import annotations from typing import Any from yuxi.channels.base import BaseChannelAdapter from yuxi.channels.capabilities import ChannelCapabilities from yuxi.channels.meta import ChannelMeta from yuxi.channels.registry import _BUILTIN_ADAPTERS def channel_plugin( cls: type[BaseChannelAdapter] | None = None, *, channel_id: str | None = None, capabilities: ChannelCapabilities | None = None, meta: ChannelMeta | None = None, order: int = 100, ) -> type[BaseChannelAdapter]: """渠道插件注册装饰器 — 组合入口, 替代 @register_builtin_adapter 用法: @channel_plugin( capabilities=ChannelCapabilities(...), meta=ChannelMeta(id="telegram", label="Telegram"), ) class TelegramAdapter(BaseChannelAdapter): ... 当用作无参装饰器时, 从类属性自动推断 channel_id: @channel_plugin class TelegramAdapter(BaseChannelAdapter): channel_id = "telegram" """ def _decorate(_cls: type[BaseChannelAdapter]) -> type[BaseChannelAdapter]: cid = channel_id or getattr(_cls, "channel_id", None) if not cid: raise ValueError( f"channel_plugin requires channel_id for {_cls.__name__}. " "Set channel_id as a ClassVar or pass it to the decorator." ) if capabilities is not None: _cls.capabilities = capabilities # type: ignore[attr-defined] if meta is not None: _cls.meta = meta # type: ignore[attr-defined] _BUILTIN_ADAPTERS[cid] = _cls return _cls if cls is not None: return _decorate(cls) return _decorate class ChannelPlugin: """渠道插件组合入口 — 聚合协议和能力声明的便利构造器 为渠道开发者提供声明式 API,在适配器类上组合配置: plugin = ChannelPlugin( channel_id="telegram", channel_type=ChannelType.TELEGRAM, capabilities=ChannelCapabilities( chat_types=["direct", "group", "thread"], polls=True, reactions=True, edit=True, supports_streaming=True, streaming_modes=["off", "partial", "block", "progress"], ), meta=ChannelMeta(id="telegram", label="Telegram"), ) @plugin.register class TelegramAdapter(BaseChannelAdapter): ... """ def __init__( self, *, channel_id: str, channel_type: Any = None, capabilities: ChannelCapabilities | None = None, meta: ChannelMeta | None = None, order: int = 100, pairing: dict | None = None, conversation_bindings: dict | None = None, agent_prompt: str | None = None, messaging: dict | None = None, directory: Any = None, ): self.channel_id = channel_id self.channel_type = channel_type self.capabilities = capabilities self.meta = meta or ChannelMeta(id=channel_id, label=channel_id) self.order = order self.pairing = pairing or {} self.conversation_bindings = conversation_bindings or {} self.agent_prompt = agent_prompt self.messaging = messaging or {} self.directory = directory def register(self, cls: type[BaseChannelAdapter]) -> type[BaseChannelAdapter]: if self.capabilities is not None: cls.capabilities = self.capabilities # type: ignore[attr-defined] if self.meta is not None: cls.meta = self.meta # type: ignore[attr-defined] _BUILTIN_ADAPTERS[self.channel_id] = cls return cls