Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 32 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Universal library for AI code execution sandboxes.

`sandboxes` provides a unified interface for sandboxed code execution across multiple providers:

- **Current providers**: E2B, Modal, Daytona, Hopx, Sprites (Fly.io)
- **Current providers**: E2B, Modal, Daytona, Hopx, Vercel, Sprites (Fly.io)
- **Experimental**: Cloudflare (requires self-hosted Worker deployment)

Write your code once and switch between providers with a single line change, or let the library automatically select a provider.
Expand Down Expand Up @@ -104,7 +104,7 @@ async def main():
print(result.stdout)

# Behind the scenes, run() does this:
# 1. Auto-detects available providers (e.g., E2B, Modal, Daytona)
# 1. Auto-detects available providers (e.g., E2B, Modal, Daytona, Vercel)
# 2. Creates a new sandbox with the first available provider
# 3. Executes your command in that isolated environment
# 4. Returns the result
Expand Down Expand Up @@ -412,6 +412,9 @@ export E2B_API_KEY="..."
export MODAL_TOKEN_ID="..." # Or use `modal token set`
export DAYTONA_API_KEY="..."
export HOPX_API_KEY="hopx_live_<keyId>.<secret>"
export VERCEL_TOKEN="..."
export VERCEL_PROJECT_ID="..."
export VERCEL_TEAM_ID="..."
export SPRITES_TOKEN="..." # Or use `sprite login` for CLI mode
export CLOUDFLARE_SANDBOX_BASE_URL="https://your-worker.workers.dev"
export CLOUDFLARE_API_TOKEN="..."
Expand All @@ -436,8 +439,9 @@ When you call `Sandbox.create()` or `run()`, the library checks for providers in
2. **E2B** - Looks for `E2B_API_KEY`
3. **Sprites** - Looks for `SPRITES_TOKEN` or `sprite` CLI login
4. **Hopx** - Looks for `HOPX_API_KEY`
5. **Modal** - Looks for `~/.modal.toml` or `MODAL_TOKEN_ID`
6. **Cloudflare** *(experimental)* - Looks for `CLOUDFLARE_SANDBOX_BASE_URL` + `CLOUDFLARE_API_TOKEN`
5. **Vercel** - Looks for `VERCEL_TOKEN` + `VERCEL_PROJECT_ID` + `VERCEL_TEAM_ID`
6. **Modal** - Looks for `~/.modal.toml` or `MODAL_TOKEN_ID`
7. **Cloudflare** *(experimental)* - Looks for `CLOUDFLARE_SANDBOX_BASE_URL` + `CLOUDFLARE_API_TOKEN`

**The first provider with valid credentials becomes the default.** Cloudflare requires deploying your own Worker.

Expand Down Expand Up @@ -495,6 +499,7 @@ from sandboxes.providers import (
ModalProvider,
DaytonaProvider,
HopxProvider,
VercelProvider,
SpritesProvider,
CloudflareProvider,
)
Expand All @@ -511,6 +516,9 @@ provider = DaytonaProvider()
# Hopx - Uses HOPX_API_KEY env var
provider = HopxProvider()

# Vercel - Uses VERCEL_TOKEN + VERCEL_PROJECT_ID + VERCEL_TEAM_ID env vars
provider = VercelProvider()

