114 lines
3.6 KiB
Python
114 lines
3.6 KiB
Python
|
|
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
|