ForcePilot/backend/package/yuxi/channel/sdk/actions/adapter.py
Kris b438af3ba8 feat(channel-sdk): 新增完整的渠道SDK工具链
本提交新增了全渠道SDK核心模块:
1.  异步锁、目标解析、动作调度等基础工具
2.  消息动作注册与统一调度系统
3.  测试套件与契约测试框架
4.  完整的目标解析流水线与工具函数
5.  资源依赖注入与生命周期管理
2026-05-21 10:29:12 +08:00

116 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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