mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-25 21:36:52 +08:00
Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: Engel Nyst <engel.nyst@gmail.com>
96 lines
3.4 KiB
Python
96 lines
3.4 KiB
Python
import asyncio
|
|
import os
|
|
from abc import ABC, abstractmethod
|
|
|
|
from openhands.agent_server import env_parser
|
|
from openhands.app_server.errors import SandboxError
|
|
from openhands.app_server.sandbox.sandbox_spec_models import (
|
|
SandboxSpecInfo,
|
|
SandboxSpecInfoPage,
|
|
)
|
|
from openhands.app_server.services.injector import Injector
|
|
from openhands.sdk.utils.models import DiscriminatedUnionMixin
|
|
|
|
# The version of the agent server to use for deployments.
|
|
# Typically this will be the same as the values from the pyproject.toml
|
|
AGENT_SERVER_IMAGE = 'ghcr.io/openhands/agent-server:97652be-python'
|
|
|
|
|
|
class SandboxSpecService(ABC):
|
|
"""Service for managing Sandbox specs.
|
|
|
|
At present this is read only. The plan is that later this class will allow building
|
|
and deleting sandbox specs and limiting access by user and group. It would also be
|
|
nice to be able to set the desired number of warm sandboxes for a spec and scale
|
|
this up and down.
|
|
"""
|
|
|
|
@abstractmethod
|
|
async def search_sandbox_specs(
|
|
self, page_id: str | None = None, limit: int = 100
|
|
) -> SandboxSpecInfoPage:
|
|
"""Search for sandbox specs."""
|
|
|
|
@abstractmethod
|
|
async def get_sandbox_spec(self, sandbox_spec_id: str) -> SandboxSpecInfo | None:
|
|
"""Get a single sandbox spec, returning None if not found."""
|
|
|
|
async def get_default_sandbox_spec(self) -> SandboxSpecInfo:
|
|
"""Get the default sandbox spec."""
|
|
page = await self.search_sandbox_specs()
|
|
if not page.items:
|
|
raise SandboxError('No sandbox specs available!')
|
|
return page.items[0]
|
|
|
|
async def batch_get_sandbox_specs(
|
|
self, sandbox_spec_ids: list[str]
|
|
) -> list[SandboxSpecInfo | None]:
|
|
"""Get a batch of sandbox specs, returning None for any not found."""
|
|
results = await asyncio.gather(
|
|
*[
|
|
self.get_sandbox_spec(sandbox_spec_id)
|
|
for sandbox_spec_id in sandbox_spec_ids
|
|
]
|
|
)
|
|
return results
|
|
|
|
|
|
class SandboxSpecServiceInjector(
|
|
DiscriminatedUnionMixin, Injector[SandboxSpecService], ABC
|
|
):
|
|
pass
|
|
|
|
|
|
def get_agent_server_image() -> str:
|
|
agent_server_image_repository = os.getenv('AGENT_SERVER_IMAGE_REPOSITORY')
|
|
agent_server_image_tag = os.getenv('AGENT_SERVER_IMAGE_TAG')
|
|
if agent_server_image_repository and agent_server_image_tag:
|
|
return f'{agent_server_image_repository}:{agent_server_image_tag}'
|
|
return AGENT_SERVER_IMAGE
|
|
|
|
|
|
def get_agent_server_env() -> dict[str, str]:
|
|
"""Get environment variables to be injected into agent server sandbox environments.
|
|
|
|
This function reads environment variable overrides from the OH_AGENT_SERVER_ENV
|
|
environment variable, which should contain a JSON string mapping variable names
|
|
to their values.
|
|
|
|
Usage:
|
|
Set OH_AGENT_SERVER_ENV to a JSON string:
|
|
OH_AGENT_SERVER_ENV='{"DEBUG": "true", "LOG_LEVEL": "info", "CUSTOM_VAR": "value"}'
|
|
|
|
This will inject the following environment variables into all sandbox environments:
|
|
- DEBUG=true
|
|
- LOG_LEVEL=info
|
|
- CUSTOM_VAR=value
|
|
|
|
Returns:
|
|
dict[str, str]: Dictionary of environment variable names to values.
|
|
Returns empty dict if OH_AGENT_SERVER_ENV is not set or invalid.
|
|
|
|
Raises:
|
|
JSONDecodeError: If OH_AGENT_SERVER_ENV contains invalid JSON.
|
|
"""
|
|
return env_parser.from_env(dict[str, str], 'OH_AGENT_SERVER_ENV')
|