Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
5 changes: 5 additions & 0 deletions .changeset/add-opencode-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cloudflare/sandbox': patch
---

Add OpenCode integration with createOpencode() and proxyToOpencode() helpers
19 changes: 18 additions & 1 deletion .github/workflows/pkg-pr-new.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,22 @@ jobs:
build-args: |
SANDBOX_VERSION=${{ steps.package-version.outputs.version }}

- name: Build and push Docker image (opencode)
uses: docker/build-push-action@v6
with:
context: .
file: packages/sandbox/Dockerfile
target: opencode
platforms: linux/amd64
push: true
tags: cloudflare/sandbox:${{ steps.package-version.outputs.version }}-opencode
cache-from: |
type=gha,scope=preview-pr-${{ github.event.pull_request.number }}-opencode
type=gha,scope=release-opencode
cache-to: type=gha,mode=max,scope=preview-pr-${{ github.event.pull_request.number }}-opencode
build-args: |
SANDBOX_VERSION=${{ steps.package-version.outputs.version }}

- name: Publish to pkg.pr.new
run: npx pkg-pr-new publish './packages/sandbox'

Expand All @@ -119,7 +135,8 @@ jobs:
const version = '${{ steps.package-version.outputs.version }}';
const defaultTag = `cloudflare/sandbox:${version}`;
const pythonTag = `cloudflare/sandbox:${version}-python`;
const body = `### 🐳 Docker Images Published\n\n**Default (no Python):**\n\`\`\`dockerfile\nFROM ${defaultTag}\n\`\`\`\n\n**With Python:**\n\`\`\`dockerfile\nFROM ${pythonTag}\n\`\`\`\n\n**Version:** \`${version}\`\n\nUse the \`-python\` variant if you need Python code execution.`;
const opencodeTag = `cloudflare/sandbox:${version}-opencode`;
const body = `### 🐳 Docker Images Published\n\n**Default (no Python):**\n\`\`\`dockerfile\nFROM ${defaultTag}\n\`\`\`\n\n**With Python:**\n\`\`\`dockerfile\nFROM ${pythonTag}\n\`\`\`\n\n**With OpenCode:**\n\`\`\`dockerfile\nFROM ${opencodeTag}\n\`\`\`\n\n**Version:** \`${version}\`\n\nUse the \`-python\` variant for Python code execution, or \`-opencode\` for the OpenCode AI coding agent.`;

