Newer
Older
navi-1 / navi / tools / code_exec.py
"""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)