Skip to content

zidane/contexts/e2b

A sandbox provider backed by E2B micro-VMs, hosted or self-hosted.

import { … } from 'zidane/contexts/e2b'src/contexts/e2b.ts

Use createE2BProvider for isolation off your machine. It returns a SandboxProvider that you wrap with createSandboxContext, needs the optional e2b package, and reads E2B_API_KEY when you pass no key. The other exports cover readiness checks, template and environment resolution, and the setup script that runs before the first turn.

TypeScript
import { createSandboxContext } from 'zidane/contexts/sandbox'
import { createE2BProvider } from 'zidane/contexts/e2b'

const context = createSandboxContext(createE2BProvider({ cwd: '/home/user/project', onDestroy: 'pause' }))
const handle = await context.spawn()
await context.exec(handle, 'bun --version')
await context.destroy(handle)

Functions#

createE2BProviderFunction#

Build a SandboxProvider backed by E2B.

The provider owns a registry of live E2BSandbox instances keyed by sandbox id, so the per-call exec/readFile/… hooks reuse the same warm connection rather than reconnecting each time.

TypeScript
function createE2BProvider(options?: E2BProviderOptions): SandboxProvider
Parameters
NameTypeDefaultDescription
options?E2BProviderOptions{}

API key, domain, template, sandbox id, working directory, timeout, environment and readiness settings.

Example
import { createE2BProvider } from 'zidane/contexts/e2b'
import { createSandboxContext } from 'zidane/contexts/sandbox'

const execution = createSandboxContext(
  createE2BProvider({ apiKey: process.env.E2B_API_KEY, timeoutSeconds: 600 }),
)

src/contexts/e2b.ts:350

resolveE2BEnvFunction#

Merge the factory-level E2BProviderOptions.env with the per-spawn SpawnConfig.env, per-spawn winning on key conflicts. Returns undefined when the merge is empty so callers can skip setting envs on the create options entirely (rather than handing the SDK an empty object).

TypeScript
function resolveE2BEnv(
  optionEnv: Record<string, string> | undefined,
  perSpawnEnv: Record<string, string> | undefined,
): Record<string, string> | undefined
Parameters
NameTypeDescription
optionEnvRecord<string, string> | undefined

Environment from the provider options.

perSpawnEnvRecord<string, string> | undefined

Environment from the spawn config. Wins on key conflicts.

Record<string, string> | undefined

src/contexts/e2b-sdk.ts:129

resolveE2BTemplateFunction#

