Skip to content

feat(sandbox-cloudflare): Cloudflare Sandbox provider worker (iii worker + CF Worker bridge)#97

Open
rohitg00 wants to merge 5 commits into
mainfrom
sandbox-cloudflare
Open

feat(sandbox-cloudflare): Cloudflare Sandbox provider worker (iii worker + CF Worker bridge)#97
rohitg00 wants to merge 5 commits into
mainfrom
sandbox-cloudflare

Conversation

@rohitg00
Copy link
Copy Markdown
Contributor

@rohitg00 rohitg00 commented May 7, 2026

Member of the sandbox worker family. Read `sandbox-CONTEXT.md` (lands via #92) for family terminology and ABI contract.

What ships — two artifacts

CF Sandbox lives inside a Durable Object on the Cloudflare Workers V8 runtime, so the iii engine cannot host it directly. This crate ships two artifacts:

  1. `sandbox-cloudflare/` — Node iii worker (runs on iii engine host). Registers `sandbox::cloudflare::*` functions. Talks HTTPS to (2).
  2. `sandbox-cloudflare/bridge/` — thin CF Worker deployed via wrangler. Hosts the `Sandbox` Durable Object class re-exported from `@cloudflare/sandbox`. Exposes HTTPS routes the iii worker calls.

The bridge is implementation detail of the iii worker per the CONTEXT.md "iii primitives" decision (Q3): trigger ABI is the contract, transport is free. Caller never invokes the bridge directly.

Functions (iii worker side)

SDK shapes verified via context7 against `/cloudflare/sandbox-sdk`:

Function Bridge route Backed by
`sandbox::cloudflare::create` `POST /create` `getSandbox(env.Sandbox, id, {sleepAfter})` + `exec('true')` to provision
`sandbox::cloudflare::exec` `POST /exec` `sandbox.exec(cmd, {timeout, cwd, env})`
`sandbox::cloudflare::stop` `POST /stop` `sandbox.destroy()`
`sandbox::cloudflare::list` `GET /list` 501 — SDK has no list primitive; iii worker falls back to local `in_flight`
`sandbox::cloudflare::expose_port` `POST /expose-port` `sandbox.exposePort(port, {hostname, name})`
`sandbox::cloudflare::fs::read` `POST /fs/read` `sandbox.readFile(path)`
`sandbox::cloudflare::fs::write` `POST /fs/write` `sandbox.writeFile(path, content)`

Capabilities advertised: `["expose_port","fs"]`. No `snapshot`, no `branch`.

Setup (two steps)

  1. Deploy the bridge (see `bridge/README.md`):
    ```bash
    cd bridge && npm install
    wrangler secret put CLOUDFLARE_BRIDGE_TOKEN
    wrangler deploy
    ```
  2. Run the iii worker:
    ```bash
    export CLOUDFLARE_BRIDGE_URL=https://...workers.dev
    export CLOUDFLARE_BRIDGE_TOKEN=
    iii worker add sandbox-cloudflare
    ```

`PREVIEW_HOSTNAME` is required on the bridge if callers will use `expose_port` (CF Sandbox's preview URLs need a custom domain — `workers.dev` won't work).

Tests

5 vitest cases on the iii worker side (allowlist, in_flight rollback, exec validation, stop error mapping, list envelope) plus `bridge/` typecheck. Bridge runtime tests (miniflare-based) deferred to a follow-up; the iii worker side has full smoke against a mocked bridge response.

Live test status

Bridge wired against documented SDK; not yet deployed end-to-end.

Pin

`iii-sdk: 0.11.6` (iii worker side only — bridge pulls `@cloudflare/sandbox` separately).

rohitg00 added 4 commits May 7, 2026 12:31
…rker + CF Worker bridge)

CF Sandbox lives inside a Durable Object on the Workers V8 runtime, so
the iii engine cannot host it directly. This crate ships two artifacts:

  1. sandbox-cf/         iii worker (Node, runs on iii engine host).
                         Registers sandbox::cf::* functions (create, exec,
                         stop, list, expose_port, fs::read, fs::write).
                         Talks HTTPS to the bridge.
  2. sandbox-cf/bridge/  Thin CF Worker deployed via wrangler. Hosts the
                         Sandbox Durable Object class re-exported from
                         @cloudflare/sandbox. Exposes HTTPS routes
                         matching the iii worker's function set. Auth
                         via shared bearer token (CF_BRIDGE_TOKEN).

Capabilities advertised: ["expose_port", "fs"]. CF Sandbox does not
ship snapshot or branch — callers that need those should pick a
different provider.

