"""Code execution tool — run Python code in a subprocess sandbox."""
import asyncio
import sys
import tempfile
from pathlib import Path
from .base import Tool, ToolResult
_TIMEOUT = 30
class CodeExecTool(Tool):
name = "code_exec"
description = (
"Execute Python code and return stdout/stderr. "
"Each execution runs in an isolated subprocess with a fresh interpreter. "
"No persistent state between calls."
)
parameters = {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Python code to execute",
},
"language": {
"type": "string",
"enum": ["python"],
"description": "Programming language (currently only python)",
"default": "python",
},
},
"required": ["code"],
}
async def execute(self, params: dict) -> ToolResult:
code = params["code"]
language = params.get("language", "python")
if language != "python":
return ToolResult(
success=False,
output=f"Language '{language}' is not supported.",
error="unsupported_language",
)
with tempfile.NamedTemporaryFile(suffix=".py", mode="w", delete=False) as f:
f.write(code)
script_path = f.name
try:
proc = await asyncio.create_subprocess_exec(
sys.executable,
script_path,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=tempfile.gettempdir(),
)
try:
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=_TIMEOUT)
except asyncio.TimeoutError:
proc.kill()
return ToolResult(
success=False,
output=f"Code execution timed out after {_TIMEOUT}s",
error="timeout",
)
output_parts = []
if stdout:
output_parts.append(stdout.decode(errors="replace"))
if stderr:
output_parts.append(f"[stderr]\n{stderr.decode(errors='replace')}")
success = proc.returncode == 0
return ToolResult(
success=success,
output="\n".join(output_parts) or "(no output)",
metadata={"returncode": proc.returncode},
error=None if success else f"Exit code {proc.returncode}",
)
except Exception as e:
return ToolResult(success=False, output=f"Execution error: {e}", error=str(e))
finally:
Path(script_path).unlink(missing_ok=True)