Resolve the effective E2B template, highest precedence first: the per-spawn SpawnConfig.sandbox.template, then the factory template option, then the E2B_TEMPLATE env var. Returns undefined (→ SDK's base default) when none is set. The E2B SDK reads E2B_API_KEY/E2B_DOMAIN from the env but NOT E2B_TEMPLATE, so this provider has to honor it itself.

TypeScript
function resolveE2BTemplate(
  perSpawnTemplate: string | undefined,
  optionTemplate: string | undefined,
  env?: Record<string, string | undefined>,
): string | undefined
Parameters
NameTypeDefaultDescription
perSpawnTemplatestring | undefined

Template from the spawn config.

optionTemplatestring | undefined

Template from the provider options.

env?Record<string, string | undefined>process.env

Environment to read E2B_TEMPLATE from.

string | undefined

src/contexts/e2b.ts:320

runE2BPregameFunction#

Upload a setup ("pregame") script into a ready sandbox and execute it.

Writes the script to /tmp/<name>, then chmod +x's and runs it directly (so a #!/bin/bash / #!/usr/bin/env python3 shebang chooses the interpreter) in cwd with envs applied — the same env the sandbox was created with, so the script sees the run's --env / --pass-env. A generous PREGAME_TIMEOUT_MS ceiling is used so long apt/pip installs aren't killed by E2B's 60s command default. A non-zero exit (whether E2B returns it or raises on it) throws, so the caller can tear the sandbox down rather than prompt against a broken environment.

TypeScript
function runE2BPregame(
  sandbox: E2BSandbox,
  logger: Logger,
  opts: { content: string; cwd?: string; envs?: Record<string, string>; name: string },
): Promise<void>
Parameters
NameTypeDescription
sandboxE2BSandbox

Sandbox to run the script in.

loggerLogger

Logger for progress and failure lines.

opts{ content: string; cwd?: string; envs?: Record<string, string>; name: string }

Script name (file name under /tmp), content, and optional cwd and envs for the run.

Promise<void>

src/contexts/e2b.ts:209

waitForE2BReadyFunction#

Block until an E2B sandbox is ready to run commands, or throw if it never becomes ready within the deadline.

The E2B control plane can hand back a sandbox id before the sandbox's envd is accepting commands; the first real command then absorbs that cold start and may time out. This polls a trivial true command on a short backoff, logging a waiting… line up front and a ready line (with elapsedMs) on success. On deadline exhaustion it best-effort kills the sandbox and throws.

now/sleep are injectable purely for deterministic tests; in production they default to Date.now and a real timer.

TypeScript
function waitForE2BReady(
  sandbox: E2BSandbox,
  logger: Logger,
  opts: E2BReadinessOptions,
): Promise<void>
Parameters
NameTypeDescription
sandboxE2BSandbox

Sandbox to probe.

loggerLogger

Logger for progress lines.

optsE2BReadinessOptions

Deadline and probe settings.

Promise<void>

src/contexts/e2b.ts:277

Interfaces#

E2BLifecycleInterface#

What E2B does when a sandbox's timeout elapses (e2b SDK SandboxLifecycle).

TypeScript
interface E2BLifecycle
E2BLifecycle properties
NameTypeDescription
autoResume?boolean

Resume a paused sandbox on the next connect / request (needs onTimeout: 'pause').

onTimeout'pause' | 'kill'

Action when the sandbox timeout elapses: pause keeps its state, kill destroys it.

src/contexts/e2b.ts:50

E2BProviderOptionsInterface#

Options for createE2BProvider.

TypeScript
interface E2BProviderOptions
E2BProviderOptions properties
NameTypeDescription
apiKey?string

E2B API key. Falls back to the E2B_API_KEY env var when omitted.

cwd?string

Default working directory, applied when a spawn doesn't set its own SpawnConfig.cwd. Created on spawn. Defaults to E2B's /home/user.

domain?string

E2B API domain. Set this to point at a self-hosted / on-prem cluster (e.g. e2b.my-company.internal). Falls back to the E2B_DOMAIN env var, then to E2B's hosted default.

env?Record<string, string>

Environment variables baked into every sandbox at create time (passed to E2B's Sandbox.create({ envs })), so every command run in the sandbox sees them. Per-spawn SpawnConfig.env is merged over these.

keepAliveSeconds?number

Keep the sandbox alive while it's used: at most once a minute, any command or file call pushes its timeout out to this many seconds from now (sandbox.setTimeout). An idle sandbox still times out. Off when unset.

lifecycle?E2BLifecycle

What happens when a created sandbox's timeout elapses. { onTimeout: 'pause', autoResume: true } makes a timeout a pause instead of a kill — what long-lived sessions want. Default: E2B's (kill).

logger?Logger

Logger for provider-level lifecycle lines (notably the readiness wait). Defaults to a consoleSink-backed logger so the messages reach stderr without the caller wiring one up.

onDestroy?'pause' | 'kill' | 'leave'

What to do with an owned sandbox when destroy is called:

  • 'kill' (default) — call sandbox.kill(). The sandbox is deleted and its state is gone.
  • 'pause' — call sandbox.pause() (falling back to the older betaPause() on e2b 2.0–2.26). The sandbox's filesystem and process state are preserved; reconnect later with the sandboxId option (E2B's Sandbox.connect auto-resumes a paused sandbox).
  • 'leave' — drop the local handle without making any API call. The sandbox keeps running until E2B's own timeout fires.

Has no effect on sandboxes the provider only connected to (the sandboxId option) — those are always left untouched on destroy since the caller owns their lifecycle.

pregame?{ content: string; name: string }

A setup script to upload and run once the sandbox is ready, before any prompting begins (see runE2BPregame). name is the basename used for the uploaded file; content is the script body. The script is run directly so its shebang selects the interpreter. A non-zero exit aborts spawn (and tears the sandbox down) — a broken setup must not silently hand back a half-provisioned sandbox.

readinessTimeoutSeconds?number

Total deadline, in seconds, for the post-create readiness probe (see waitForE2BReady). A freshly-created sandbox can return its id before envd accepts commands; spawn blocks until a trivial probe succeeds so the first real command never absorbs the cold start. Defaults to 60. Set to 0 to disable the gate.

sandboxId?string

Connect to a pre-existing sandbox by id instead of creating a fresh one (E2B's Sandbox.connect). When set, template is ignored — you're attaching to a sandbox that already exists. The provider treats such a sandbox as externally owned: it is NOT killed on destroy, nor torn down if the readiness probe / pregame fails, since the caller manages its lifecycle. Per-spawn SpawnConfig.sandbox.sandboxId overrides this.

template?string

Sandbox template id / name to launch. Falls back to the E2B_TEMPLATE env var, then to E2B's hosted base template. Note that base does not exist on self-hosted clusters — supply a template that does.

timeoutSeconds?number

Sandbox lifetime in seconds before E2B auto-kills it. Defaults to the SDK default (300s). Per-spawn SpawnConfig.limits.timeout overrides this.

src/contexts/e2b.ts:82

E2BReadinessOptionsInterface#

Options for waitForE2BReady.

TypeScript
interface E2BReadinessOptions
E2BReadinessOptions properties
NameTypeDescription
killOnTimeout?boolean

Whether to kill the sandbox if it never becomes ready. Defaults to true (we created it, so a dead sandbox is ours to clean up). Pass false when attaching to an externally-owned sandbox — failing the probe shouldn't tear down a sandbox the caller is managing.

now?() => number

Clock source — injectable so tests can drive the deadline deterministically.

sleep?(ms: number) => Promise<void>

Sleep between probes — injectable so tests can advance the clock without waiting.

timeoutSecondsnumber

Total deadline in seconds. <= 0 disables the probe entirely.

src/contexts/e2b.ts:244