# Sprites - Uses SPRITES_TOKEN or sprite CLI login
provider = SpritesProvider() # SDK mode with SPRITES_TOKEN
provider = SpritesProvider(use_cli=True) # CLI mode with sprite login
Expand All @@ -527,6 +535,7 @@ Each provider requires appropriate authentication:
- **Modal**: Run `modal token set` to configure
- **Daytona**: Set `DAYTONA_API_KEY` environment variable
- **Hopx**: Set `HOPX_API_KEY` environment variable (format: `hopx_live_<keyId>.<secret>`)
- **Vercel**: Set `VERCEL_TOKEN`, `VERCEL_PROJECT_ID`, and `VERCEL_TEAM_ID`
- **Sprites**: Set `SPRITES_TOKEN` environment variable, or run `sprite login` for CLI mode
- **Cloudflare** *(experimental)*: Deploy the [Cloudflare sandbox Worker](https://github.com/cloudflare/sandbox-sdk) and set `CLOUDFLARE_SANDBOX_BASE_URL`, `CLOUDFLARE_API_TOKEN`, and (optionally) `CLOUDFLARE_ACCOUNT_ID`

Expand Down Expand Up @@ -556,7 +565,15 @@ Each provider requires appropriate authentication:
```python
import asyncio
from sandboxes import Manager, SandboxConfig
from sandboxes.providers import E2BProvider, ModalProvider, DaytonaProvider, SpritesProvider, CloudflareProvider
from sandboxes.providers import (
E2BProvider,
ModalProvider,
DaytonaProvider,
HopxProvider,
VercelProvider,
SpritesProvider,
CloudflareProvider,
)

async def main():
# Initialize manager and register providers
Expand All @@ -566,6 +583,15 @@ async def main():
manager.register_provider("modal", ModalProvider, {})
manager.register_provider("daytona", DaytonaProvider, {})
manager.register_provider("hopx", HopxProvider, {})
manager.register_provider(
"vercel",
VercelProvider,
{
"token": "...",
"project_id": "...",
"team_id": "...",
},
)
manager.register_provider("sprites", SpritesProvider, {"use_cli": True})
manager.register_provider(
"cloudflare",
Expand Down Expand Up @@ -959,4 +985,4 @@ MIT License - see [LICENSE](LICENSE) file for details.

Built by [Cased](https://cased.com)

Thanks to the teams at E2B, Modal, Daytona, Hopx, Fly.io (Sprites), and Cloudflare for their excellent sandbox platforms.
Thanks to the teams at E2B, Modal, Daytona, Hopx, Vercel, Fly.io (Sprites), and Cloudflare for their excellent sandbox platforms.
9 changes: 6 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies = [
"e2b>=2.13.2",
"daytona>=0.143.0",
"hopx-ai>=0.5.0",
"vercel>=0.4.0",
"httpx>=0.27.0",
]

Expand All @@ -46,9 +47,9 @@ modal = [
hopx = [
"hopx-ai>=0.5.0", # Official Hopx SDK for secure cloud sandboxes
]
# vercel = [
# "vercel-sdk>=0.1.0", # When available
# ]
vercel = [
"vercel>=0.4.0", # Official Vercel SDK with Sandbox APIs
]
# cloudflare = [
# "cloudflare-workers-sdk>=0.1.0", # When available
# ]
Expand All @@ -57,6 +58,7 @@ all = [
"e2b>=2.13.2",
"modal==1.3.3",
"hopx-ai>=0.5.0",
"vercel>=0.4.0",
]
dev = [
"pytest>=7.4.0",
Expand Down Expand Up @@ -155,6 +157,7 @@ markers = [
"modal: marks tests that require Modal API",
"daytona: marks tests that require Daytona API",
"hopx: marks tests that require Hopx API",
"vercel: marks tests that require Vercel API",
"cloudflare: marks tests that require Cloudflare API",
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
]
Expand Down
28 changes: 28 additions & 0 deletions sandboxes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,41 @@ class Sandbox:
metadata: dict[str, Any] = field(default_factory=dict)


@dataclass(frozen=True)
class ProviderCapabilities:
"""Feature flags exposed by a provider."""

persistent: bool = False
snapshot: bool = False
streaming: bool = False
file_upload: bool = False
interactive_shell: bool = False
gpu: bool = False

def as_dict(self) -> dict[str, bool]:
"""Return capabilities as a plain dictionary."""
return asdict(self)


class SandboxProvider(ABC):
"""Abstract base class for sandbox providers."""

CAPABILITIES = ProviderCapabilities()

def __init__(self, **config):
"""Initialize provider with configuration."""
self.config = config

@classmethod
def get_capabilities(cls) -> ProviderCapabilities:
"""Return static capabilities for this provider class."""
return cls.CAPABILITIES

@property
def capabilities(self) -> ProviderCapabilities:
"""Return capabilities for this provider instance."""
return self.get_capabilities()

@property
@abstractmethod
def name(self) -> str:
Expand Down
Loading