本提交新增了全渠道SDK核心模块: 1. 异步锁、目标解析、动作调度等基础工具 2. 消息动作注册与统一调度系统 3. 测试套件与契约测试框架 4. 完整的目标解析流水线与工具函数 5. 资源依赖注入与生命周期管理
116 lines
3.8 KiB
Python
116 lines
3.8 KiB
Python
from __future__ import annotations
|
||
|
||
import logging
|
||
from collections.abc import Callable, Awaitable
|
||
from dataclasses import dataclass, field
|
||
|
||
from yuxi.channel.sdk.actions.names import MessageAction
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
type ActionHandler = Callable[..., Awaitable[dict]]
|
||
|
||
|
||
@dataclass
|
||
class ActionDescriptor:
|
||
action: MessageAction
|
||
description: str = ""
|
||
parameters: list[dict] = field(default_factory=list)
|
||
requires_trusted_sender: bool = False
|
||
|
||
def to_capability(self) -> dict:
|
||
return {
|
||
"action": self.action.value,
|
||
"description": self.description,
|
||
"parameters": self.parameters,
|
||
"requires_trusted_sender": self.requires_trusted_sender,
|
||
}
|
||
|
||
|
||
class MessageActionRegistry:
|
||
"""消息动作注册表。
|
||
|
||
渠道插件通过 register() 声明支持的动作及对应 handler。
|
||
一次注册同时提供 supports_action() 查询和 execute_action() 执行能力。
|
||
消除"声明了但没实现"或"实现了但没声明"的不一致。
|
||
|
||
对齐 OpenClaw ChannelMessageActionAdapter 的设计理念:
|
||
- 注册即声明:register() 同时维护 handlers 和 descriptors
|
||
- supports_action() 和 execute_action() 共用同一注册表
|
||
"""
|
||
|
||
def __init__(self):
|
||
self._handlers: dict[MessageAction, ActionHandler] = {}
|
||
self._descriptors: dict[MessageAction, ActionDescriptor] = {}
|
||
|
||
def register(
|
||
self,
|
||
action: MessageAction,
|
||
handler: ActionHandler,
|
||
*,
|
||
description: str = "",
|
||
parameters: list[dict] | None = None,
|
||
requires_trusted_sender: bool = False,
|
||
) -> MessageActionRegistry:
|
||
self._handlers[action] = handler
|
||
self._descriptors[action] = ActionDescriptor(
|
||
action=action,
|
||
description=description,
|
||
parameters=parameters or [],
|
||
requires_trusted_sender=requires_trusted_sender,
|
||
)
|
||
return self
|
||
|
||
def supports_action(self, action: MessageAction | str) -> bool:
|
||
if isinstance(action, str):
|
||
try:
|
||
action = MessageAction(action)
|
||
except ValueError:
|
||
return False
|
||
return action in self._handlers
|
||
|
||
def describe_actions(self) -> list[ActionDescriptor]:
|
||
return list(self._descriptors.values())
|
||
|
||
def list_actions(self) -> list[MessageAction]:
|
||
return list(self._handlers.keys())
|
||
|
||
def requires_trusted_sender(self, action: MessageAction | str) -> bool:
|
||
if isinstance(action, str):
|
||
try:
|
||
action = MessageAction(action)
|
||
except ValueError:
|
||
return False
|
||
desc = self._descriptors.get(action)
|
||
return desc.requires_trusted_sender if desc else False
|
||
|
||
async def execute_action(
|
||
self,
|
||
action: MessageAction | str,
|
||
params: dict,
|
||
context: dict | None = None,
|
||
) -> dict:
|
||
if isinstance(action, str):
|
||
try:
|
||
action = MessageAction(action)
|
||
except ValueError:
|
||
return {"success": False, "error": f"Unknown action: {action}"}
|
||
|
||
handler = self._handlers.get(action)
|
||
if handler is None:
|
||
return {
|
||
"success": False,
|
||
"error": f"Action '{action.value}' is not supported by this channel",
|
||
}
|
||
|
||
try:
|
||
if context:
|
||
return await handler(params=params, context=context)
|
||
return await handler(**params)
|
||
except Exception as e:
|
||
logger.exception("Action '%s' execution failed", action.value)
|
||
return {"success": False, "error": str(e)}
|
||
|
||
def get_message_actions(self) -> list[dict]:
|
||
"""兼容 BaseChannelPlugin.get_message_actions() 返回格式。"""
|
||
return [desc.to_capability() for desc in self.describe_actions()] |