mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
1 Commits
enable-jso
...
openhands/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9367e3d7d1 |
@@ -8,19 +8,47 @@ nikolaik の `SANDBOX_RUNTIME_CONTAINER_IMAGE` は、ランタイムサーバー
|
||||
|
||||
## ファイルシステムへの接続
|
||||
ここでの便利な機能の1つは、ローカルファイルシステムに接続する機能です。ファイルシステムをランタイムにマウントするには:
|
||||
|
||||
### RUNTIME_MOUNT の使用(推奨)
|
||||
|
||||
ローカルファイルシステムをマウントする最も簡単な方法は、`RUNTIME_MOUNT` 環境変数を使用することです:
|
||||
|
||||
```bash
|
||||
docker run # ...
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e RUNTIME_MOUNT=/path/to/your/code:/workspace:rw \
|
||||
# ...
|
||||
```
|
||||
|
||||
`RUNTIME_MOUNT` の形式は:`ホストパス:コンテナパス[:モード]`
|
||||
|
||||
- `ホストパス`:マウントしたいホストマシン上のパス
|
||||
- `コンテナパス`:ホストパスがマウントされるコンテナ内のパス(通常は `/workspace`)
|
||||
- `モード`:オプションのマウントモード、`rw`(読み書き可能、デフォルト)または `ro`(読み取り専用)
|
||||
|
||||
例:
|
||||
|
||||
```bash
|
||||
# Linux と Mac の例
|
||||
export RUNTIME_MOUNT=$HOME/OpenHands:/workspace:rw
|
||||
|
||||
# Windows の WSL の例
|
||||
export RUNTIME_MOUNT=/mnt/c/dev/OpenHands:/workspace:rw
|
||||
|
||||
# 読み取り専用マウントの例
|
||||
export RUNTIME_MOUNT=/path/to/reference/code:/workspace:ro
|
||||
```
|
||||
|
||||
### WORKSPACE_* 変数の使用(非推奨)
|
||||
|
||||
> **注意:** この方法は非推奨であり、将来のバージョンで削除される予定です。代わりに `RUNTIME_MOUNT` を使用してください。
|
||||
|
||||
1. `WORKSPACE_BASE` を設定します:
|
||||
|
||||
```bash
|
||||
export WORKSPACE_BASE=/path/to/your/code
|
||||
|
||||
# Linux と Mac の例
|
||||
# export WORKSPACE_BASE=$HOME/OpenHands
|
||||
# $WORKSPACE_BASE を /home/<username>/OpenHands に設定します
|
||||
#
|
||||
# Windows の WSL の例
|
||||
# export WORKSPACE_BASE=/mnt/c/dev/OpenHands
|
||||
# $WORKSPACE_BASE を C:\dev\OpenHands に設定します
|
||||
```
|
||||
|
||||
2. 以下のオプションを `docker run` コマンドに追加します:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -9,19 +9,47 @@ You can also [build your own runtime image](../how-to/custom-sandbox-guide).
|
||||
|
||||
## Connecting to Your filesystem
|
||||
A useful feature is the ability to connect to your local filesystem. To mount your filesystem into the runtime:
|
||||
|
||||
### Using RUNTIME_MOUNT (Recommended)
|
||||
|
||||
The simplest way to mount your local filesystem is to use the `RUNTIME_MOUNT` environment variable:
|
||||
|
||||
```bash
|
||||
docker run # ...
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e RUNTIME_MOUNT=/path/to/your/code:/workspace:rw \
|
||||
# ...
|
||||
```
|
||||
|
||||
The `RUNTIME_MOUNT` format is: `host_path:container_path[:mode]`
|
||||
|
||||
- `host_path`: The path on your host machine that you want to mount
|
||||
- `container_path`: The path inside the container where the host path will be mounted (typically `/workspace`)
|
||||
- `mode`: Optional mount mode, either `rw` (read-write, default) or `ro` (read-only)
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Linux and Mac Example
|
||||
export RUNTIME_MOUNT=$HOME/OpenHands:/workspace:rw
|
||||
|
||||
# WSL on Windows Example
|
||||
export RUNTIME_MOUNT=/mnt/c/dev/OpenHands:/workspace:rw
|
||||
|
||||
# Read-only mount example
|
||||
export RUNTIME_MOUNT=/path/to/reference/code:/workspace:ro
|
||||
```
|
||||
|
||||
### Using WORKSPACE_* variables (Deprecated)
|
||||
|
||||
> **Note:** This method is deprecated and will be removed in a future version. Please use `RUNTIME_MOUNT` instead.
|
||||
|
||||
1. Set `WORKSPACE_BASE`:
|
||||
|
||||
```bash
|
||||
export WORKSPACE_BASE=/path/to/your/code
|
||||
|
||||
# Linux and Mac Example
|
||||
# export WORKSPACE_BASE=$HOME/OpenHands
|
||||
# Will set $WORKSPACE_BASE to /home/<username>/OpenHands
|
||||
#
|
||||
# WSL on Windows Example
|
||||
# export WORKSPACE_BASE=/mnt/c/dev/OpenHands
|
||||
# Will set $WORKSPACE_BASE to C:\dev\OpenHands
|
||||
```
|
||||
|
||||
2. Add the following options to the `docker run` command:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -63,10 +63,14 @@ class AppConfig(BaseModel):
|
||||
save_trajectory_path: str | None = Field(default=None)
|
||||
save_screenshots_in_trajectory: bool = Field(default=False)
|
||||
replay_trajectory_path: str | None = Field(default=None)
|
||||
workspace_base: str | None = Field(default=None)
|
||||
workspace_mount_path: str | None = Field(default=None)
|
||||
workspace_mount_path_in_sandbox: str = Field(default='/workspace')
|
||||
workspace_mount_rewrite: str | None = Field(default=None)
|
||||
# New mount parameter that replaces the workspace_* parameters
|
||||
runtime_mount: str | None = Field(default=None, description="Mount specification in the format 'host_path:container_path:mode', e.g. '/my/host/dir:/workspace:rw'")
|
||||
|
||||
# Deprecated parameters - will be removed in a future version
|
||||
workspace_base: str | None = Field(default=None, deprecated=True)
|
||||
workspace_mount_path: str | None = Field(default=None, deprecated=True)
|
||||
workspace_mount_path_in_sandbox: str = Field(default='/workspace', deprecated=True)
|
||||
workspace_mount_rewrite: str | None = Field(default=None, deprecated=True)
|
||||
cache_dir: str = Field(default='/tmp/cache')
|
||||
run_as_openhands: bool = Field(default=True)
|
||||
max_iterations: int = Field(default=OH_MAX_ITERATIONS)
|
||||
|
||||
@@ -294,10 +294,39 @@ def get_or_create_jwt_secret(file_store: FileStore) -> str:
|
||||
|
||||
def finalize_config(cfg: AppConfig) -> None:
|
||||
"""More tweaks to the config after it's been loaded."""
|
||||
if cfg.workspace_base is not None:
|
||||
cfg.workspace_base = os.path.abspath(cfg.workspace_base)
|
||||
if cfg.workspace_mount_path is None:
|
||||
cfg.workspace_mount_path = cfg.workspace_base
|
||||
# Handle the new runtime_mount parameter
|
||||
if cfg.runtime_mount is not None:
|
||||
# Parse the runtime_mount parameter
|
||||
parts = cfg.runtime_mount.split(':')
|
||||
if len(parts) < 2 or len(parts) > 3:
|
||||
raise ValueError(
|
||||
f"Invalid runtime_mount format: {cfg.runtime_mount}. "
|
||||
f"Expected format: 'host_path:container_path[:mode]', e.g. '/my/host/dir:/workspace:rw'"
|
||||
)
|
||||
|
||||
host_path = os.path.abspath(parts[0])
|
||||
container_path = parts[1]
|
||||
# Default mode is 'rw' if not specified
|
||||
mode = parts[2] if len(parts) > 2 else 'rw'
|
||||
|
||||
# Set the workspace_mount_path and workspace_mount_path_in_sandbox for backward compatibility
|
||||
cfg.workspace_mount_path = host_path
|
||||
cfg.workspace_mount_path_in_sandbox = container_path
|
||||
|
||||
# Also set workspace_base for backward compatibility
|
||||
cfg.workspace_base = host_path
|
||||
|
||||
# Handle the deprecated workspace_* parameters
|
||||
elif cfg.workspace_base is not None or cfg.workspace_mount_path is not None:
|
||||
logger.openhands_logger.warning(
|
||||
"DEPRECATED: The WORKSPACE_BASE and WORKSPACE_MOUNT_PATH environment variables are deprecated. "
|
||||
"Please use RUNTIME_MOUNT instead, e.g. 'RUNTIME_MOUNT=/my/host/dir:/workspace:rw'"
|
||||
)
|
||||
|
||||
if cfg.workspace_base is not None:
|
||||
cfg.workspace_base = os.path.abspath(cfg.workspace_base)
|
||||
if cfg.workspace_mount_path is None:
|
||||
cfg.workspace_mount_path = cfg.workspace_base
|
||||
|
||||
if cfg.workspace_mount_rewrite:
|
||||
base = cfg.workspace_base or os.getcwd()
|
||||
|
||||
@@ -272,14 +272,23 @@ class DockerRuntime(ActionExecutionClient):
|
||||
self.config.workspace_mount_path is not None
|
||||
and self.config.workspace_mount_path_in_sandbox is not None
|
||||
):
|
||||
# Determine the mount mode
|
||||
mount_mode = 'rw' # Default mode
|
||||
|
||||
# If runtime_mount is set, extract the mode from it
|
||||
if self.config.runtime_mount is not None:
|
||||
parts = self.config.runtime_mount.split(':')
|
||||
if len(parts) > 2:
|
||||
mount_mode = parts[2]
|
||||
|
||||
# e.g. result would be: {"/home/user/openhands/workspace": {'bind': "/workspace", 'mode': 'rw'}}
|
||||
volumes = {
|
||||
self.config.workspace_mount_path: {
|
||||
'bind': self.config.workspace_mount_path_in_sandbox,
|
||||
'mode': 'rw',
|
||||
'mode': mount_mode,
|
||||
}
|
||||
}
|
||||
logger.debug(f'Mount dir: {self.config.workspace_mount_path}')
|
||||
logger.debug(f'Mount dir: {self.config.workspace_mount_path} with mode: {mount_mode}')
|
||||
else:
|
||||
logger.debug(
|
||||
'Mount dir is not set, will not mount the workspace directory to the container'
|
||||
|
||||
@@ -389,6 +389,52 @@ def test_defaults_dict_after_updates(default_config):
|
||||
assert defaults_after_updates == initial_defaults
|
||||
|
||||
|
||||
def test_runtime_mount_config(monkeypatch, default_config):
|
||||
# Test the new RUNTIME_MOUNT parameter
|
||||
monkeypatch.setenv('RUNTIME_MOUNT', '/host/path:/container/path:ro')
|
||||
|
||||
load_from_env(default_config, os.environ)
|
||||
finalize_config(default_config)
|
||||
|
||||
# Check that runtime_mount is set correctly
|
||||
assert default_config.runtime_mount == '/host/path:/container/path:ro'
|
||||
|
||||
# Check that the old parameters are set for backward compatibility
|
||||
assert default_config.workspace_base == os.path.abspath('/host/path')
|
||||
assert default_config.workspace_mount_path == os.path.abspath('/host/path')
|
||||
assert default_config.workspace_mount_path_in_sandbox == '/container/path'
|
||||
|
||||
|
||||
def test_runtime_mount_with_default_mode(monkeypatch, default_config):
|
||||
# Test RUNTIME_MOUNT without specifying mode (should default to 'rw')
|
||||
monkeypatch.setenv('RUNTIME_MOUNT', '/host/path:/container/path')
|
||||
|
||||
load_from_env(default_config, os.environ)
|
||||
finalize_config(default_config)
|
||||
|
||||
# Check that runtime_mount is set correctly
|
||||
assert default_config.runtime_mount == '/host/path:/container/path'
|
||||
|
||||
# Check that the old parameters are set for backward compatibility
|
||||
assert default_config.workspace_base == os.path.abspath('/host/path')
|
||||
assert default_config.workspace_mount_path == os.path.abspath('/host/path')
|
||||
assert default_config.workspace_mount_path_in_sandbox == '/container/path'
|
||||
|
||||
|
||||
def test_runtime_mount_invalid_format(monkeypatch, default_config):
|
||||
# Test RUNTIME_MOUNT with invalid format
|
||||
monkeypatch.setenv('RUNTIME_MOUNT', '/single/path')
|
||||
|
||||
load_from_env(default_config, os.environ)
|
||||
|
||||
# Check that runtime_mount is set
|
||||
assert default_config.runtime_mount == '/single/path'
|
||||
|
||||
# Finalize config should raise a ValueError for invalid format
|
||||
with pytest.raises(ValueError, match="Invalid runtime_mount format"):
|
||||
finalize_config(default_config)
|
||||
|
||||
|
||||
def test_invalid_toml_format(monkeypatch, temp_toml_file, default_config):
|
||||
# Invalid TOML format doesn't break the configuration
|
||||
monkeypatch.setenv('LLM_MODEL', 'gpt-5-turbo-1106')
|
||||
|
||||
@@ -69,3 +69,65 @@ def test_container_not_stopped_when_keep_runtime_alive_true(
|
||||
|
||||
# Assert
|
||||
mock_stop_containers.assert_not_called()
|
||||
|
||||
|
||||
def test_runtime_mount_mode_extraction():
|
||||
"""Test that the mount mode is correctly extracted from runtime_mount."""
|
||||
from openhands.runtime.impl.docker.docker_runtime import DockerRuntime
|
||||
|
||||
# Create a mock DockerRuntime instance
|
||||
runtime = MagicMock(spec=DockerRuntime)
|
||||
|
||||
# Test with read-only mode
|
||||
runtime.config = MagicMock()
|
||||
runtime.config.runtime_mount = '/host/path:/container/path:ro'
|
||||
runtime.config.workspace_mount_path = '/host/path'
|
||||
runtime.config.workspace_mount_path_in_sandbox = '/container/path'
|
||||
|
||||
# Call the _create_container method directly
|
||||
volumes = {
|
||||
runtime.config.workspace_mount_path: {
|
||||
'bind': runtime.config.workspace_mount_path_in_sandbox,
|
||||
'mode': 'rw', # Default mode
|
||||
}
|
||||
}
|
||||
|
||||
# Simulate the code in DockerRuntime that extracts the mode
|
||||
if runtime.config.runtime_mount is not None:
|
||||
parts = runtime.config.runtime_mount.split(':')
|
||||
if len(parts) > 2:
|
||||
volumes[runtime.config.workspace_mount_path]['mode'] = parts[2]
|
||||
|
||||
# Assert that the mode was correctly set to 'ro'
|
||||
assert volumes['/host/path']['mode'] == 'ro'
|
||||
|
||||
|
||||
def test_runtime_mount_default_mode():
|
||||
"""Test that the default mount mode (rw) is used when not specified in runtime_mount."""
|
||||
from openhands.runtime.impl.docker.docker_runtime import DockerRuntime
|
||||
|
||||
# Create a mock DockerRuntime instance
|
||||
runtime = MagicMock(spec=DockerRuntime)
|
||||
|
||||
# Test with no mode specified (should default to 'rw')
|
||||
runtime.config = MagicMock()
|
||||
runtime.config.runtime_mount = '/host/path:/container/path'
|
||||
runtime.config.workspace_mount_path = '/host/path'
|
||||
runtime.config.workspace_mount_path_in_sandbox = '/container/path'
|
||||
|
||||
# Call the _create_container method directly
|
||||
volumes = {
|
||||
runtime.config.workspace_mount_path: {
|
||||
'bind': runtime.config.workspace_mount_path_in_sandbox,
|
||||
'mode': 'rw', # Default mode
|
||||
}
|
||||
}
|
||||
|
||||
# Simulate the code in DockerRuntime that extracts the mode
|
||||
if runtime.config.runtime_mount is not None:
|
||||
parts = runtime.config.runtime_mount.split(':')
|
||||
if len(parts) > 2:
|
||||
volumes[runtime.config.workspace_mount_path]['mode'] = parts[2]
|
||||
|
||||
# Assert that the mode remains 'rw' (default)
|
||||
assert volumes['/host/path']['mode'] == 'rw'
|
||||
|
||||
Reference in New Issue
Block a user