Skip to content

feat(sandbox): migrate opencode harness + snapshot template to Daytona#337

Open
ishaan-berri wants to merge 7 commits into
mainfrom
litellm_opencode-daytona-sandbox
Open

feat(sandbox): migrate opencode harness + snapshot template to Daytona#337
ishaan-berri wants to merge 7 commits into
mainfrom
litellm_opencode-daytona-sandbox

Conversation

@ishaan-berri
Copy link
Copy Markdown
Contributor

Cut the opencode sandbox MCP over to the platform-side Daytona provider, port e2b/e2b.Dockerfile to a Daytona snapshot template. Keeps the E2B provider (src/server/sandbox/e2b.ts) intact so other LAP users can still pick e2b via SANDBOX_CHOICE.

What changes

1. harnesses/opencode/sandbox-mcp.mjs — drop the direct-E2B fallback

The MCP had two modes: platform delegation (when session_id was available) and a direct-E2B fallback (no session_id). The direct branch duplicated SDK logic that already lives in src/server/sandbox/e2b.ts and was a second place to chase provider quirks (sandbox.kill, setTimeout keepalive, file-read formats).

Now: every tool routes through the platform endpoint, which picks the configured provider (Daytona/E2B) via SANDBOX_CHOICE. When no session_id is available the four tools return a shared error pointing the agent at the <lap_session_id> tag — no silent fallback. e2b is dropped from harnesses/opencode/package.json.

2. daytona/ — new snapshot template

Port of e2b/:

  • daytona.DockerfileFROM ubuntu:22.04 (E2B base had python preinstalled; this installs it), explicit useradd user, same postgres + litellm[proxy] + uv pre-bake.
  • build-snapshot.mjsImage.fromDockerfile + daytona.snapshot.create. Run once: DAYTONA_API_KEY=<key> node daytona/build-snapshot.mjs.
  • README.md — env vars to set after.
  • cloud-vault-ca.crt, start-db.sh, dev-up.sh — copied verbatim from e2b/.

Operational follow-up (not in this PR)

  1. Set on the LAP platform Render service: DAYTONA_API_KEY=<key>, DAYTONA_IMAGE=ubuntu:22.04 (or DAYTONA_SNAPSHOT=litellm-8gb once built), SANDBOX_CHOICE=daytona.
  2. Build the snapshot once with the script.
  3. After Daytona is verified healthy in prod, decide whether to also unset E2B_API_KEY (keeping the E2B provider available for other users either way).

Same pre-bake as e2b/e2b.Dockerfile (postgres + litellm[proxy] + uv)
so the first execute on a Daytona sandbox doesn't wait 15 min for apt
+ pip. Notable diffs vs the E2B version:

- FROM ubuntu:22.04 (e2bdev/code-interpreter had python preinstalled;
  this installs python3/python3-pip explicitly).
