friday-framework-sandbox 0.1.0a0__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.
- friday_framework_sandbox-0.1.0a0/.gitignore +23 -0
- friday_framework_sandbox-0.1.0a0/PKG-INFO +14 -0
- friday_framework_sandbox-0.1.0a0/docker/Dockerfile.python +45 -0
- friday_framework_sandbox-0.1.0a0/docker/build.sh +34 -0
- friday_framework_sandbox-0.1.0a0/pyproject.toml +39 -0
- friday_framework_sandbox-0.1.0a0/src/friday_sandbox/__init__.py +80 -0
- friday_framework_sandbox-0.1.0a0/src/friday_sandbox/config.py +252 -0
- friday_framework_sandbox-0.1.0a0/src/friday_sandbox/docker.py +331 -0
- friday_framework_sandbox-0.1.0a0/src/friday_sandbox/mock.py +295 -0
- friday_framework_sandbox-0.1.0a0/src/friday_sandbox/models.py +155 -0
- friday_framework_sandbox-0.1.0a0/src/friday_sandbox/protocols.py +113 -0
- friday_framework_sandbox-0.1.0a0/src/friday_sandbox/serialization.py +196 -0
- friday_framework_sandbox-0.1.0a0/src/friday_sandbox/subprocess.py +233 -0
- friday_framework_sandbox-0.1.0a0/src/friday_sandbox/workspace.py +386 -0
- friday_framework_sandbox-0.1.0a0/tests_sandbox/__init__.py +2 -0
- friday_framework_sandbox-0.1.0a0/tests_sandbox/conftest.py +53 -0
- friday_framework_sandbox-0.1.0a0/tests_sandbox/test_serialization.py +132 -0
- friday_framework_sandbox-0.1.0a0/tests_sandbox/test_subprocess_sandbox.py +119 -0
- friday_framework_sandbox-0.1.0a0/tests_sandbox/test_workspace_manager.py +198 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.pyc
|
|
3
|
+
.env
|
|
4
|
+
.venv/
|
|
5
|
+
.idea/
|
|
6
|
+
.vscode/
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
chroma_db/
|
|
11
|
+
.friday/
|
|
12
|
+
/vector/
|
|
13
|
+
/exports/
|
|
14
|
+
tests/integration/memory/vector/*/
|
|
15
|
+
tests/integration/memory/vector/backups/*/
|
|
16
|
+
tests/integration/memory/vector/reorganization_logs/*
|
|
17
|
+
*.gpickle
|
|
18
|
+
keys.txt
|
|
19
|
+
experiments/*/config/runtime.local.yaml
|
|
20
|
+
experiments/*/artifacts/*
|
|
21
|
+
!experiments/*/artifacts/.gitkeep
|
|
22
|
+
scripts/source-friday-chat-example-env.sh
|
|
23
|
+
/docs/reference/PyPI-Recovery-Codes-cichuck-2026-07-20T15_36_49.709334.txt
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: friday-framework-sandbox
|
|
3
|
+
Version: 0.1.0a0
|
|
4
|
+
Summary: Docker-based code execution sandbox for AI agents
|
|
5
|
+
Author: Friday Team
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: agents,ai,code-execution,docker,sandbox
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: docker>=7.0.0
|
|
10
|
+
Requires-Dist: pydantic>=2.0.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
13
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Friday Sandbox Python Image
|
|
2
|
+
# Provides a secure, minimal Python environment for code execution
|
|
3
|
+
#
|
|
4
|
+
# Ref: docs/specs/sandbox_specifications.md - Section 9: Docker Image
|
|
5
|
+
|
|
6
|
+
FROM python:3.11-slim-bookworm
|
|
7
|
+
|
|
8
|
+
# Labels
|
|
9
|
+
LABEL org.opencontainers.image.title="Friday Sandbox Python"
|
|
10
|
+
LABEL org.opencontainers.image.description="Secure Python execution environment for AI agents"
|
|
11
|
+
LABEL org.opencontainers.image.version="0.1.0"
|
|
12
|
+
LABEL org.opencontainers.image.vendor="Friday Team"
|
|
13
|
+
|
|
14
|
+
# Security: Create non-root user
|
|
15
|
+
RUN groupadd -r sandbox && useradd -r -g sandbox sandbox
|
|
16
|
+
|
|
17
|
+
# Install common Python packages for data processing and analysis
|
|
18
|
+
RUN pip install --no-cache-dir \
|
|
19
|
+
# Data processing
|
|
20
|
+
numpy>=1.24.0 \
|
|
21
|
+
pandas>=2.0.0 \
|
|
22
|
+
# JSON/YAML handling
|
|
23
|
+
pyyaml>=6.0 \
|
|
24
|
+
# HTTP requests
|
|
25
|
+
requests>=2.28.0 \
|
|
26
|
+
httpx>=0.24.0 \
|
|
27
|
+
# Pydantic for structured returns
|
|
28
|
+
pydantic>=2.0.0 \
|
|
29
|
+
# Date handling
|
|
30
|
+
python-dateutil>=2.8.0 \
|
|
31
|
+
# Math
|
|
32
|
+
sympy>=1.12 \
|
|
33
|
+
&& rm -rf /root/.cache/pip
|
|
34
|
+
|
|
35
|
+
# Create workspace directory with correct permissions
|
|
36
|
+
RUN mkdir -p /workspace && chown sandbox:sandbox /workspace
|
|
37
|
+
|
|
38
|
+
# Set working directory
|
|
39
|
+
WORKDIR /workspace
|
|
40
|
+
|
|
41
|
+
# Switch to non-root user
|
|
42
|
+
USER sandbox
|
|
43
|
+
|
|
44
|
+
# Default command (will be overridden)
|
|
45
|
+
CMD ["python", "--version"]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Build the Friday Sandbox Python Docker image
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# ./build.sh # Build with default tag
|
|
6
|
+
# ./build.sh v1.0.0 # Build with specific version tag
|
|
7
|
+
#
|
|
8
|
+
# Ref: docs/specs/sandbox_specifications.md - Section 9: Docker Image
|
|
9
|
+
|
|
10
|
+
set -e
|
|
11
|
+
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
IMAGE_NAME="friday-sandbox-python"
|
|
14
|
+
VERSION="${1:-latest}"
|
|
15
|
+
|
|
16
|
+
echo "Building Friday Sandbox Python image..."
|
|
17
|
+
echo " Image: ${IMAGE_NAME}:${VERSION}"
|
|
18
|
+
|
|
19
|
+
docker build \
|
|
20
|
+
-t "${IMAGE_NAME}:${VERSION}" \
|
|
21
|
+
-f "${SCRIPT_DIR}/Dockerfile.python" \
|
|
22
|
+
"${SCRIPT_DIR}"
|
|
23
|
+
|
|
24
|
+
# Also tag as latest if version was specified
|
|
25
|
+
if [ "${VERSION}" != "latest" ]; then
|
|
26
|
+
docker tag "${IMAGE_NAME}:${VERSION}" "${IMAGE_NAME}:latest"
|
|
27
|
+
echo " Also tagged as: ${IMAGE_NAME}:latest"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
echo ""
|
|
31
|
+
echo "Build complete!"
|
|
32
|
+
echo ""
|
|
33
|
+
echo "Test the image with:"
|
|
34
|
+
echo " docker run --rm ${IMAGE_NAME}:${VERSION} python -c 'print(\"Hello from Friday Sandbox!\")'"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "friday-framework-sandbox"
|
|
3
|
+
version = "0.1.0a0"
|
|
4
|
+
description = "Docker-based code execution sandbox for AI agents"
|
|
5
|
+
requires-python = ">=3.10"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [{ name = "Friday Team" }]
|
|
8
|
+
keywords = ["ai", "agents", "sandbox", "docker", "code-execution"]
|
|
9
|
+
|
|
10
|
+
dependencies = [
|
|
11
|
+
"docker>=7.0.0",
|
|
12
|
+
"pydantic>=2.0.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
dev = [
|
|
17
|
+
"pytest>=8.0.0",
|
|
18
|
+
"pytest-asyncio>=0.23.0",
|
|
19
|
+
"pytest-mock>=3.10.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["hatchling"]
|
|
24
|
+
build-backend = "hatchling.build"
|
|
25
|
+
|
|
26
|
+
[tool.hatch.build.targets.wheel]
|
|
27
|
+
packages = ["src/friday_sandbox"]
|
|
28
|
+
|
|
29
|
+
[tool.pytest.ini_options]
|
|
30
|
+
testpaths = ["tests_sandbox"]
|
|
31
|
+
asyncio_mode = "auto"
|
|
32
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
33
|
+
|
|
34
|
+
[tool.ruff]
|
|
35
|
+
line-length = 88
|
|
36
|
+
target-version = "py310"
|
|
37
|
+
|
|
38
|
+
[tool.ruff.lint]
|
|
39
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# mypy: ignore-errors
|
|
2
|
+
"""
|
|
3
|
+
Friday Sandbox - Docker-based code execution for AI agents.
|
|
4
|
+
|
|
5
|
+
This package provides secure, isolated code execution environments
|
|
6
|
+
for AI agents to run dynamically generated code safely.
|
|
7
|
+
|
|
8
|
+
Ref: docs/reference/specs/sandbox_specifications.md
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .config import DockerConfig, SandboxConfig, WorkspaceConfig
|
|
12
|
+
from .models import (
|
|
13
|
+
ExecutionResult,
|
|
14
|
+
SandboxError,
|
|
15
|
+
SandboxExecutionError,
|
|
16
|
+
SandboxTimeoutError,
|
|
17
|
+
Workspace,
|
|
18
|
+
WorkspaceExpiredError,
|
|
19
|
+
WorkspaceNotFoundError,
|
|
20
|
+
)
|
|
21
|
+
from .protocols import SandboxProtocol
|
|
22
|
+
from .workspace import WorkspaceManager
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Lazy imports for sandbox implementations to avoid import errors
|
|
26
|
+
# when Docker SDK is not installed
|
|
27
|
+
def get_docker_sandbox():
|
|
28
|
+
"""Get DockerSandbox class (requires docker SDK)."""
|
|
29
|
+
from .docker import DockerSandbox
|
|
30
|
+
|
|
31
|
+
return DockerSandbox
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_subprocess_sandbox():
|
|
35
|
+
"""Get SubprocessSandbox class."""
|
|
36
|
+
from .subprocess import SubprocessSandbox
|
|
37
|
+
|
|
38
|
+
return SubprocessSandbox
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_mock_sandbox():
|
|
42
|
+
"""Get MockSandbox class."""
|
|
43
|
+
from .mock import MockSandbox
|
|
44
|
+
|
|
45
|
+
return MockSandbox
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_mock_sandbox_builder():
|
|
49
|
+
"""Get MockSandboxBuilder class."""
|
|
50
|
+
from .mock import MockSandboxBuilder
|
|
51
|
+
|
|
52
|
+
return MockSandboxBuilder
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
# Protocols
|
|
57
|
+
"SandboxProtocol",
|
|
58
|
+
# Models
|
|
59
|
+
"ExecutionResult",
|
|
60
|
+
"Workspace",
|
|
61
|
+
# Errors
|
|
62
|
+
"SandboxError",
|
|
63
|
+
"SandboxTimeoutError",
|
|
64
|
+
"SandboxExecutionError",
|
|
65
|
+
"WorkspaceNotFoundError",
|
|
66
|
+
"WorkspaceExpiredError",
|
|
67
|
+
# Config
|
|
68
|
+
"SandboxConfig",
|
|
69
|
+
"WorkspaceConfig",
|
|
70
|
+
"DockerConfig",
|
|
71
|
+
# Manager
|
|
72
|
+
"WorkspaceManager",
|
|
73
|
+
# Sandbox implementations (lazy loaded)
|
|
74
|
+
"get_docker_sandbox",
|
|
75
|
+
"get_subprocess_sandbox",
|
|
76
|
+
"get_mock_sandbox",
|
|
77
|
+
"get_mock_sandbox_builder",
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# mypy: ignore-errors
|
|
2
|
+
"""
|
|
3
|
+
Configuration models for sandbox and workspace management.
|
|
4
|
+
|
|
5
|
+
Ref: docs/reference/specs/sandbox_specifications.md - Section 4: Configuration
|
|
6
|
+
|
|
7
|
+
Configuration follows a layered approach:
|
|
8
|
+
1. Package defaults (defined here)
|
|
9
|
+
2. Environment variables (FRIDAY_SANDBOX_*)
|
|
10
|
+
3. Agent profile settings
|
|
11
|
+
4. Per-execution overrides
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from pydantic import BaseModel, Field, field_validator
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class WorkspaceConfig(BaseModel):
|
|
24
|
+
"""
|
|
25
|
+
Configuration for workspace lifecycle management.
|
|
26
|
+
|
|
27
|
+
Controls TTL, cleanup behavior, and resource limits for workspaces.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# TTL Configuration
|
|
31
|
+
ttl_seconds: int = Field(
|
|
32
|
+
default=3600,
|
|
33
|
+
ge=60,
|
|
34
|
+
le=86400,
|
|
35
|
+
description="Default workspace TTL in seconds (1 hour default)",
|
|
36
|
+
)
|
|
37
|
+
ttl_soft_limit_seconds: int = Field(
|
|
38
|
+
default=3600,
|
|
39
|
+
ge=60,
|
|
40
|
+
description="Soft limit before warning (1 hour)",
|
|
41
|
+
)
|
|
42
|
+
ttl_hard_limit_seconds: int = Field(
|
|
43
|
+
default=86400,
|
|
44
|
+
ge=3600,
|
|
45
|
+
description="Hard limit - force cleanup (24 hours)",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Cleanup Configuration
|
|
49
|
+
cleanup_interval_seconds: int = Field(
|
|
50
|
+
default=300,
|
|
51
|
+
ge=60,
|
|
52
|
+
description="Interval between cleanup sweeps (5 minutes)",
|
|
53
|
+
)
|
|
54
|
+
preserve_on_error: bool = Field(
|
|
55
|
+
default=True,
|
|
56
|
+
description="Preserve workspace if execution fails (for debugging)",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Resource Limits
|
|
60
|
+
max_workspace_size_mb: int = Field(
|
|
61
|
+
default=100,
|
|
62
|
+
ge=10,
|
|
63
|
+
le=1000,
|
|
64
|
+
description="Maximum workspace size in MB",
|
|
65
|
+
)
|
|
66
|
+
max_files_per_workspace: int = Field(
|
|
67
|
+
default=1000,
|
|
68
|
+
ge=10,
|
|
69
|
+
description="Maximum number of files in workspace",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def from_environment(cls) -> WorkspaceConfig:
|
|
74
|
+
"""Create config from environment variables."""
|
|
75
|
+
return cls(
|
|
76
|
+
ttl_seconds=int(os.getenv("FRIDAY_SANDBOX_WORKSPACE_TTL", "3600")),
|
|
77
|
+
ttl_soft_limit_seconds=int(
|
|
78
|
+
os.getenv("FRIDAY_SANDBOX_WORKSPACE_TTL_SOFT", "3600")
|
|
79
|
+
),
|
|
80
|
+
ttl_hard_limit_seconds=int(
|
|
81
|
+
os.getenv("FRIDAY_SANDBOX_WORKSPACE_TTL_HARD", "86400")
|
|
82
|
+
),
|
|
83
|
+
cleanup_interval_seconds=int(
|
|
84
|
+
os.getenv("FRIDAY_SANDBOX_CLEANUP_INTERVAL", "300")
|
|
85
|
+
),
|
|
86
|
+
preserve_on_error=os.getenv(
|
|
87
|
+
"FRIDAY_SANDBOX_PRESERVE_ON_ERROR", "true"
|
|
88
|
+
).lower()
|
|
89
|
+
== "true",
|
|
90
|
+
max_workspace_size_mb=int(
|
|
91
|
+
os.getenv("FRIDAY_SANDBOX_MAX_WORKSPACE_SIZE_MB", "100")
|
|
92
|
+
),
|
|
93
|
+
max_files_per_workspace=int(os.getenv("FRIDAY_SANDBOX_MAX_FILES", "1000")),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class DockerConfig(BaseModel):
|
|
98
|
+
"""
|
|
99
|
+
Configuration for Docker sandbox execution.
|
|
100
|
+
|
|
101
|
+
Controls container resources, networking, and security settings.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
# Image Configuration
|
|
105
|
+
image: str = Field(
|
|
106
|
+
default="friday-sandbox-python:latest",
|
|
107
|
+
description="Docker image to use for execution",
|
|
108
|
+
)
|
|
109
|
+
pull_policy: str = Field(
|
|
110
|
+
default="if-not-present",
|
|
111
|
+
description="Image pull policy: always, if-not-present, never",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Resource Limits
|
|
115
|
+
memory_limit: str = Field(
|
|
116
|
+
default="512m",
|
|
117
|
+
description="Container memory limit",
|
|
118
|
+
)
|
|
119
|
+
cpu_limit: float = Field(
|
|
120
|
+
default=1.0,
|
|
121
|
+
ge=0.1,
|
|
122
|
+
le=4.0,
|
|
123
|
+
description="CPU core limit",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Execution Limits
|
|
127
|
+
default_timeout_seconds: int = Field(
|
|
128
|
+
default=30,
|
|
129
|
+
ge=1,
|
|
130
|
+
le=300,
|
|
131
|
+
description="Default execution timeout",
|
|
132
|
+
)
|
|
133
|
+
max_timeout_seconds: int = Field(
|
|
134
|
+
default=300,
|
|
135
|
+
ge=30,
|
|
136
|
+
le=600,
|
|
137
|
+
description="Maximum allowed timeout",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Security
|
|
141
|
+
network_enabled: bool = Field(
|
|
142
|
+
default=False,
|
|
143
|
+
description="Enable network access in container",
|
|
144
|
+
)
|
|
145
|
+
read_only_rootfs: bool = Field(
|
|
146
|
+
default=True,
|
|
147
|
+
description="Mount root filesystem as read-only",
|
|
148
|
+
)
|
|
149
|
+
user: str = Field(
|
|
150
|
+
default="sandbox:sandbox",
|
|
151
|
+
description="User:group to run as in container",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Paths
|
|
155
|
+
workspace_base_path: Path = Field(
|
|
156
|
+
default=Path("/tmp/friday-workspaces"),
|
|
157
|
+
description="Base path for workspace directories",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
@field_validator("pull_policy")
|
|
161
|
+
@classmethod
|
|
162
|
+
def validate_pull_policy(cls, v: str) -> str:
|
|
163
|
+
valid = {"always", "if-not-present", "never"}
|
|
164
|
+
if v not in valid:
|
|
165
|
+
raise ValueError(f"pull_policy must be one of: {valid}")
|
|
166
|
+
return v
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def from_environment(cls) -> DockerConfig:
|
|
170
|
+
"""Create config from environment variables."""
|
|
171
|
+
return cls(
|
|
172
|
+
image=os.getenv("FRIDAY_SANDBOX_IMAGE", "friday-sandbox-python:latest"),
|
|
173
|
+
pull_policy=os.getenv("FRIDAY_SANDBOX_PULL_POLICY", "if-not-present"),
|
|
174
|
+
memory_limit=os.getenv("FRIDAY_SANDBOX_MEMORY_LIMIT", "512m"),
|
|
175
|
+
cpu_limit=float(os.getenv("FRIDAY_SANDBOX_CPU_LIMIT", "1.0")),
|
|
176
|
+
default_timeout_seconds=int(
|
|
177
|
+
os.getenv("FRIDAY_SANDBOX_DEFAULT_TIMEOUT", "30")
|
|
178
|
+
),
|
|
179
|
+
max_timeout_seconds=int(os.getenv("FRIDAY_SANDBOX_MAX_TIMEOUT", "300")),
|
|
180
|
+
network_enabled=os.getenv("FRIDAY_SANDBOX_NETWORK_ENABLED", "false").lower()
|
|
181
|
+
== "true",
|
|
182
|
+
read_only_rootfs=os.getenv(
|
|
183
|
+
"FRIDAY_SANDBOX_READ_ONLY_ROOTFS", "true"
|
|
184
|
+
).lower()
|
|
185
|
+
== "true",
|
|
186
|
+
workspace_base_path=Path(
|
|
187
|
+
os.getenv("FRIDAY_SANDBOX_WORKSPACE_PATH", "/tmp/friday-workspaces")
|
|
188
|
+
),
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class SandboxConfig(BaseModel):
|
|
193
|
+
"""
|
|
194
|
+
Top-level sandbox configuration combining all settings.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
workspace: WorkspaceConfig = Field(default_factory=WorkspaceConfig)
|
|
198
|
+
docker: DockerConfig = Field(default_factory=DockerConfig)
|
|
199
|
+
|
|
200
|
+
# Sandbox Selection
|
|
201
|
+
sandbox_type: str = Field(
|
|
202
|
+
default="docker",
|
|
203
|
+
description="Sandbox type: docker, subprocess, mock",
|
|
204
|
+
)
|
|
205
|
+
fallback_to_subprocess: bool = Field(
|
|
206
|
+
default=True,
|
|
207
|
+
description="Fall back to subprocess if Docker unavailable",
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
@field_validator("sandbox_type")
|
|
211
|
+
@classmethod
|
|
212
|
+
def validate_sandbox_type(cls, v: str) -> str:
|
|
213
|
+
valid = {"docker", "subprocess", "mock"}
|
|
214
|
+
if v not in valid:
|
|
215
|
+
raise ValueError(f"sandbox_type must be one of: {valid}")
|
|
216
|
+
return v
|
|
217
|
+
|
|
218
|
+
@classmethod
|
|
219
|
+
def from_environment(cls) -> SandboxConfig:
|
|
220
|
+
"""Create complete config from environment variables."""
|
|
221
|
+
return cls(
|
|
222
|
+
workspace=WorkspaceConfig.from_environment(),
|
|
223
|
+
docker=DockerConfig.from_environment(),
|
|
224
|
+
sandbox_type=os.getenv("FRIDAY_SANDBOX_TYPE", "docker"),
|
|
225
|
+
fallback_to_subprocess=os.getenv(
|
|
226
|
+
"FRIDAY_SANDBOX_FALLBACK_SUBPROCESS", "true"
|
|
227
|
+
).lower()
|
|
228
|
+
== "true",
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
def merge_overrides(self, overrides: dict[str, Any]) -> SandboxConfig:
|
|
232
|
+
"""
|
|
233
|
+
Create a new config with overrides applied.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
overrides: Dictionary of override values.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
New SandboxConfig with overrides merged.
|
|
240
|
+
"""
|
|
241
|
+
data = self.model_dump()
|
|
242
|
+
for key, value in overrides.items():
|
|
243
|
+
if "." in key:
|
|
244
|
+
# Handle nested keys like "workspace.ttl_seconds"
|
|
245
|
+
parts = key.split(".")
|
|
246
|
+
current = data
|
|
247
|
+
for part in parts[:-1]:
|
|
248
|
+
current = current.setdefault(part, {})
|
|
249
|
+
current[parts[-1]] = value
|
|
250
|
+
else:
|
|
251
|
+
data[key] = value
|
|
252
|
+
return SandboxConfig.model_validate(data)
|