from typing import Any, Dict, List from src.common.logger import get_logger from src.config.config import global_config logger = get_logger("plugin_runtime.integration") def _get_nested_config_value(source: Any, key: str, default: Any = None) -> Any: """从嵌套对象或字典中读取配置值。 Args: source: 配置对象或字典。 key: 以点号分隔的路径。 default: 未命中时返回的默认值。 Returns: Any: 命中的值;读取失败时返回默认值。 """ current = source try: for part in key.split("."): if isinstance(current, dict) and part in current: current = current[part] continue if hasattr(current, part): current = getattr(current, part) continue raise KeyError(part) return current except Exception: return default def _normalize_prompt_arg(prompt: Any) -> str | List[Dict[str, Any]]: """校验并规范化插件传入的提示参数。 Args: prompt: 原始提示参数。 Returns: str | List[Dict[str, Any]]: 规范化后的提示输入。 Raises: ValueError: 提示参数缺失或结构不受支持时抛出。 """ if isinstance(prompt, str): if not prompt.strip(): raise ValueError("缺少必要参数 prompt") return prompt if isinstance(prompt, list) and prompt: for index, prompt_message in enumerate(prompt, start=1): if not isinstance(prompt_message, dict): raise ValueError(f"prompt 第 {index} 项必须为字典") return prompt raise ValueError("缺少必要参数 prompt") class RuntimeCoreCapabilityMixin: """插件运行时的核心能力混入。""" async def _cap_send_text(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any: """向指定流发送文本消息。 Args: plugin_id: 插件标识。 capability: 能力名称。 args: 能力调用参数。 Returns: Any: 能力执行结果。 """ del plugin_id, capability from src.services import send_service as send_api text = str(args.get("text", "")) stream_id = str(args.get("stream_id", "")) sync_to_maisaka_history = bool(args.get("sync_to_maisaka_history", False)) maisaka_source_kind = str(args.get("maisaka_source_kind", "plugin_send") or "plugin_send") if not text or not stream_id: return {"success": False, "error": "缺少必要参数 text 或 stream_id"} try: result = await send_api.text_to_stream( text=text, stream_id=stream_id, typing=bool(args.get("typing", False)), set_reply=bool(args.get("set_reply", False)), storage_message=bool(args.get("storage_message", True)), sync_to_maisaka_history=sync_to_maisaka_history, maisaka_source_kind=maisaka_source_kind, ) return {"success": result} except Exception as exc: logger.error(f"[cap.send.text] 执行失败: {exc}", exc_info=True) return {"success": False, "error": str(exc)} async def _cap_send_emoji(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any: """向指定流发送表情图片。 Args: plugin_id: 插件标识。 capability: 能力名称。 args: 能力调用参数。 Returns: Any: 能力执行结果。 """ del plugin_id, capability from src.services import send_service as send_api emoji_base64 = str(args.get("emoji_base64", "")) stream_id = str(args.get("stream_id", "")) sync_to_maisaka_history = bool(args.get("sync_to_maisaka_history", False)) maisaka_source_kind = str(args.get("maisaka_source_kind", "plugin_send") or "plugin_send") if not emoji_base64 or not stream_id: return {"success": False, "error": "缺少必要参数 emoji_base64 或 stream_id"} try: result = await send_api.emoji_to_stream( emoji_base64=emoji_base64, stream_id=stream_id, storage_message=bool(args.get("storage_message", True)), sync_to_maisaka_history=sync_to_maisaka_history, maisaka_source_kind=maisaka_source_kind, ) return {"success": result} except Exception as exc: logger.error(f"[cap.send.emoji] 执行失败: {exc}", exc_info=True) return {"success": False, "error": str(exc)} async def _cap_send_image(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any: """向指定流发送图片。 Args: plugin_id: 插件标识。 capability: 能力名称。 args: 能力调用参数。 Returns: Any: 能力执行结果。 """ del plugin_id, capability from src.services import send_service as send_api image_base64 = str(args.get("image_base64", "")) stream_id = str(args.get("stream_id", "")) sync_to_maisaka_history = bool(args.get("sync_to_maisaka_history", False)) maisaka_source_kind = str(args.get("maisaka_source_kind", "plugin_send") or "plugin_send") if not image_base64 or not stream_id: return {"success": False, "error": "缺少必要参数 image_base64 或 stream_id"} try: result = await send_api.image_to_stream( image_base64=image_base64, stream_id=stream_id, storage_message=bool(args.get("storage_message", True)), sync_to_maisaka_history=sync_to_maisaka_history, maisaka_source_kind=maisaka_source_kind, ) return {"success": result} except Exception as exc: logger.error(f"[cap.send.image] 执行失败: {exc}", exc_info=True) return {"success": False, "error": str(exc)} async def _cap_send_command(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any: """向指定流发送命令消息。 Args: plugin_id: 插件标识。 capability: 能力名称。 args: 能力调用参数。 Returns: Any: 能力执行结果。 """ del plugin_id, capability from src.services import send_service as send_api command = str(args.get("command", "")) stream_id = str(args.get("stream_id", "")) sync_to_maisaka_history = bool(args.get("sync_to_maisaka_history", False)) maisaka_source_kind = str(args.get("maisaka_source_kind", "plugin_send") or "plugin_send") if not command or not stream_id: return {"success": False, "error": "缺少必要参数 command 或 stream_id"} try: result = await send_api.custom_to_stream( message_type="command", content=command, stream_id=stream_id, storage_message=bool(args.get("storage_message", True)), display_message=str(args.get("display_message", "")), sync_to_maisaka_history=sync_to_maisaka_history, maisaka_source_kind=maisaka_source_kind, ) return {"success": result} except Exception as exc: logger.error(f"[cap.send.command] 执行失败: {exc}", exc_info=True) return {"success": False, "error": str(exc)} async def _cap_send_custom(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any: """向指定流发送自定义消息。 Args: plugin_id: 插件标识。 capability: 能力名称。 args: 能力调用参数。 Returns: Any: 能力执行结果。 """ del plugin_id, capability from src.services import send_service as send_api message_type = str(args.get("message_type", "") or args.get("custom_type", "")) content = args.get("content") if content is None: content = args.get("data", "") stream_id = str(args.get("stream_id", "")) sync_to_maisaka_history = bool(args.get("sync_to_maisaka_history", False)) maisaka_source_kind = str(args.get("maisaka_source_kind", "plugin_send") or "plugin_send") if not message_type or not stream_id: return {"success": False, "error": "缺少必要参数 message_type 或 stream_id"} try: result = await send_api.custom_to_stream( message_type=message_type, content=content, stream_id=stream_id, display_message=str(args.get("display_message", "")), typing=bool(args.get("typing", False)), storage_message=bool(args.get("storage_message", True)), sync_to_maisaka_history=sync_to_maisaka_history, maisaka_source_kind=maisaka_source_kind, ) return {"success": result} except Exception as exc: logger.error(f"[cap.send.custom] 执行失败: {exc}", exc_info=True) return {"success": False, "error": str(exc)} async def _cap_llm_generate(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any: """执行无工具的 LLM 生成能力。 Args: plugin_id: 插件标识。 capability: 能力名称。 args: 能力调用参数。 Returns: Any: 标准化后的 LLM 响应结构。 """ del capability from src.services import llm_service as llm_api try: prompt = _normalize_prompt_arg(args.get("prompt")) task_name = llm_api.resolve_task_name(str(args.get("model", "") or args.get("model_name", ""))) result = await llm_api.generate( llm_api.LLMServiceRequest( task_name=task_name, request_type=f"plugin.{plugin_id}", prompt=prompt, temperature=args.get("temperature"), max_tokens=args.get("max_tokens"), ) ) return result.to_capability_payload() except Exception as exc: logger.error(f"[cap.llm.generate] 执行失败: {exc}", exc_info=True) return {"success": False, "error": str(exc)} async def _cap_llm_generate_with_tools(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any: """执行带工具的 LLM 生成能力。 Args: plugin_id: 插件标识。 capability: 能力名称。 args: 能力调用参数。 Returns: Any: 标准化后的 LLM 响应结构。 """ del capability from src.services import llm_service as llm_api tool_options = args.get("tools") or args.get("tool_options") if tool_options is not None and not isinstance(tool_options, list): return {"success": False, "error": "tools 必须为列表"} try: prompt = _normalize_prompt_arg(args.get("prompt")) task_name = llm_api.resolve_task_name(str(args.get("model", "") or args.get("model_name", ""))) result = await llm_api.generate( llm_api.LLMServiceRequest( task_name=task_name, request_type=f"plugin.{plugin_id}", prompt=prompt, tool_options=tool_options, temperature=args.get("temperature"), max_tokens=args.get("max_tokens"), ) ) return result.to_capability_payload() except Exception as exc: logger.error(f"[cap.llm.generate_with_tools] 执行失败: {exc}", exc_info=True) return {"success": False, "error": str(exc)} async def _cap_llm_get_available_models(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any: """获取当前宿主可用的模型任务列表。 Args: plugin_id: 插件标识。 capability: 能力名称。 args: 能力调用参数。 Returns: Any: 可用模型列表。 """ del plugin_id, capability, args from src.services import llm_service as llm_api try: models = llm_api.get_available_models() return {"success": True, "models": list(models.keys())} except Exception as exc: logger.error(f"[cap.llm.get_available_models] 执行失败: {exc}", exc_info=True) return {"success": False, "error": str(exc)} async def _cap_config_get(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any: """读取宿主全局配置中的单个字段。 Args: plugin_id: 插件标识。 capability: 能力名称。 args: 能力调用参数。 Returns: Any: 配置读取结果。 """ del plugin_id, capability key = str(args.get("key", "")) default = args.get("default") if not key: return {"success": False, "value": None, "error": "缺少必要参数 key"} try: value = _get_nested_config_value(global_config, key, default) return {"success": True, "value": value} except Exception as exc: return {"success": False, "value": None, "error": str(exc)} async def _cap_config_get_plugin(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any: """读取指定插件的配置。 Args: plugin_id: 当前插件标识。 capability: 能力名称。 args: 能力调用参数。 Returns: Any: 配置读取结果。 """ del capability from src.plugin_runtime.component_query import component_query_service plugin_name = str(args.get("plugin_name", plugin_id)) key = str(args.get("key", "")) default = args.get("default") try: config = component_query_service.get_plugin_config(plugin_name) if config is None: return {"success": False, "value": default, "error": f"未找到插件 {plugin_name} 的配置"} if key: value = _get_nested_config_value(config, key, default) return {"success": True, "value": value} return {"success": True, "value": config} except Exception as exc: return {"success": False, "value": default, "error": str(exc)} async def _cap_config_get_all(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any: """读取指定插件的全部配置。 Args: plugin_id: 当前插件标识。 capability: 能力名称。 args: 能力调用参数。 Returns: Any: 配置读取结果。 """ del capability from src.plugin_runtime.component_query import component_query_service plugin_name = str(args.get("plugin_name", plugin_id)) try: config = component_query_service.get_plugin_config(plugin_name) if config is None: return {"success": True, "value": {}} return {"success": True, "value": config} except Exception as exc: return {"success": False, "value": {}, "error": str(exc)}