438 lines
22 KiB
Python
438 lines
22 KiB
Python
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import warnings
|
|||
|
|
from dataclasses import MISSING, dataclass, field, fields
|
|||
|
|
from typing import ClassVar
|
|||
|
|
|
|||
|
|
from yuxi.channel.config.defaults import (
|
|||
|
|
BLOCK_STREAMING_BREAK,
|
|||
|
|
BLOCK_STREAMING_CHUNK_BREAK_PREFERENCE,
|
|||
|
|
BLOCK_STREAMING_CHUNK_MAX_CHARS,
|
|||
|
|
BLOCK_STREAMING_CHUNK_MIN_CHARS,
|
|||
|
|
BLOCK_STREAMING_COALESCE_IDLE_MS,
|
|||
|
|
BLOCK_STREAMING_COALESCE_MAX_CHARS,
|
|||
|
|
BLOCK_STREAMING_COALESCE_MIN_CHARS,
|
|||
|
|
SSE_MAX_REASONING_CHARS,
|
|||
|
|
STREAMING_PREVIEW_MIN_INITIAL_CHARS,
|
|||
|
|
STREAMING_PREVIEW_THROTTLE_MS,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 哨兵对象,用于标记字段尚未被显式设置
|
|||
|
|
_UNSET = object()
|
|||
|
|
|
|||
|
|
# 支持的流式传输模式
|
|||
|
|
STREAMING_MODES: tuple[str, ...] = ("card_kit", "block", "c2c_stream_api", "raw")
|
|||
|
|
# 块级流式传输的分割断点类型
|
|||
|
|
BLOCK_STREAMING_BREAKS: tuple[str, ...] = ("text_end", "paragraph", "sentence", "token")
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class ChannelCapabilities:
|
|||
|
|
"""定义通道(Channel)支持的各种能力配置。"""
|
|||
|
|
|
|||
|
|
# 聊天类型,默认支持私聊和群聊
|
|||
|
|
chat_types: list[str] = field(default_factory=lambda: ["direct", "group"])
|
|||
|
|
# 消息类型,默认仅支持文本
|
|||
|
|
message_types: list[str] = field(default_factory=lambda: ["text"])
|
|||
|
|
# 交互方式列表
|
|||
|
|
interactions: list[str] = field(default_factory=list)
|
|||
|
|
|
|||
|
|
# 以下为各类功能开关,默认使用 _UNSET 表示未显式设置
|
|||
|
|
reactions: bool = field(default=_UNSET) # 是否支持消息表情反应
|
|||
|
|
typing_indicator: bool = field(default=_UNSET) # 是否支持输入状态指示
|
|||
|
|
threads: bool = field(default=_UNSET) # 是否支持话题线程
|
|||
|
|
edit: bool = field(default=_UNSET) # 是否支持编辑消息
|
|||
|
|
unsend: bool = field(default=_UNSET) # 是否支持撤回消息
|
|||
|
|
reply: bool = field(default=_UNSET) # 是否支持回复消息
|
|||
|
|
media: bool = field(default=_UNSET) # 是否支持发送媒体
|
|||
|
|
effects: bool = field(default=_UNSET) # 是否支持消息特效
|
|||
|
|
native_commands: bool = field(default=_UNSET) # 是否支持原生命令
|
|||
|
|
polls: bool = field(default=_UNSET) # 是否支持投票
|
|||
|
|
pins: bool = field(default=_UNSET) # 是否支持置顶消息
|
|||
|
|
group_management: bool = field(default=_UNSET) # 是否支持群组管理
|
|||
|
|
adaptive_cards: bool = field(default=_UNSET) # 是否支持自适应卡片
|
|||
|
|
tasks: bool = field(default=_UNSET) # 是否支持任务
|
|||
|
|
events: bool = field(default=_UNSET) # 是否支持事件
|
|||
|
|
notes: bool = field(default=_UNSET) # 是否支持笔记
|
|||
|
|
task_modules: bool = field(default=_UNSET) # 是否支持任务模块
|
|||
|
|
messaging_extensions: bool = field(default=_UNSET) # 是否支持消息扩展
|
|||
|
|
incoming_webhooks: bool = field(default=_UNSET) # 是否支持传入的 Webhook
|
|||
|
|
bot_state: bool = field(default=_UNSET) # 是否支持机器人状态
|
|||
|
|
|
|||
|
|
# 流式传输相关配置
|
|||
|
|
streaming: bool = field(default=_UNSET) # 是否启用流式传输
|
|||
|
|
streaming_mode: str | None = None # 流式传输模式
|
|||
|
|
preview_stream_throttle_ms: int = field(default=_UNSET) # 预览流节流间隔(毫秒)
|
|||
|
|
preview_min_initial_chars: int = field(default=_UNSET) # 预览最小初始字符数
|
|||
|
|
|
|||
|
|
# 块级流式传输配置
|
|||
|
|
block_streaming: bool = field(default=_UNSET) # 是否启用块级流式传输
|
|||
|
|
block_streaming_break: str = field(default=_UNSET) # 块级流式分割断点
|
|||
|
|
block_streaming_chunk_min_chars: int = field(default=_UNSET) # 块最小字符数
|
|||
|
|
block_streaming_chunk_max_chars: int = field(default=_UNSET) # 块最大字符数
|
|||
|
|
block_streaming_chunk_break_preference: str = field(default=_UNSET) # 块分割偏好
|
|||
|
|
block_streaming_coalesce_min_chars: int | None = None # 合并块最小字符数
|
|||
|
|
block_streaming_coalesce_max_chars: int | None = None # 合并块最大字符数
|
|||
|
|
block_streaming_coalesce_idle_ms: int = field(default=_UNSET) # 合并空闲等待时间(毫秒)
|
|||
|
|
|
|||
|
|
# SSE 与轮询配置
|
|||
|
|
sse_support: bool = field(default=_UNSET) # 是否支持 SSE
|
|||
|
|
sse_max_reasoning_chars: int = field(default=_UNSET) # SSE 最大推理字符数
|
|||
|
|
polling_fallback: bool = field(default=_UNSET) # 是否启用轮询降级
|
|||
|
|
|
|||
|
|
# TTS(文本转语音)配置
|
|||
|
|
tts: dict | None = None
|
|||
|
|
|
|||
|
|
# 类变量:已警告的冻结字段集合
|
|||
|
|
_WARNED_FROZEN_FIELDS: ClassVar[frozenset[str]] = frozenset()
|
|||
|
|
|
|||
|
|
def __post_init__(self) -> None:
|
|||
|
|
"""初始化后处理:记录原始值并对未设置的字段应用默认值。"""
|
|||
|
|
self._raw_values = dict(self.__dict__)
|
|||
|
|
# 布尔功能开关默认值均为 False
|
|||
|
|
if self.reactions is _UNSET:
|
|||
|
|
self.reactions = False
|
|||
|
|
if self.typing_indicator is _UNSET:
|
|||
|
|
self.typing_indicator = False
|
|||
|
|
if self.threads is _UNSET:
|
|||
|
|
self.threads = False
|
|||
|
|
if self.edit is _UNSET:
|
|||
|
|
self.edit = False
|
|||
|
|
if self.unsend is _UNSET:
|
|||
|
|
self.unsend = False
|
|||
|
|
if self.reply is _UNSET:
|
|||
|
|
self.reply = False
|
|||
|
|
if self.media is _UNSET:
|
|||
|
|
self.media = False
|
|||
|
|
if self.effects is _UNSET:
|
|||
|
|
self.effects = False
|
|||
|
|
if self.native_commands is _UNSET:
|
|||
|
|
self.native_commands = False
|
|||
|
|
if self.polls is _UNSET:
|
|||
|
|
self.polls = False
|
|||
|
|
if self.pins is _UNSET:
|
|||
|
|
self.pins = False
|
|||
|
|
if self.group_management is _UNSET:
|
|||
|
|
self.group_management = False
|
|||
|
|
if self.adaptive_cards is _UNSET:
|
|||
|
|
self.adaptive_cards = False
|
|||
|
|
if self.tasks is _UNSET:
|
|||
|
|
self.tasks = False
|
|||
|
|
if self.events is _UNSET:
|
|||
|
|
self.events = False
|
|||
|
|
if self.notes is _UNSET:
|
|||
|
|
self.notes = False
|
|||
|
|
if self.task_modules is _UNSET:
|
|||
|
|
self.task_modules = False
|
|||
|
|
if self.messaging_extensions is _UNSET:
|
|||
|
|
self.messaging_extensions = False
|
|||
|
|
if self.incoming_webhooks is _UNSET:
|
|||
|
|
self.incoming_webhooks = False
|
|||
|
|
if self.bot_state is _UNSET:
|
|||
|
|
self.bot_state = False
|
|||
|
|
if self.streaming is _UNSET:
|
|||
|
|
self.streaming = False
|
|||
|
|
if self.block_streaming is _UNSET:
|
|||
|
|
self.block_streaming = False
|
|||
|
|
if self.sse_support is _UNSET:
|
|||
|
|
self.sse_support = False
|
|||
|
|
if self.polling_fallback is _UNSET:
|
|||
|
|
self.polling_fallback = False
|
|||
|
|
|
|||
|
|
# 数值类型字段默认值(来源:config/defaults.py)
|
|||
|
|
if self.preview_stream_throttle_ms is _UNSET:
|
|||
|
|
self.preview_stream_throttle_ms = STREAMING_PREVIEW_THROTTLE_MS
|
|||
|
|
if self.preview_min_initial_chars is _UNSET:
|
|||
|
|
self.preview_min_initial_chars = STREAMING_PREVIEW_MIN_INITIAL_CHARS
|
|||
|
|
|
|||
|
|
if self.block_streaming_break is _UNSET:
|
|||
|
|
self.block_streaming_break = BLOCK_STREAMING_BREAK
|
|||
|
|
if self.block_streaming_chunk_min_chars is _UNSET:
|
|||
|
|
self.block_streaming_chunk_min_chars = BLOCK_STREAMING_CHUNK_MIN_CHARS
|
|||
|
|
if self.block_streaming_chunk_max_chars is _UNSET:
|
|||
|
|
self.block_streaming_chunk_max_chars = BLOCK_STREAMING_CHUNK_MAX_CHARS
|
|||
|
|
if self.block_streaming_chunk_break_preference is _UNSET:
|
|||
|
|
self.block_streaming_chunk_break_preference = BLOCK_STREAMING_CHUNK_BREAK_PREFERENCE
|
|||
|
|
if self.block_streaming_coalesce_idle_ms is _UNSET:
|
|||
|
|
self.block_streaming_coalesce_idle_ms = BLOCK_STREAMING_COALESCE_IDLE_MS
|
|||
|
|
if self.sse_max_reasoning_chars is _UNSET:
|
|||
|
|
self.sse_max_reasoning_chars = SSE_MAX_REASONING_CHARS
|
|||
|
|
|
|||
|
|
# 校验流式传输模式是否合法
|
|||
|
|
if self.streaming_mode is not None and self.streaming_mode not in STREAMING_MODES:
|
|||
|
|
warnings.warn(
|
|||
|
|
f"Unknown streaming_mode: {self.streaming_mode!r}. Known modes: {STREAMING_MODES}",
|
|||
|
|
stacklevel=3,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 校验块级流式分割断点是否合法
|
|||
|
|
if self.block_streaming_break not in BLOCK_STREAMING_BREAKS:
|
|||
|
|
warnings.warn(
|
|||
|
|
f"Unknown block_streaming_break: {self.block_streaming_break!r}. "
|
|||
|
|
f"Known breaks: {BLOCK_STREAMING_BREAKS}",
|
|||
|
|
stacklevel=3,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 校验块字符数范围合理性
|
|||
|
|
if self.block_streaming_chunk_min_chars > self.block_streaming_chunk_max_chars:
|
|||
|
|
raise ValueError(
|
|||
|
|
f"block_streaming_chunk_min_chars ({self.block_streaming_chunk_min_chars}) "
|
|||
|
|
f"must be <= block_streaming_chunk_max_chars ({self.block_streaming_chunk_max_chars})"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 块级流式传输仅在流式传输开启时生效
|
|||
|
|
if self.block_streaming and not self.streaming:
|
|||
|
|
warnings.warn(
|
|||
|
|
"block_streaming=True but streaming=False, block_streaming has no effect",
|
|||
|
|
stacklevel=3,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 校验合并块字符数范围合理性
|
|||
|
|
if self.block_streaming_coalesce_min_chars is not None and self.block_streaming_coalesce_max_chars is not None:
|
|||
|
|
if self.block_streaming_coalesce_min_chars > self.block_streaming_coalesce_max_chars:
|
|||
|
|
raise ValueError(
|
|||
|
|
f"block_streaming_coalesce_min_chars ({self.block_streaming_coalesce_min_chars}) "
|
|||
|
|
f"must be <= block_streaming_coalesce_max_chars ({self.block_streaming_coalesce_max_chars})"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def to_dict(self) -> dict:
|
|||
|
|
"""将能力配置转换为字典格式。"""
|
|||
|
|
return {
|
|||
|
|
"chat_types": list(self.chat_types),
|
|||
|
|
"message_types": list(self.message_types),
|
|||
|
|
"interactions": list(self.interactions),
|
|||
|
|
"reactions": self.reactions,
|
|||
|
|
"typing_indicator": self.typing_indicator,
|
|||
|
|
"threads": self.threads,
|
|||
|
|
"edit": self.edit,
|
|||
|
|
"unsend": self.unsend,
|
|||
|
|
"reply": self.reply,
|
|||
|
|
"media": self.media,
|
|||
|
|
"effects": self.effects,
|
|||
|
|
"native_commands": self.native_commands,
|
|||
|
|
"polls": self.polls,
|
|||
|
|
"pins": self.pins,
|
|||
|
|
"group_management": self.group_management,
|
|||
|
|
"adaptive_cards": self.adaptive_cards,
|
|||
|
|
"tasks": self.tasks,
|
|||
|
|
"events": self.events,
|
|||
|
|
"notes": self.notes,
|
|||
|
|
"task_modules": self.task_modules,
|
|||
|
|
"messaging_extensions": self.messaging_extensions,
|
|||
|
|
"incoming_webhooks": self.incoming_webhooks,
|
|||
|
|
"bot_state": self.bot_state,
|
|||
|
|
"streaming": self.streaming,
|
|||
|
|
"streaming_mode": self.streaming_mode,
|
|||
|
|
"preview_stream_throttle_ms": self.preview_stream_throttle_ms,
|
|||
|
|
"preview_min_initial_chars": self.preview_min_initial_chars,
|
|||
|
|
"block_streaming": self.block_streaming,
|
|||
|
|
"block_streaming_break": self.block_streaming_break,
|
|||
|
|
"block_streaming_chunk_min_chars": self.block_streaming_chunk_min_chars,
|
|||
|
|
"block_streaming_chunk_max_chars": self.block_streaming_chunk_max_chars,
|
|||
|
|
"block_streaming_chunk_break_preference": self.block_streaming_chunk_break_preference,
|
|||
|
|
"block_streaming_coalesce_min_chars": self.block_streaming_coalesce_min_chars,
|
|||
|
|
"block_streaming_coalesce_max_chars": self.block_streaming_coalesce_max_chars,
|
|||
|
|
"block_streaming_coalesce_idle_ms": self.block_streaming_coalesce_idle_ms,
|
|||
|
|
"sse_support": self.sse_support,
|
|||
|
|
"sse_max_reasoning_chars": self.sse_max_reasoning_chars,
|
|||
|
|
"polling_fallback": self.polling_fallback,
|
|||
|
|
"tts": dict(self.tts) if self.tts is not None else None,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def override_with(self, overrides: ChannelCapabilities | dict) -> ChannelCapabilities:
|
|||
|
|
"""使用另一组能力配置覆盖当前配置,返回新的 ChannelCapabilities 实例。"""
|
|||
|
|
if isinstance(overrides, dict):
|
|||
|
|
override_dict: dict | None = overrides
|
|||
|
|
overrides = ChannelCapabilities(**overrides)
|
|||
|
|
else:
|
|||
|
|
override_dict = None
|
|||
|
|
|
|||
|
|
if not isinstance(overrides, ChannelCapabilities):
|
|||
|
|
raise TypeError(f"Expected ChannelCapabilities or dict, got {type(overrides).__name__}")
|
|||
|
|
|
|||
|
|
def _merge_list(base: list[str], override: list[str]) -> list[str]:
|
|||
|
|
"""合并两个列表并去重,保持原有顺序。"""
|
|||
|
|
return list(dict.fromkeys(base + override))
|
|||
|
|
|
|||
|
|
def _should_override(name: str) -> bool:
|
|||
|
|
"""判断指定字段是否需要被覆盖。"""
|
|||
|
|
if override_dict is not None:
|
|||
|
|
return name in override_dict
|
|||
|
|
raw = getattr(overrides, "_raw_values", None)
|
|||
|
|
if raw is None:
|
|||
|
|
return True
|
|||
|
|
raw_value = raw.get(name)
|
|||
|
|
if raw_value is _UNSET:
|
|||
|
|
return False
|
|||
|
|
field_default = _FIELD_DEFAULTS.get(name)
|
|||
|
|
if field_default is not _UNSET:
|
|||
|
|
return raw_value != field_default
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
return ChannelCapabilities(
|
|||
|
|
chat_types=_merge_list(self.chat_types, overrides.chat_types),
|
|||
|
|
message_types=_merge_list(self.message_types, overrides.message_types),
|
|||
|
|
interactions=_merge_list(self.interactions, overrides.interactions),
|
|||
|
|
reactions=overrides.reactions if _should_override("reactions") else self.reactions,
|
|||
|
|
typing_indicator=overrides.typing_indicator if _should_override("typing_indicator") else self.typing_indicator,
|
|||
|
|
threads=overrides.threads if _should_override("threads") else self.threads,
|
|||
|
|
edit=overrides.edit if _should_override("edit") else self.edit,
|
|||
|
|
unsend=overrides.unsend if _should_override("unsend") else self.unsend,
|
|||
|
|
reply=overrides.reply if _should_override("reply") else self.reply,
|
|||
|
|
media=overrides.media if _should_override("media") else self.media,
|
|||
|
|
effects=overrides.effects if _should_override("effects") else self.effects,
|
|||
|
|
native_commands=overrides.native_commands if _should_override("native_commands") else self.native_commands,
|
|||
|
|
polls=overrides.polls if _should_override("polls") else self.polls,
|
|||
|
|
pins=overrides.pins if _should_override("pins") else self.pins,
|
|||
|
|
group_management=overrides.group_management if _should_override("group_management") else self.group_management,
|
|||
|
|
adaptive_cards=overrides.adaptive_cards if _should_override("adaptive_cards") else self.adaptive_cards,
|
|||
|
|
tasks=overrides.tasks if _should_override("tasks") else self.tasks,
|
|||
|
|
events=overrides.events if _should_override("events") else self.events,
|
|||
|
|
notes=overrides.notes if _should_override("notes") else self.notes,
|
|||
|
|
task_modules=overrides.task_modules if _should_override("task_modules") else self.task_modules,
|
|||
|
|
messaging_extensions=overrides.messaging_extensions if _should_override("messaging_extensions") else self.messaging_extensions,
|
|||
|
|
incoming_webhooks=overrides.incoming_webhooks if _should_override("incoming_webhooks") else self.incoming_webhooks,
|
|||
|
|
bot_state=overrides.bot_state if _should_override("bot_state") else self.bot_state,
|
|||
|
|
streaming=overrides.streaming if _should_override("streaming") else self.streaming,
|
|||
|
|
streaming_mode=overrides.streaming_mode if _should_override("streaming_mode") else self.streaming_mode,
|
|||
|
|
preview_stream_throttle_ms=overrides.preview_stream_throttle_ms if _should_override("preview_stream_throttle_ms") else self.preview_stream_throttle_ms,
|
|||
|
|
preview_min_initial_chars=overrides.preview_min_initial_chars if _should_override("preview_min_initial_chars") else self.preview_min_initial_chars,
|
|||
|
|
block_streaming=overrides.block_streaming if _should_override("block_streaming") else self.block_streaming,
|
|||
|
|
block_streaming_break=overrides.block_streaming_break if _should_override("block_streaming_break") else self.block_streaming_break,
|
|||
|
|
block_streaming_chunk_min_chars=overrides.block_streaming_chunk_min_chars if _should_override("block_streaming_chunk_min_chars") else self.block_streaming_chunk_min_chars,
|
|||
|
|
block_streaming_chunk_max_chars=overrides.block_streaming_chunk_max_chars if _should_override("block_streaming_chunk_max_chars") else self.block_streaming_chunk_max_chars,
|
|||
|
|
block_streaming_chunk_break_preference=overrides.block_streaming_chunk_break_preference if _should_override("block_streaming_chunk_break_preference") else self.block_streaming_chunk_break_preference,
|
|||
|
|
block_streaming_coalesce_min_chars=overrides.block_streaming_coalesce_min_chars if _should_override("block_streaming_coalesce_min_chars") else self.block_streaming_coalesce_min_chars,
|
|||
|
|
block_streaming_coalesce_max_chars=overrides.block_streaming_coalesce_max_chars if _should_override("block_streaming_coalesce_max_chars") else self.block_streaming_coalesce_max_chars,
|
|||
|
|
block_streaming_coalesce_idle_ms=overrides.block_streaming_coalesce_idle_ms if _should_override("block_streaming_coalesce_idle_ms") else self.block_streaming_coalesce_idle_ms,
|
|||
|
|
sse_support=overrides.sse_support if _should_override("sse_support") else self.sse_support,
|
|||
|
|
sse_max_reasoning_chars=overrides.sse_max_reasoning_chars if _should_override("sse_max_reasoning_chars") else self.sse_max_reasoning_chars,
|
|||
|
|
polling_fallback=overrides.polling_fallback if _should_override("polling_fallback") else self.polling_fallback,
|
|||
|
|
tts=overrides.tts if _should_override("tts") else self.tts,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def supports(self, feature: str) -> bool:
|
|||
|
|
"""检查是否支持指定功能。"""
|
|||
|
|
return bool(getattr(self, feature, False))
|
|||
|
|
|
|||
|
|
def get_action_registry(self, plugin: object) -> MessageActionRegistry:
|
|||
|
|
"""根据当前能力配置生成消息动作注册表。"""
|
|||
|
|
from yuxi.channel.sdk.actions import MessageAction, MessageActionRegistry
|
|||
|
|
|
|||
|
|
registry = MessageActionRegistry()
|
|||
|
|
|
|||
|
|
base_actions = {MessageAction.SEND, MessageAction.SEND_MEDIA}
|
|||
|
|
if self.reply:
|
|||
|
|
base_actions.add(MessageAction.REPLY)
|
|||
|
|
|
|||
|
|
if self.edit:
|
|||
|
|
base_actions.add(MessageAction.EDIT)
|
|||
|
|
if self.unsend:
|
|||
|
|
base_actions.add(MessageAction.UNSEND)
|
|||
|
|
|
|||
|
|
if self.reactions:
|
|||
|
|
base_actions.add(MessageAction.REACT)
|
|||
|
|
base_actions.add(MessageAction.REACT_REMOVE)
|
|||
|
|
base_actions.add(MessageAction.REACTIONS)
|
|||
|
|
|
|||
|
|
if self.pins:
|
|||
|
|
base_actions.add(MessageAction.PIN)
|
|||
|
|
base_actions.add(MessageAction.UNPIN)
|
|||
|
|
|
|||
|
|
if self.polls:
|
|||
|
|
base_actions.add(MessageAction.POLL)
|
|||
|
|
base_actions.add(MessageAction.POLL_VOTE)
|
|||
|
|
|
|||
|
|
if self.group_management:
|
|||
|
|
base_actions |= {
|
|||
|
|
MessageAction.RENAME_GROUP,
|
|||
|
|
MessageAction.ADD_PARTICIPANT,
|
|||
|
|
MessageAction.REMOVE_PARTICIPANT,
|
|||
|
|
MessageAction.MEMBER_INFO,
|
|||
|
|
MessageAction.PERMISSIONS,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if self.threads:
|
|||
|
|
base_actions |= {
|
|||
|
|
MessageAction.THREAD_CREATE,
|
|||
|
|
MessageAction.THREAD_LIST,
|
|||
|
|
MessageAction.THREAD_REPLY,
|
|||
|
|
MessageAction.TOPIC_CREATE,
|
|||
|
|
MessageAction.TOPIC_EDIT,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for action in base_actions:
|
|||
|
|
registry.register(action, lambda **kwargs: {"success": True})
|
|||
|
|
|
|||
|
|
return registry
|
|||
|
|
|
|||
|
|
def derive_from_adapter(self, adapter) -> ChannelCapabilities:
|
|||
|
|
"""从适配器推导能力配置。"""
|
|||
|
|
actions = {a.value for a in adapter.list_actions()}
|
|||
|
|
return ChannelCapabilities(
|
|||
|
|
chat_types=self.chat_types,
|
|||
|
|
message_types=self.message_types,
|
|||
|
|
reactions="react" in actions or "react_remove" in actions,
|
|||
|
|
edit="edit" in actions,
|
|||
|
|
unsend="unsend" in actions,
|
|||
|
|
polls="poll" in actions or "poll_vote" in actions,
|
|||
|
|
pins="pin" in actions or "unpin" in actions,
|
|||
|
|
group_management=(
|
|||
|
|
"add_participant" in actions
|
|||
|
|
or "remove_participant" in actions
|
|||
|
|
or "rename_group" in actions
|
|||
|
|
),
|
|||
|
|
threads="thread_reply" in actions or "thread_create" in actions or "thread_list" in actions,
|
|||
|
|
media="send_media" in actions,
|
|||
|
|
reply="reply" in actions,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def get_effective_streaming_mode(self) -> str:
|
|||
|
|
"""获取实际生效的流式传输模式。"""
|
|||
|
|
if not self.streaming:
|
|||
|
|
return "none"
|
|||
|
|
if self.streaming_mode:
|
|||
|
|
return self.streaming_mode
|
|||
|
|
return "preview"
|
|||
|
|
|
|||
|
|
def get_chunk_limit(self) -> int:
|
|||
|
|
"""获取分块大小限制。"""
|
|||
|
|
return 4096
|
|||
|
|
|
|||
|
|
def get_effective_block_config(self) -> dict:
|
|||
|
|
"""获取实际生效的块级流式传输配置。"""
|
|||
|
|
text_limit = self.get_chunk_limit()
|
|||
|
|
|
|||
|
|
chunk_min = max(1, self.block_streaming_chunk_min_chars)
|
|||
|
|
chunk_max = max(chunk_min, min(self.block_streaming_chunk_max_chars, text_limit))
|
|||
|
|
|
|||
|
|
coalesce_min = self.block_streaming_coalesce_min_chars or chunk_min
|
|||
|
|
coalesce_max = self.block_streaming_coalesce_max_chars or chunk_max
|
|||
|
|
coalesce_min = min(coalesce_min, coalesce_max)
|
|||
|
|
coalesce_max = min(coalesce_max, chunk_max)
|
|||
|
|
|
|||
|
|
coalesce_idle_ms = max(0, min(self.block_streaming_coalesce_idle_ms, 5000))
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"chunk": {
|
|||
|
|
"min_chars": chunk_min,
|
|||
|
|
"max_chars": chunk_max,
|
|||
|
|
"break_preference": self.block_streaming_chunk_break_preference,
|
|||
|
|
},
|
|||
|
|
"coalesce": {
|
|||
|
|
"min_chars": coalesce_min,
|
|||
|
|
"max_chars": coalesce_max,
|
|||
|
|
"idle_ms": coalesce_idle_ms,
|
|||
|
|
},
|
|||
|
|
"text_limit": text_limit,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 构建字段默认值映射表,用于 override_with 中的覆盖判断
|
|||
|
|
_FIELD_DEFAULTS: dict[str, object] = {}
|
|||
|
|
for _f in fields(ChannelCapabilities):
|
|||
|
|
if _f.default is not MISSING:
|
|||
|
|
_FIELD_DEFAULTS[_f.name] = _f.default
|
|||
|
|
elif _f.default_factory is not MISSING:
|
|||
|
|
_FIELD_DEFAULTS[_f.name] = _f.default_factory()
|