forked from OpenHands/OpenHands
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmixin.py
More file actions
98 lines (80 loc) · 3.96 KB
/
mixin.py
File metadata and controls
98 lines (80 loc) · 3.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import os
from typing import Protocol
from opendevin.core.logger import opendevin_logger as logger
from opendevin.core.schema import CancellableStream
from opendevin.runtime.plugins.requirement import PluginRequirement
class SandboxProtocol(Protocol):
# https://stackoverflow.com/questions/51930339/how-do-i-correctly-add-type-hints-to-mixin-classes
@property
def initialize_plugins(self) -> bool: ...
def execute(
self, cmd: str, stream: bool = False
) -> tuple[int, str | CancellableStream]: ...
def copy_to(self, host_src: str, sandbox_dest: str, recursive: bool = False): ...
def _source_bashrc(sandbox: SandboxProtocol):
exit_code, output = sandbox.execute(
'source /opendevin/bash.bashrc && source ~/.bashrc'
)
if exit_code != 0:
raise RuntimeError(
f'Failed to source /opendevin/bash.bashrc and ~/.bashrc with exit code {exit_code} and output: {output}'
)
logger.info('Sourced /opendevin/bash.bashrc and ~/.bashrc successfully')
class PluginMixin:
"""Mixin for Sandbox to support plugins."""
def init_plugins(self: SandboxProtocol, requirements: list[PluginRequirement]):
"""Load a plugin into the sandbox."""
if hasattr(self, 'plugin_initialized') and self.plugin_initialized:
return
if self.initialize_plugins:
logger.info('Initializing plugins in the sandbox')
# clean-up ~/.bashrc and touch ~/.bashrc
exit_code, output = self.execute('rm -f ~/.bashrc && touch ~/.bashrc')
if exit_code != 0:
logger.warning(
f'Failed to clean-up ~/.bashrc with exit code {exit_code} and output: {output}'
)
for requirement in requirements:
# source bashrc file when plugin loads
_source_bashrc(self)
# copy over the files
self.copy_to(
requirement.host_src, requirement.sandbox_dest, recursive=True
)
logger.info(
f'Copied files from [{requirement.host_src}] to [{requirement.sandbox_dest}] inside sandbox.'
)
# Execute the bash script
abs_path_to_bash_script = os.path.join(
requirement.sandbox_dest, requirement.bash_script_path
)
logger.info(
f'Initializing plugin [{requirement.name}] by executing [{abs_path_to_bash_script}] in the sandbox.'
)
exit_code, output = self.execute(abs_path_to_bash_script, stream=True)
if isinstance(output, CancellableStream):
total_output = ''
for line in output:
# Removes any trailing whitespace, including \n and \r\n
line = line.rstrip()
# logger.debug(line)
# Avoid text from lines running into each other
total_output += line + ' '
_exit_code = output.exit_code()
output.close()
if _exit_code != 0:
raise RuntimeError(
f'Failed to initialize plugin {requirement.name} with exit code {_exit_code} and output: {total_output.strip()}'
)
logger.info(f'Plugin {requirement.name} initialized successfully')
else:
if exit_code != 0:
raise RuntimeError(
f'Failed to initialize plugin {requirement.name} with exit code {exit_code} and output: {output}'
)
logger.info(f'Plugin {requirement.name} initialized successfully.')
else:
logger.info('Skipping plugin initialization in the sandbox')
if len(requirements) > 0:
_source_bashrc(self)
self.plugin_initialized = True