From 3c158760f9611c49b2e2ba7035cb550066f7b1d0 Mon Sep 17 00:00:00 2001 From: whatevertogo Date: Fri, 13 Mar 2026 02:37:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=8D=8F=E8=AE=AE?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=8F=8A=E6=B6=88=E6=81=AF=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=EF=BC=8C=E8=AF=A6=E7=BB=86=E8=AF=B4=E6=98=8E=E6=96=B0=E7=89=88?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E4=B8=8E=E6=97=A7=E7=89=88=E7=9A=84=E5=B7=AE?= =?UTF-8?q?=E5=BC=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-new/astrbot_sdk/protocol/__init__.py | 55 +++++ src-new/astrbot_sdk/protocol/descriptors.py | 150 ++++++++++++ .../astrbot_sdk/protocol/legacy_adapter.py | 96 ++++++++ src-new/astrbot_sdk/protocol/messages.py | 222 ++++++++++++++++++ 4 files changed, 523 insertions(+) diff --git a/src-new/astrbot_sdk/protocol/__init__.py b/src-new/astrbot_sdk/protocol/__init__.py index 882adb401..f0e7c55f6 100644 --- a/src-new/astrbot_sdk/protocol/__init__.py +++ b/src-new/astrbot_sdk/protocol/__init__.py @@ -1,3 +1,58 @@ +"""协议模块。 + +定义 AstrBot SDK 的消息协议和描述符,用于插件与核心之间的通信。 +所有消息均使用 Pydantic 定义,确保类型安全和序列化一致性。 + +架构说明: + 旧版: + - 使用标准 JSON-RPC 2.0 协议 + - 消息类型较少: Request, SuccessResponse, ErrorResponse + - 特定请求类型绑定了 AstrMessageEventModel 事件模型 + - 使用 dataclass 和 pydantic 混合定义 + + 新版: + - 全新的自描述协议,使用 `type` 字段区分消息类型 + - 更丰富的消息类型: Initialize, Result, Invoke, Event, Cancel + - 强大的描述符系统: HandlerDescriptor, CapabilityDescriptor + - 多种触发器类型: Command, Message, Event, Schedule + - 纯 Pydantic 定义,支持严格验证 + - 提供 LegacyAdapter 实现新旧协议互操作 + +协议消息流程: + 1. Initialize: 握手建立连接,交换能力和处理器信息 + 2. Invoke: 调用远程能力 + 3. Event: 流式事件通知 (started/delta/completed/failed) + 4. Result: 调用结果返回 + 5. Cancel: 取消正在进行的调用 + +与旧版对比: + 旧版 JSON-RPC 消息: + { + "jsonrpc": "2.0", + "id": "xxx", + "method": "call_handler", + "params": {"handler_full_name": "...", "event": {...}} + } + + 新版协议消息: + { + "type": "invoke", + "id": "xxx", + "capability": "handler.invoke", + "input": {"handler_id": "...", "event": {...}} + } + +TODO: (功能完善): + - 添加消息签名验证支持,确保消息来源可信 + - 添加消息压缩支持,减少大数据传输开销 + - 添加批量消息支持 (BatchMessage),提高传输效率 + - 添加消息追踪 ID (trace_id) 支持,便于日志关联 + - CapabilityDescriptor 缺少 rate_limit 限流配置 + - HandlerDescriptor 缺少 timeout 超时配置 + - 缺少心跳消息 (HeartbeatMessage) 支持 + - 缺少健康检查消息 (HealthCheckMessage) 支持 +""" + from .descriptors import CapabilityDescriptor, HandlerDescriptor, Permissions from .messages import ( CancelMessage, diff --git a/src-new/astrbot_sdk/protocol/descriptors.py b/src-new/astrbot_sdk/protocol/descriptors.py index 1481c5132..f33412ccf 100644 --- a/src-new/astrbot_sdk/protocol/descriptors.py +++ b/src-new/astrbot_sdk/protocol/descriptors.py @@ -1,3 +1,41 @@ +"""描述符模块。 + +定义处理器和能力的描述符,用于声明式注册和发现。 + +描述符类型概览: + HandlerDescriptor: 处理器描述符,描述一个事件处理函数 + CapabilityDescriptor: 能力描述符,描述一个可调用的远程能力 + Permissions: 权限配置,控制处理器的访问权限 + Trigger: 触发器联合类型,支持多种触发方式 + +触发器类型: + CommandTrigger: 命令触发器,响应特定命令(如 /help) + MessageTrigger: 消息触发器,响应匹配正则或关键词的消息 + EventTrigger: 事件触发器,响应特定类型的事件 + ScheduleTrigger: 定时触发器,按 cron 或间隔时间执行 + +与旧版对比: + 旧版: + - 处理器元信息分散在 handshake 响应中 + - 使用 event_type 整数区分事件类型 + - 缺少声明式的触发器定义 + - 配置通过 extras_configs 字典传递 + + 新版: + - 使用 HandlerDescriptor 统一描述处理器 + - 使用字符串 event_type,更语义化 + - 支持多种触发器类型,声明式定义 + - 使用 Pydantic 模型,类型安全 + +TODO: + - HandlerDescriptor 缺少 timeout 超时配置 + - HandlerDescriptor 缺少 retry 重试配置 + - CapabilityDescriptor 缺少 rate_limit 限流配置 + - ScheduleTrigger 缺错时错过执行的处理策略 + - 缺少 HandlerGroupDescriptor 处理器组描述符 + - 缺少 DependencyDescriptor 依赖声明 +""" + from __future__ import annotations from typing import Annotated, Any, Literal @@ -10,11 +48,36 @@ class _DescriptorBase(BaseModel): class Permissions(_DescriptorBase): + """权限配置,控制处理器的访问权限。 + + 与旧版对比: + 旧版: 通过 extras_configs 字典配置 + {"require_admin": true, "level": 1} + 新版: 使用 Permissions 模型,类型安全 + + Attributes: + require_admin: 是否需要管理员权限 + level: 权限等级,数值越高权限越大 + """ + require_admin: bool = False level: int = 0 class CommandTrigger(_DescriptorBase): + """命令触发器,响应特定命令。 + + 与旧版对比: + 旧版: 使用 @command_handler("help") 装饰器注册 + 新版: 使用 CommandTrigger 声明式定义,支持别名 + + Attributes: + type: 触发器类型,固定为 "command" + command: 命令名称(不含前缀,如 "help") + aliases: 命令别名列表 + description: 命令描述,用于帮助文档 + """ + type: Literal["command"] = "command" command: str aliases: list[str] = Field(default_factory=list) @@ -22,6 +85,19 @@ class CommandTrigger(_DescriptorBase): class MessageTrigger(_DescriptorBase): + """消息触发器,响应匹配正则或关键词的消息。 + + 与旧版对比: + 旧版: 使用 @regex_handler(r"pattern") 或 @message_handler 装饰器 + 新版: 使用 MessageTrigger 声明式定义,支持正则、关键词和平台过滤 + + Attributes: + type: 触发器类型,固定为 "message" + regex: 正则表达式模式,匹配消息文本 + keywords: 关键词列表,消息包含任一关键词即触发 + platforms: 目标平台列表,为空表示所有平台 + """ + type: Literal["message"] = "message" regex: str | None = None keywords: list[str] = Field(default_factory=list) @@ -29,11 +105,37 @@ class MessageTrigger(_DescriptorBase): class EventTrigger(_DescriptorBase): + """事件触发器,响应特定类型的事件。 + + 与旧版对比: + 旧版: 使用整数 event_type,如 3 表示消息事件 + 新版: 使用字符串 event_type,如 "message" 或 "3",更灵活 + + Attributes: + type: 触发器类型,固定为 "event" + event_type: 事件类型,字符串形式(如 "message"、"notice") + """ + type: Literal["event"] = "event" event_type: str class ScheduleTrigger(_DescriptorBase): + """定时触发器,按 cron 表达式或固定间隔执行。 + + 与旧版对比: + 旧版: 使用 @scheduled("0 * * * *") 装饰器 + 新版: 使用 ScheduleTrigger 声明式定义 + + Attributes: + type: 触发器类型,固定为 "schedule" + cron: cron 表达式(如 "0 9 * * *" 表示每天 9 点) + interval_seconds: 执行间隔(秒) + + Note: + cron 和 interval_seconds 必须且只能有一个非空。 + """ + type: Literal["schedule"] = "schedule" cron: str | None = None interval_seconds: int | None = None @@ -51,9 +153,38 @@ Trigger = Annotated[ CommandTrigger | MessageTrigger | EventTrigger | ScheduleTrigger, Field(discriminator="type"), ] +"""触发器联合类型,使用 type 字段作为判别器自动解析具体类型。""" class HandlerDescriptor(_DescriptorBase): + """处理器描述符,描述一个事件处理函数的元信息。 + + 与旧版对比: + 旧版 handshake 响应中的处理器信息: + { + "event_type": 3, + "handler_full_name": "plugin.handler", + "handler_name": "handler", + "handler_module_path": "plugin", + "desc": "描述", + "extras_configs": {"priority": 0, "require_admin": false} + } + + 新版 HandlerDescriptor: + { + "id": "plugin.handler", + "trigger": {"type": "event", "event_type": "message"}, + "priority": 0, + "permissions": {"require_admin": false, "level": 0} + } + + Attributes: + id: 处理器唯一标识,通常是 "模块.函数名" 格式 + trigger: 触发器配置,决定何时执行该处理器 + priority: 优先级,数值越大越先执行 + permissions: 权限配置,控制谁可以触发该处理器 + """ + id: str trigger: Trigger priority: int = 0 @@ -61,6 +192,25 @@ class HandlerDescriptor(_DescriptorBase): class CapabilityDescriptor(_DescriptorBase): + """能力描述符,描述一个可调用的远程能力。 + + 与旧版对比: + 旧版: 无独立的能力描述,通过 method 名称隐式定义 + 新版: 使用 CapabilityDescriptor 显式声明能力,支持 JSON Schema 验证 + + 能力命名规范: + - 使用 "namespace.action" 格式,如 "llm.chat"、"db.set" + - 内置能力以 "internal." 开头,如 "internal.legacy.call_context_function" + + Attributes: + name: 能力名称,格式为 "namespace.action" + description: 能力描述,用于文档和调试 + input_schema: 输入参数的 JSON Schema,用于验证 + output_schema: 输出结果的 JSON Schema,用于验证 + supports_stream: 是否支持流式响应 + cancelable: 是否支持取消 + """ + name: str description: str input_schema: dict[str, Any] | None = None diff --git a/src-new/astrbot_sdk/protocol/legacy_adapter.py b/src-new/astrbot_sdk/protocol/legacy_adapter.py index d65f4502a..21ac1bec7 100644 --- a/src-new/astrbot_sdk/protocol/legacy_adapter.py +++ b/src-new/astrbot_sdk/protocol/legacy_adapter.py @@ -1,3 +1,37 @@ +"""旧版协议适配器模块。 + +提供旧版 JSON-RPC 协议与新版协议之间的双向转换。 +支持旧版插件与新版核心的互操作。 + +主要功能: + - 将旧版 JSON-RPC 请求转换为新版 InvokeMessage + - 将旧版 JSON-RPC 响应转换为新版 ResultMessage + - 将旧版 handshake 转换为新版 InitializeMessage + - 将新版消息转换回旧版格式(用于与旧版核心通信) + +转换映射表: + 旧版 method -> 新版 capability + ------------------------------------------------ + handshake -> InitializeMessage + call_handler -> handler.invoke + call_context_function -> internal.legacy.call_context_function + handler_stream_start -> EventMessage(phase="started") + handler_stream_update -> EventMessage(phase="delta") + handler_stream_end -> EventMessage(phase="completed"/"failed") + cancel -> CancelMessage + +注意事项: + - 旧版 handshake 的 metadata 信息可能丢失部分字段 + - 新版触发器的详细信息在转换时可能丢失 + - 使用 LEGACY_HANDSHAKE_METADATA_KEY 保留原始握手数据 + +TODO: + - 添加旧版消息版本检测和兼容性警告 + - 添加消息转换日志记录,便于调试 + - 支持自定义转换规则扩展 + - 添加转换性能监控 +""" + from __future__ import annotations import json @@ -17,10 +51,19 @@ from .messages import ( ) LEGACY_JSONRPC_VERSION = "2.0" +"""旧版 JSON-RPC 协议版本。""" + LEGACY_CONTEXT_CAPABILITY = "internal.legacy.call_context_function" +"""旧版上下文函数调用的能力名称。""" + LEGACY_HANDSHAKE_METADATA_KEY = "legacy_handshake_payload" +"""在 InitializeMessage.metadata 中存储原始握手数据的键。""" + LEGACY_PLUGIN_KEYS_METADATA_KEY = "legacy_plugin_keys" +"""在 InitializeMessage.metadata 中存储原始插件键列表的键。""" + LEGACY_ADAPTER_MESSAGE_EVENT = 3 +"""默认的事件类型,用于无法识别的旧版处理器。""" class _LegacyMessageBase(BaseModel): @@ -28,12 +71,29 @@ class _LegacyMessageBase(BaseModel): class LegacyErrorData(_LegacyMessageBase): + """旧版 JSON-RPC 错误数据。 + + Attributes: + code: 错误码,整数类型(旧版规范) + message: 错误消息 + data: 附加错误数据 + """ + code: int = -32000 message: str data: Any | None = None class LegacyRequest(_LegacyMessageBase): + """旧版 JSON-RPC 请求。 + + Attributes: + jsonrpc: 协议版本,固定为 "2.0" + id: 请求 ID,可选 + method: 方法名称 + params: 参数字典 + """ + jsonrpc: Literal["2.0"] = LEGACY_JSONRPC_VERSION id: str | None = None method: str @@ -46,20 +106,56 @@ class _LegacyResponse(_LegacyMessageBase): class LegacySuccessResponse(_LegacyResponse): + """旧版 JSON-RPC 成功响应。 + + Attributes: + result: 返回结果 + """ + result: Any = Field(default_factory=dict) class LegacyErrorResponse(_LegacyResponse): + """旧版 JSON-RPC 错误响应。 + + Attributes: + error: 错误数据 + """ + error: LegacyErrorData LegacyMessage = LegacyRequest | LegacySuccessResponse | LegacyErrorResponse +"""旧版 JSON-RPC 消息联合类型。""" + LegacyToV4Message = ( InitializeMessage | InvokeMessage | ResultMessage | EventMessage | CancelMessage ) +"""旧版消息转换后的新版消息类型。""" class LegacyAdapter: + """旧版协议适配器,提供新旧协议之间的双向转换。 + + 使用场景: + 1. 旧版插件连接新版核心:将旧版 JSON-RPC 转换为新版协议 + 2. 新版插件连接旧版核心:将新版协议转换为旧版 JSON-RPC + 3. 测试和迁移:验证协议转换的正确性 + + 转换规则: + - handshake <-> InitializeMessage + - call_handler <-> InvokeMessage(capability="handler.invoke") + - call_context_function <-> InvokeMessage(capability="internal.legacy...") + - handler_stream_* <-> EventMessage + - cancel <-> CancelMessage + + Attributes: + protocol_version: 新版协议版本号 + legacy_peer_name: 默认的对等节点名称 + legacy_peer_role: 默认的对等节点角色 + legacy_peer_version: 默认的对等节点版本 + """ + def __init__( self, *, diff --git a/src-new/astrbot_sdk/protocol/messages.py b/src-new/astrbot_sdk/protocol/messages.py index 7fa3f9952..96a50dce4 100644 --- a/src-new/astrbot_sdk/protocol/messages.py +++ b/src-new/astrbot_sdk/protocol/messages.py @@ -1,3 +1,49 @@ +"""协议消息定义模块。 + +定义 AstrBot SDK 的核心消息类型,所有消息均继承自 Pydantic BaseModel。 + +消息类型概览: + InitializeMessage: 握手初始化消息,包含 Peer 信息和处理器列表 + ResultMessage: 调用结果消息,包含成功/失败状态和输出数据 + InvokeMessage: 能力调用消息,指定目标能力和输入参数 + EventMessage: 流式事件消息,用于流式调用的状态通知 + CancelMessage: 取消消息,用于取消正在进行的调用 + +消息生命周期: + 握手阶段: + Plugin -> Core: InitializeMessage (注册处理器) + Core -> Plugin: ResultMessage (确认或拒绝) + + 调用阶段: + Plugin -> Core: InvokeMessage (调用能力) + Core -> Plugin: ResultMessage (返回结果) + 或者 (流式): + Core -> Plugin: EventMessage (started -> delta* -> completed/failed) + + 取消阶段: + Plugin -> Core: CancelMessage (取消调用) + +与旧版对比: + 旧版 JSON-RPC: + - 使用 method 字段区分操作类型 + - 使用 jsonrpc: "2.0" 标识协议版本 + - 错误码为整数 (如 -32000) + - 无专门的取消消息类型 + + 新版协议: + - 使用 type 字段区分消息类型 + - 使用 protocol_version 字段标识版本 + - 错误码为字符串 (如 "internal_error") + - 有专门的 CancelMessage 取消消息 + +TODO: + - 添加消息过期时间 (expires_at) 支持 + - 添加消息优先级 (priority) 支持 + - 添加消息重试计数 (retry_count) 支持 + - ErrorPayload 缺少 stack_trace 字段(调试用) + - InitializeMessage 缺少 authentication 认证字段 +""" + from __future__ import annotations import json @@ -13,6 +59,19 @@ class _MessageBase(BaseModel): class ErrorPayload(_MessageBase): + """错误载荷,用于 ResultMessage 和 EventMessage 中传递错误信息。 + + 与旧版 JSON-RPC 错误对比: + 旧版: code 为整数,如 -32000 + 新版: code 为字符串,如 "internal_error" + + Attributes: + code: 错误码,字符串类型,便于语义化错误分类 + message: 错误消息,人类可读的错误描述 + hint: 错误提示,可选的解决方案或建议 + retryable: 是否可重试,标识该错误是否可通过重试解决 + """ + code: str message: str hint: str = "" @@ -20,12 +79,55 @@ class ErrorPayload(_MessageBase): class PeerInfo(_MessageBase): + """对等节点信息,标识消息发送方的身份。 + + 与旧版对比: + 旧版: 通过 handshake params 中的 plugin_name 隐式传递 + 新版: 显式的 PeerInfo 结构,支持 plugin 和 core 两种角色 + + Attributes: + name: 节点名称,通常是插件 ID 或核心标识 + role: 节点角色,"plugin" 或 "core" + version: 节点版本号,可选 + """ + name: str role: Literal["plugin", "core"] version: str | None = None class InitializeMessage(_MessageBase): + """初始化消息,用于建立连接时交换信息。 + + 与旧版 JSON-RPC handshake 对比: + 旧版: + { + "jsonrpc": "2.0", + "id": "xxx", + "method": "handshake", + "params": {} + } + 响应包含插件元信息和处理器列表 + + 新版: + { + "type": "initialize", + "id": "xxx", + "protocol_version": "1.0", + "peer": {"name": "...", "role": "plugin", "version": "..."}, + "handlers": [...], + "metadata": {...} + } + + Attributes: + type: 消息类型,固定为 "initialize" + id: 消息 ID,用于关联响应 + protocol_version: 协议版本号 + peer: 发送方节点信息 + handlers: 注册的处理器描述符列表 + metadata: 扩展元数据,可存储插件配置等信息 + """ + type: Literal["initialize"] = "initialize" id: str protocol_version: str @@ -35,12 +137,46 @@ class InitializeMessage(_MessageBase): class InitializeOutput(_MessageBase): + """初始化输出,作为 InitializeMessage 的响应数据。 + + 与旧版对比: + 旧版: handshake 响应中包含完整的插件信息 + 新版: 仅返回对等方信息和能力列表,更简洁 + + Attributes: + peer: 接收方(核心)节点信息 + capabilities: 核心提供的能力描述符列表 + metadata: 扩展元数据 + """ + peer: PeerInfo capabilities: list[CapabilityDescriptor] = Field(default_factory=list) metadata: dict[str, Any] = Field(default_factory=dict) class ResultMessage(_MessageBase): + """结果消息,用于返回能力调用的结果。 + + 与旧版 JSON-RPC 响应对比: + 旧版成功响应: + {"jsonrpc": "2.0", "id": "xxx", "result": {...}} + 旧版错误响应: + {"jsonrpc": "2.0", "id": "xxx", "error": {"code": -32000, "message": "..."}} + + 新版成功结果: + {"type": "result", "id": "xxx", "success": true, "output": {...}} + 新版失败结果: + {"type": "result", "id": "xxx", "success": false, "error": {...}} + + Attributes: + type: 消息类型,固定为 "result" + id: 关联的请求 ID + kind: 结果类型,可选,如 "initialize_result" 标识初始化结果 + success: 是否成功 + output: 成功时的输出数据 + error: 失败时的错误信息 + """ + type: Literal["result"] = "result" id: str kind: str | None = None @@ -50,6 +186,34 @@ class ResultMessage(_MessageBase): class InvokeMessage(_MessageBase): + """调用消息,用于请求执行远程能力。 + + 与旧版 JSON-RPC 请求对比: + 旧版: + { + "jsonrpc": "2.0", + "id": "xxx", + "method": "call_handler", + "params": {"handler_full_name": "...", "event": {...}} + } + + 新版: + { + "type": "invoke", + "id": "xxx", + "capability": "handler.invoke", + "input": {"handler_id": "...", "event": {...}}, + "stream": false + } + + Attributes: + type: 消息类型,固定为 "invoke" + id: 请求 ID,用于关联响应 + capability: 目标能力名称,格式为 "namespace.action" + input: 调用输入参数 + stream: 是否期望流式响应,若为 True 将收到 EventMessage 序列 + """ + type: Literal["invoke"] = "invoke" id: str capability: str @@ -58,6 +222,32 @@ class InvokeMessage(_MessageBase): class EventMessage(_MessageBase): + """事件消息,用于流式调用的状态通知。 + + 流式调用生命周期: + 1. started: 调用开始,所有字段为空 + 2. delta: 数据增量更新,包含 data 字段 + 3. completed: 调用完成,包含 output 字段 + 4. failed: 调用失败,包含 error 字段 + + 与旧版 JSON-RPC 通知对比: + 旧版使用独立的 method 区分: + - handler_stream_start + - handler_stream_update + - handler_stream_end + + 新版使用统一的 EventMessage,通过 phase 字段区分: + {"type": "event", "id": "xxx", "phase": "delta", "data": {...}} + + Attributes: + type: 消息类型,固定为 "event" + id: 关联的请求 ID + phase: 事件阶段,started/delta/completed/failed + data: 增量数据,仅 delta 阶段有效 + output: 最终输出,仅 completed 阶段有效 + error: 错误信息,仅 failed 阶段有效 + """ + type: Literal["event"] = "event" id: str phase: Literal["started", "delta", "completed", "failed"] @@ -97,6 +287,18 @@ class EventMessage(_MessageBase): class CancelMessage(_MessageBase): + """取消消息,用于取消正在进行的调用。 + + 与旧版对比: + 旧版: 使用 {"jsonrpc": "2.0", "method": "cancel", "params": {"reason": "..."}} + 新版: 专门的 CancelMessage 类型,语义更明确 + + Attributes: + type: 消息类型,固定为 "cancel" + id: 要取消的请求 ID + reason: 取消原因,默认为 "user_cancelled" + """ + type: Literal["cancel"] = "cancel" id: str reason: str = "user_cancelled" @@ -105,9 +307,29 @@ class CancelMessage(_MessageBase): ProtocolMessage = ( InitializeMessage | ResultMessage | InvokeMessage | EventMessage | CancelMessage ) +"""协议消息联合类型,所有有效消息类型的联合。""" def parse_message(payload: str | bytes | dict[str, Any]) -> ProtocolMessage: + """解析协议消息。 + + 从原始载荷(字符串、字节或字典)解析为对应的 ProtocolMessage 类型。 + 根据 "type" 字段自动识别消息类型并验证。 + + Args: + payload: 原始消息载荷,支持 JSON 字符串、字节或字典 + + Returns: + 解析后的协议消息对象 + + Raises: + ValueError: 未知的消息类型 + + Example: + >>> msg = parse_message('{"type": "invoke", "id": "1", "capability": "test"}') + >>> isinstance(msg, InvokeMessage) + True + """ if isinstance(payload, bytes): payload = payload.decode("utf-8") if isinstance(payload, str):