The iii worker side ships full smoke tests (5/5) against a stubbed
bridge client; the bridge ships the auth check + route shell. Both
halves' upstream calls are stubbed pending verified deploy of
@cloudflare/sandbox's getSandbox() against a real CF account.

Wire protocol between worker and bridge is stable. Lint clean (biome
+ tsc on both halves). Part of the sandbox-as-worker family.
Replaces 501 stubs with real getSandbox().* calls verified via context7
against /cloudflare/sandbox-sdk:

- POST /create   → getSandbox(env.Sandbox, id, {sleepAfter}); exec('true')
                   to force container provisioning; returns {sandbox_id,
                   image, started_at}
- POST /exec     → sandbox.exec(buildExecCommand(cmd, args), {timeout,
                   cwd, env}) → maps {stdout, stderr, exitCode, success}
                   to the iii ABI
- POST /stop     → sandbox.destroy()
- GET  /list     → 501 'sdk has no list primitive' — the iii worker side
                   already falls back to local in_flight accounting when
                   the bridge can't answer (matches the resilience the
                   reconciliation pattern guarantees for every provider)
- POST /expose-port → sandbox.exposePort(port, {hostname, name}) using a
                      PREVIEW_HOSTNAME var; surfaces a clear 500 if the
                      operator forgot to set it (custom domain required
                      per the SDK contract — workers.dev won't work)
- POST /fs/read  → sandbox.readFile(path); base64-encoded for the wire
- POST /fs/write → sandbox.writeFile(path, content)

proxyToSandbox runs at the top of fetch() so preview-port URLs get
routed before the bearer auth check (preview URLs carry their own
token).

Existing 5 wiremock tests on the iii worker side keep passing; bridge
typecheck clean. Live verification deferred until someone deploys the
bridge with wrangler.
iii worker side only — bridge runs CF Worker runtime and pulls
@cloudflare/sandbox separately. Existing tests + typecheck still green.
Spell out the provider name everywhere — folder, branch, function ids
(`sandbox::cloudflare::*`), package names (`sandbox-cloudflare`,
`sandbox-cloudflare-bridge`), and env vars (`CLOUDFLARE_BRIDGE_URL`,
`CLOUDFLARE_BRIDGE_TOKEN`, `SANDBOX_CLOUDFLARE_CONFIG`).

`cf` was an internal shorthand that read like a half-baked acronym
to anyone who hadn't been in the conversation. Matches the explicit
naming the rest of the family uses (sandbox-e2b, sandbox-daytona,
sandbox-morph, sandbox-vercel, sandbox-modal).

Tests: 5/5 vitest, biome check + ci clean, top-level tsc clean,
bridge tsc clean.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Warning

Rate limit exceeded

@rohitg00 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 55 minutes and 19 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 036faf5e-f265-4f09-ae29-44230de20360

📥 Commits

Reviewing files that changed from the base of the PR and between 2e53b44 and 6df8393.

📒 Files selected for processing (17)
  • sandbox-cloudflare/.gitignore
  • sandbox-cloudflare/Dockerfile
  • sandbox-cloudflare/README.md
  • sandbox-cloudflare/bridge/README.md
  • sandbox-cloudflare/bridge/package.json
  • sandbox-cloudflare/bridge/src/index.ts
  • sandbox-cloudflare/bridge/tsconfig.json
  • sandbox-cloudflare/bridge/wrangler.jsonc
  • sandbox-cloudflare/iii.worker.yaml
  • sandbox-cloudflare/package.json
  • sandbox-cloudflare/src/client.ts
  • sandbox-cloudflare/src/config.ts
  • sandbox-cloudflare/src/handlers.ts
  • sandbox-cloudflare/src/index.ts
  • sandbox-cloudflare/src/sandbox.ts
  • sandbox-cloudflare/tests/handlers.test.ts
  • sandbox-cloudflare/tsconfig.json
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sandbox-cloudflare

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…lare::*

Stops shadowing the bare sandbox::* namespace. Routes through the
sandbox router worker (provider="cloudflare"); direct invocation
stays supported and stable. CF Worker bridge unchanged.

Handler logic, tests, S-code mapping unchanged.
@rohitg00
Copy link
Copy Markdown
Contributor Author

Refactored to register only sandbox::provider::cloudflare::*. Caller-facing sandbox::* now owned by the new sandbox router worker in #119. Direct invocation of sandbox::provider::cloudflare::* stays supported.

Validation: cargo fmt --check + cargo clippy -D warnings + tests clean. Depends on #119 for end-to-end use via the router; this PR is independently mergeable if direct invocation is sufficient.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant