ForcePilot/backend/package/yuxi/channel/sdk/actions/adapter.py

116 lines
3.8 KiB
Python
Raw Normal View History

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