Spaces:
Runtime error
Runtime error
File size: 8,887 Bytes
b567e74 ab4accd b567e74 ab4accd b567e74 c37b3fd b567e74 c37b3fd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 | """
Execution dispatching for ToolStore tools.
Routes execute calls to the appropriate backend:
- MCP → FullMCPClient (connection pool)
- Skill → SkillManager (local SKILL.md execution)
- Toolset → local import + call, or remote Docker container
"""
from __future__ import annotations
import json as _json
from typing import Any, Dict
from toolstore.schema_converter import flatten_mcp_content
from toolstore.skill_manager import get_skill_manager
def execute_tool(tool: Dict[str, Any], args: Dict[str, Any],
config_manager, index_manager) -> str:
"""Dispatch a single tool execution to the correct backend.
Returns the result as a JSON or plain-text string.
"""
tool_name = tool["name"]
tool_type = tool.get("type", "unknown")
if tool_type == "mcp":
return _execute_mcp(tool, args, config_manager)
elif tool_type == "skill":
return _execute_skill(tool, args, config_manager)
elif tool_type == "toolset":
return _execute_toolset(tool, args)
else:
return f"Error: Unknown tool type '{tool_type}'"
# ---------------------------------------------------------------------------
# MCP execution (via FullMCPClient + connection pool)
# ---------------------------------------------------------------------------
def _execute_mcp(tool: Dict[str, Any], args: Dict[str, Any],
config_manager) -> str:
from toolstore.mcp_client import get_client
server_name = tool.get("mcp_server")
if not server_name:
return "Error: Tool definition missing 'mcp_server'"
servers = config_manager.get_mcp_servers()
config = servers.get(server_name)
if not config:
return f"Error: MCP server '{server_name}' not found in config."
try:
client = get_client(server_name, config)
result = client.call_tool(tool["name"], args)
content = result.get("content", [])
if result.get("isError"):
return "[TOOL ERROR] " + flatten_mcp_content(content)
return flatten_mcp_content(content)
except Exception as exc:
return f"Error executing MCP tool: {str(exc)}"
# ---------------------------------------------------------------------------
# Skill execution
# ---------------------------------------------------------------------------
def _execute_skill(tool: Dict[str, Any], args: Dict[str, Any],
config_manager) -> str:
# Strip "skill:" prefix if present (index uses prefixed names)
skill_name = tool["name"]
if skill_name.startswith("skill:"):
skill_name = skill_name[len("skill:"):]
skill_action = args.get("action", "load")
sm = get_skill_manager(config_manager.get_skill_dirs())
# Lazily scan if not already loaded
if not sm.get_skill(skill_name):
sm.scan()
if skill_action == "load":
body = sm.get_skill_body(skill_name)
if body is None:
return f"Error: Skill '{skill_name}' not loaded."
return body
elif skill_action == "files":
sd = sm.get_skill(skill_name)
if not sd:
return f"Error: Skill '{skill_name}' not found."
flist = [str(f) for f in sd.list_files()]
return "\n".join(flist) if flist else "(no additional files bundled)"
elif skill_action == "file":
file_path = args.get("file_path", "")
if not file_path:
return "Error: 'file_path' is required for action='file'."
content = sm.get_skill_file(skill_name, file_path)
if content is None:
return f"Error: File '{file_path}' not found in skill '{skill_name}'."
return content
elif skill_action == "run":
script = args.get("script", "")
if not script:
return "Error: 'script' argument is required for action='run'."
return sm.run_skill_script(skill_name, script)
else:
return f"Error: Unknown skill action '{skill_action}'. Use 'load', 'files', or 'file'."
# ---------------------------------------------------------------------------
# Toolset execution
# ---------------------------------------------------------------------------
def _execute_toolset(tool: Dict[str, Any], args: Dict[str, Any]) -> str:
"""Execute a toolset.
Two modes:
- **Local** (has ``toolset_dir``): import + call directly in-process.
No Docker, no sandbox — the toolset is installed on the host.
- **Remote** (has ``code``, no ``toolset_dir``): run in a dedicated
ephemeral Docker container with the toolset's pre-configured
environment. Zero approval required.
The agent passes ``{"function": "...", ...}`` in arguments.
"""
# Take a copy so we don't mutate the caller's dict.
args = dict(args)
# 1. Argument validation — which function?
function_name = args.pop("function", None)
if not function_name:
bindings = tool.get("bindings", {})
if len(bindings) == 1:
function_name = next(iter(bindings))
else:
names = list(bindings.keys()) if bindings else []
return (
f"Error: 'function' argument required. "
f"Available functions: {', '.join(names) or '(none)'}"
)
# 2. Validate the binding exists
bindings = tool.get("bindings", {})
if function_name not in bindings:
names = list(bindings.keys())
return (
f"Error: Unknown function '{function_name}'. "
f"Available: {', '.join(names)}"
)
# 3. Dispatch: local (in-process) vs remote (dedicated container)
toolset_dir = tool.get("toolset_dir")
if toolset_dir:
return _execute_toolset_local(toolset_dir, function_name, args)
code = tool.get("code") or tool.get("code_base64")
if code:
return _execute_toolset_remote(tool, function_name, args)
return "Error: toolset has neither 'toolset_dir' nor 'code' — cannot execute"
def _execute_toolset_local(toolset_dir: str, function_name: str,
args: Dict[str, Any]) -> str:
"""Run a local toolset directly in-process — just import and call."""
import importlib.util
from pathlib import Path
from toolstore.toolset import clear_registry, get_tool
code_path = Path(toolset_dir) / "toolset.py"
if not code_path.exists():
return f"Error: toolset.py not found at {code_path}"
try:
clear_registry()
# Dynamically load the toolset module
spec = importlib.util.spec_from_file_location(
"toolset_local", str(code_path)
)
if spec is None or spec.loader is None:
return "Error: failed to create module spec for toolset.py"
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
fn = get_tool(function_name)
if fn is None:
from toolstore.toolset import get_tool_names
names = get_tool_names()
return (
f"Error: Function '{function_name}' not found in toolset. "
f"Available: {', '.join(names) or '(none)'}"
)
result = fn(**args)
clear_registry()
return _json.dumps(result, default=str, indent=2)
except Exception as exc:
clear_registry()
return f"Error executing local toolset '{function_name}': {exc}"
def _execute_toolset_remote(tool: Dict[str, Any], function_name: str,
args: Dict[str, Any]) -> str:
"""Run a registry toolset in-process — no Docker needed.
Writes code to a temp directory, pip‑installs deps, then imports
and calls the function just like _execute_toolset_local.
"""
import base64
import subprocess
import sys
import tempfile
from pathlib import Path
code = tool.get("code", "")
code_b64 = tool.get("code_base64", "")
if code_b64 and not code:
code = base64.b64decode(code_b64).decode("utf-8")
if not code:
return "Error: toolset has no code to execute"
# Temp directory — lives for the duration of the function call
with tempfile.TemporaryDirectory(prefix="toolset_") as tmp_dir:
tmp = Path(tmp_dir)
(tmp / "toolset.py").write_text(code, encoding="utf-8")
# Install requirements if present
requirements = tool.get("requirements", [])
if isinstance(requirements, str):
requirements = [r.strip() for r in requirements.split("\n") if r.strip()]
if requirements:
return (
f"Error: This toolset requires packages that aren't installed: "
f"{', '.join(requirements)}.\n"
f"Install them first: pip install {' '.join(requirements)}"
)
# Now delegate to the local runner
return _execute_toolset_local(str(tmp), function_name, args)
|