404 lines
16 KiB
Python
404 lines
16 KiB
Python
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)}
|