openhands-workspace 1.8.1__tar.gz → 1.8.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/PKG-INFO +1 -1
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/apptainer/workspace.py +23 -27
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/docker/workspace.py +21 -7
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/remote_api/workspace.py +17 -6
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands_workspace.egg-info/PKG-INFO +1 -1
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/pyproject.toml +1 -1
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/__init__.py +0 -0
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/apptainer/__init__.py +0 -0
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/cloud/__init__.py +0 -0
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/cloud/workspace.py +0 -0
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/docker/__init__.py +0 -0
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/docker/dev_workspace.py +0 -0
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/py.typed +0 -0
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/remote_api/__init__.py +0 -0
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands_workspace.egg-info/SOURCES.txt +0 -0
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands_workspace.egg-info/dependency_links.txt +0 -0
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands_workspace.egg-info/requires.txt +0 -0
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands_workspace.egg-info/top_level.txt +0 -0
- {openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/setup.cfg +0 -0
{openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/apptainer/workspace.py
RENAMED
|
@@ -102,25 +102,25 @@ class ApptainerWorkspace(RemoteWorkspace):
|
|
|
102
102
|
"Set to False if fakeroot is not supported in your environment."
|
|
103
103
|
),
|
|
104
104
|
)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
description=(
|
|
108
|
-
"Whether to over-ride the `mount hostfs = yes` setting in apptainer.conf"
|
|
109
|
-
"Set to False if you want to disable --no-mount hostfs"
|
|
110
|
-
),
|
|
111
|
-
)
|
|
112
|
-
disable_mount_bind_path: bool = Field(
|
|
105
|
+
|
|
106
|
+
enable_docker_compat: bool = Field(
|
|
113
107
|
default=True,
|
|
114
108
|
description=(
|
|
115
|
-
"Whether to
|
|
116
|
-
"
|
|
109
|
+
"Whether to use --compat for maximum Docker compatibility. "
|
|
110
|
+
"Check this URL for documentation: "
|
|
111
|
+
"https://apptainer.org/docs/user/main/docker_and_oci.html#docker-like-compat-flag"
|
|
112
|
+
" Set to False if you want custom Apptainer behavior."
|
|
117
113
|
),
|
|
118
114
|
)
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
|
|
116
|
+
disable_mount_locations: list[str] = Field(
|
|
117
|
+
default=["hostfs", "bind-paths"],
|
|
121
118
|
description=(
|
|
122
|
-
"
|
|
123
|
-
"
|
|
119
|
+
"List of locations to disable mounting for. "
|
|
120
|
+
"Helpful for disabling system-level mounts/binds from apptainer.conf. "
|
|
121
|
+
"Check this URL for documentation: "
|
|
122
|
+
"https://apptainer.org/docs/user/main/bind_paths_and_mounts.html. "
|
|
123
|
+
"Specify locations to disable mounts for custom Apptainer behavior."
|
|
124
124
|
),
|
|
125
125
|
)
|
|
126
126
|
|
|
@@ -252,23 +252,19 @@ class ApptainerWorkspace(RemoteWorkspace):
|
|
|
252
252
|
)
|
|
253
253
|
|
|
254
254
|
# Build container options
|
|
255
|
-
container_opts: list[str] = [
|
|
256
|
-
"--writable-tmpfs",
|
|
257
|
-
"--cleanenv", # Prevent host environment leakage
|
|
258
|
-
"--no-home", # Don't mount host home directory
|
|
259
|
-
"--no-mount",
|
|
260
|
-
"tmp", # Don't bind host /tmp (avoids tmux socket conflicts)
|
|
261
|
-
]
|
|
255
|
+
container_opts: list[str] = []
|
|
262
256
|
|
|
263
257
|
# Add fakeroot for consistent file ownership (user appears as root)
|
|
264
258
|
if self.use_fakeroot:
|
|
265
259
|
container_opts.append("--fakeroot")
|
|
266
|
-
if self.
|
|
267
|
-
container_opts
|
|
268
|
-
if self.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
260
|
+
if self.enable_docker_compat:
|
|
261
|
+
container_opts.append("--compat")
|
|
262
|
+
if self.disable_mount_locations:
|
|
263
|
+
for loc in self.disable_mount_locations:
|
|
264
|
+
container_opts += [
|
|
265
|
+
"--no-mount",
|
|
266
|
+
loc,
|
|
267
|
+
] # Disable specified mount locations
|
|
272
268
|
|
|
273
269
|
# Run the agent server using apptainer run to respect the image's entrypoint
|
|
274
270
|
# This works with both 'source' and 'binary' build targets
|
{openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/docker/workspace.py
RENAMED
|
@@ -13,6 +13,7 @@ from pydantic import Field, PrivateAttr, model_validator
|
|
|
13
13
|
|
|
14
14
|
from openhands.sdk.logger import get_logger
|
|
15
15
|
from openhands.sdk.utils.command import execute_command
|
|
16
|
+
from openhands.sdk.utils.deprecation import warn_deprecated
|
|
16
17
|
from openhands.sdk.workspace import PlatformType, RemoteWorkspace
|
|
17
18
|
|
|
18
19
|
|
|
@@ -79,7 +80,7 @@ class DockerWorkspace(RemoteWorkspace):
|
|
|
79
80
|
|
|
80
81
|
# Docker-specific configuration
|
|
81
82
|
server_image: str | None = Field(
|
|
82
|
-
default=
|
|
83
|
+
default="ghcr.io/openhands/agent-server:latest-python",
|
|
83
84
|
description="Pre-built agent server image to use.",
|
|
84
85
|
)
|
|
85
86
|
host_port: int | None = Field(
|
|
@@ -94,6 +95,10 @@ class DockerWorkspace(RemoteWorkspace):
|
|
|
94
95
|
default=None,
|
|
95
96
|
description="Optional host directory to mount into the container.",
|
|
96
97
|
)
|
|
98
|
+
volumes: list[str] = Field(
|
|
99
|
+
default_factory=list,
|
|
100
|
+
description="Additional volume mounts for the Docker container.",
|
|
101
|
+
)
|
|
97
102
|
detach_logs: bool = Field(
|
|
98
103
|
default=True, description="Whether to stream Docker logs in background."
|
|
99
104
|
)
|
|
@@ -125,6 +130,18 @@ class DockerWorkspace(RemoteWorkspace):
|
|
|
125
130
|
raise ValueError("server_image must be provided")
|
|
126
131
|
return self
|
|
127
132
|
|
|
133
|
+
@model_validator(mode="after")
|
|
134
|
+
def _validate_mount_dir(self):
|
|
135
|
+
if self.mount_dir:
|
|
136
|
+
warn_deprecated(
|
|
137
|
+
"DockerWorkspace.mount_dir",
|
|
138
|
+
deprecated_in="1.10.0",
|
|
139
|
+
removed_in=None,
|
|
140
|
+
details="Use DockerWorkspace.volumes instead",
|
|
141
|
+
)
|
|
142
|
+
self.volumes.append(f"{self.mount_dir}:/workspace")
|
|
143
|
+
return self
|
|
144
|
+
|
|
128
145
|
def model_post_init(self, context: Any) -> None:
|
|
129
146
|
"""Set up the Docker container and initialize the remote workspace."""
|
|
130
147
|
# Subclasses should call get_image() to get the image to use
|
|
@@ -192,12 +209,9 @@ class DockerWorkspace(RemoteWorkspace):
|
|
|
192
209
|
if key in os.environ:
|
|
193
210
|
flags += ["-e", f"{key}={os.environ[key]}"]
|
|
194
211
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
logger.info(
|
|
199
|
-
f"Mounting host dir {self.mount_dir} to container path {mount_path}"
|
|
200
|
-
)
|
|
212
|
+
for volume in self.volumes:
|
|
213
|
+
flags += ["-v", volume]
|
|
214
|
+
logger.info(f"Adding volume mount: {volume}")
|
|
201
215
|
|
|
202
216
|
ports = ["-p", f"{self.host_port}:8000"]
|
|
203
217
|
if self.extra_ports:
|
{openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/remote_api/workspace.py
RENAMED
|
@@ -64,6 +64,11 @@ class APIRemoteWorkspace(RemoteWorkspace):
|
|
|
64
64
|
init_timeout: float = Field(
|
|
65
65
|
default=300.0, description="Runtime init timeout (seconds)"
|
|
66
66
|
)
|
|
67
|
+
startup_wait_timeout: float = Field(
|
|
68
|
+
default=300.0,
|
|
69
|
+
description="Max seconds to wait for runtime to become ready",
|
|
70
|
+
gt=0,
|
|
71
|
+
)
|
|
67
72
|
api_timeout: float = Field(
|
|
68
73
|
default=60.0, description="API request timeout (seconds)"
|
|
69
74
|
)
|
|
@@ -275,14 +280,20 @@ class APIRemoteWorkspace(RemoteWorkspace):
|
|
|
275
280
|
if not self._runtime_id or not self._runtime_url:
|
|
276
281
|
raise ValueError(f"Invalid runtime response: {data}")
|
|
277
282
|
|
|
278
|
-
@tenacity.retry(
|
|
279
|
-
stop=tenacity.stop_after_delay(300),
|
|
280
|
-
wait=tenacity.wait_exponential(multiplier=1, min=2, max=10),
|
|
281
|
-
retry=tenacity.retry_if_exception_type(RuntimeError),
|
|
282
|
-
reraise=True,
|
|
283
|
-
)
|
|
284
283
|
def _wait_until_runtime_alive(self) -> None:
|
|
285
284
|
"""Wait until the runtime becomes alive and responsive."""
|
|
285
|
+
retryer = tenacity.Retrying(
|
|
286
|
+
stop=tenacity.stop_after_delay(self.startup_wait_timeout),
|
|
287
|
+
wait=tenacity.wait_exponential(multiplier=1, min=2, max=10),
|
|
288
|
+
retry=tenacity.retry_if_exception_type(RuntimeError),
|
|
289
|
+
reraise=True,
|
|
290
|
+
)
|
|
291
|
+
for attempt in retryer:
|
|
292
|
+
with attempt:
|
|
293
|
+
self._wait_until_runtime_alive_once()
|
|
294
|
+
|
|
295
|
+
def _wait_until_runtime_alive_once(self) -> None:
|
|
296
|
+
"""Single attempt to check runtime readiness."""
|
|
286
297
|
logger.info("Waiting for runtime to become alive...")
|
|
287
298
|
|
|
288
299
|
resp = self._send_api_request(
|
|
File without changes
|
{openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/apptainer/__init__.py
RENAMED
|
File without changes
|
{openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/cloud/__init__.py
RENAMED
|
File without changes
|
{openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/cloud/workspace.py
RENAMED
|
File without changes
|
{openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/docker/__init__.py
RENAMED
|
File without changes
|
{openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/docker/dev_workspace.py
RENAMED
|
File without changes
|
|
File without changes
|
{openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands/workspace/remote_api/__init__.py
RENAMED
|
File without changes
|
{openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands_workspace.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands_workspace.egg-info/requires.txt
RENAMED
|
File without changes
|
{openhands_workspace-1.8.1 → openhands_workspace-1.8.2}/openhands_workspace.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|