File size: 5,890 Bytes
a9dc537
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Code Execution Tools for SPARKNET
Tools for executing Python and bash code
"""

import subprocess
import sys
from io import StringIO
from contextlib import redirect_stdout, redirect_stderr
from typing import Optional
from loguru import logger
from .base_tool import BaseTool, ToolResult


class PythonExecutorTool(BaseTool):
    """Tool for executing Python code."""

    def __init__(self, sandbox: bool = True):
        super().__init__(
            name="python_executor",
            description="Execute Python code and return the output",
        )
        self.sandbox = sandbox
        self.add_parameter("code", "str", "Python code to execute", required=True)
        self.add_parameter("timeout", "int", "Execution timeout in seconds", required=False, default=30)

    async def execute(self, code: str, timeout: int = 30, **kwargs) -> ToolResult:
        """
        Execute Python code.

        Args:
            code: Python code to execute
            timeout: Execution timeout

        Returns:
            ToolResult with execution output
        """
        try:
            # Capture stdout and stderr
            stdout_capture = StringIO()
            stderr_capture = StringIO()

            # Create a restricted namespace for sandboxing
            if self.sandbox:
                # Limited built-ins for safety
                safe_builtins = {
                    "print": print,
                    "len": len,
                    "range": range,
                    "str": str,
                    "int": int,
                    "float": float,
                    "bool": bool,
                    "list": list,
                    "dict": dict,
                    "tuple": tuple,
                    "set": set,
                    "sum": sum,
                    "min": min,
                    "max": max,
                    "abs": abs,
                    "round": round,
                    "enumerate": enumerate,
                    "zip": zip,
                }
                namespace = {"__builtins__": safe_builtins}
            else:
                namespace = {}

            # Execute code
            with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture):
                exec(code, namespace)

            stdout_text = stdout_capture.getvalue()
            stderr_text = stderr_capture.getvalue()

            output = stdout_text
            if stderr_text:
                output += f"\nSTDERR:\n{stderr_text}"

            return ToolResult(
                success=True,
                output=output or "Code executed successfully (no output)",
                metadata={
                    "sandbox": self.sandbox,
                    "stdout": stdout_text,
                    "stderr": stderr_text,
                },
            )

        except Exception as e:
            logger.error(f"Python execution error: {e}")
            return ToolResult(
                success=False,
                output=None,
                error=f"Execution error: {str(e)}",
            )


class BashExecutorTool(BaseTool):
    """Tool for executing bash commands."""

    def __init__(self, allowed_commands: Optional[list[str]] = None):
        super().__init__(
            name="bash_executor",
            description="Execute bash commands and return the output",
        )
        self.allowed_commands = allowed_commands
        self.add_parameter("command", "str", "Bash command to execute", required=True)
        self.add_parameter("timeout", "int", "Execution timeout in seconds", required=False, default=30)
        self.add_parameter("working_dir", "str", "Working directory", required=False, default=".")

    async def execute(
        self,
        command: str,
        timeout: int = 30,
        working_dir: str = ".",
        **kwargs,
    ) -> ToolResult:
        """
        Execute bash command.

        Args:
            command: Bash command to execute
            timeout: Execution timeout
            working_dir: Working directory

        Returns:
            ToolResult with command output
        """
        try:
            # Check if command is allowed (if whitelist is set)
            if self.allowed_commands:
                cmd_name = command.split()[0]
                if cmd_name not in self.allowed_commands:
                    return ToolResult(
                        success=False,
                        output=None,
                        error=f"Command '{cmd_name}' not allowed. Allowed: {self.allowed_commands}",
                    )

            # Execute command
            result = subprocess.run(
                command,
                shell=True,
                capture_output=True,
                text=True,
                timeout=timeout,
                cwd=working_dir,
            )

            output = result.stdout
            if result.stderr:
                output += f"\nSTDERR:\n{result.stderr}"

            return ToolResult(
                success=result.returncode == 0,
                output=output or "(no output)",
                error=None if result.returncode == 0 else f"Command failed with code {result.returncode}",
                metadata={
                    "return_code": result.returncode,
                    "stdout": result.stdout,
                    "stderr": result.stderr,
                    "command": command,
                },
            )

        except subprocess.TimeoutExpired:
            return ToolResult(
                success=False,
                output=None,
                error=f"Command timed out after {timeout} seconds",
            )
        except Exception as e:
            logger.error(f"Bash execution error: {e}")
            return ToolResult(
                success=False,
                output=None,
                error=f"Execution error: {str(e)}",
            )