mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
* start moving files * initial refactor * factor out command management * fix command runner * add workspace to gitignore * factor out command manager * remove dupe add_event * update docs * fix init * fix langchain agent after merge
90 lines
3.2 KiB
Python
90 lines
3.2 KiB
Python
import subprocess
|
|
import select
|
|
from typing import List
|
|
|
|
from opendevin.lib.event import Event
|
|
|
|
class BackgroundCommand:
|
|
def __init__(self, id: int, command: str, process: subprocess.Popen):
|
|
self.command = command
|
|
self.id = id
|
|
self.process = process
|
|
|
|
def _get_log_from_stream(self, stream):
|
|
logs = ""
|
|
while True:
|
|
readable, _, _ = select.select([stream], [], [], .1)
|
|
if not readable:
|
|
break
|
|
next = stream.readline()
|
|
if next == '':
|
|
break
|
|
logs += next
|
|
if logs == "": return
|
|
return logs
|
|
|
|
def get_logs(self):
|
|
stdout = self._get_log_from_stream(self.process.stdout)
|
|
stderr = self._get_log_from_stream(self.process.stderr)
|
|
exit_code = self.process.poll()
|
|
return stdout, stderr, exit_code
|
|
|
|
class CommandManager:
|
|
def __init__(self):
|
|
self.cur_id = 0
|
|
self.background_commands = {}
|
|
|
|
def run_command(self, command: str, background=False) -> str:
|
|
if background:
|
|
return self.run_background(command)
|
|
else:
|
|
return self.run_immediately(command)
|
|
|
|
def run_immediately(self, command: str) -> str:
|
|
result = subprocess.run(["/bin/bash", "-c", command], capture_output=True, text=True)
|
|
output = result.stdout + result.stderr
|
|
exit_code = result.returncode
|
|
if exit_code != 0:
|
|
raise ValueError('Command failed with exit code ' + str(exit_code) + ': ' + output)
|
|
return output
|
|
|
|
def run_background(self, command: str) -> str:
|
|
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True)
|
|
bg_cmd = BackgroundCommand(self.cur_id, command, process)
|
|
self.cur_id += 1
|
|
self.background_commands[bg_cmd.id] = bg_cmd
|
|
return "Background command started. To stop it, send a `kill` action with id " + str(bg_cmd.id)
|
|
|
|
def kill_command(self, id: int) -> str:
|
|
# TODO: get log events before killing
|
|
self.background_commands[id].processs.kill()
|
|
del self.background_commands[id]
|
|
|
|
def get_background_events(self) -> List[Event]:
|
|
events = []
|
|
for id, cmd in self.background_commands.items():
|
|
stdout, stderr, exit_code = cmd.get_logs()
|
|
if stdout is not None:
|
|
events.append(Event('output', {
|
|
'output': stdout,
|
|
'stream': 'stdout',
|
|
'id': id,
|
|
'command': cmd.command,
|
|
}))
|
|
if stderr is not None:
|
|
events.append(Event('output', {
|
|
'output': stderr,
|
|
'stream': 'stderr',
|
|
'id': id,
|
|
'command': cmd.command,
|
|
}))
|
|
if exit_code is not None:
|
|
events.append(Event('output', {
|
|
'exit_code': exit_code,
|
|
'output': 'Background command %d exited with code %d' % (idx, exit_code),
|
|
'id': id,
|
|
'command': cmd.command,
|
|
}))
|
|
del self.background_commands[id]
|
|
return events
|