2026-05-12 00:53:57 +08:00
|
|
|
|
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
|
2026-05-13 16:41:11 +08:00
|
|
|
|
from yuxi.channels.registry import _register_builtin
|
2026-05-12 00:53:57 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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]
|
|
|
|
|
|
|
2026-05-13 16:41:11 +08:00
|
|
|
|
_register_builtin(cid, _cls)
|
2026-05-12 00:53:57 +08:00
|
|
|
|
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"),
|
2026-05-13 16:41:11 +08:00
|
|
|
|
pairing={"auto_pair": True},
|
|
|
|
|
|
conversation_bindings={"max_bindings": 5},
|
|
|
|
|
|
agent_prompt="You are a Telegram bot",
|
2026-05-12 00:53:57 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@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]
|
2026-05-13 16:41:11 +08:00
|
|
|
|
if self.channel_type is not None:
|
|
|
|
|
|
cls.channel_type = self.channel_type # type: ignore[attr-defined]
|
|
|
|
|
|
if self.pairing:
|
|
|
|
|
|
cls.pairing = self.pairing # type: ignore[attr-defined]
|
|
|
|
|
|
if self.conversation_bindings:
|
|
|
|
|
|
cls.conversation_bindings = self.conversation_bindings # type: ignore[attr-defined]
|
|
|
|
|
|
if self.agent_prompt is not None:
|
|
|
|
|
|
cls.agent_prompt = self.agent_prompt # type: ignore[attr-defined]
|
|
|
|
|
|
if self.messaging:
|
|
|
|
|
|
cls.messaging = self.messaging # type: ignore[attr-defined]
|
|
|
|
|
|
if self.directory is not None:
|
|
|
|
|
|
cls.directory = self.directory # type: ignore[attr-defined]
|
|
|
|
|
|
|
|
|
|
|
|
_register_builtin(self.channel_id, cls)
|
2026-05-12 00:53:57 +08:00
|
|
|
|
return cls
|