tinyagent-py 0.0.13__py3-none-any.whl → 0.0.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tinyagent/code_agent/helper.py +2 -2
- tinyagent/code_agent/modal_sandbox.py +1 -1
- tinyagent/code_agent/providers/__init__.py +14 -1
- tinyagent/code_agent/providers/base.py +181 -7
- tinyagent/code_agent/providers/modal_provider.py +150 -27
- tinyagent/code_agent/providers/seatbelt_provider.py +1065 -0
- tinyagent/code_agent/safety.py +6 -2
- tinyagent/code_agent/tiny_code_agent.py +973 -12
- tinyagent/code_agent/utils.py +263 -2
- tinyagent/hooks/__init__.py +3 -1
- tinyagent/hooks/jupyter_notebook_callback.py +1464 -0
- tinyagent/hooks/token_tracker.py +564 -0
- tinyagent/prompts/summarize.yaml +96 -0
- tinyagent/prompts/truncation.yaml +13 -0
- tinyagent/tiny_agent.py +811 -49
- {tinyagent_py-0.0.13.dist-info → tinyagent_py-0.0.16.dist-info}/METADATA +25 -1
- tinyagent_py-0.0.16.dist-info/RECORD +38 -0
- tinyagent_py-0.0.13.dist-info/RECORD +0 -33
- {tinyagent_py-0.0.13.dist-info → tinyagent_py-0.0.16.dist-info}/WHEEL +0 -0
- {tinyagent_py-0.0.13.dist-info → tinyagent_py-0.0.16.dist-info}/licenses/LICENSE +0 -0
- {tinyagent_py-0.0.13.dist-info → tinyagent_py-0.0.16.dist-info}/top_level.txt +0 -0
tinyagent/code_agent/helper.py
CHANGED
@@ -47,13 +47,13 @@ You are an Agent, You need to solve the task, not suggesting user about how to s
|
|
47
47
|
|
48
48
|
""")
|
49
49
|
|
50
|
-
def load_template(path: str) -> str:
|
50
|
+
def load_template(path: str,key:str="system_prompt") -> str:
|
51
51
|
"""
|
52
52
|
Load the YAML file and extract its 'system_prompt' field.
|
53
53
|
"""
|
54
54
|
with open(path, "r") as f:
|
55
55
|
data = yaml.safe_load(f)
|
56
|
-
return data[
|
56
|
+
return data[key]
|
57
57
|
|
58
58
|
def render_system_prompt(template_str: str,
|
59
59
|
tools: dict,
|
@@ -78,7 +78,7 @@ def create_sandbox(
|
|
78
78
|
|
79
79
|
if apt_packages is None:
|
80
80
|
# Always install the basics required for most workflows
|
81
|
-
apt_packages = ("git", "curl", "nodejs", "npm")
|
81
|
+
apt_packages = ("git", "curl", "nodejs", "npm","ripgrep","tree")
|
82
82
|
|
83
83
|
if default_packages is None:
|
84
84
|
default_packages = (
|
@@ -1,4 +1,17 @@
|
|
1
1
|
from .base import CodeExecutionProvider
|
2
2
|
from .modal_provider import ModalProvider
|
3
3
|
|
4
|
-
|
4
|
+
# Import SeatbeltProvider conditionally to avoid errors on non-macOS systems
|
5
|
+
import platform
|
6
|
+
if platform.system() == "Darwin":
|
7
|
+
try:
|
8
|
+
from .seatbelt_provider import SeatbeltProvider
|
9
|
+
except ImportError:
|
10
|
+
# If there's an issue importing, just don't make it available
|
11
|
+
pass
|
12
|
+
|
13
|
+
__all__ = ["CodeExecutionProvider", "ModalProvider"]
|
14
|
+
|
15
|
+
# Add SeatbeltProvider to __all__ if it was successfully imported
|
16
|
+
if platform.system() == "Darwin" and "SeatbeltProvider" in globals():
|
17
|
+
__all__.append("SeatbeltProvider")
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
|
-
from typing import Dict, List, Any, Optional
|
2
|
+
from typing import Dict, List, Any, Optional, Set
|
3
3
|
from tinyagent.hooks.logging_manager import LoggingManager
|
4
4
|
import cloudpickle
|
5
5
|
|
@@ -21,6 +21,9 @@ class CodeExecutionProvider(ABC):
|
|
21
21
|
pip_packages: List[str] = None,
|
22
22
|
secrets: Dict[str, Any] = None,
|
23
23
|
lazy_init: bool = True,
|
24
|
+
bypass_shell_safety: bool = False,
|
25
|
+
additional_safe_shell_commands: Optional[List[str]] = None,
|
26
|
+
additional_safe_control_operators: Optional[List[str]] = None,
|
24
27
|
**kwargs
|
25
28
|
):
|
26
29
|
self.log_manager = log_manager
|
@@ -35,6 +38,36 @@ class CodeExecutionProvider(ABC):
|
|
35
38
|
self._locals_dict = kwargs.get("locals_dict", {})
|
36
39
|
self._user_variables = {}
|
37
40
|
self.code_tools_definitions = []
|
41
|
+
|
42
|
+
# Shell safety configuration
|
43
|
+
self.bypass_shell_safety = bypass_shell_safety
|
44
|
+
|
45
|
+
# Safe shell commands that don't modify the system or access sensitive data
|
46
|
+
self.safe_shell_commands: Set[str] = {
|
47
|
+
"ls", "cat", "grep", "find", "echo", "pwd", "whoami", "date",
|
48
|
+
"head", "tail", "wc", "sort", "uniq", "tr", "cut", "sed", "awk",
|
49
|
+
"ps", "df", "du", "uname", "which", "type", "file", "stat", "rg", "if",
|
50
|
+
"tree"
|
51
|
+
}
|
52
|
+
|
53
|
+
# Add additional safe shell commands if provided
|
54
|
+
if additional_safe_shell_commands:
|
55
|
+
if "*" in additional_safe_shell_commands:
|
56
|
+
# If wildcard is provided, allow all commands (effectively bypassing the check)
|
57
|
+
self.bypass_shell_safety = True
|
58
|
+
else:
|
59
|
+
self.safe_shell_commands.update(additional_safe_shell_commands)
|
60
|
+
|
61
|
+
# Safe control operators for shell commands
|
62
|
+
self.safe_control_operators: Set[str] = {"&&", "||", ";", "|"}
|
63
|
+
|
64
|
+
# Add additional safe control operators if provided
|
65
|
+
if additional_safe_control_operators:
|
66
|
+
if "*" in additional_safe_control_operators:
|
67
|
+
# If wildcard is provided, allow all operators
|
68
|
+
self.safe_control_operators = set("*")
|
69
|
+
else:
|
70
|
+
self.safe_control_operators.update(additional_safe_control_operators)
|
38
71
|
|
39
72
|
@abstractmethod
|
40
73
|
async def execute_python(
|
@@ -58,6 +91,133 @@ class CodeExecutionProvider(ABC):
|
|
58
91
|
"""
|
59
92
|
pass
|
60
93
|
|
94
|
+
@abstractmethod
|
95
|
+
async def execute_shell(
|
96
|
+
self,
|
97
|
+
command: List[str],
|
98
|
+
timeout: int = 10,
|
99
|
+
workdir: Optional[str] = None
|
100
|
+
) -> Dict[str, Any]:
|
101
|
+
"""
|
102
|
+
Execute a shell command securely and return the result.
|
103
|
+
|
104
|
+
Args:
|
105
|
+
command: List of command parts to execute
|
106
|
+
timeout: Maximum execution time in seconds
|
107
|
+
workdir: Working directory for command execution
|
108
|
+
|
109
|
+
Returns:
|
110
|
+
Dictionary containing execution results with keys:
|
111
|
+
- stdout: stdout from the execution
|
112
|
+
- stderr: stderr from the execution
|
113
|
+
- exit_code: exit code from the command
|
114
|
+
"""
|
115
|
+
pass
|
116
|
+
|
117
|
+
def is_safe_command(self, command: List[str]) -> Dict[str, Any]:
|
118
|
+
"""
|
119
|
+
Check if a shell command is safe to execute.
|
120
|
+
|
121
|
+
Args:
|
122
|
+
command: List of command parts to check
|
123
|
+
|
124
|
+
Returns:
|
125
|
+
Dictionary with:
|
126
|
+
- safe: Boolean indicating if command is safe
|
127
|
+
- reason: Reason why command is not safe (if applicable)
|
128
|
+
"""
|
129
|
+
# If shell safety checks are bypassed, consider all commands safe
|
130
|
+
if self.bypass_shell_safety:
|
131
|
+
return {"safe": True}
|
132
|
+
|
133
|
+
if type(command) == str:
|
134
|
+
command = command.split(" ")
|
135
|
+
if not command or not isinstance(command, list) or len(command) == 0:
|
136
|
+
return {"safe": False, "reason": "Empty or invalid command"}
|
137
|
+
|
138
|
+
# Special handling for bash -c or bash -lc commands
|
139
|
+
if len(command) >= 3 and command[0] == "bash" and command[1] in ["-c", "-lc"]:
|
140
|
+
# For bash -c or bash -lc, we need to parse the command string that follows
|
141
|
+
# We'll extract commands from the bash command string and check them
|
142
|
+
bash_cmd_str = command[2]
|
143
|
+
|
144
|
+
# Simple parsing of the bash command to extract command names
|
145
|
+
# This is a basic implementation and might not cover all edge cases
|
146
|
+
import shlex
|
147
|
+
import re
|
148
|
+
|
149
|
+
try:
|
150
|
+
# Shell script keywords that should be allowed
|
151
|
+
shell_keywords = {
|
152
|
+
"if", "then", "else", "elif", "fi", "for", "do", "done",
|
153
|
+
"while", "until", "case", "esac", "in", "function", "select",
|
154
|
+
"time", "coproc", "true", "false"
|
155
|
+
}
|
156
|
+
|
157
|
+
# Split the command by common shell operators
|
158
|
+
cmd_parts = re.split(r'(\||;|&&|\|\||>|>>|<|<<)', bash_cmd_str)
|
159
|
+
commands_to_check = []
|
160
|
+
|
161
|
+
for part in cmd_parts:
|
162
|
+
part = part.strip()
|
163
|
+
if part and part not in ['|', ';', '&&', '||', '>', '>>', '<', '<<']:
|
164
|
+
# Get the first word which is typically the command
|
165
|
+
try:
|
166
|
+
words = shlex.split(part)
|
167
|
+
if words:
|
168
|
+
cmd_name = words[0].split('/')[-1] # Extract binary name
|
169
|
+
|
170
|
+
# Skip shell keywords
|
171
|
+
if cmd_name in shell_keywords:
|
172
|
+
continue
|
173
|
+
|
174
|
+
# Skip variable assignments (e.g., VAR=value)
|
175
|
+
if re.match(r'^[A-Za-z_][A-Za-z0-9_]*=', cmd_name):
|
176
|
+
continue
|
177
|
+
|
178
|
+
if cmd_name not in self.safe_shell_commands and '*' not in cmd_name and '?' not in cmd_name:
|
179
|
+
return {"safe": False, "reason": f"Unsafe command in bash script: {cmd_name}"}
|
180
|
+
except Exception:
|
181
|
+
# If parsing fails, be cautious and reject
|
182
|
+
return {"safe": False, "reason": "Could not parse bash command safely"}
|
183
|
+
|
184
|
+
# All commands in the bash script are safe
|
185
|
+
return {"safe": True}
|
186
|
+
except Exception as e:
|
187
|
+
return {"safe": False, "reason": f"Error parsing bash command: {str(e)}"}
|
188
|
+
|
189
|
+
# Normal command processing for non-bash -c commands
|
190
|
+
# Shell operators that might be passed as separate arguments
|
191
|
+
shell_operators = ['|', '>', '<', '>>', '<<', '&&', '||', ';']
|
192
|
+
|
193
|
+
# Extract actual commands from the command list, ignoring shell operators
|
194
|
+
commands_to_check = []
|
195
|
+
i = 0
|
196
|
+
while i < len(command):
|
197
|
+
if command[i] in shell_operators:
|
198
|
+
i += 1
|
199
|
+
continue
|
200
|
+
|
201
|
+
# Extract the binary name
|
202
|
+
bin_name = command[i].split("/")[-1]
|
203
|
+
commands_to_check.append(bin_name)
|
204
|
+
|
205
|
+
# Skip to next command after an operator
|
206
|
+
i += 1
|
207
|
+
while i < len(command) and command[i] not in shell_operators:
|
208
|
+
i += 1
|
209
|
+
|
210
|
+
# Check if all commands are in the safe list
|
211
|
+
for cmd in commands_to_check:
|
212
|
+
# Handle wildcards in command names (e.g., *.py)
|
213
|
+
if '*' in cmd or '?' in cmd:
|
214
|
+
continue
|
215
|
+
|
216
|
+
if cmd not in self.safe_shell_commands:
|
217
|
+
return {"safe": False, "reason": f"Unsafe command: {cmd}"}
|
218
|
+
|
219
|
+
return {"safe": True}
|
220
|
+
|
61
221
|
@abstractmethod
|
62
222
|
async def cleanup(self):
|
63
223
|
"""Clean up any resources used by the provider."""
|
@@ -129,14 +289,14 @@ class CodeExecutionProvider(ABC):
|
|
129
289
|
if variables_str_list:
|
130
290
|
# Find where to insert (after tools section if it exists)
|
131
291
|
insert_index = 0
|
132
|
-
for i, code in enumerate(self.
|
292
|
+
for i, code in enumerate(self.code_tools_definitions):
|
133
293
|
if "###########</tools>###########" in code:
|
134
294
|
insert_index = i + 1
|
135
295
|
break
|
136
296
|
|
137
297
|
# Insert the variables code
|
138
298
|
for j, var_code in enumerate(variables_str_list):
|
139
|
-
self.
|
299
|
+
self.code_tools_definitions.insert(insert_index + j, var_code)
|
140
300
|
|
141
301
|
def _remove_existing_user_variables(self) -> None:
|
142
302
|
"""Remove existing user variables from default python codes."""
|
@@ -144,16 +304,16 @@ class CodeExecutionProvider(ABC):
|
|
144
304
|
start_index = None
|
145
305
|
end_index = None
|
146
306
|
|
147
|
-
for i, code in enumerate(self.
|
307
|
+
for i, code in enumerate(self.code_tools_definitions):
|
148
308
|
if "###########<user_variables>###########" in code:
|
149
|
-
start_index = i - 1 if i > 0 and "import cloudpickle" in self.
|
309
|
+
start_index = i - 1 if i > 0 and "import cloudpickle" in self.code_tools_definitions[i-1] else i
|
150
310
|
elif "###########</user_variables>###########" in code:
|
151
311
|
end_index = i + 2 # Include the newline after
|
152
312
|
break
|
153
313
|
|
154
314
|
if start_index is not None and end_index is not None:
|
155
315
|
# Remove the old variables section
|
156
|
-
del self.
|
316
|
+
del self.code_tools_definitions[start_index:end_index]
|
157
317
|
|
158
318
|
def get_user_variables(self) -> Dict[str, Any]:
|
159
319
|
"""
|
@@ -204,4 +364,18 @@ class CodeExecutionProvider(ABC):
|
|
204
364
|
self._user_variables[var_name] = var_value
|
205
365
|
except Exception:
|
206
366
|
# If serialization fails, skip this variable
|
207
|
-
pass
|
367
|
+
pass
|
368
|
+
|
369
|
+
def shell_response_to_llm_understandable(self, response: Dict[str, Any]) -> str:
|
370
|
+
"""
|
371
|
+
Convert a shell command response to a format that is understandable by the LLM.
|
372
|
+
"""
|
373
|
+
if response.get('stderr',None) not in [None,""]:
|
374
|
+
error_message = "Bash Error: " + response['stderr']
|
375
|
+
if "No such file or directory" in response['stderr']:
|
376
|
+
error_message.replace("No such file or directory", "No such file or directory, Have you provided the correct absolute path? If you are unsure use ls first to make sure the path exists")
|
377
|
+
if "Command timed out after" in response['stderr']:
|
378
|
+
error_message += ", Make sure your command is specific enough. And only if it is the most specific and optimized command then try to increase the timeout parameter if you need to more time for this command."
|
379
|
+
return error_message
|
380
|
+
else:
|
381
|
+
return response['stdout']
|
@@ -1,9 +1,22 @@
|
|
1
1
|
import sys
|
2
2
|
import modal
|
3
3
|
import cloudpickle
|
4
|
+
from pprint import pprint
|
4
5
|
from typing import Dict, List, Any, Optional, Union
|
5
6
|
from .base import CodeExecutionProvider
|
6
|
-
from ..utils import clean_response, make_session_blob, _run_python
|
7
|
+
from ..utils import clean_response, make_session_blob, _run_python, _run_shell
|
8
|
+
try:
|
9
|
+
from ..modal_sandbox import COLOR
|
10
|
+
except ImportError:
|
11
|
+
# Fallback colors if modal_sandbox is not available
|
12
|
+
COLOR = {
|
13
|
+
"HEADER": "\033[95m",
|
14
|
+
"BLUE": "\033[94m",
|
15
|
+
"GREEN": "\033[92m",
|
16
|
+
"RED": "\033[91m",
|
17
|
+
"ENDC": "\033[0m",
|
18
|
+
}
|
19
|
+
|
7
20
|
|
8
21
|
|
9
22
|
class ModalProvider(CodeExecutionProvider):
|
@@ -16,6 +29,7 @@ class ModalProvider(CodeExecutionProvider):
|
|
16
29
|
"""
|
17
30
|
|
18
31
|
PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}"
|
32
|
+
TIMEOUT_MAX = 120
|
19
33
|
|
20
34
|
def __init__(
|
21
35
|
self,
|
@@ -27,25 +41,45 @@ class ModalProvider(CodeExecutionProvider):
|
|
27
41
|
apt_packages: Optional[List[str]] = None,
|
28
42
|
python_version: Optional[str] = None,
|
29
43
|
authorized_imports: list[str] | None = None,
|
44
|
+
authorized_functions: list[str] | None = None,
|
30
45
|
modal_secrets: Dict[str, Union[str, None]] | None = None,
|
31
46
|
lazy_init: bool = True,
|
32
47
|
sandbox_name: str = "tinycodeagent-sandbox",
|
33
48
|
local_execution: bool = False,
|
49
|
+
check_string_obfuscation: bool = True,
|
50
|
+
bypass_shell_safety: bool = False, # Default to False for ModalProvider
|
51
|
+
additional_safe_shell_commands: Optional[List[str]] = None,
|
52
|
+
additional_safe_control_operators: Optional[List[str]] = None,
|
34
53
|
**kwargs
|
35
54
|
):
|
36
|
-
"""
|
37
|
-
|
38
|
-
|
39
|
-
base class but accepted here for forward-compatibility.
|
40
|
-
|
55
|
+
"""
|
56
|
+
Initialize Modal-based code execution provider.
|
57
|
+
|
41
58
|
Args:
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
59
|
+
log_manager: Log manager instance
|
60
|
+
default_python_codes: List of Python code snippets to execute before user code
|
61
|
+
code_tools: List of code tools to make available
|
62
|
+
pip_packages: List of pip packages to install in the sandbox
|
63
|
+
default_packages: List of default pip packages to install in the sandbox
|
64
|
+
apt_packages: List of apt packages to install in the sandbox
|
65
|
+
python_version: Python version to use in the sandbox
|
66
|
+
authorized_imports: Optional allow-list of modules the user code is permitted to import
|
67
|
+
authorized_functions: Optional allow-list of dangerous functions the user code is permitted to use
|
68
|
+
modal_secrets: Dictionary of secrets to make available to the sandbox
|
69
|
+
lazy_init: Whether to initialize Modal app lazily
|
70
|
+
sandbox_name: Name of the Modal sandbox
|
71
|
+
local_execution: Whether to execute code locally
|
72
|
+
check_string_obfuscation: If True (default), check for string obfuscation techniques. Set to False to allow legitimate use of base64 encoding and other string manipulations.
|
73
|
+
bypass_shell_safety: If True, bypass shell command safety checks (default: False for modal)
|
74
|
+
additional_safe_shell_commands: Additional shell commands to consider safe
|
75
|
+
additional_safe_control_operators: Additional shell control operators to consider safe
|
76
|
+
**kwargs: Additional keyword arguments
|
77
|
+
|
78
|
+
Note:
|
79
|
+
The Modal sandbox is a secure environment for executing untrusted code.
|
80
|
+
It provides isolation from the host system and other sandboxes.
|
81
|
+
|
82
|
+
Default packages are always installed, while pip_packages are added to
|
49
83
|
(git, curl, …) so you only need to specify the extras.
|
50
84
|
python_version: Python version used for the sandbox image. If
|
51
85
|
``None`` the current interpreter version is used.
|
@@ -63,7 +97,7 @@ class ModalProvider(CodeExecutionProvider):
|
|
63
97
|
]
|
64
98
|
|
65
99
|
if apt_packages is None:
|
66
|
-
apt_packages = ["git", "curl", "nodejs", "npm"]
|
100
|
+
apt_packages = ["git", "curl", "nodejs", "npm","ripgrep"]
|
67
101
|
|
68
102
|
if python_version is None:
|
69
103
|
python_version = self.PYTHON_VERSION
|
@@ -74,6 +108,8 @@ class ModalProvider(CodeExecutionProvider):
|
|
74
108
|
self.python_version: str = python_version
|
75
109
|
self.authorized_imports = authorized_imports
|
76
110
|
|
111
|
+
self.authorized_functions = authorized_functions or []
|
112
|
+
self.check_string_obfuscation = check_string_obfuscation
|
77
113
|
# ----------------------------------------------------------------------
|
78
114
|
final_packages = list(set(self.default_packages + (pip_packages or [])))
|
79
115
|
|
@@ -84,6 +120,9 @@ class ModalProvider(CodeExecutionProvider):
|
|
84
120
|
pip_packages=final_packages,
|
85
121
|
secrets=modal_secrets or {},
|
86
122
|
lazy_init=lazy_init,
|
123
|
+
bypass_shell_safety=bypass_shell_safety,
|
124
|
+
additional_safe_shell_commands=additional_safe_shell_commands,
|
125
|
+
additional_safe_control_operators=additional_safe_control_operators,
|
87
126
|
**kwargs
|
88
127
|
)
|
89
128
|
|
@@ -92,6 +131,7 @@ class ModalProvider(CodeExecutionProvider):
|
|
92
131
|
self.modal_secrets = modal.Secret.from_dict(self.secrets)
|
93
132
|
self.app = None
|
94
133
|
self._app_run_python = None
|
134
|
+
self._app_run_shell = None
|
95
135
|
self.is_trusted_code = kwargs.get("trust_code", False)
|
96
136
|
|
97
137
|
self._setup_modal_app()
|
@@ -117,6 +157,7 @@ class ModalProvider(CodeExecutionProvider):
|
|
117
157
|
)
|
118
158
|
|
119
159
|
self._app_run_python = self.app.function()(_run_python)
|
160
|
+
self._app_run_shell = self.app.function()(_run_shell)
|
120
161
|
|
121
162
|
# Add tools if provided
|
122
163
|
if self.code_tools:
|
@@ -139,7 +180,7 @@ class ModalProvider(CodeExecutionProvider):
|
|
139
180
|
full_code = "\n".join(code_lines)
|
140
181
|
|
141
182
|
print("#" * 100)
|
142
|
-
print("
|
183
|
+
print("##########################################code##########################################")
|
143
184
|
print(full_code)
|
144
185
|
print("#" * 100)
|
145
186
|
|
@@ -170,6 +211,91 @@ class ModalProvider(CodeExecutionProvider):
|
|
170
211
|
|
171
212
|
return clean_response(response)
|
172
213
|
|
214
|
+
async def execute_shell(
|
215
|
+
self,
|
216
|
+
command: List[str],
|
217
|
+
timeout: int = 30,
|
218
|
+
workdir: Optional[str] = None
|
219
|
+
) -> Dict[str, Any]:
|
220
|
+
"""
|
221
|
+
Execute a shell command securely using Modal.
|
222
|
+
|
223
|
+
Args:
|
224
|
+
command: List of command parts to execute
|
225
|
+
timeout: Maximum execution time in seconds
|
226
|
+
workdir: Working directory for command execution
|
227
|
+
|
228
|
+
Returns:
|
229
|
+
Dictionary containing execution results with keys:
|
230
|
+
- stdout: stdout from the execution
|
231
|
+
- stderr: stderr from the execution
|
232
|
+
- exit_code: exit code from the command
|
233
|
+
"""
|
234
|
+
# First, check if the command is safe to execute
|
235
|
+
timeout = min(timeout, self.TIMEOUT_MAX)
|
236
|
+
if type(command) == str:
|
237
|
+
command = command.split(" ")
|
238
|
+
|
239
|
+
print("#########################<Bash>#########################")
|
240
|
+
print(f"{COLOR['BLUE']}>{command}{COLOR['ENDC']}")
|
241
|
+
safety_check = self.is_safe_command(command)
|
242
|
+
if not safety_check["safe"]:
|
243
|
+
|
244
|
+
response = {
|
245
|
+
"stdout": "",
|
246
|
+
"stderr": f"Command rejected for security reasons: {safety_check.get('reason', 'Unsafe command')}",
|
247
|
+
"exit_code": 1
|
248
|
+
}
|
249
|
+
print(f"{COLOR['RED']}{response['stderr']}{COLOR['ENDC']}")
|
250
|
+
return response
|
251
|
+
#execution_mode = "🏠 LOCALLY" if self.local_execution else "☁️ REMOTELY"
|
252
|
+
#print(f"Executing shell command {execution_mode} via Modal: {' '.join(command)}")
|
253
|
+
|
254
|
+
# Show working directory information
|
255
|
+
if workdir:
|
256
|
+
print(f"Working directory: {workdir}")
|
257
|
+
|
258
|
+
# If using Modal for remote execution
|
259
|
+
if not self.local_execution:
|
260
|
+
try:
|
261
|
+
with self.app.run():
|
262
|
+
result = self._app_run_shell.remote(
|
263
|
+
command=command,
|
264
|
+
timeout=timeout,
|
265
|
+
workdir=workdir
|
266
|
+
)
|
267
|
+
|
268
|
+
|
269
|
+
print(f"{COLOR['GREEN']}{result}{COLOR['ENDC']}")
|
270
|
+
return result
|
271
|
+
except Exception as e:
|
272
|
+
response = {
|
273
|
+
"stdout": "",
|
274
|
+
"stderr": f"Error executing shell command: {str(e)}",
|
275
|
+
"exit_code": 1
|
276
|
+
}
|
277
|
+
|
278
|
+
print(f"{COLOR['RED']}{response['stderr']}{COLOR['ENDC']}")
|
279
|
+
return response
|
280
|
+
# If executing locally
|
281
|
+
else:
|
282
|
+
try:
|
283
|
+
result = self._app_run_shell.local(
|
284
|
+
command=command,
|
285
|
+
timeout=timeout,
|
286
|
+
workdir=workdir
|
287
|
+
)
|
288
|
+
print(f"{COLOR['GREEN']}{result}{COLOR['ENDC']}")
|
289
|
+
return result
|
290
|
+
except Exception as e:
|
291
|
+
response = {
|
292
|
+
"stdout": "",
|
293
|
+
"stderr": f"Error executing shell command: {str(e)}",
|
294
|
+
"exit_code": 1
|
295
|
+
}
|
296
|
+
print(f"{COLOR['RED']}{response['stderr']}{COLOR['ENDC']}")
|
297
|
+
return response
|
298
|
+
|
173
299
|
def _python_executor(self, code: str, globals_dict: Dict[str, Any] = None, locals_dict: Dict[str, Any] = None):
|
174
300
|
"""Execute Python code using Modal's native .local() or .remote() methods."""
|
175
301
|
execution_mode = "🏠 LOCALLY" if self.local_execution else "☁️ REMOTELY"
|
@@ -191,8 +317,10 @@ class ModalProvider(CodeExecutionProvider):
|
|
191
317
|
full_code,
|
192
318
|
globals_dict or {},
|
193
319
|
locals_dict or {},
|
194
|
-
self.authorized_imports,
|
195
|
-
self.
|
320
|
+
authorized_imports=self.authorized_imports,
|
321
|
+
authorized_functions=self.authorized_functions,
|
322
|
+
trusted_code=self.is_trusted_code,
|
323
|
+
check_string_obfuscation=self.check_string_obfuscation,
|
196
324
|
)
|
197
325
|
else:
|
198
326
|
with self.app.run():
|
@@ -200,8 +328,10 @@ class ModalProvider(CodeExecutionProvider):
|
|
200
328
|
full_code,
|
201
329
|
globals_dict or {},
|
202
330
|
locals_dict or {},
|
203
|
-
self.authorized_imports,
|
204
|
-
self.
|
331
|
+
authorized_imports=self.authorized_imports,
|
332
|
+
authorized_functions=self.authorized_functions,
|
333
|
+
trusted_code=self.is_trusted_code,
|
334
|
+
check_string_obfuscation=self.check_string_obfuscation,
|
205
335
|
)
|
206
336
|
|
207
337
|
def _log_response(self, response: Dict[str, Any]):
|
@@ -224,14 +354,7 @@ class ModalProvider(CodeExecutionProvider):
|
|
224
354
|
# Check if this is a security exception and highlight it in red if so
|
225
355
|
error_text = response["error_traceback"]
|
226
356
|
if "SECURITY" in error_text:
|
227
|
-
|
228
|
-
from ..modal_sandbox import COLOR
|
229
|
-
except ImportError:
|
230
|
-
# Fallback colors if modal_sandbox is not available
|
231
|
-
COLOR = {
|
232
|
-
"RED": "\033[91m",
|
233
|
-
"ENDC": "\033[0m",
|
234
|
-
}
|
357
|
+
|
235
358
|
print(f"{COLOR['RED']}{error_text}{COLOR['ENDC']}")
|
236
359
|
else:
|
237
360
|
print(error_text)
|