- useradd `user` explicitly (E2B's base provided it).
- No e2b.toml; resources are passed at snapshot/sandbox create.
Programmatic equivalent of `e2b template build`. Uses @daytona/sdk's
Image.fromDockerfile + daytona.snapshot.create to register the snapshot
once so subsequent sandbox creates reference it by name. Streams build
logs and bakes resources (cpu/memory/disk) at snapshot time.
Same files the E2B template ships; the Daytona Dockerfile expects them in
its build context. Kept verbatim to make the diff obvious.
The MCP now delegates every call through the platform sandbox endpoint,
which picks the configured provider (Daytona/E2B) via SANDBOX_CHOICE.
The direct-E2B path duplicated SDK logic that already lives in
src/server/sandbox/e2b.ts and was a second place to chase provider
quirks (sandbox.kill, sandbox.setTimeout keepalive, file read formats).

When no session_id is available (env or tool arg) the four tools now
return a single shared error pointing the agent at the
<lap_session_id> tag — no silent fallback. The E2B *provider*
(src/server/sandbox/e2b.ts) is kept intact so other LAP users can still
pick "e2b" via SANDBOX_CHOICE.
The MCP no longer imports the e2b SDK after removing the direct fallback.
@daytona/sdk isn't added here either — the MCP only speaks to the LAP
platform's sandbox endpoint; the platform owns the provider SDK.
The first build attempt failed: 'Disk request 20GB exceeds maximum allowed
per sandbox (10GB)'. Lower the default to 10 in build-snapshot.mjs and
note in the README that callers need to ask Daytona support for more.
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 26, 2026

Greptile Summary

This PR migrates the opencode harness sandbox MCP from a dual-mode (platform + direct-E2B fallback) implementation to a single platform-delegation mode, and adds a new daytona/ snapshot template as a port of e2b/ targeting ubuntu:22.04.

  • harnesses/opencode/sandbox-mcp.mjs: Removes ~80 lines of direct-E2B SDK code (sandbox create, keepalive setTimeout, command execution, file reads); all four tools now route exclusively through the LAP platform endpoint, eliminating a second place to track provider changes.
  • daytona/: New snapshot template comprising a Dockerfile, one-shot build script, start-db.sh/dev-up.sh scripts (copied from e2b/), and a CA certificate; pre-bakes postgres + litellm[proxy] + uv at snapshot time so agent cold-starts don't block on a full install.

Confidence Score: 4/5

The MCP refactor is clean and the removal of the direct-E2B fallback is well-scoped; no logic that could silently corrupt data or break auth flows was introduced.

The core MCP change eliminates duplicated SDK code and the platform-only path is simpler to reason about. The findings are limited to a misleading error message, stale comments, and dev credentials baked into image ENV layers that must not be promoted to production.

daytona/daytona.Dockerfile warrants a second look before any production snapshot is built from it, given the dev credentials baked into ENV layers.

Important Files Changed

Filename Overview
harnesses/opencode/sandbox-mcp.mjs Removes the direct-E2B fallback path; all four tools now route exclusively through the LAP platform endpoint. Logic is clean, but the shared missingSessionError message conflates two distinct failure conditions (missing session_id vs missing BASE URL).
daytona/daytona.Dockerfile Port of the E2B Dockerfile to ubuntu:22.04 with explicit user creation and CA bundle setup. Hardcoded dev credentials are baked into image ENV layers — acceptable for dev but risky if the same image is accidentally promoted to production.
daytona/build-snapshot.mjs Straightforward one-shot script to build the Daytona snapshot; streams logs, validates API key presence, and passes a 1800-second (30 min) timeout which matches the Daytona SDK's seconds-based convention.
daytona/start-db.sh Copied verbatim from e2b/; functionally correct but retains stale comments referencing E2B's start_cmd / e2b.toml which no longer apply in the Daytona context.
harnesses/opencode/package.json Removes the e2b dependency and updates the description to reflect provider-agnostic delegation.
daytona/dev-up.sh Copied from e2b/; starts postgres and exports dev env vars. Clean and correct for the Daytona context.
daytona/README.md Documents the snapshot build process, resource overrides, and differences from the E2B template. Accurate and complete.
daytona/cloud-vault-ca.crt Internal CA certificate copied from e2b/; used to trust the cloud-vault HTTPS proxy inside sandboxes.

Reviews (1): Last reviewed commit: "fix(daytona): default disk to 10GB — Day..." | Re-trigger Greptile

Comment on lines +29 to +35
function missingSessionError(tool) {
return textResult(
`${tool} failed: no session_id available. Pass session_id from the ` +
`<lap_session_id> tag in your context (and ensure LAP_BASE_URL is set).`,
true,
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 missingSessionError message is misleading when BASE is the failing condition

The guard !effectiveSid || !BASE fires when either value is absent, but the error text always leads with "no session_id available". If SESSION_ID is correctly set but LAP_BASE_URL is missing (e.g. a misconfigured deployment), the agent and operator will look for a session-ID problem first and waste time before finding the parenthetical "(and ensure LAP_BASE_URL is set)". Splitting the two cases into distinct messages would make the failure immediately actionable.

Comment thread daytona/start-db.sh
Comment on lines +4 to +11
# Used two ways:
# 1. As the E2B sandbox start command (e2b.toml `start_cmd`) so the DB is up
# the moment a sandbox boots — no agent action required.
# 2. By dev-up.sh, for humans in an interactive shell.
#
# The cluster is owned by `user` and postgres refuses to run as root. E2B runs
# the start_cmd as root, so we drop to `user` when invoked as root; when a human
# runs it (already `user`), we run directly.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Stale E2B reference: this script now lives in daytona/ and Daytona has no start_cmd / e2b.toml equivalent. The comment describes the old E2B usage path, which no longer applies here.

Suggested change
# Used two ways:
# 1. As the E2B sandbox start command (e2b.toml `start_cmd`) so the DB is up
# the moment a sandbox boots — no agent action required.
# 2. By dev-up.sh, for humans in an interactive shell.
#
# The cluster is owned by `user` and postgres refuses to run as root. E2B runs
# the start_cmd as root, so we drop to `user` when invoked as root; when a human
# runs it (already `user`), we run directly.
# Used two ways:
# 1. Wired into the sandbox at provision time (e.g. as a wrapper command) so
# the DB is up the moment a sandbox boots — no agent action required.
# 2. By dev-up.sh, for humans in an interactive shell.
#
# The cluster is owned by `user` and postgres refuses to run as root. When
# invoked as root we drop to `user`; when already `user`, we run directly.

Comment on lines +101 to +104
ENV DATABASE_URL=postgresql://litellm:litellm@localhost:5432/litellm
ENV LITELLM_MASTER_KEY=sk-1234
ENV LITELLM_SALT_KEY=sk-litellm-salt-dev-unsafe
ENV STORE_MODEL_IN_DB=True
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Hardcoded dev credentials baked into image ENV layers

The LITELLM_MASTER_KEY and LITELLM_SALT_KEY are set as ENV instructions, making them permanently visible in docker inspect and docker history for any image derived from this snapshot. Because they are baked into a layer rather than injected at runtime, they cannot be removed by the orchestrator at run time — they are always present in the image metadata. The *-dev-unsafe naming signals this is intentional for local dev, but a production snapshot built from this Dockerfile would carry the same well-known values. It is worth explicitly noting in the README that this image must not be used as-is for a production snapshot.

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