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()]