"""运行插件命令工具""" import json from typing import Optional, Type from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.core.event import eventmanager from app.core.plugin import PluginManager from app.log import logger from app.schemas.types import EventType, MessageChannel class RunPluginCommandInput(BaseModel): """运行插件命令工具的输入参数模型""" explanation: str = Field( ..., description="Clear explanation of why this tool is being used in the current context", ) command: str = Field( ..., description="The slash command to execute, e.g. '/cookiecloud'. " "Must start with '/'. Can include arguments after the command, e.g. '/command arg1 arg2'. " "Use query_plugin_capabilities tool to discover available commands first.", ) class RunPluginCommandTool(MoviePilotTool): name: str = "run_plugin_command" description: str = ( "Execute a plugin command by sending a CommandExcute event. " "Plugin commands are slash-commands (starting with '/') registered by plugins. " "Use the query_plugin_capabilities tool first to discover available commands and their descriptions. " "The command will be executed asynchronously. " "Note: This tool triggers the command execution but the actual processing happens in the background." ) args_schema: Type[BaseModel] = RunPluginCommandInput def get_tool_message(self, **kwargs) -> Optional[str]: """生成友好的提示消息""" command = kwargs.get("command", "") return f"正在执行插件命令: {command}" async def run(self, command: str, **kwargs) -> str: logger.info(f"执行工具: {self.name}, 参数: command={command}") try: # 确保命令以 / 开头 if not command.startswith("/"): command = f"/{command}" # 验证命令是否存在 plugin_manager = PluginManager() registered_commands = plugin_manager.get_plugin_commands() cmd_name = command.split()[0] matched_command = None for cmd in registered_commands: if cmd.get("cmd") == cmd_name: matched_command = cmd break if not matched_command: # 列出可用命令帮助用户 available_cmds = [ f"{cmd.get('cmd')} - {cmd.get('desc', '无描述')}" for cmd in registered_commands ] result = { "success": False, "message": f"命令 {cmd_name} 不存在", } if available_cmds: result["available_commands"] = available_cmds return json.dumps(result, ensure_ascii=False, indent=2) # 构建消息渠道,优先使用当前会话的渠道信息 channel = None if self._channel: try: channel = MessageChannel(self._channel) except (ValueError, KeyError): channel = None # 发送命令执行事件,与 message.py 中的方式一致 eventmanager.send_event( EventType.CommandExcute, { "cmd": command, "user": self._user_id, "channel": channel, "source": self._source, }, ) result = { "success": True, "message": f"命令 {cmd_name} 已触发执行", "command": command, "command_desc": matched_command.get("desc", ""), "plugin_id": matched_command.get("pid", ""), } return json.dumps(result, ensure_ascii=False, indent=2) except Exception as e: logger.error(f"执行插件命令失败: {e}", exc_info=True) return json.dumps( {"success": False, "message": f"执行插件命令时发生错误: {str(e)}"}, ensure_ascii=False, )