mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Add VSCode URL support and worker ports to sandbox services (#11426)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
parent
2889f736d9
commit
6d137e883f
3943
enterprise/poetry.lock
generated
3943
enterprise/poetry.lock
generated
File diff suppressed because one or more lines are too long
@ -19,6 +19,8 @@ from openhands.app_server.sandbox.docker_sandbox_spec_service import get_docker_
|
||||
from openhands.app_server.sandbox.sandbox_models import (
|
||||
AGENT_SERVER,
|
||||
VSCODE,
|
||||
WORKER_1,
|
||||
WORKER_2,
|
||||
ExposedUrl,
|
||||
SandboxInfo,
|
||||
SandboxPage,
|
||||
@ -124,6 +126,10 @@ class DockerSandboxService(SandboxService):
|
||||
session_api_key = None
|
||||
|
||||
if status == SandboxStatus.RUNNING:
|
||||
# Get session API key first
|
||||
env = self._get_container_env_vars(container)
|
||||
session_api_key = env.get(SESSION_API_KEY_VARIABLE)
|
||||
|
||||
# Get the first exposed port mapping
|
||||
exposed_urls = []
|
||||
port_bindings = container.attrs.get('NetworkSettings', {}).get('Ports', {})
|
||||
@ -141,19 +147,19 @@ class DockerSandboxService(SandboxService):
|
||||
None,
|
||||
)
|
||||
if exposed_port:
|
||||
url = self.container_url_pattern.format(port=host_port)
|
||||
|
||||
# VSCode URLs require the api_key and working dir
|
||||
if exposed_port.name == VSCODE:
|
||||
url += f'/?tkn={session_api_key}&folder={container.attrs["Config"]["WorkingDir"]}'
|
||||
|
||||
exposed_urls.append(
|
||||
ExposedUrl(
|
||||
name=exposed_port.name,
|
||||
url=self.container_url_pattern.format(
|
||||
port=host_port
|
||||
),
|
||||
url=url,
|
||||
)
|
||||
)
|
||||
|
||||
# Get session API key
|
||||
env = self._get_container_env_vars(container)
|
||||
session_api_key = env[SESSION_API_KEY_VARIABLE]
|
||||
|
||||
return SandboxInfo(
|
||||
id=container.name,
|
||||
created_by_user_id=None,
|
||||
@ -394,6 +400,20 @@ class DockerSandboxServiceInjector(SandboxServiceInjector):
|
||||
),
|
||||
container_port=8001,
|
||||
),
|
||||
ExposedPort(
|
||||
name=WORKER_1,
|
||||
description=(
|
||||
'The first port on which the agent should start application servers.'
|
||||
),
|
||||
container_port=8011,
|
||||
),
|
||||
ExposedPort(
|
||||
name=WORKER_2,
|
||||
description=(
|
||||
'The first port on which the agent should start application servers.'
|
||||
),
|
||||
container_port=8012,
|
||||
),
|
||||
]
|
||||
)
|
||||
health_check_path: str | None = Field(
|
||||
|
||||
@ -14,7 +14,7 @@ from openhands.app_server.sandbox.sandbox_spec_models import (
|
||||
SandboxSpecInfo,
|
||||
)
|
||||
from openhands.app_server.sandbox.sandbox_spec_service import (
|
||||
AGENT_SERVER_VERSION,
|
||||
AGENT_SERVER_IMAGE,
|
||||
SandboxSpecService,
|
||||
SandboxSpecServiceInjector,
|
||||
)
|
||||
@ -34,7 +34,7 @@ def get_docker_client() -> docker.DockerClient:
|
||||
def get_default_sandbox_specs():
|
||||
return [
|
||||
SandboxSpecInfo(
|
||||
id=f'ghcr.io/all-hands-ai/agent-server:{AGENT_SERVER_VERSION[:7]}-python',
|
||||
id=AGENT_SERVER_IMAGE,
|
||||
command=['--port', '8000'],
|
||||
initial_env={
|
||||
'OPENVSCODE_SERVER_ROOT': '/openhands/.openvscode-server',
|
||||
|
||||
@ -10,7 +10,7 @@ from openhands.app_server.sandbox.sandbox_spec_models import (
|
||||
SandboxSpecInfo,
|
||||
)
|
||||
from openhands.app_server.sandbox.sandbox_spec_service import (
|
||||
AGENT_SERVER_VERSION,
|
||||
AGENT_SERVER_IMAGE,
|
||||
SandboxSpecService,
|
||||
SandboxSpecServiceInjector,
|
||||
)
|
||||
@ -20,7 +20,7 @@ from openhands.app_server.services.injector import InjectorState
|
||||
def get_default_sandbox_specs():
|
||||
return [
|
||||
SandboxSpecInfo(
|
||||
id=AGENT_SERVER_VERSION,
|
||||
id=AGENT_SERVER_IMAGE,
|
||||
command=['python', '-m', 'openhands.agent_server'],
|
||||
initial_env={
|
||||
# VSCode disabled for now
|
||||
|
||||
@ -26,6 +26,9 @@ from openhands.app_server.event_callback.event_callback_service import (
|
||||
)
|
||||
from openhands.app_server.sandbox.sandbox_models import (
|
||||
AGENT_SERVER,
|
||||
VSCODE,
|
||||
WORKER_1,
|
||||
WORKER_2,
|
||||
ExposedUrl,
|
||||
SandboxInfo,
|
||||
SandboxPage,
|
||||
@ -144,6 +147,17 @@ class RemoteSandboxService(SandboxService):
|
||||
url = runtime.get('url', None)
|
||||
if url:
|
||||
exposed_urls.append(ExposedUrl(name=AGENT_SERVER, url=url))
|
||||
vscode_url = (
|
||||
_build_service_url(url, 'vscode')
|
||||
+ f'/?tkn={session_api_key}&folder={runtime["working_dir"]}'
|
||||
)
|
||||
exposed_urls.append(ExposedUrl(name=VSCODE, url=vscode_url))
|
||||
exposed_urls.append(
|
||||
ExposedUrl(name=WORKER_1, url=_build_service_url(url, 'work-1'))
|
||||
)
|
||||
exposed_urls.append(
|
||||
ExposedUrl(name=WORKER_2, url=_build_service_url(url, 'work-2'))
|
||||
)
|
||||
else:
|
||||
exposed_urls = None
|
||||
else:
|
||||
@ -383,6 +397,11 @@ class RemoteSandboxService(SandboxService):
|
||||
return False
|
||||
|
||||
|
||||
def _build_service_url(url: str, service_name: str):
|
||||
scheme, host_and_path = url.split('://')
|
||||
return scheme + '://' + service_name + '-' + host_and_path
|
||||
|
||||
|
||||
async def poll_agent_servers(api_url: str, api_key: str, sleep_interval: int):
|
||||
"""When the app server does not have a public facing url, we poll the agent
|
||||
servers for the most recent data.
|
||||
|
||||
@ -10,7 +10,7 @@ from openhands.app_server.sandbox.sandbox_spec_models import (
|
||||
SandboxSpecInfo,
|
||||
)
|
||||
from openhands.app_server.sandbox.sandbox_spec_service import (
|
||||
AGENT_SERVER_VERSION,
|
||||
AGENT_SERVER_IMAGE,
|
||||
SandboxSpecService,
|
||||
SandboxSpecServiceInjector,
|
||||
)
|
||||
@ -20,7 +20,7 @@ from openhands.app_server.services.injector import InjectorState
|
||||
def get_default_sandbox_specs():
|
||||
return [
|
||||
SandboxSpecInfo(
|
||||
id=f'ghcr.io/all-hands-ai/agent-server:{AGENT_SERVER_VERSION[:7]}-python',
|
||||
id=AGENT_SERVER_IMAGE,
|
||||
command=['/usr/local/bin/openhands-agent-server', '--port', '60000'],
|
||||
initial_env={
|
||||
'OPENVSCODE_SERVER_ROOT': '/openhands/.openvscode-server',
|
||||
@ -28,6 +28,7 @@ def get_default_sandbox_specs():
|
||||
'OH_ENABLE_VNC': '0',
|
||||
'OH_CONVERSATIONS_PATH': '/workspace/conversations',
|
||||
'OH_BASH_EVENTS_DIR': '/workspace/bash_events',
|
||||
'OH_VSCODE_PORT': '60001',
|
||||
},
|
||||
working_dir='/workspace/projects',
|
||||
)
|
||||
|
||||
@ -25,6 +25,8 @@ class ExposedUrl(BaseModel):
|
||||
# Standard names
|
||||
AGENT_SERVER = 'AGENT_SERVER'
|
||||
VSCODE = 'VSCODE'
|
||||
WORKER_1 = 'WORKER_1'
|
||||
WORKER_2 = 'WORKER_2'
|
||||
|
||||
|
||||
class SandboxInfo(BaseModel):
|
||||
|
||||
@ -11,7 +11,7 @@ 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_VERSION = '08cf609a996523c0199c61c768d74417b7e96109'
|
||||
AGENT_SERVER_IMAGE = 'ghcr.io/all-hands-ai/agent-server:ab36fd6-python'
|
||||
|
||||
|
||||
class SandboxSpecService(ABC):
|
||||
|
||||
4161
poetry.lock
generated
4161
poetry.lock
generated
File diff suppressed because one or more lines are too long
@ -113,10 +113,10 @@ e2b-code-interpreter = { version = "^2.0.0", optional = true }
|
||||
pybase62 = "^1.0.0"
|
||||
|
||||
# V1 dependencies
|
||||
openhands-agent-server = { git = "https://github.com/All-Hands-AI/agent-sdk.git", subdirectory = "openhands/agent_server", rev = "08cf609a996523c0199c61c768d74417b7e96109" }
|
||||
openhands-sdk = { git = "https://github.com/All-Hands-AI/agent-sdk.git", subdirectory = "openhands/sdk", rev = "08cf609a996523c0199c61c768d74417b7e96109" }
|
||||
openhands-agent-server = { git = "https://github.com/All-Hands-AI/agent-sdk.git", subdirectory = "openhands-agent-server", rev = "512399d896521aee3131eea4bb59087fb9dfa243" }
|
||||
openhands-sdk = { git = "https://github.com/All-Hands-AI/agent-sdk.git", subdirectory = "openhands-sdk", rev = "512399d896521aee3131eea4bb59087fb9dfa243" }
|
||||
# This refuses to install
|
||||
# openhands-tools = { git = "https://github.com/All-Hands-AI/agent-sdk.git", subdirectory = "openhands/tools", rev = "08cf609a996523c0199c61c768d74417b7e96109" }
|
||||
openhands-tools = { git = "https://github.com/All-Hands-AI/agent-sdk.git", subdirectory = "openhands-tools", rev = "512399d896521aee3131eea4bb59087fb9dfa243" }
|
||||
python-jose = { version = ">=3.3", extras = [ "cryptography" ] }
|
||||
sqlalchemy = { extras = [ "asyncio" ], version = "^2.0.40" }
|
||||
pg8000 = "^1.31.5"
|
||||
|
||||
@ -94,7 +94,8 @@ def mock_running_container():
|
||||
container.attrs = {
|
||||
'Created': '2024-01-15T10:30:00.000000000Z',
|
||||
'Config': {
|
||||
'Env': ['OH_SESSION_API_KEYS_0=session_key_123', 'OTHER_VAR=other_value']
|
||||
'Env': ['OH_SESSION_API_KEYS_0=session_key_123', 'OTHER_VAR=other_value'],
|
||||
'WorkingDir': '/workspace',
|
||||
},
|
||||
'NetworkSettings': {
|
||||
'Ports': {
|
||||
@ -629,7 +630,10 @@ class TestDockerSandboxService:
|
||||
assert agent_url.url == 'http://localhost:12345'
|
||||
|
||||
vscode_url = next(url for url in result.exposed_urls if url.name == VSCODE)
|
||||
assert vscode_url.url == 'http://localhost:12346'
|
||||
assert (
|
||||
vscode_url.url
|
||||
== 'http://localhost:12346/?tkn=session_key_123&folder=/workspace'
|
||||
)
|
||||
|
||||
async def test_container_to_sandbox_info_invalid_created_time(self, service):
|
||||
"""Test conversion with invalid creation timestamp."""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user