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

102 lines
3.3 KiB
Python

from __future__ import annotations
from abc import ABC, abstractmethod
from collections.abc import AsyncIterator, Awaitable, Callable
from typing import Any, ClassVar
from yuxi.channels.capabilities import CAPS_SIMPLE_TEXT, ChannelCapabilities
from yuxi.channels.meta import ChannelMeta
from yuxi.channels.models import (
ChannelMessage,
ChannelResponse,
ChannelType,
DeliveryResult,
HealthStatus,
)
class BaseChannelAdapter(ABC):
channel_id: ClassVar[str]
channel_type: ClassVar[ChannelType]
text_chunk_limit: ClassVar[int] = 4096
supports_markdown: ClassVar[bool] = False
supports_streaming: ClassVar[bool] = False
streaming_modes: ClassVar[list[str]] = ["off"]
max_media_size_mb: ClassVar[int] = 100
webhook_path: ClassVar[str | None] = None
capabilities: ClassVar[ChannelCapabilities] = CAPS_SIMPLE_TEXT
meta: ClassVar[ChannelMeta] = ChannelMeta(id="", label="")
def __init__(self, config: dict[str, Any] | None = None):
self.config = config or {}
self._message_handler: Callable[[ChannelMessage], Awaitable[None]] | None = None
@abstractmethod
async def connect(self) -> None: ...
@abstractmethod
async def disconnect(self) -> None: ...
@abstractmethod
async def send(self, response: ChannelResponse) -> DeliveryResult: ...
async def receive(self) -> AsyncIterator[ChannelMessage]:
return
yield # type: ignore[misc]
@abstractmethod
def normalize_inbound(self, raw: Any) -> ChannelMessage: ...
@abstractmethod
def format_outbound(self, response: ChannelResponse) -> Any: ...
@abstractmethod
async def health_check(self) -> HealthStatus: ...
def on_message(self, handler: Callable[[ChannelMessage], Awaitable[None]]) -> None:
self._message_handler = handler
async def _handle_message(self, message: ChannelMessage) -> None:
if self._message_handler:
await self._message_handler(message)
async def send_media(self, chat_id: str, media_type: str, data: Any) -> DeliveryResult:
raise NotImplementedError
async def edit_message(self, chat_id: str, msg_id: str, content: str) -> DeliveryResult:
raise NotImplementedError
async def delete_message(self, chat_id: str, msg_id: str) -> DeliveryResult:
raise NotImplementedError
async def send_reaction(self, chat_id: str, msg_id: str, emoji: str) -> DeliveryResult:
raise NotImplementedError
async def send_stream_chunk(self, chat_id: str, msg_id: str, chunk: str, finished: bool) -> DeliveryResult:
identity = self._build_stream_identity(chat_id, msg_id)
response = ChannelResponse(identity=identity, content=chunk)
return await self.send(response)
def _build_stream_identity(self, chat_id: str, msg_id: str) -> Any:
from yuxi.channels.models import ChannelIdentity
return ChannelIdentity(
channel_id=self.channel_id,
channel_type=self.channel_type,
channel_user_id="",
channel_chat_id=chat_id,
channel_message_id=msg_id,
)
async def verify_webhook_signature(self, headers: dict, body: bytes) -> bool:
return True
async def _refresh_token_if_needed(self) -> bool:
return True
async def pre_connect(self) -> dict:
return {}