Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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