Skip to content
Open
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
14 changes: 12 additions & 2 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, Vercel, Sprites (Fly.io)
- **Current providers**: E2B, Modal, Daytona, Hopx, Vercel, Sprites (Fly.io), Tenki
- **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 @@ -418,6 +418,7 @@ 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="..."
export TENKI_API_KEY="..."
```

Then just use:
Expand All @@ -442,6 +443,7 @@ When you call `Sandbox.create()` or `run()`, the library checks for providers in
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`
8. **Tenki** - Looks for `TENKI_API_KEY`

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

Expand Down Expand Up @@ -502,6 +504,7 @@ from sandboxes.providers import (
VercelProvider,
SpritesProvider,
CloudflareProvider,
TenkiProvider,
)

# E2B - Uses E2B_API_KEY env var
Expand All @@ -528,6 +531,9 @@ provider = CloudflareProvider(
base_url="https://your-worker.workers.dev",
api_token="your-token",
)

# Tenki - Uses TENKI_API_KEY env var
provider = TenkiProvider()
```

Each provider requires appropriate authentication:
Expand All @@ -538,6 +544,7 @@ Each provider requires appropriate authentication:
- **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`
- **Tenki**: Set `TENKI_API_KEY` environment variable (get one at [tenki.cloud](https://tenki.cloud))

> **Cloudflare setup tips (experimental)**
>
Expand Down Expand Up @@ -720,6 +727,9 @@ export SPRITES_TOKEN="..."
export CLOUDFLARE_SANDBOX_BASE_URL="https://your-worker.workers.dev"
export CLOUDFLARE_API_TOKEN="..."
export CLOUDFLARE_ACCOUNT_ID="..." # Optional

# Tenki
export TENKI_API_KEY="..."
```

## Multi-Language Support
Expand Down Expand Up @@ -985,4 +995,4 @@ MIT License - see [LICENSE](LICENSE) file for details.

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

Thanks to the teams at E2B, Modal, Daytona, Hopx, Vercel, Fly.io (Sprites), and Cloudflare for their excellent sandbox platforms.
Thanks to the teams at E2B, Modal, Daytona, Hopx, Vercel, Fly.io (Sprites), Cloudflare, and Tenki for their excellent sandbox platforms.
7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ readme = "README.md"
homepage = "https://github.com/cased/sandboxes"
repository = "https://github.com/cased/sandboxes"
documentation = "https://github.com/cased/sandboxes"
keywords = ["sandbox", "ai", "code-execution", "e2b", "modal", "daytona", "hopx", "vercel", "cloudflare"]
keywords = ["sandbox", "ai", "code-execution", "e2b", "modal", "daytona", "hopx", "vercel", "cloudflare", "tenki"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
Expand Down Expand Up @@ -50,6 +50,9 @@ hopx = [
vercel = [
"vercel>=0.4.0", # Official Vercel SDK with Sandbox APIs
]
tenki = [
"tenki-sandbox>=0.3.6", # Official Tenki Sandbox async SDK (Connect/gRPC)
]
# cloudflare = [
# "cloudflare-workers-sdk>=0.1.0", # When available
# ]
Expand All @@ -59,6 +62,7 @@ all = [
"modal==1.3.3",
"hopx-ai>=0.5.0",
"vercel>=0.4.0",
"tenki-sandbox>=0.3.6",
]
dev = [
"pytest>=7.4.0",
Expand Down Expand Up @@ -159,6 +163,7 @@ markers = [
"hopx: marks tests that require Hopx API",
"vercel: marks tests that require Vercel API",
"cloudflare: marks tests that require Cloudflare API",
"tenki: marks tests that require Tenki API",
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
]

Expand Down
11 changes: 11 additions & 0 deletions sandboxes/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ def _provider_classes():
except ImportError:
pass

try:
from sandboxes.providers.tenki import TenkiProvider

providers["tenki"] = TenkiProvider
except ImportError:
pass

return providers


Expand Down Expand Up @@ -516,6 +523,7 @@ def providers(capabilities):
"Cloudflare Workers + Containers (⚠️ experimental)",
True,
),
("tenki", "TENKI_API_KEY", "Tenki managed microVM sandboxes", False),
]

for name, auth, description, is_experimental in providers:
Expand Down Expand Up @@ -546,6 +554,8 @@ def providers(capabilities):
(os.getenv("CLOUDFLARE_API_TOKEN") or os.getenv("CLOUDFLARE_API_KEY"))
and os.getenv("CLOUDFLARE_SANDBOX_BASE_URL")
)
elif name == "tenki":
configured = bool(os.getenv("TENKI_API_KEY") or os.getenv("TENKI_AUTH_TOKEN"))
else:
configured = False

Expand Down Expand Up @@ -612,6 +622,7 @@ def format_row(row: list[str]) -> str:
click.echo(
" Cloudflare (experimental): Deploy Worker from https://github.com/cloudflare/sandbox-sdk"
)
click.echo(" Tenki: export TENKI_API_KEY=your_key")


def _run_claude_sprites(name: str | None, keep: bool, list_sandboxes: bool):
Expand Down
7 changes: 7 additions & 0 deletions sandboxes/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@
except ImportError:
pass

try:
from .tenki import TenkiProvider

_providers["tenki"] = TenkiProvider
except ImportError:
pass


def get_provider(name: str) -> type[SandboxProvider] | None:
"""Get a provider class by name."""
Expand Down
Loading