ForcePilot/backend/package/yuxi/channels/plugin.py
Kris 7a972055b7 feat: 新增渠道服务基础框架与核心工具类
新增大量渠道适配器相关的协议、策略、工具类与基础设施代码,包括:
1.  多协议定义:认证、消息、配置、网关等核心接口
2.  策略模块:上下文、群聊、去重、防抖等业务策略
3.  工具集:重试、去重、文本分块、消息格式化等SDK工具
4.  基础设施:外部进程管理、事件广播、熔断机制等
5.  账户与管道系统:账户管理、消息处理管道实现
6.  运行时服务:状态收集、维护任务、日志等后台服务
2026-05-12 00:53:57 +08:00

114 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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