inspect-sandboxes 0.1.1.dev1__tar.gz → 0.1.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.
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/PKG-INFO +1 -1
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/_version.py +2 -2
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/daytona/README.md +9 -1
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/daytona/_compose.py +5 -0
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/daytona/_daytona.py +11 -6
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/modal/README.md +8 -0
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/modal/_compose.py +30 -2
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/modal/_modal.py +12 -7
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/.gitignore +0 -0
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/LICENSE +0 -0
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/README.md +0 -0
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/pyproject.toml +0 -0
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/__init__.py +0 -0
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/_registry.py +0 -0
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/_util/__init__.py +0 -0
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/_util/compose.py +0 -0
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/daytona/__init__.py +0 -0
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/modal/__init__.py +0 -0
- {inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: inspect_sandboxes
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Collection of sandboxes for Inspect AI
|
|
5
5
|
Project-URL: Source Code, https://github.com/meridianlabs-ai/inspect_sandboxes
|
|
6
6
|
Project-URL: Issue Tracker, https://github.com/meridianlabs-ai/inspect_sandboxes/issues
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.1.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
31
|
+
__version__ = version = '0.1.2'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 2)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
{inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/daytona/README.md
RENAMED
|
@@ -88,5 +88,13 @@ task = Task(
|
|
|
88
88
|
## Notes
|
|
89
89
|
|
|
90
90
|
- **stderr**: The Daytona API returns a single combined output; `stderr` is always empty and all output appears in `stdout`.
|
|
91
|
-
- **stdin**: The `input` parameter to `exec()` is
|
|
91
|
+
- **stdin**: The `input` parameter to `exec()` is supported by uploading a temp file and piping it into the command.
|
|
92
92
|
- **user**: The `user` parameter to `exec()` is not supported; use `os_user` in `x-daytona` to set the OS user.
|
|
93
|
+
- **network_mode**: Docker Compose `network_mode` is automatically translated to Daytona's `network_block_all` parameter. `network_mode: none` blocks all network access; any other value (e.g. `bridge`) allows it. The `x-daytona` extension `network_block_all` takes precedence if set.
|
|
94
|
+
|
|
95
|
+
## Known Limitations
|
|
96
|
+
|
|
97
|
+
- **Architecture**: Daytona runners use `linux/amd64`. Tasks with arm64-only base images cannot run on Daytona. The image publisher must add amd64 support.
|
|
98
|
+
- **Resource limits**: Daytona enforces per-sandbox limits. Tasks requesting more will fail with `CPU request N exceeds maximum` or `Memory request exceeds maximum`. Use `override_cpus` / `override_memory_mb` task args to cap resources, or contact Daytona support for higher limits.
|
|
99
|
+
- **Single service**: Only one service is supported per sandbox. Multi-service compose files are not supported.
|
|
100
|
+
- **Internet access**: Outbound internet access from sandboxes depends on your Daytona billing tier. Lower tiers (Tier 1–2) have restricted network access that cannot be overridden by the `network_block_all` parameter. If tasks fail with `curl: (35) Recv failure: Connection reset by peer` or similar network errors despite `network_block_all: false`, upgrade to a higher tier (Tier 3+) for full outbound internet access.
|
{inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/daytona/_compose.py
RENAMED
|
@@ -57,6 +57,11 @@ def convert_compose_to_daytona_params(
|
|
|
57
57
|
if service.user:
|
|
58
58
|
sandbox_params["os_user"] = service.user
|
|
59
59
|
|
|
60
|
+
# Translate Docker network_mode to Daytona network_block_all.
|
|
61
|
+
# Only set as a default; x-daytona extensions can override below.
|
|
62
|
+
if service.network_mode is not None and "network_block_all" not in sandbox_params:
|
|
63
|
+
sandbox_params["network_block_all"] = service.network_mode == "none"
|
|
64
|
+
|
|
60
65
|
_apply_daytona_extensions(sandbox_params, config.extensions)
|
|
61
66
|
|
|
62
67
|
return image, resources, sandbox_params
|
{inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/daytona/_daytona.py
RENAMED
|
@@ -4,6 +4,7 @@ import errno
|
|
|
4
4
|
import os
|
|
5
5
|
import shlex
|
|
6
6
|
import sys
|
|
7
|
+
import uuid
|
|
7
8
|
from contextvars import ContextVar
|
|
8
9
|
from logging import getLogger
|
|
9
10
|
from pathlib import PurePosixPath
|
|
@@ -306,14 +307,18 @@ class DaytonaSandboxEnvironment(SandboxEnvironment):
|
|
|
306
307
|
"Commands will run as the container's default user.",
|
|
307
308
|
)
|
|
308
309
|
|
|
310
|
+
# Daytona's process.exec() doesn't support stdin natively.
|
|
311
|
+
# When input is provided, write it to a temp file and pipe it into the command.
|
|
309
312
|
if input is not None:
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
313
|
+
data = input.encode("utf-8") if isinstance(input, str) else input
|
|
314
|
+
stdin_file = f"/tmp/.inspect-stdin-{uuid.uuid4().hex}"
|
|
315
|
+
await self.sandbox.fs.upload_file(data, stdin_file)
|
|
316
|
+
command = (
|
|
317
|
+
f"set -o pipefail; cat {shlex.quote(stdin_file)} | {shlex.join(cmd)}"
|
|
318
|
+
f"; _ec=$?; rm -f {shlex.quote(stdin_file)}; exit $_ec"
|
|
314
319
|
)
|
|
315
|
-
|
|
316
|
-
|
|
320
|
+
else:
|
|
321
|
+
command = shlex.join(cmd)
|
|
317
322
|
|
|
318
323
|
async def _run(t: int | None) -> ExecResult[str]:
|
|
319
324
|
response = await self.sandbox.process.exec(
|
{inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/modal/README.md
RENAMED
|
@@ -81,6 +81,14 @@ task = Task(
|
|
|
81
81
|
)
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
## Notes
|
|
85
|
+
|
|
86
|
+
- **network_mode**: Docker Compose `network_mode` is automatically translated to Modal's `block_network` parameter. `network_mode: none` blocks all network access; any other value (e.g. `bridge`) allows it. The `x-modal` extension `block_network` takes precedence if set.
|
|
87
|
+
|
|
88
|
+
## Known Limitations
|
|
89
|
+
|
|
90
|
+
- **Single service**: Only one compose service is supported per sandbox. Multi-service compose files will raise a `ValueError`.
|
|
91
|
+
|
|
84
92
|
## Credits
|
|
85
93
|
|
|
86
94
|
Based on [@anthonyduong9](https://github.com/anthonyduong9)'s work.
|
{inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/modal/_compose.py
RENAMED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import shlex
|
|
2
|
+
from dataclasses import dataclass, field
|
|
1
3
|
from pathlib import Path
|
|
2
4
|
from typing import Any
|
|
3
5
|
|
|
@@ -11,9 +13,22 @@ from inspect_sandboxes._util.compose import (
|
|
|
11
13
|
)
|
|
12
14
|
|
|
13
15
|
|
|
16
|
+
@dataclass
|
|
17
|
+
class ModalSandboxParams:
|
|
18
|
+
"""Parameters for modal.Sandbox.create().
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
command: Positional command args passed before **kwargs to Sandbox.create().
|
|
22
|
+
kwargs: Keyword arguments passed to Sandbox.create().
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
command: list[str] = field(default_factory=list)
|
|
26
|
+
kwargs: dict[str, Any] = field(default_factory=dict)
|
|
27
|
+
|
|
28
|
+
|
|
14
29
|
def convert_compose_to_modal_params(
|
|
15
30
|
config: ComposeConfig, compose_path: str | None
|
|
16
|
-
) ->
|
|
31
|
+
) -> ModalSandboxParams:
|
|
17
32
|
"""Convert a ComposeConfig to Modal Sandbox.create() parameters.
|
|
18
33
|
|
|
19
34
|
Args:
|
|
@@ -27,6 +42,7 @@ def convert_compose_to_modal_params(
|
|
|
27
42
|
service = config.services.get("default") or next(iter(config.services.values()))
|
|
28
43
|
|
|
29
44
|
params: dict[str, Any] = {}
|
|
45
|
+
command: list[str] = []
|
|
30
46
|
compose_dir = Path(compose_path).parent if compose_path else Path.cwd()
|
|
31
47
|
|
|
32
48
|
if service.build:
|
|
@@ -40,6 +56,13 @@ def convert_compose_to_modal_params(
|
|
|
40
56
|
elif service.image:
|
|
41
57
|
params["image"] = modal.Image.from_registry(service.image)
|
|
42
58
|
|
|
59
|
+
if service.command:
|
|
60
|
+
command = (
|
|
61
|
+
service.command
|
|
62
|
+
if isinstance(service.command, list)
|
|
63
|
+
else shlex.split(service.command)
|
|
64
|
+
)
|
|
65
|
+
|
|
43
66
|
if service.working_dir:
|
|
44
67
|
params["workdir"] = service.working_dir
|
|
45
68
|
|
|
@@ -58,9 +81,14 @@ def convert_compose_to_modal_params(
|
|
|
58
81
|
if gpu is not None:
|
|
59
82
|
params["gpu"] = gpu
|
|
60
83
|
|
|
84
|
+
# Translate Docker network_mode to Modal block_network.
|
|
85
|
+
# Only set as a default; x-modal extensions can override below.
|
|
86
|
+
if service.network_mode is not None:
|
|
87
|
+
params["block_network"] = service.network_mode == "none"
|
|
88
|
+
|
|
61
89
|
_apply_modal_extensions(params, config.extensions)
|
|
62
90
|
|
|
63
|
-
return params
|
|
91
|
+
return ModalSandboxParams(command=command, kwargs=params)
|
|
64
92
|
|
|
65
93
|
|
|
66
94
|
def _apply_modal_extensions(params: dict[str, Any], extensions: dict[str, Any]) -> None:
|
{inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/modal/_modal.py
RENAMED
|
@@ -102,24 +102,27 @@ class ModalSandboxEnvironment(SandboxEnvironment):
|
|
|
102
102
|
) -> dict[str, SandboxEnvironment]:
|
|
103
103
|
app = await cls._lookup_app(MODAL_APP_NAME)
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
sandbox_kwargs: dict[str, Any] = {
|
|
106
106
|
"app": app,
|
|
107
107
|
"timeout": 60 * 60 * 24,
|
|
108
108
|
}
|
|
109
|
+
command: list[str] = []
|
|
109
110
|
|
|
110
111
|
if config is None:
|
|
111
112
|
trace_message(
|
|
112
113
|
logger, "modal", f"Using default Modal image for task '{task_name}'"
|
|
113
114
|
)
|
|
114
115
|
elif is_dockerfile(config):
|
|
115
|
-
|
|
116
|
+
sandbox_kwargs["image"] = modal.Image.from_dockerfile(config)
|
|
116
117
|
elif is_compose_yaml(config):
|
|
117
118
|
compose_config = parse_compose_yaml(config, multiple_services=False)
|
|
118
119
|
modal_params = convert_compose_to_modal_params(compose_config, config)
|
|
119
|
-
|
|
120
|
+
command = modal_params.command
|
|
121
|
+
sandbox_kwargs.update(modal_params.kwargs)
|
|
120
122
|
elif isinstance(config, ComposeConfig):
|
|
121
123
|
modal_params = convert_compose_to_modal_params(config, None)
|
|
122
|
-
|
|
124
|
+
command = modal_params.command
|
|
125
|
+
sandbox_kwargs.update(modal_params.kwargs)
|
|
123
126
|
else:
|
|
124
127
|
raise ValueError(
|
|
125
128
|
f"Unrecognized config: {config}. "
|
|
@@ -127,7 +130,7 @@ class ModalSandboxEnvironment(SandboxEnvironment):
|
|
|
127
130
|
"ComposeConfig object, or None."
|
|
128
131
|
)
|
|
129
132
|
|
|
130
|
-
sandbox = await cls._create_sandbox(
|
|
133
|
+
sandbox = await cls._create_sandbox(command, sandbox_kwargs)
|
|
131
134
|
await sandbox.set_tags.aio(INSPECT_SANDBOX_TAG)
|
|
132
135
|
running_sandboxes().append(sandbox.object_id)
|
|
133
136
|
|
|
@@ -421,8 +424,10 @@ class ModalSandboxEnvironment(SandboxEnvironment):
|
|
|
421
424
|
|
|
422
425
|
@staticmethod
|
|
423
426
|
@_standard_retry
|
|
424
|
-
async def _create_sandbox(
|
|
425
|
-
|
|
427
|
+
async def _create_sandbox(
|
|
428
|
+
command: list[str], kwargs: dict[str, Any]
|
|
429
|
+
) -> modal.Sandbox:
|
|
430
|
+
return await modal.Sandbox.create.aio(*command, **kwargs)
|
|
426
431
|
|
|
427
432
|
@staticmethod
|
|
428
433
|
@_standard_retry
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/_util/__init__.py
RENAMED
|
File without changes
|
{inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/_util/compose.py
RENAMED
|
File without changes
|
{inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/daytona/__init__.py
RENAMED
|
File without changes
|
{inspect_sandboxes-0.1.1.dev1 → inspect_sandboxes-0.1.2}/src/inspect_sandboxes/modal/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|