v2 no longer ships opencode on trunk. The skill now: - Fetches origin/providers - Copies opencode source files to their target paths - Appends self-registration imports to both provider barrels - Adds @opencode-ai/sdk@1.4.3 as a pinned agent-runner dep - Adds OPENCODE_VERSION ARG + opencode-ai pnpm global install to Dockerfile - Rebuilds host + container All steps idempotent. Credential/env/Zen docs unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
192 lines
8.3 KiB
Markdown
192 lines
8.3 KiB
Markdown
---
|
|
name: add-opencode
|
|
description: Use OpenCode as an agent provider on NanoClaw v2 (AGENT_PROVIDER=opencode). OpenRouter, OpenAI, Google, DeepSeek, etc. via OpenCode config — not the Anthropic Agent SDK. Per-session and per-group via agent_provider; host passes OPENCODE_* and XDG mount when spawning containers.
|
|
---
|
|
|
|
# OpenCode agent provider (v2)
|
|
|
|
NanoClaw **v2** runs agents in a long-lived **poll loop** inside the container. The backend is selected with **`AGENT_PROVIDER`** (`claude` | `opencode` | `mock`).
|
|
|
|
v2 trunk ships with only the `claude` provider baked in. This skill copies the OpenCode provider files in from the `providers` branch, wires them into the host and container barrels, installs dependencies, and rebuilds the image.
|
|
|
|
## Install
|
|
|
|
### Pre-flight
|
|
|
|
If all of the following are already present, skip to **Configuration**:
|
|
|
|
- `src/providers/opencode.ts`
|
|
- `container/agent-runner/src/providers/opencode.ts`
|
|
- `import './opencode.js';` line in `src/providers/index.ts`
|
|
- `import './opencode.js';` line in `container/agent-runner/src/providers/index.ts`
|
|
- `@opencode-ai/sdk` in `container/agent-runner/package.json`
|
|
- `opencode-ai@${OPENCODE_VERSION}` in the pnpm global-install block in `container/Dockerfile`
|
|
|
|
Missing pieces — continue below. All steps are idempotent; re-running is safe.
|
|
|
|
### 1. Fetch the providers branch
|
|
|
|
```bash
|
|
git fetch origin providers
|
|
```
|
|
|
|
### 2. Copy the OpenCode source files
|
|
|
|
Wholesale copies (owned entirely by this skill — user edits to these files won't survive a re-run, as designed):
|
|
|
|
```bash
|
|
git show origin/providers:src/providers/opencode.ts > src/providers/opencode.ts
|
|
git show origin/providers:container/agent-runner/src/providers/opencode.ts > container/agent-runner/src/providers/opencode.ts
|
|
git show origin/providers:container/agent-runner/src/providers/mcp-to-opencode.ts > container/agent-runner/src/providers/mcp-to-opencode.ts
|
|
git show origin/providers:container/agent-runner/src/providers/mcp-to-opencode.test.ts > container/agent-runner/src/providers/mcp-to-opencode.test.ts
|
|
git show origin/providers:container/agent-runner/src/providers/opencode.factory.test.ts > container/agent-runner/src/providers/opencode.factory.test.ts
|
|
```
|
|
|
|
### 3. Append the self-registration imports
|
|
|
|
Each barrel gets one line appended at the end — skip if the line is already present.
|
|
|
|
`src/providers/index.ts`:
|
|
|
|
```typescript
|
|
import './opencode.js';
|
|
```
|
|
|
|
`container/agent-runner/src/providers/index.ts`:
|
|
|
|
```typescript
|
|
import './opencode.js';
|
|
```
|
|
|
|
### 4. Add the agent-runner dependency
|
|
|
|
Pinned. Bump deliberately, not with `bun update`.
|
|
|
|
```bash
|
|
cd container/agent-runner && bun add @opencode-ai/sdk@1.4.3 && cd -
|
|
```
|
|
|
|
### 5. Add `opencode-ai` to the container Dockerfile
|
|
|
|
Two edits to `container/Dockerfile`, both idempotent (skip if already present):
|
|
|
|
**(a)** In the "Pin CLI versions" ARG block (around line 18), add after `ARG VERCEL_VERSION=latest`:
|
|
|
|
```dockerfile
|
|
ARG OPENCODE_VERSION=latest
|
|
```
|
|
|
|
**(b)** In the `pnpm install -g` block (around line 80), append `"opencode-ai@${OPENCODE_VERSION}"` to the list:
|
|
|
|
```dockerfile
|
|
pnpm install -g \
|
|
"@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}" \
|
|
"agent-browser@${AGENT_BROWSER_VERSION}" \
|
|
"vercel@${VERCEL_VERSION}" \
|
|
"opencode-ai@${OPENCODE_VERSION}"
|
|
```
|
|
|
|
### 6. Build
|
|
|
|
```bash
|
|
pnpm run build # host
|
|
pnpm exec tsc -p container/agent-runner/tsconfig.json --noEmit # container typecheck
|
|
./container/build.sh # agent image
|
|
```
|
|
|
|
## Configuration
|
|
|
|
### Host `.env` (typical)
|
|
|
|
Set model/provider strings in the form OpenCode expects (often `provider/model-id`). **Put comments on their own lines** — a `#` inside a value is kept verbatim and breaks model IDs.
|
|
|
|
These variables are read **on the host** and passed into the container only when the effective provider is `opencode`. They do not switch the provider by themselves; the DB still needs `agent_provider` set (below).
|
|
|
|
- `OPENCODE_PROVIDER` — OpenCode provider id, e.g. `openrouter`, `anthropic` (if unset, the runner defaults to `anthropic`).
|
|
- `OPENCODE_MODEL` — full model id, e.g. `openrouter/anthropic/claude-sonnet-4`.
|
|
- `OPENCODE_SMALL_MODEL` — optional second model for "small" tasks.
|
|
|
|
Credentials: OneCLI / credential proxy patterns are unchanged. For non-`anthropic` OpenCode providers, the runner registers a placeholder API key and **`ANTHROPIC_BASE_URL`** (the credential proxy) as `baseURL` so the real key never lives in the container.
|
|
|
|
#### Example: OpenRouter
|
|
|
|
```env
|
|
# OpenCode — host passes these into the container when agent_provider is opencode
|
|
OPENCODE_PROVIDER=openrouter
|
|
OPENCODE_MODEL=openrouter/anthropic/claude-sonnet-4
|
|
OPENCODE_SMALL_MODEL=openrouter/anthropic/claude-haiku-4.5
|
|
```
|
|
|
|
#### Example: Anthropic via existing proxy env
|
|
|
|
When `OPENCODE_PROVIDER` is `anthropic`, OpenCode uses normal Anthropic env inside the container (proxy + placeholder key pattern unchanged).
|
|
|
|
```env
|
|
OPENCODE_PROVIDER=anthropic
|
|
OPENCODE_MODEL=anthropic/claude-sonnet-4-20250514
|
|
```
|
|
|
|
#### Example: only a main model
|
|
|
|
```env
|
|
OPENCODE_PROVIDER=openrouter
|
|
OPENCODE_MODEL=openrouter/google/gemini-2.5-pro-preview
|
|
```
|
|
|
|
#### OpenCode Zen (`x-api-key`, not Bearer)
|
|
|
|
Zen's HTTP API (e.g. `POST …/zen/v1/messages`) expects the key in the **`x-api-key`** header. If OneCLI injects **`Authorization: Bearer …`** only, Zen often returns **401 / "Missing API key"** even though the gateway is working.
|
|
|
|
**Naming:** NanoClaw **`AGENT_PROVIDER=opencode`** (v2 DB `agent_provider`) means "run the **OpenCode agent provider**." Separately, **`OPENCODE_PROVIDER=opencode`** in `.env` is OpenCode's **Zen provider id** inside the OpenCode config (see [Zen docs](https://opencode.ai/docs/zen/)).
|
|
|
|
**Host `.env` (typical Zen shape):**
|
|
|
|
```env
|
|
# NanoClaw still resolves AGENT_PROVIDER from agent_groups / sessions; set agent_provider to opencode there.
|
|
# OpenCode SDK: Zen as the upstream provider + models under opencode/…
|
|
OPENCODE_PROVIDER=opencode
|
|
OPENCODE_MODEL=opencode/big-pickle
|
|
OPENCODE_SMALL_MODEL=opencode/big-pickle
|
|
|
|
# Point the credential proxy at Zen's Anthropic-compatible base URL (host + OneCLI must forward this host).
|
|
ANTHROPIC_BASE_URL=https://opencode.ai/zen/v1
|
|
```
|
|
|
|
Use a real Zen model id from the docs; `big-pickle` is one example.
|
|
|
|
**OneCLI:** register the Zen key with **`x-api-key`**, not Bearer:
|
|
|
|
```bash
|
|
onecli secrets create --name "OpenCode Zen" --type generic \
|
|
--value YOUR_ZEN_KEY --host-pattern opencode.ai \
|
|
--header-name "x-api-key" --value-format "{value}"
|
|
```
|
|
|
|
For comparison, OpenRouter uses `Authorization` + `Bearer {value}`. Zen is different by design.
|
|
|
|
### Per group / per session
|
|
|
|
v2 schema: **`agent_groups.agent_provider`** and **`sessions.agent_provider`**. Set to `opencode` for groups or sessions that should use OpenCode. The container receives `AGENT_PROVIDER` from the resolved value (session overrides group).
|
|
|
|
Extra MCP servers still come from **`NANOCLAW_MCP_SERVERS`** / `container_config.mcpServers` on the host; the runner merges them into the same `mcpServers` object passed to **both** Claude and OpenCode providers.
|
|
|
|
## Operational notes
|
|
|
|
- OpenCode keeps a local **`opencode serve`** process and SSE subscription; the provider tears down with **`stream.return`** and **SIGKILL** on the server process on **`abort()`** / shared runtime reset to avoid MCP/zombie hangs.
|
|
- Session continuation is opaque (`ses_*` ids); stale sessions are cleared using **`isSessionInvalid`** on OpenCode-specific errors (timeouts, connection resets, not-found patterns) in addition to the poll-loop's existing recovery.
|
|
- **`NO_PROXY`** for localhost matters when the OpenCode client talks to `127.0.0.1` inside the container while HTTP(S)_PROXY is set (e.g. OneCLI).
|
|
|
|
## Verify
|
|
|
|
```bash
|
|
grep -q "./opencode.js" container/agent-runner/src/providers/index.ts && echo "container barrel: OK"
|
|
grep -q "./opencode.js" src/providers/index.ts && echo "host barrel: OK"
|
|
grep -q "@opencode-ai/sdk" container/agent-runner/package.json && echo "agent-runner dep: OK"
|
|
grep -q "opencode-ai@" container/Dockerfile && echo "Dockerfile install: OK"
|
|
cd container/agent-runner && bun test src/providers/ && cd -
|
|
```
|
|
|
|
## Migrate from v1 wording
|
|
|
|
If documentation or habits still say **`AGENT_RUNNER=opencode`**, update to **`AGENT_PROVIDER=opencode`** and store **`agent_provider`** in v2 tables, not v1 runner columns.
|