// Find existing comment
const { data: comments } = await github.rest.issues.listComments({
Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/pullrequest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,18 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build test worker Docker images (base + python)
- name: Build test worker Docker images (base + python + opencode)
run: |
VERSION=${{ needs.unit-tests.outputs.version || '0.0.0' }}
# Build base image (no Python) - used by SandboxBase binding
# Build base image (no Python) - used by Sandbox binding
docker build -f packages/sandbox/Dockerfile --target default --platform linux/amd64 \
--build-arg SANDBOX_VERSION=$VERSION -t cloudflare/sandbox-test:$VERSION .
# Build python image - used by Sandbox binding
# Build python image - used by SandboxPython binding
docker build -f packages/sandbox/Dockerfile --target python --platform linux/amd64 \
--build-arg SANDBOX_VERSION=$VERSION -t cloudflare/sandbox-test:$VERSION-python .
# Build opencode image - used by SandboxOpencode binding
docker build -f packages/sandbox/Dockerfile --target opencode --platform linux/amd64 \
--build-arg SANDBOX_VERSION=$VERSION -t cloudflare/sandbox-test:$VERSION-opencode .

# Deploy test worker using official Cloudflare action
- name: Deploy test worker
Expand Down
19 changes: 18 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build test worker Docker images (base + python)
- name: Build test worker Docker images (base + python + opencode)
run: |
VERSION=${{ needs.unit-tests.outputs.version }}
# Build base image (no Python) - used by Sandbox binding
Expand All @@ -117,6 +117,9 @@ jobs:
# Build python image - used by SandboxPython binding
docker build -f packages/sandbox/Dockerfile --target python --platform linux/amd64 \
--build-arg SANDBOX_VERSION=$VERSION -t cloudflare/sandbox-test:$VERSION-python .
# Build opencode image - used by SandboxOpencode binding
docker build -f packages/sandbox/Dockerfile --target opencode --platform linux/amd64 \
--build-arg SANDBOX_VERSION=$VERSION -t cloudflare/sandbox-test:$VERSION-opencode .

- name: Deploy test worker
uses: cloudflare/wrangler-action@v3
Expand Down Expand Up @@ -213,6 +216,20 @@ jobs:
build-args: |
SANDBOX_VERSION=${{ needs.unit-tests.outputs.version }}

- name: Build and push Docker image (opencode)
uses: docker/build-push-action@v6
with:
context: .
file: packages/sandbox/Dockerfile
target: opencode
platforms: linux/amd64
push: true
tags: cloudflare/sandbox:${{ needs.unit-tests.outputs.version }}-opencode
cache-from: type=gha,scope=release-opencode
cache-to: type=gha,mode=max,scope=release-opencode
build-args: |
SANDBOX_VERSION=${{ needs.unit-tests.outputs.version }}

- id: changesets
uses: changesets/action@v1
with:
Expand Down
1 change: 1 addition & 0 deletions examples/opencode/.dev.vars.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ANTHROPIC_API_KEY=<YOUR-KEY-HERE>
199 changes: 199 additions & 0 deletions examples/opencode/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# Created by https://www.toptal.com/developers/gitignore/api/macos,node,git
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,node,git

### Git ###
# Created by git for backups. To disable backups in Git:
# $ git config --global mergetool.keepBackup false
*.orig

# Created by git when using merge tools for conflicts
*.BACKUP.*
*.BASE.*
*.LOCAL.*
*.REMOTE.*
*_BACKUP_*.txt
*_BASE_*.txt
*_LOCAL_*.txt
*_REMOTE_*.txt

### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon


# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### macOS Patch ###
# iCloud generated files
*.icloud

### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

### Node Patch ###
# Serverless Webpack directories
.webpack/

# Optional stylelint cache

# SvelteKit build / generate output
.svelte-kit

# End of https://www.toptal.com/developers/gitignore/api/macos,node,git

### Wrangler ###
.wrangler/
.env*
!.env.example
.dev.vars*
!.dev.vars.example
19 changes: 19 additions & 0 deletions examples/opencode/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM docker.io/cloudflare/sandbox:0.6.3

# Add opencode install location to PATH before installation
ENV PATH="/root/.opencode/bin:${PATH}"

# Install OpenCode CLI
RUN curl -fsSL https://opencode.ai/install -o /tmp/install-opencode.sh \
&& bash /tmp/install-opencode.sh \
&& rm /tmp/install-opencode.sh \
&& opencode --version

# Clone sample project for the web UI to work with
RUN git clone --depth 1 https://github.com/cloudflare/agents.git /home/user/agents

# Start in the sample project directory
WORKDIR /home/user/agents

# Expose OpenCode server port
EXPOSE 4096
45 changes: 45 additions & 0 deletions examples/opencode/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# OpenCode + Sandbox SDK

Run OpenCode inside Cloudflare Sandboxes! Just open the worker URL in your browser to get the full OpenCode web experience.

## Quick Start

1. Copy `.dev.vars.example` to `.dev.vars` and add your Anthropic API key:

```bash
cp .dev.vars.example .dev.vars
# Edit .dev.vars with your ANTHROPIC_API_KEY
```

2. Install dependencies and run:

```bash
npm install
npm run dev
```

3. Open http://localhost:8787 in your browser - you'll see the OpenCode web UI!

## How It Works

The worker acts as a transparent proxy to OpenCode running in the container:

```
Browser → Worker → Sandbox DO → Container :4096 → OpenCode Server
Proxies UI from desktop.dev.opencode.ai
```

OpenCode handles everything:

- API routes (`/session/*`, `/event`, etc.)
- Web UI (proxied from `desktop.dev.opencode.ai`)
- WebSocket for terminal

## Key Benefits

- **Web UI** - Full browser-based OpenCode experience
- **Isolated execution** - Code runs in secure sandbox containers
- **Persistent sessions** - Sessions survive across requests

Happy hacking!
25 changes: 25 additions & 0 deletions examples/opencode/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@cloudflare/sandbox-opencode-example",
"version": "1.0.0",
"type": "module",
"private": true,
"description": "Example of running OpenCode web UI inside Sandbox",
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"types": "wrangler types",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@opencode-ai/sdk": "^1.0.137"
},
"devDependencies": {
"@cloudflare/sandbox": "*",
"@types/node": "^24.10.1",
"typescript": "^5.9.3",
"wrangler": "^4.50.0"
},
"author": "",
"license": "MIT"
}
Loading
Loading