Opencode V2 sbx agent
This commit is contained in:
commit
962002aec1
6 changed files with 345 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
*.tar
|
||||||
|
*.log
|
||||||
|
.env
|
||||||
|
.claude
|
||||||
|
.opencode
|
||||||
12
Dockerfile
Normal file
12
Dockerfile
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Pins the OpenCode CLI version inside the sandbox to OpenCode v2.
|
||||||
|
#
|
||||||
|
# docker/sandbox-templates:opencode bakes in OpenCode v1 (the `opencode-ai`
|
||||||
|
# npm package, 1.18.32 as of writing). OpenCode v2 ships as a different npm
|
||||||
|
# package, `@opencode/cli` (confirmed via `npm view @opencode/cli version`
|
||||||
|
# and by running `opencode --version` in the built image, which reports
|
||||||
|
# "opencode v2.0.26"). Both packages install a binary named `opencode` into
|
||||||
|
# the same global npm prefix, so the old one has to be removed first.
|
||||||
|
FROM docker/sandbox-templates:opencode
|
||||||
|
ARG OPENCODE_VERSION=2.0.26
|
||||||
|
RUN npm uninstall -g opencode-ai \
|
||||||
|
&& npm install -g @opencode/cli@${OPENCODE_VERSION}
|
||||||
38
Makefile
Normal file
38
Makefile
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Utilities for running the OpenCode agent in a Docker Sandbox.
|
||||||
|
# Requires the `sbx` CLI: https://docs.docker.com/ai/sandboxes/install/
|
||||||
|
#
|
||||||
|
# `sbx` doesn't have a nested sandbox-in-sandbox mode, so these targets are
|
||||||
|
# meant to be run on your host machine from this directory (where sbxenv.yaml
|
||||||
|
# lives), not from inside another sandbox.
|
||||||
|
|
||||||
|
NAME ?= opencode-v2
|
||||||
|
AGENT ?= opencode
|
||||||
|
IMAGE_TAG ?= opencode-v2:local
|
||||||
|
OPENCODE_VERSION ?= 2.0.26
|
||||||
|
|
||||||
|
.PHONY: help cli-check image build install
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "NAME=$(NAME) AGENT=$(AGENT) OPENCODE_VERSION=$(OPENCODE_VERSION)"
|
||||||
|
@echo ""
|
||||||
|
@echo " make image build the $(IMAGE_TAG) image pinning the opencode CLI version"
|
||||||
|
@echo " make build [OPENCODE_VERSION=...] build the image and register it with sbx as a local template"
|
||||||
|
@echo " make install install the opencode-sandbox shim to ~/.local/bin"
|
||||||
|
|
||||||
|
cli-check:
|
||||||
|
@command -v sbx >/dev/null 2>&1 || { \
|
||||||
|
echo "sbx CLI not found. Install it: https://docs.docker.com/ai/sandboxes/install/"; \
|
||||||
|
exit 1; \
|
||||||
|
}
|
||||||
|
@sbx version
|
||||||
|
|
||||||
|
image:
|
||||||
|
docker build --build-arg OPENCODE_VERSION=$(OPENCODE_VERSION) -t $(IMAGE_TAG) .
|
||||||
|
|
||||||
|
build: cli-check image
|
||||||
|
docker image save $(IMAGE_TAG) -o /tmp/opencode-v2.tar
|
||||||
|
sbx template load /tmp/opencode-v2.tar
|
||||||
|
|
||||||
|
install:
|
||||||
|
sh ./install.sh
|
||||||
|
|
||||||
157
README.md
Normal file
157
README.md
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
# opencodev2
|
||||||
|
|
||||||
|
Config and `make` utilities for running **OpenCode v2** inside a
|
||||||
|
[Docker Sandbox](https://docs.docker.com/ai/sandboxes/) — an isolated
|
||||||
|
environment that mounts your working directory read-write, limits network
|
||||||
|
access by policy, and keeps agent-installed tooling out of your host machine.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. Install the `sbx` CLI (one-time, on your host):
|
||||||
|
|
||||||
|
```console
|
||||||
|
# macOS
|
||||||
|
brew trust docker/tap
|
||||||
|
brew install docker/tap/sbx
|
||||||
|
|
||||||
|
# Ubuntu (with Docker Engine already installed)
|
||||||
|
curl -fsSL https://get.docker.com | sudo SBX=1 sh
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
winget install -h Docker.sbx
|
||||||
|
```
|
||||||
|
|
||||||
|
See [docs.docker.com/ai/sandboxes/install](https://docs.docker.com/ai/sandboxes/install/)
|
||||||
|
for other platforms and system-wide installs.
|
||||||
|
|
||||||
|
2. Authenticate and store a credential for whichever model provider OpenCode
|
||||||
|
should use (one-time, host-level secrets — not stored in this repo):
|
||||||
|
|
||||||
|
```console
|
||||||
|
sbx login
|
||||||
|
sbx secret set anthropic # or: openai, google, openrouter, ...
|
||||||
|
sbx secret set github # optional, for repo/PR access via `gh auth token`
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```console
|
||||||
|
make build # build the image, register it with sbx as a local template
|
||||||
|
make install # install the opencode-sandbox shim (one-time)
|
||||||
|
. ~/.bashrc # or ~/.zshrc / restart your shell — whatever install.sh edited
|
||||||
|
|
||||||
|
cd ~/some-project
|
||||||
|
opencode-sandbox # launch OpenCode v2 in a sandbox, workspace = current directory
|
||||||
|
```
|
||||||
|
|
||||||
|
`opencode-sandbox` works from **any** directory once installed — it's not tied
|
||||||
|
to this project directory, and it's not named `opencode` so it won't shadow a
|
||||||
|
real, locally-installed OpenCode CLI on your PATH. Re-run `make build` whenever
|
||||||
|
you bump `OPENCODE_VERSION` or change [`./Dockerfile`](./Dockerfile);
|
||||||
|
`make install` only needs to run once per machine.
|
||||||
|
|
||||||
|
On first run, `sbx` asks you to pick a global network policy (Open / Balanced /
|
||||||
|
Locked Down). **Balanced** is a reasonable default. Adjust anytime:
|
||||||
|
|
||||||
|
```console
|
||||||
|
sbx policy ls # show current rules
|
||||||
|
sbx policy allow network <host> # allow an extra host
|
||||||
|
```
|
||||||
|
|
||||||
|
## What `make install` does
|
||||||
|
|
||||||
|
[`install.sh`](./install.sh) is POSIX `/bin/sh` (not shell-specific) and does
|
||||||
|
two things, both idempotent — safe to re-run:
|
||||||
|
|
||||||
|
1. Writes a shim to `~/.local/bin/opencode-sandbox`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/bin/sh
|
||||||
|
exec sbx run -t localhost/opencode-v2:local "$@" opencode
|
||||||
|
```
|
||||||
|
|
||||||
|
`-t localhost/opencode-v2:local` points at the template `make build`
|
||||||
|
registers (see below); `opencode` is the agent name; `"$@"` forwards any
|
||||||
|
extra args (e.g. `opencode-sandbox --model claude-sonnet-5`). Being a real
|
||||||
|
executable on `PATH` rather than a shell function means it works
|
||||||
|
identically under bash, zsh, fish, or anything else.
|
||||||
|
|
||||||
|
2. If `~/.local/bin` isn't already on `PATH`, adds it — detecting your login
|
||||||
|
shell via `$SHELL` (not whatever shell happens to run this script) and
|
||||||
|
picking the right config file: `~/.zshrc` for zsh (falling back to an
|
||||||
|
existing `~/.zshenv` if that's what you already use), `~/.bash_profile` on
|
||||||
|
macOS / `~/.bashrc` elsewhere for bash, `fish_add_path` for fish (no file
|
||||||
|
to edit), or `~/.profile` otherwise.
|
||||||
|
|
||||||
|
This pattern (shim binary + per-shell/per-OS config detection + marker-based
|
||||||
|
idempotent edit) is adapted from
|
||||||
|
[`install-shortcut.sh`](https://cloud.inputobjects.eu/forge/jrozanski/opencode-sandbox/src/branch/main/scripts/install-shortcut.sh)
|
||||||
|
in a related project.
|
||||||
|
|
||||||
|
## Running OpenCode v2 (not the built-in v1 template)
|
||||||
|
|
||||||
|
`sbx run opencode` on its own uses Docker's stock image, `docker/sandbox-templates:opencode`,
|
||||||
|
which bakes in **OpenCode v1** — the `opencode-ai` npm package, pinned at `1.18.32` as of
|
||||||
|
this writing (verified with
|
||||||
|
`docker run --rm --entrypoint sh docker/sandbox-templates:opencode -c 'opencode --version'`).
|
||||||
|
|
||||||
|
OpenCode v2 is real and actively published, but as a **different npm package**:
|
||||||
|
`@opencode/cli` (currently `2.0.26` — verify with `npm view @opencode/cli version`). The
|
||||||
|
old `opencode-ai` line never became v2; it was superseded by this new package, which is
|
||||||
|
why checking `opencode-ai`'s version history makes v2 look like it doesn't exist. Both
|
||||||
|
packages install a binary named `opencode` into the same global npm prefix, so getting v2
|
||||||
|
means removing the v1 package first, then installing the v2 one — confirmed by actually
|
||||||
|
building this and running `opencode --version` inside the result, which reports
|
||||||
|
`opencode v2.0.26`.
|
||||||
|
|
||||||
|
[`./Dockerfile`](./Dockerfile) does the swap at the image level:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM docker/sandbox-templates:opencode
|
||||||
|
ARG OPENCODE_VERSION=2.0.26
|
||||||
|
RUN npm uninstall -g opencode-ai \
|
||||||
|
&& npm install -g @opencode/cli@${OPENCODE_VERSION}
|
||||||
|
```
|
||||||
|
|
||||||
|
`sbx` runs each sandbox in its own isolated microVM with its own Docker
|
||||||
|
daemon/image cache, so a plain `docker build` on your host is invisible to it
|
||||||
|
— `make build` closes that gap with `docker image save` + `sbx template load`,
|
||||||
|
which registers the image as a local template `sbx run -t localhost/...` (or
|
||||||
|
`sbxenv.yaml`'s `sandboxOptions.template`) can find.
|
||||||
|
|
||||||
|
To bump to a newer v2 release:
|
||||||
|
|
||||||
|
```console
|
||||||
|
make build OPENCODE_VERSION=2.0.27 # or any other published @opencode/cli version
|
||||||
|
```
|
||||||
|
|
||||||
|
or edit the `OPENCODE_VERSION` default in the [`Makefile`](./Makefile). Avoid pinning to
|
||||||
|
`latest` long-term — once the image layer is cached, Docker won't re-resolve `latest` on
|
||||||
|
later builds, so it'll silently stop tracking new releases; use `--no-cache` or bump to
|
||||||
|
an explicit version instead.
|
||||||
|
|
||||||
|
**Note:** [`sbxenv.yaml`](./sbxenv.yaml)'s `sandboxOptions.template` is currently set to
|
||||||
|
the bare tag `opencode-v2:local`, while the `opencode-sandbox` shim above (and `sbx
|
||||||
|
template load`'s own output) use the `localhost/opencode-v2:local` form. If running
|
||||||
|
`sbx run opencode` from inside this directory (reading `sbxenv.yaml`) doesn't find the
|
||||||
|
template, try adding the `localhost/` prefix there too.
|
||||||
|
|
||||||
|
## Makefile targets
|
||||||
|
|
||||||
|
```console
|
||||||
|
make image build the opencode-v2:local image (OPENCODE_VERSION=2.0.26 by default)
|
||||||
|
make build image + register it with sbx as a local template
|
||||||
|
make install install the opencode-sandbox shim to ~/.local/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
Everything else — `sbx login`, `sbx secret set`, `sbx policy`, `sbx run`,
|
||||||
|
`sbx stop`/`rm`/`ls`, `sbx ports` — is plain `sbx`; there's no Makefile wrapper
|
||||||
|
for those.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- `make build` is meant to run on your **host** machine — `sbx` has no nested
|
||||||
|
sandbox-in-sandbox mode.
|
||||||
|
- The published [CLI reference](https://docs.docker.com/reference/cli/sbx/) page is
|
||||||
|
JS-rendered and didn't fully resolve during research for this README; if a command
|
||||||
|
here doesn't match your installed `sbx` version, check `sbx <command> --help`.
|
||||||
88
install.sh
Executable file
88
install.sh
Executable file
|
|
@ -0,0 +1,88 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Installs a global `opencode-sandbox` shim at ~/.local/bin so that command,
|
||||||
|
# run from any directory, launches OpenCode v2 from the local sbx template
|
||||||
|
# registered by `make build` — with the directory you're standing in as the
|
||||||
|
# sandbox's workspace.
|
||||||
|
#
|
||||||
|
# POSIX sh only (no bashisms) -- this may run under any login shell's `sh`,
|
||||||
|
# not necessarily the user's interactive shell.
|
||||||
|
#
|
||||||
|
# Named opencode-sandbox, not opencode, so it doesn't shadow a real,
|
||||||
|
# locally-installed opencode CLI on PATH.
|
||||||
|
#
|
||||||
|
# Pattern (shell/OS detection, marker-based idempotent PATH edit, resolving
|
||||||
|
# the login shell via $SHELL rather than the script's own running shell)
|
||||||
|
# adapted from https://cloud.inputobjects.eu/forge/jrozanski/opencode-sandbox/src/branch/main/scripts/install-shortcut.sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
BIN_DIR="$HOME/.local/bin"
|
||||||
|
SHIM="$BIN_DIR/opencode-sandbox"
|
||||||
|
|
||||||
|
mkdir -p "$BIN_DIR"
|
||||||
|
|
||||||
|
cat > "$SHIM" <<'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
exec sbx run -t localhost/opencode-v2:local "$@" opencode
|
||||||
|
EOF
|
||||||
|
chmod +x "$SHIM"
|
||||||
|
echo "==> Installed shim: $SHIM"
|
||||||
|
|
||||||
|
# Already on PATH? Nothing left to do.
|
||||||
|
case ":$PATH:" in
|
||||||
|
*":$BIN_DIR:"*)
|
||||||
|
echo "==> $BIN_DIR is already on PATH."
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Resolve the user's *login* shell via $SHELL, not the shell running this
|
||||||
|
# script, so the right config file gets edited regardless of how this
|
||||||
|
# script itself was invoked.
|
||||||
|
shell_name=$(basename "${SHELL:-}")
|
||||||
|
|
||||||
|
if [ "$shell_name" = fish ] && command -v fish >/dev/null 2>&1; then
|
||||||
|
# fish_add_path persists via a universal variable -- no config file to edit.
|
||||||
|
fish -c "fish_add_path $BIN_DIR"
|
||||||
|
echo "==> Added $BIN_DIR to PATH via fish_add_path."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$shell_name" in
|
||||||
|
zsh)
|
||||||
|
# Prefer .zshrc (the standard interactive-shell file); if it doesn't
|
||||||
|
# exist but .zshenv does, respect that existing setup instead of
|
||||||
|
# scattering PATH across two files. Only create .zshrc if neither exists.
|
||||||
|
if [ -f "$HOME/.zshrc" ]; then
|
||||||
|
rc="$HOME/.zshrc"
|
||||||
|
elif [ -f "$HOME/.zshenv" ]; then
|
||||||
|
rc="$HOME/.zshenv"
|
||||||
|
else
|
||||||
|
rc="$HOME/.zshrc"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
bash)
|
||||||
|
# macOS Terminal.app runs bash as a login shell, sourcing
|
||||||
|
# ~/.bash_profile (not ~/.bashrc) unless the user's profile chains to it.
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Darwin) rc="$HOME/.bash_profile" ;;
|
||||||
|
*) rc="$HOME/.bashrc" ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*) rc="$HOME/.profile" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
marker_begin="# >>> opencode-sandbox >>>"
|
||||||
|
marker_end="# <<< opencode-sandbox <<<"
|
||||||
|
|
||||||
|
if [ -f "$rc" ] && grep -qF "$marker_begin" "$rc"; then
|
||||||
|
echo "==> $rc already configures PATH for opencode-sandbox; skipping."
|
||||||
|
else
|
||||||
|
touch "$rc"
|
||||||
|
{
|
||||||
|
printf '\n%s\n' "$marker_begin"
|
||||||
|
printf 'export PATH="%s:$PATH"\n' "$BIN_DIR"
|
||||||
|
printf '%s\n' "$marker_end"
|
||||||
|
} >> "$rc"
|
||||||
|
echo "==> Added $BIN_DIR to PATH in $rc."
|
||||||
|
echo "==> Restart your shell (or run: . $rc) to pick it up."
|
||||||
|
fi
|
||||||
45
sbxenv.yaml
Normal file
45
sbxenv.yaml
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
schemaVersion: "1"
|
||||||
|
name: opencodev2
|
||||||
|
agent: opencode
|
||||||
|
|
||||||
|
# Mount this project directory into the sandbox as the primary workspace.
|
||||||
|
workspace: .
|
||||||
|
|
||||||
|
env: {}
|
||||||
|
|
||||||
|
# Use the image built by `make image` instead of Docker's stock opencode
|
||||||
|
# template, so the sandbox runs the OpenCode CLI version pinned in ./Dockerfile
|
||||||
|
# rather than whatever version Docker last baked into docker/sandbox-templates:opencode.
|
||||||
|
sandboxOptions:
|
||||||
|
template: opencode-v2:local
|
||||||
|
# memory: 8g
|
||||||
|
# cpus: 4
|
||||||
|
|
||||||
|
# Provider credentials are stored on the host (outside this file) with the
|
||||||
|
# sbx CLI, not here. Run one of these once before `make run`:
|
||||||
|
# sbx secret set anthropic
|
||||||
|
# sbx secret set openai
|
||||||
|
# sbx secret set google
|
||||||
|
# sbx secret set openrouter
|
||||||
|
# sbx secret set github --command 'gh auth token' # for PRs/repo access
|
||||||
|
#
|
||||||
|
# OpenCode Zen or another custom API key:
|
||||||
|
# sbx secret set-custom --host opencode.ai --env OPENCODE_API_KEY --value "$OPENCODE_API_KEY"
|
||||||
|
# sbx policy allow network opencode.ai:443
|
||||||
|
|
||||||
|
# Uncomment to expose a port the agent starts inside the sandbox (e.g. a dev
|
||||||
|
# server) on the host:
|
||||||
|
# ports:
|
||||||
|
# - sandbox: 3000
|
||||||
|
# host: 3000
|
||||||
|
# protocol: tcp4
|
||||||
|
|
||||||
|
# lifecycle.* commands (initialize/postCreate/preRemove) run on the HOST, not
|
||||||
|
# inside the sandbox, so they're for preparing the workspace on disk (cloning
|
||||||
|
# repos, host-side `npm install` into a mounted dir) — not for changing tools
|
||||||
|
# installed inside the sandbox. Use ./Dockerfile + sandboxOptions.template for
|
||||||
|
# that instead.
|
||||||
|
# lifecycle:
|
||||||
|
# postCreate:
|
||||||
|
# - command: npm install
|
||||||
|
# workdir: .
|
||||||
Loading…
Add table
Add a link
Reference in a new issue