openhands-workspace 1.8.1__py3-none-any.whl → 1.9.0__py3-none-any.whl

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.
@@ -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
- disable_mount_hostfs: bool = Field(
106
- default=True,
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 ignore the custom bind path entries in apptainer.conf"
116
- "Set to False if you want to disable --no-mount bind-paths"
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
- enable_containall: bool = Field(
120
- default=True,
115
+
116
+ disable_mount_locations: list[str] = Field(
117
+ default=["hostfs", "bind-paths"],
121
118
  description=(
122
- "Whether to use --containall for maximum container isolation."
123
- "Set to False if you do not want to start the apptainer with --containall"
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.disable_mount_hostfs:
267
- container_opts += ["--no-mount", "hostfs"] # Disable hostfs mount
268
- if self.disable_mount_bind_path:
269
- container_opts += ["--no-mount", "bind-paths"] # Disable custom bind paths
270
- if self.enable_containall:
271
- container_opts.append("--containall") # Maximum isolation
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
@@ -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=None,
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
- if self.mount_dir:
196
- mount_path = "/workspace"
197
- flags += ["-v", f"{self.mount_dir}:{mount_path}"]
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:
@@ -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(
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: openhands-workspace
3
+ Version: 1.9.0
4
+ Summary: OpenHands Workspace - Docker and container-based workspace implementations
5
+ Project-URL: Source, https://github.com/OpenHands/software-agent-sdk
6
+ Project-URL: Homepage, https://github.com/OpenHands/software-agent-sdk
7
+ Project-URL: Documentation, https://docs.openhands.dev/sdk
8
+ Project-URL: Bug Tracker, https://github.com/OpenHands/software-agent-sdk/issues
9
+ Requires-Python: >=3.12
10
+ Requires-Dist: openhands-sdk
11
+ Requires-Dist: openhands-agent-server
12
+ Requires-Dist: pydantic>=2.11.7
@@ -1,15 +1,15 @@
1
1
  openhands/workspace/__init__.py,sha256=zH8byAoOm-gyoBCi3H33c-qKk9KvTa2KxEMDbfOZogg,887
2
2
  openhands/workspace/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  openhands/workspace/apptainer/__init__.py,sha256=jYRsgL9z3m2wwj7ztWsFCDz44TYk9dtoD34QNzYpvUw,120
4
- openhands/workspace/apptainer/workspace.py,sha256=iF96fqUrrSdYkApR4b4VZEAqqkhJwTDk34eC3SXaGbg,14170
4
+ openhands/workspace/apptainer/workspace.py,sha256=QI2mI29VIB8KykTrfy-gtNSID5pwpXUoEcbOUArYU0M,13981
5
5
  openhands/workspace/cloud/__init__.py,sha256=Ck8mDOf1YF4M1vn-vNkqxqGaEgB1qsQ7lLT9lTaS6gM,136
6
6
  openhands/workspace/cloud/workspace.py,sha256=1_9pMiTn1SjcwDSaPxY_g6Z7uGt1c1ASO4_2CWx_tTc,12891
7
7
  openhands/workspace/docker/__init__.py,sha256=vhMjEqbG5NmwL2UINxtAZj_y5lKlaLlqUNz2f3owJVQ,539
8
8
  openhands/workspace/docker/dev_workspace.py,sha256=5HZFXhtP9UZRST76E705TmYtVJcYbJIyq38GQFxiefE,3993
9
- openhands/workspace/docker/workspace.py,sha256=ZXxmVzRTa0pmZ_FZYQIFIZcSFoEOfjx5lWGoXjm8m9M,13931
9
+ openhands/workspace/docker/workspace.py,sha256=2j14G3lOYNvm0Jc7agUUi9Nvr80ee_WYfXlfYjVsP98,14460
10
10
  openhands/workspace/remote_api/__init__.py,sha256=RxDLFcveTGG-uLm-PInfxFnYwYPdehRmLf1r0_KTaP4,122
11
- openhands/workspace/remote_api/workspace.py,sha256=0ph92TJHS819VQuItLOyydvxeC3tC53f1CPP0AlqaBg,15194
12
- openhands_workspace-1.8.1.dist-info/METADATA,sha256=4xkZFzZFpk_qGxl02wMENauk_gyedhSNZdIDKzUqDxM,270
13
- openhands_workspace-1.8.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- openhands_workspace-1.8.1.dist-info/top_level.txt,sha256=jHgVu9I0Blam8BXFgedoGKfglPF8XvW1TsJFIjcgP4E,10
15
- openhands_workspace-1.8.1.dist-info/RECORD,,
11
+ openhands/workspace/remote_api/workspace.py,sha256=178lzpm_8p3lJCWpNUNrswHTf9jdJ4AeNeeiC7o2k_s,15631
12
+ openhands_workspace-1.9.0.dist-info/METADATA,sha256=ovrSeBXs3OF8O-pDLl1m9IXqEtbPO59LOMH9C-HNgfI,550
13
+ openhands_workspace-1.9.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
14
+ openhands_workspace-1.9.0.dist-info/top_level.txt,sha256=jHgVu9I0Blam8BXFgedoGKfglPF8XvW1TsJFIjcgP4E,10
15
+ openhands_workspace-1.9.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,8 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: openhands-workspace
3
- Version: 1.8.1
4
- Summary: OpenHands Workspace - Docker and container-based workspace implementations
5
- Requires-Python: >=3.12
6
- Requires-Dist: openhands-sdk
7
- Requires-Dist: openhands-agent-server
8
- Requires-Dist: pydantic>=2.11.7