Spaces:
Runtime error
Runtime error
import requests | |
import json | |
from ..tool import Tool | |
import os | |
import platform | |
import re | |
import subprocess | |
from typing import TYPE_CHECKING, List, Union | |
from uuid import uuid4 | |
import pexpect | |
def _lazy_import_pexpect() -> pexpect: | |
"""Import pexpect only when needed.""" | |
if platform.system() == "Windows": | |
raise ValueError("Persistent bash processes are not yet supported on Windows.") | |
try: | |
import pexpect | |
except ImportError: | |
raise ImportError( | |
"pexpect required for persistent bash processes." | |
" To install, run `pip install pexpect`." | |
) | |
return pexpect | |
class BashProcess: | |
"""Executes bash commands and returns the output.""" | |
def __init__( | |
self, | |
strip_newlines: bool = False, | |
return_err_output: bool = False, | |
persistent: bool = False, | |
): | |
"""Initialize with stripping newlines.""" | |
self.strip_newlines = strip_newlines | |
self.return_err_output = return_err_output | |
self.prompt = "" | |
self.process = None | |
if persistent: | |
self.prompt = str(uuid4()) | |
self.process = self._initialize_persistent_process(self.prompt) | |
def _initialize_persistent_process(prompt: str) -> pexpect.spawn: | |
# Start bash in a clean environment | |
# Doesn't work on windows | |
pexpect = _lazy_import_pexpect() | |
process = pexpect.spawn( | |
"env", ["-i", "bash", "--norc", "--noprofile"], encoding="utf-8" | |
) | |
# Set the custom prompt | |
process.sendline("PS1=" + prompt) | |
process.expect_exact(prompt, timeout=10) | |
return process | |
def run(self, commands: Union[str, List[str]]) -> str: | |
"""Run commands and return final output.""" | |
print("entering run") | |
if isinstance(commands, str): | |
commands = [commands] | |
commands = ";".join(commands) | |
print(commands) | |
if self.process is not None: | |
return self._run_persistent( | |
commands, | |
) | |
else: | |
return self._run(commands) | |
def _run(self, command: str) -> str: | |
"""Run commands and return final output.""" | |
print("entering _run") | |
try: | |
output = subprocess.run( | |
command, | |
shell=True, | |
check=True, | |
stdout=subprocess.PIPE, | |
stderr=subprocess.STDOUT, | |
).stdout.decode() | |
except subprocess.CalledProcessError as error: | |
if self.return_err_output: | |
return error.stdout.decode() | |
return str(error) | |
if self.strip_newlines: | |
output = output.strip() | |
return output | |
def process_output(self, output: str, command: str) -> str: | |
# Remove the command from the output using a regular expression | |
pattern = re.escape(command) + r"\s*\n" | |
output = re.sub(pattern, "", output, count=1) | |
return output.strip() | |
def _run_persistent(self, command: str) -> str: | |
"""Run commands and return final output.""" | |
print("entering _run_persistent") | |
pexpect = _lazy_import_pexpect() | |
if self.process is None: | |
raise ValueError("Process not initialized") | |
self.process.sendline(command) | |
# Clear the output with an empty string | |
self.process.expect(self.prompt, timeout=10) | |
self.process.sendline("") | |
try: | |
self.process.expect([self.prompt, pexpect.EOF], timeout=10) | |
except pexpect.TIMEOUT: | |
return f"Timeout error while executing command {command}" | |
if self.process.after == pexpect.EOF: | |
return f"Exited with error status: {self.process.exitstatus}" | |
output = self.process.before | |
output = self.process_output(output, command) | |
if self.strip_newlines: | |
return output.strip() | |
return output | |
def get_platform() -> str: | |
"""Get platform.""" | |
system = platform.system() | |
if system == "Darwin": | |
return "MacOS" | |
return system | |
def get_default_bash_process() -> BashProcess: | |
"""Get file path from string.""" | |
return BashProcess(return_err_output=True) | |
def build_tool(config) -> Tool: | |
tool = Tool( | |
"Terminal", | |
"Tool to run shell commands.", | |
name_for_model="Terminal", | |
description_for_model=f"Run shell commands on this {get_platform()} machine.", | |
logo_url="https://your-app-url.com/.well-known/logo.png", | |
contact_email="[email protected]", | |
legal_info_url="[email protected]", | |
) | |
process: BashProcess = get_default_bash_process() | |
"""Bash process to run commands.""" | |
def shell_run(commands: str): | |
"""Run commands and return final output. | |
Queries to shell_run must ALWAYS have this structure: {\"commands\": query}.\n", | |
and query should be a command string. | |
""" | |
return process.run(commands) | |
return tool | |