2424from metagpt .actions .action import Action
2525from metagpt .logs import logger
2626from metagpt .schema import RunCodeContext , RunCodeResult
27+ from metagpt .tools .libs .sandbox_executor import get_sandbox_executor
2728from metagpt .utils .exceptions import handle_exception
2829
2930PROMPT_TEMPLATE = """
@@ -79,8 +80,21 @@ class RunCode(Action):
7980 name : str = "RunCode"
8081 i_context : RunCodeContext = Field (default_factory = RunCodeContext )
8182
83+ def _sandbox_enabled (self ) -> bool :
84+ """Check if sandbox execution is enabled in config."""
85+ try :
86+ return self .context .config .sandbox .enabled
87+ except (AttributeError , Exception ):
88+ return False
89+
8290 @classmethod
83- async def run_text (cls , code ) -> Tuple [str , str ]:
91+ async def run_text (cls , code , sandbox_config = None ) -> Tuple [str , str ]:
92+ if sandbox_config and sandbox_config .enabled :
93+ try :
94+ executor = await get_sandbox_executor (sandbox_config )
95+ return await executor .run_code (code , language = "python" )
96+ except Exception as e :
97+ logger .warning (f"Sandbox execution failed, falling back to local: { e } " )
8498 try :
8599 # We will document_store the result in this dictionary
86100 namespace = {}
@@ -93,6 +107,9 @@ async def run_script(self, working_directory, additional_python_paths=[], comman
93107 working_directory = str (working_directory )
94108 additional_python_paths = [str (path ) for path in additional_python_paths ]
95109
110+ if self ._sandbox_enabled ():
111+ return await self ._run_script_in_sandbox (working_directory , additional_python_paths , command )
112+
96113 # Copy the current environment variables
97114 env = self .context .new_environ ()
98115
@@ -117,6 +134,32 @@ async def run_script(self, working_directory, additional_python_paths=[], comman
117134 stdout , stderr = process .communicate ()
118135 return stdout .decode ("utf-8" ), stderr .decode ("utf-8" )
119136
137+ async def _run_script_in_sandbox (self , working_directory , additional_python_paths , command ) -> Tuple [str , str ]:
138+ """Execute a script inside the OpenSandbox."""
139+ executor = await get_sandbox_executor (self .context .config .sandbox )
140+ sandbox_workdir = "/workspace"
141+
142+ # Upload source files and requirements to sandbox
143+ work_path = Path (working_directory )
144+ if work_path .exists ():
145+ for f in work_path .iterdir ():
146+ if f .is_file () and f .suffix in (".py" , ".txt" , ".json" , ".yaml" , ".yml" ):
147+ await executor .upload_file (str (f ), f"{ sandbox_workdir } /{ f .name } " )
148+
149+ # Install dependencies inside sandbox
150+ req_file = work_path / "requirements.txt"
151+ if req_file .exists () and req_file .stat ().st_size > 0 :
152+ await executor .run_command (
153+ f"pip install -r { sandbox_workdir } /requirements.txt" , working_directory = sandbox_workdir
154+ )
155+ await executor .run_command ("pip install pytest" , working_directory = sandbox_workdir )
156+
157+ # Build and run the command inside sandbox
158+ cmd_str = " " .join (command )
159+ logger .info (f"Running in sandbox: { cmd_str } " )
160+ stdout , stderr , _ = await executor .run_command (cmd_str , working_directory = sandbox_workdir )
161+ return stdout , stderr
162+
120163 async def run (self , * args , ** kwargs ) -> RunCodeResult :
121164 logger .info (f"Running { ' ' .join (self .i_context .command )} " )
122165 if self .i_context .mode == "script" :
@@ -126,7 +169,8 @@ async def run(self, *args, **kwargs) -> RunCodeResult:
126169 additional_python_paths = self .i_context .additional_python_paths ,
127170 )
128171 elif self .i_context .mode == "text" :
129- outs , errs = await self .run_text (code = self .i_context .code )
172+ sandbox_config = self .context .config .sandbox if self ._sandbox_enabled () else None
173+ outs , errs = await self .run_text (code = self .i_context .code , sandbox_config = sandbox_config )
130174
131175 logger .info (f"{ outs = } " )
132176 logger .info (f"{ errs = } " )
0 commit comments