mixin.py 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. import os
  2. from typing import Protocol
  3. from opendevin.core.logger import opendevin_logger as logger
  4. from opendevin.core.schema import CancellableStream
  5. from opendevin.runtime.plugins.requirement import PluginRequirement
  6. class SandboxProtocol(Protocol):
  7. # https://stackoverflow.com/questions/51930339/how-do-i-correctly-add-type-hints-to-mixin-classes
  8. def execute(
  9. self, cmd: str, stream: bool = False
  10. ) -> tuple[int, str | CancellableStream]: ...
  11. def copy_to(self, host_src: str, sandbox_dest: str, recursive: bool = False): ...
  12. class PluginMixin:
  13. """Mixin for Sandbox to support plugins."""
  14. def init_plugins(self: SandboxProtocol, requirements: list[PluginRequirement]):
  15. """Load a plugin into the sandbox."""
  16. # clean-up ~/.bashrc and touch ~/.bashrc
  17. exit_code, output = self.execute('rm -f ~/.bashrc && touch ~/.bashrc')
  18. for requirement in requirements:
  19. # copy over the files
  20. self.copy_to(requirement.host_src, requirement.sandbox_dest, recursive=True)
  21. logger.info(
  22. f'Copied files from [{requirement.host_src}] to [{requirement.sandbox_dest}] inside sandbox.'
  23. )
  24. # Execute the bash script
  25. abs_path_to_bash_script = os.path.join(
  26. requirement.sandbox_dest, requirement.bash_script_path
  27. )
  28. logger.info(
  29. f'Initializing plugin [{requirement.name}] by executing [{abs_path_to_bash_script}] in the sandbox.'
  30. )
  31. exit_code, output = self.execute(abs_path_to_bash_script, stream=True)
  32. if isinstance(output, CancellableStream):
  33. for line in output:
  34. if line.endswith('\n'):
  35. line = line[:-1]
  36. _exit_code = output.exit_code()
  37. output.close()
  38. if _exit_code != 0:
  39. raise RuntimeError(
  40. f'Failed to initialize plugin {requirement.name} with exit code {_exit_code} and output {output}'
  41. )
  42. logger.info(f'Plugin {requirement.name} initialized successfully')
  43. else:
  44. if exit_code != 0:
  45. raise RuntimeError(
  46. f'Failed to initialize plugin {requirement.name} with exit code {exit_code} and output: {output}'
  47. )
  48. logger.info(f'Plugin {requirement.name} initialized successfully.')
  49. if len(requirements) > 0:
  50. exit_code, output = self.execute('source ~/.bashrc')
  51. if exit_code != 0:
  52. raise RuntimeError(
  53. f'Failed to source ~/.bashrc with exit code {exit_code} and output: {output}'
  54. )
  55. logger.info('Sourced ~/.bashrc successfully')