mixin.py 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  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. @property
  9. def initialize_plugins(self) -> bool: ...
  10. def execute(
  11. self, cmd: str, stream: bool = False
  12. ) -> tuple[int, str | CancellableStream]: ...
  13. def copy_to(self, host_src: str, sandbox_dest: str, recursive: bool = False): ...
  14. class PluginMixin:
  15. """Mixin for Sandbox to support plugins."""
  16. def init_plugins(self: SandboxProtocol, requirements: list[PluginRequirement]):
  17. """Load a plugin into the sandbox."""
  18. if hasattr(self, 'plugin_initialized') and self.plugin_initialized:
  19. return
  20. if self.initialize_plugins:
  21. logger.info('Initializing plugins in the sandbox')
  22. # clean-up ~/.bashrc and touch ~/.bashrc
  23. exit_code, output = self.execute('rm -f ~/.bashrc && touch ~/.bashrc')
  24. for requirement in requirements:
  25. # copy over the files
  26. self.copy_to(
  27. requirement.host_src, requirement.sandbox_dest, recursive=True
  28. )
  29. logger.info(
  30. f'Copied files from [{requirement.host_src}] to [{requirement.sandbox_dest}] inside sandbox.'
  31. )
  32. # Execute the bash script
  33. abs_path_to_bash_script = os.path.join(
  34. requirement.sandbox_dest, requirement.bash_script_path
  35. )
  36. logger.info(
  37. f'Initializing plugin [{requirement.name}] by executing [{abs_path_to_bash_script}] in the sandbox.'
  38. )
  39. exit_code, output = self.execute(abs_path_to_bash_script, stream=True)
  40. if isinstance(output, CancellableStream):
  41. for line in output:
  42. if line.endswith('\n'):
  43. line = line[:-1]
  44. _exit_code = output.exit_code()
  45. output.close()
  46. if _exit_code != 0:
  47. raise RuntimeError(
  48. f'Failed to initialize plugin {requirement.name} with exit code {_exit_code} and output {output}'
  49. )
  50. logger.info(f'Plugin {requirement.name} initialized successfully')
  51. else:
  52. if exit_code != 0:
  53. raise RuntimeError(
  54. f'Failed to initialize plugin {requirement.name} with exit code {exit_code} and output: {output}'
  55. )
  56. logger.info(f'Plugin {requirement.name} initialized successfully.')
  57. else:
  58. logger.info('Skipping plugin initialization in the sandbox')
  59. if len(requirements) > 0:
  60. exit_code, output = self.execute('source ~/.bashrc')
  61. if exit_code != 0:
  62. raise RuntimeError(
  63. f'Failed to source ~/.bashrc with exit code {exit_code} and output: {output}'
  64. )
  65. logger.info('Sourced ~/.bashrc successfully')
  66. self.plugin_initialized = True