@@ -84,6 +84,8 @@ class TerminalToolkit(BaseToolkit):
8484 when safe_mode is True. If None, uses default safety rules.
8585 clone_current_env (bool): Whether to clone the current Python
8686 environment for local execution. Defaults to False.
87+ install_dependencies (List): A list of user specified libraries
88+ to install.
8789 """
8890
8991 def __init__ (
@@ -96,6 +98,7 @@ def __init__(
9698 safe_mode : bool = True ,
9799 allowed_commands : Optional [List [str ]] = None ,
98100 clone_current_env : bool = False ,
101+ install_dependencies : Optional [List [str ]] = None ,
99102 ):
100103 self .use_docker_backend = use_docker_backend
101104 self .timeout = timeout
@@ -145,6 +148,7 @@ def __init__(
145148 self .cloned_env_path : Optional [str ] = None
146149 self .initial_env_path : Optional [str ] = None
147150 self .python_executable = sys .executable
151+ self .install_dependencies = install_dependencies or []
148152
149153 self .log_dir = os .path .abspath (
150154 session_logs_dir or os .path .join (self .working_dir , "terminal_logs" )
@@ -228,6 +232,10 @@ def __init__(
228232 "- container is already isolated"
229233 )
230234
235+ # Install dependencies
236+ if self .install_dependencies :
237+ self ._install_dependencies ()
238+
231239 def _setup_cloned_environment (self ):
232240 r"""Set up a cloned Python environment."""
233241 self .cloned_env_path = os .path .join (self .working_dir , ".venv" )
@@ -255,6 +263,80 @@ def update_callback(msg: str):
255263 "using system Python"
256264 )
257265
266+ def _install_dependencies (self ):
267+ r"""Install user specified dependencies in the current environment."""
268+ if not self .install_dependencies :
269+ return
270+
271+ logger .info ("Installing dependencies..." )
272+
273+ if self .use_docker_backend :
274+ pkg_str = " " .join (
275+ shlex .quote (p ) for p in self .install_dependencies
276+ )
277+ install_cmd = f'sh -lc "pip install { pkg_str } "'
278+
279+ try :
280+ exec_id = self .docker_api_client .exec_create (
281+ self .container .id , install_cmd
282+ )["Id" ]
283+ log = self .docker_api_client .exec_start (exec_id )
284+ logger .info (f"Package installation output:\n { log } " )
285+
286+ # Check exit code to ensure installation succeeded
287+ exec_info = self .docker_api_client .exec_inspect (exec_id )
288+ if exec_info ['ExitCode' ] != 0 :
289+ error_msg = (
290+ f"Failed to install dependencies in Docker: "
291+ f"{ log .decode ('utf-8' , errors = 'ignore' )} "
292+ )
293+ logger .error (error_msg )
294+ raise RuntimeError (error_msg )
295+
296+ logger .info (
297+ "Successfully installed all dependencies in Docker."
298+ )
299+ except Exception as e :
300+ if not isinstance (e , RuntimeError ):
301+ logger .error (f"Docker dependency installation error: { e } " )
302+ raise RuntimeError (
303+ f"Docker dependency installation error: { e } "
304+ ) from e
305+ raise
306+
307+ else :
308+ pip_cmd = [
309+ self .python_executable ,
310+ "-m" ,
311+ "pip" ,
312+ "install" ,
313+ "--upgrade" ,
314+ * self .install_dependencies ,
315+ ]
316+
317+ try :
318+ subprocess .run (
319+ pip_cmd ,
320+ check = True ,
321+ cwd = self .working_dir ,
322+ capture_output = True ,
323+ text = True ,
324+ timeout = 300 , # 5 minutes timeout for installation
325+ )
326+ logger .info ("Successfully installed all dependencies." )
327+ except subprocess .CalledProcessError as e :
328+ logger .error (f"Failed to install dependencies: { e .stderr } " )
329+ raise RuntimeError (
330+ f"Failed to install dependencies: { e .stderr } "
331+ ) from e
332+ except subprocess .TimeoutExpired :
333+ logger .error (
334+ "Dependency installation timed out after 5 minutes"
335+ )
336+ raise RuntimeError (
337+ "Dependency installation timed out after 5 minutes"
338+ )
339+
258340 def _setup_initial_environment (self ):
259341 r"""Set up an initial environment with Python 3.10."""
260342 self .initial_env_path = os .path .join (self .working_dir , ".initial_env" )
0 commit comments