CubeSandbox-ready opencode Web template generated from agent-deploy-kit and then customized for a basic coding-agent demo.
It runs three surfaces in one sandbox:
49983: CubeSandboxenvdcontrol plane and readiness probe.49999: small adapter API for/v1/init, health, and a simple/v1/agent/rundemo endpoint.4096: native opencode Web UI and native opencode HTTP API.
See HTTP_API_USAGE.md for the sandbox HTTP API calling guide and protocol examples.
The adapter starts opencode web --hostname 0.0.0.0 --port 4096 inside /workspace/repo. If that directory is empty, it seeds a tiny Python project with app.py and test_app.py so the first demo can ask opencode to modify code.
The adapter keeps ADK's CubeSandbox contract and observability layout. Calls to POST /v1/agent/run create or reuse an opencode session, send a text prompt to the native POST /session/{id}/message API, and return the opencode response plus the session_id.
docker buildx build --platform linux/amd64 \
-f Dockerfile.cubesandbox \
-t <registry>/opencode-cubesandbox-web:latest \
--push .For reproducible builds, pin OPENCODE_VERSION:
docker buildx build --platform linux/amd64 \
--build-arg OPENCODE_VERSION=<version> \
-f Dockerfile.cubesandbox \
-t <registry>/opencode-cubesandbox-web:<version> \
--push .For release builds that need stronger reproducibility, also pin base image digests:
docker buildx build --platform linux/amd64 \
--build-arg OPENCODE_VERSION=1.14.48 \
--build-arg CUBESANDBOX_BASE='ghcr.io/tencentcloud/cubesandbox-base:2026.16@sha256:4e6ef7cdcfe9d5d9221f9206a3cedb669eea25c2d35709af8fd199147ad6ceb9' \
--build-arg NODE_IMAGE='node:22-bookworm@sha256:62e4daa6819762bbd3072af77cc282ab72c631c4aed30dd7980192babaf385b3' \
-f Dockerfile.cubesandbox \
-t <registry>/opencode-cubesandbox-web:1.14.48 \
--push .cubemastercli tpl create-from-image \
--image <registry>/opencode-cubesandbox-web:latest \
--writable-layer-size 2G \
--expose-port 49983 \
--expose-port 49999 \
--expose-port 4096 \
--probe 49983 \
--probe-path /healthagent.build.yaml contains the same port contract for platforms that consume it directly.
Use the adapter API after sandbox boot to inject model credentials and provider settings. Do not bake shared API keys into the image.
curl -X POST http://<sandbox-host>:49999/v1/init \
-H 'Content-Type: application/json' \
-d '{
"config": {
"llm_base_url": "https://api.openai.com/v1",
"llm_api_key": "sk-...",
"llm_model": "gpt-5.2-codex",
"opencode_provider_id": "openai",
"opencode_agent": "build",
"opencode_workdir": "/workspace/repo"
}
}'Common aliases also work: OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL, OPENCODE_MODEL, and OPENCODE_WORKSPACE.
If OPENCODE_SERVER_PASSWORD is set before boot, native opencode Web/API uses Basic auth. Supplying opencode_server_password in /v1/init stores it for adapter-to-opencode calls and restarts opencode, but it is best treated as a boot-time setting.
curl -X POST http://<sandbox-host>:49999/v1/agent/run \
-H 'Content-Type: application/json' \
-d '{
"query": "Modify app.py to add a subtract(a, b) function, add tests, and run pytest.",
"input": {
"title": "add subtract demo",
"agent": "build"
}
}'The response includes:
structured_output.session_id: reuse this for follow-up turns.structured_output.message: native opencode message payload.final_text: best-effort extraction of text from opencode's response.
Follow-up turn:
curl -X POST http://<sandbox-host>:49999/v1/agent/run \
-H 'Content-Type: application/json' \
-d '{
"session_id": "ses_...",
"query": "Now update README.md to document the new subtract function."
}'Install Python dependencies and the opencode CLI, then point runtime state at a writable local directory:
UV_CACHE_DIR="$PWD/.local/uv-cache" uv sync
npm install --prefix "$PWD/.local/npm" opencode-ai
PATH="$PWD/.local/npm/node_modules/.bin:$PATH" \
OPENCODE_WORKDIR="$PWD/.local/repo" \
OPENCODE_RUNTIME_ROOT="$PWD/.local/runtime" \
AGENT_OBSERVABILITY_DIR="$PWD/.local/agent/logs" \
OPENCODE_HOST=127.0.0.1 \
OPENCODE_PORT=55112 \
./.venv/bin/uvicorn deploy_adapter.cubesandbox.server:app --host 127.0.0.1 --port 55111Then check:
curl http://127.0.0.1:55111/healthz
curl http://127.0.0.1:55112/global/healthUse POST /v1/init to inject llm_api_key, llm_base_url, and llm_model before running a real coding task.
Open the sandbox-exposed 4096 port in a browser when CubeSandbox host-based routing is available. In a typical CubeSandbox public-port layout this is something like:
https://4096-<sandbox_id>.cube.app
When only CubeSandbox path proxy URLs are available, prefer the adapter proxy on port 49999:
http://<cube-api-host>/api/v1/sandboxes/<sandbox_id>/proxy/49999/web/
The adapter proxy rewrites opencode Web's root-relative static assets and runtime API calls so the UI can run under the CubeSandbox path prefix.
Native opencode OpenAPI schema is available from the same port at:
https://4096-<sandbox_id>.cube.app/doc
Useful native APIs:
GET /global/healthGET /eventGET /config/providersPUT /auth/{providerID}POST /sessionPOST /session/{sessionID}/messagePOST /session/{sessionID}/prompt_asyncGET /session/{sessionID}/message
docker buildx build --platform linux/amd64 --load \
-f Dockerfile.cubesandbox \
-t opencode-cubesandbox-web:local .
docker run --rm \
-p 49983:49983 \
-p 49999:49999 \
-p 4096:4096 \
opencode-cubesandbox-web:localThen check:
curl -i http://127.0.0.1:49983/health
curl http://127.0.0.1:49999/healthz
curl http://127.0.0.1:4096/global/healthThe opencode Web UI is normally served from /, but CubeSandbox path proxy exposes it under:
/api/v1/sandboxes/<sandbox_id>/proxy/49999/web/
Before changing the proxy rewrite code, run the local regression checks:
./.venv/bin/python -m unittest tests.test_path_proxy_rewrite -v
./.venv/bin/python tools/mock_path_proxy_smoke.pyThe mock smoke test covers HTML entry assets, dynamic /assets/*.js chunks, CSS url(/assets/...), SSE, and prompt_async under a CubeSandbox-style path prefix.
Dockerfile.cubesandbox: CubeSandbox image with Node 22, opencode, Python adapter, and envd.agent.build.yaml: build/runtime contract.deploy_adapter/cubesandbox/server.py: adapter HTTP API.deploy_adapter/cubesandbox/opencode_service.py: opencode process and native API bridge.deploy_adapter/cubesandbox/runtime_config.py:/v1/initconfig normalization.