@khalilgharbaoui/opencode-claude-code-plugin 0.1.0 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +199 -177
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,24 +1,48 @@
|
|
|
1
1
|
# @khalilgharbaoui/opencode-claude-code-plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An [opencode](https://opencode.ai) plugin that wraps the **Claude Code CLI** (`claude`) and routes model traffic through it instead of the Anthropic HTTP API. You get to use opencode's UI, agents, MCP, and permission system while authenticating and billing through whichever method `claude` is logged into (Pro/Max plan, Bedrock, Vertex, or API key).
|
|
4
4
|
|
|
5
|
-
> Maintained fork of [`unixfox/opencode-claude-code-plugin`](https://github.com/unixfox/opencode-claude-code-plugin)
|
|
5
|
+
> Maintained fork of [`unixfox/opencode-claude-code-plugin`](https://github.com/unixfox/opencode-claude-code-plugin). Published as `@khalilgharbaoui/opencode-claude-code-plugin` on npm.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## TL;DR
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# 1. Make sure `claude` is installed and logged in
|
|
13
|
+
claude --version
|
|
14
|
+
|
|
15
|
+
# 2. Add this to your opencode.json
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"plugin": ["@khalilgharbaoui/opencode-claude-code-plugin"]
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
That's it. Restart opencode, pick a `claude-code` model, done.
|
|
25
|
+
|
|
26
|
+
The plugin self-registers the `claude-code` provider, all current Claude Code models (Haiku 4.5, Sonnet 4.5/4.6, Opus 4.5/4.6/4.7) with reasoning variants (`low` / `medium` / `high` / `xhigh` / `max`), and sensible defaults for tool proxying. You don't need to write a `provider` block at all unless you want to override something.
|
|
27
|
+
|
|
28
|
+
---
|
|
8
29
|
|
|
9
30
|
## Prerequisites
|
|
10
31
|
|
|
11
|
-
- [
|
|
12
|
-
- [
|
|
32
|
+
- [opencode](https://opencode.ai) installed
|
|
33
|
+
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated (`claude` on your `$PATH`)
|
|
34
|
+
- Node 18+ / Bun
|
|
13
35
|
|
|
14
|
-
##
|
|
36
|
+
## Install
|
|
15
37
|
|
|
16
|
-
### From npm
|
|
38
|
+
### From npm (recommended)
|
|
17
39
|
|
|
18
40
|
```bash
|
|
19
41
|
npm install @khalilgharbaoui/opencode-claude-code-plugin
|
|
20
42
|
```
|
|
21
43
|
|
|
44
|
+
Then add it to `opencode.json` as shown in the TL;DR.
|
|
45
|
+
|
|
22
46
|
### Local development
|
|
23
47
|
|
|
24
48
|
```bash
|
|
@@ -28,116 +52,108 @@ bun install
|
|
|
28
52
|
bun run build
|
|
29
53
|
```
|
|
30
54
|
|
|
31
|
-
|
|
55
|
+
In your `opencode.json`, point at the local build with a `file://` URL:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"plugin": ["file:///absolute/path/to/opencode-claude-code-plugin"]
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Models
|
|
66
|
+
|
|
67
|
+
The plugin auto-registers the following. They appear in the model picker without any extra config.
|
|
68
|
+
|
|
69
|
+
| ID | Display name | Context | Output | Reasoning variants |
|
|
70
|
+
|---|---|---|---|---|
|
|
71
|
+
| `claude-haiku-4-5` | Claude Code Haiku 4.5 | 200k | 8,192 | – |
|
|
72
|
+
| `claude-sonnet-4-5` | Claude Code Sonnet 4.5 | 1M | 16,384 | low/medium/high/xhigh/max |
|
|
73
|
+
| `claude-sonnet-4-6` | Claude Code Sonnet 4.6 | 1M | 16,384 | low/medium/high/xhigh/max |
|
|
74
|
+
| `claude-opus-4-5` | Claude Code Opus 4.5 | 1M | 16,384 | low/medium/high/xhigh/max |
|
|
75
|
+
| `claude-opus-4-6` | Claude Code Opus 4.6 | 1M | 16,384 | low/medium/high/xhigh/max |
|
|
76
|
+
| `claude-opus-4-7` | Claude Code Opus 4.7 | 1M | 16,384 | low/medium/high/xhigh/max |
|
|
77
|
+
|
|
78
|
+
Capabilities for every model: text + image input, text output, tool use, attachments. No temperature control, no PDF/audio/video, no interleaved streaming.
|
|
79
|
+
|
|
80
|
+
The model ID is passed straight through to `claude --model`, so anything Claude Code accepts works.
|
|
81
|
+
|
|
82
|
+
### Picking a variant
|
|
83
|
+
|
|
84
|
+
Variants set the underlying reasoning effort. They're regular opencode model variants — pick them in the model selector. If you'd previously declared variants in your project's `opencode.json`, they're merged on top of the defaults so nothing gets lost.
|
|
85
|
+
|
|
86
|
+
---
|
|
32
87
|
|
|
33
88
|
## Configuration
|
|
34
89
|
|
|
35
|
-
|
|
90
|
+
The minimum config is just the `plugin` entry above. Everything below is optional override that goes in a `provider.claude-code` block.
|
|
91
|
+
|
|
92
|
+
### Options reference
|
|
36
93
|
|
|
37
94
|
```json
|
|
38
95
|
{
|
|
96
|
+
"plugin": ["@khalilgharbaoui/opencode-claude-code-plugin"],
|
|
39
97
|
"provider": {
|
|
40
98
|
"claude-code": {
|
|
41
|
-
"npm": "@khalilgharbaoui/opencode-claude-code-plugin",
|
|
42
|
-
"models": {
|
|
43
|
-
"haiku": {
|
|
44
|
-
"name": "Claude Code Haiku",
|
|
45
|
-
"attachment": false,
|
|
46
|
-
"limit": { "context": 200000, "output": 8192 },
|
|
47
|
-
"capabilities": { "reasoning": false, "toolcall": true }
|
|
48
|
-
},
|
|
49
|
-
"sonnet": {
|
|
50
|
-
"name": "Claude Code Sonnet",
|
|
51
|
-
"attachment": false,
|
|
52
|
-
"limit": { "context": 1000000, "output": 16384 },
|
|
53
|
-
"capabilities": { "reasoning": true, "toolcall": true }
|
|
54
|
-
},
|
|
55
|
-
"opus": {
|
|
56
|
-
"name": "Claude Code Opus",
|
|
57
|
-
"attachment": false,
|
|
58
|
-
"limit": { "context": 1000000, "output": 16384 },
|
|
59
|
-
"capabilities": { "reasoning": true, "toolcall": true }
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
99
|
"options": {
|
|
63
100
|
"cliPath": "claude",
|
|
64
|
-
"proxyTools": ["Bash", "Edit", "Write", "WebFetch"]
|
|
101
|
+
"proxyTools": ["Bash", "Edit", "Write", "WebFetch"],
|
|
102
|
+
"skipPermissions": true,
|
|
103
|
+
"permissionMode": "default",
|
|
104
|
+
"bridgeOpencodeMcp": true,
|
|
105
|
+
"strictMcpConfig": false
|
|
65
106
|
}
|
|
66
107
|
}
|
|
67
108
|
}
|
|
68
109
|
}
|
|
69
110
|
```
|
|
70
111
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
112
|
+
| Option | Type | Default | Description |
|
|
113
|
+
|---|---|---|---|
|
|
114
|
+
| `cliPath` | string | `process.env.CLAUDE_CLI_PATH ?? "claude"` | Path to the `claude` binary. |
|
|
115
|
+
| `cwd` | string | `process.cwd()` | Working directory for the spawned CLI. Resolved **lazily per request**, so opencode's project switching works. |
|
|
116
|
+
| `skipPermissions` | boolean | `true` | Pass `--dangerously-skip-permissions` to `claude`. Ignored when `proxyTools` is set — the proxy handles permissions through opencode instead. |
|
|
117
|
+
| `permissionMode` | `acceptEdits` \| `auto` \| `bypassPermissions` \| `default` \| `dontAsk` \| `plan` | – | Forwarded to `claude --permission-mode`. |
|
|
118
|
+
| `proxyTools` | string[] | `["Bash", "Edit", "Write", "WebFetch"]` | Claude built-in tools to route through opencode's executor + permission UI. See [Selective tool proxy](#selective-tool-proxy). |
|
|
119
|
+
| `controlRequestBehavior` | `allow` \| `deny` | `allow` | Default response when `skipPermissions: false` and Claude sends a `can_use_tool` control request. |
|
|
120
|
+
| `controlRequestToolBehaviors` | `Record<string, "allow" \| "deny">` | – | Per-tool override for `can_use_tool`. Example: `{ "Bash": "deny", "Read": "allow" }`. |
|
|
121
|
+
| `controlRequestDenyMessage` | string | built-in message | Message returned to Claude on a deny. |
|
|
122
|
+
| `bridgeOpencodeMcp` | boolean | `true` | Auto-translate your opencode `mcp` block into Claude's `--mcp-config`. See [MCP bridge](#mcp-bridge). |
|
|
123
|
+
| `mcpConfig` | string \| string[] | – | Extra `--mcp-config` paths/JSON passed alongside the bridged config. |
|
|
124
|
+
| `strictMcpConfig` | boolean | `false` | Pass `--strict-mcp-config` so Claude loads **only** the configured servers and ignores `~/.claude/settings.json`. |
|
|
76
125
|
|
|
77
|
-
|
|
78
|
-
- `cwd` (string, default `process.cwd()`): working directory for the spawned CLI.
|
|
79
|
-
- `skipPermissions` (boolean, default `true`): pass `--dangerously-skip-permissions` to the CLI. Ignored when `proxyTools` is set (the proxy handles permissions instead).
|
|
80
|
-
- `permissionMode` (string, optional): pass Claude CLI `--permission-mode` (`acceptEdits`, `auto`, `bypassPermissions`, `default`, `dontAsk`, `plan`).
|
|
81
|
-
- `proxyTools` (string[], optional): list of Claude built-in tools to route through opencode instead of letting the CLI execute them directly. See [Selective Tool Proxy](#selective-tool-proxy) below.
|
|
82
|
-
- `controlRequestBehavior` (`allow` | `deny`, default `allow`): default behavior for Claude stream-json `control_request` messages with subtype `can_use_tool` when `skipPermissions` is `false`.
|
|
83
|
-
- `controlRequestToolBehaviors` (`Record<string, "allow" | "deny">`, optional): per-tool overrides for `can_use_tool` requests (eg. `{ "Bash": "deny", "Read": "allow" }`).
|
|
84
|
-
- `controlRequestDenyMessage` (string, optional): custom deny message returned to Claude for denied `can_use_tool` requests.
|
|
85
|
-
- `bridgeOpencodeMcp` (boolean, default `true`): auto-translate the `mcp` block from your opencode config (`opencode.jsonc` / `opencode.json`, discovered via `cwd`, `OPENCODE_CONFIG`, `OPENCODE_CONFIG_DIR`, and `$XDG_CONFIG_HOME/opencode`) into Claude CLI's `--mcp-config` format. Set to `false` to disable the bridge and manage MCP servers only via `~/.claude/settings.json`.
|
|
86
|
-
- `mcpConfig` (string | string[]): extra `--mcp-config` file path(s) or JSON string(s) passed through alongside the bridged config.
|
|
87
|
-
- `strictMcpConfig` (boolean, default `false`): pass `--strict-mcp-config` so the CLI loads **only** the servers from `--mcp-config` and ignores `~/.claude/settings.json` / user MCP registrations.
|
|
126
|
+
### Overriding model metadata
|
|
88
127
|
|
|
89
|
-
|
|
128
|
+
To rename a model, change a limit, or add a custom one:
|
|
90
129
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
| v
|
|
106
|
-
executed by CLI opencode tool executor
|
|
107
|
-
(bash, edit, write)
|
|
108
|
-
|
|
|
109
|
-
v
|
|
110
|
-
opencode permission UI
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"plugin": ["@khalilgharbaoui/opencode-claude-code-plugin"],
|
|
133
|
+
"provider": {
|
|
134
|
+
"claude-code": {
|
|
135
|
+
"models": {
|
|
136
|
+
"claude-sonnet-4-6": {
|
|
137
|
+
"name": "Sonnet (custom)",
|
|
138
|
+
"limit": { "context": 1000000, "output": 32768 }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
111
144
|
```
|
|
112
145
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
Sessions are keyed by `(cwd, model, opencode-session-id)`. One active Claude CLI process is kept alive per key and reused across conversation turns within that chat. The opencode session ID comes from the `x-session-affinity` header opencode sets on LLM calls to third-party providers (see `packages/opencode/src/session/llm.ts`), so two chats opened simultaneously in the same project against the same model get separate CLI processes instead of racing on one.
|
|
116
|
-
|
|
117
|
-
- **Same chat, multiple turns**: the CLI process stays alive between messages. Claude retains full native context.
|
|
118
|
-
- **New chat**: a first message with no prior history spawns a fresh process under the new session key.
|
|
119
|
-
- **Resumed chat after restart**: in-memory session state is lost; a new CLI process is spawned and the conversation history is summarized and prepended as context.
|
|
120
|
-
- **Abort (Ctrl+C)**: the stream closes but the CLI process stays alive for the next message in that chat.
|
|
121
|
-
- **Eviction**: live CLI processes are capped at 16 with LRU eviction to avoid accumulating one subprocess per chat indefinitely.
|
|
146
|
+
Anything you supply is merged on top of the defaults; you don't need to redeclare every model.
|
|
122
147
|
|
|
123
|
-
|
|
148
|
+
---
|
|
124
149
|
|
|
125
|
-
|
|
150
|
+
## Selective tool proxy
|
|
126
151
|
|
|
127
|
-
|
|
152
|
+
This is the core feature.
|
|
128
153
|
|
|
129
|
-
|
|
154
|
+
By default, when Claude Code's CLI uses `Bash`, `Edit`, `Write`, etc., it executes them itself — bypassing opencode's permission UI, audit trail, and policy rules entirely. With `proxyTools`, you tell the plugin to disable Claude's built-in version of a tool and expose an equivalent through an in-process MCP server. Claude calls the MCP version, which blocks until opencode runs the tool through its own executor.
|
|
130
155
|
|
|
131
|
-
|
|
132
|
-
2. For each tool listed in `proxyTools`, the plugin:
|
|
133
|
-
- Passes `--disallowedTools <ToolName>` to the CLI, disabling Claude's built-in version.
|
|
134
|
-
- Exposes an equivalent tool via the MCP server (e.g. `mcp__opencode_proxy__bash`).
|
|
135
|
-
3. When Claude decides to use a proxied tool, the MCP call blocks.
|
|
136
|
-
4. The plugin emits a client-executed `tool-call` to opencode.
|
|
137
|
-
5. Opencode runs the tool through its own executor (with permission checks, UI prompts, etc.).
|
|
138
|
-
6. The tool result flows back into the blocked MCP call, and Claude continues.
|
|
139
|
-
|
|
140
|
-
**Supported proxy tools**:
|
|
156
|
+
### Default proxied tools
|
|
141
157
|
|
|
142
158
|
| `proxyTools` value | Claude built-in disabled | Proxy MCP tool exposed |
|
|
143
159
|
|---|---|---|
|
|
@@ -146,133 +162,139 @@ The key feature of this plugin is the ability to selectively route Claude's buil
|
|
|
146
162
|
| `"Write"` | `Write` | `mcp__opencode_proxy__write` |
|
|
147
163
|
| `"WebFetch"` | `WebFetch` | `mcp__opencode_proxy__webfetch` |
|
|
148
164
|
|
|
149
|
-
|
|
165
|
+
Only those four values are actually proxied; anything else you put in `proxyTools` is ignored. Note that `MultiEdit` is **not** disabled when you proxy `Edit` — Claude can still use its built-in `MultiEdit` directly, which won't go through opencode's permission UI. If that matters, manage `MultiEdit` separately through your Claude settings.
|
|
150
166
|
|
|
151
|
-
|
|
167
|
+
To turn off proxying entirely:
|
|
152
168
|
|
|
153
169
|
```json
|
|
154
|
-
{
|
|
155
|
-
"provider": {
|
|
156
|
-
"claude-code": {
|
|
157
|
-
"npm": "@khalilgharbaoui/opencode-claude-code-plugin",
|
|
158
|
-
"options": {
|
|
159
|
-
"cliPath": "claude",
|
|
160
|
-
"proxyTools": ["Bash", "Edit", "Write", "WebFetch"]
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
170
|
+
"options": { "proxyTools": [] }
|
|
165
171
|
```
|
|
166
172
|
|
|
167
|
-
|
|
168
|
-
- All LLM reasoning, planning, and tool selection
|
|
169
|
-
- System prompts, conversation state, multi-turn continuation
|
|
170
|
-
- Native execution of non-proxied tools (Read, Glob, Grep, TodoWrite, etc.)
|
|
171
|
-
- Authentication via your Claude CLI subscription
|
|
173
|
+
### What you get with proxying on
|
|
172
174
|
|
|
173
|
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
- Policy enforcement via opencode's permission rules
|
|
175
|
+
- opencode's **permission prompts** for every Bash/Edit/Write/WebFetch call (the default `claude --dangerously-skip-permissions` is NOT applied to proxied tools).
|
|
176
|
+
- opencode's **audit log** captures the calls.
|
|
177
|
+
- Per-tool **policy rules** in opencode apply.
|
|
177
178
|
|
|
178
|
-
###
|
|
179
|
+
### What you give up
|
|
179
180
|
|
|
180
|
-
|
|
181
|
+
- A small per-call latency hop through `127.0.0.1:<random>/mcp`.
|
|
182
|
+
- Some Claude-specific tool features stay on the built-in side (notably `MultiEdit` — see the note above).
|
|
181
183
|
|
|
182
|
-
|
|
184
|
+
---
|
|
183
185
|
|
|
184
|
-
|
|
185
|
-
- **Built-in tools**: `Edit` -> `edit`, `Write` -> `write`, `Bash` -> `bash`, etc. (lowercased)
|
|
186
|
-
- **MCP tools**: `mcp__server__tool` -> `server_tool` (Claude CLI format to opencode format)
|
|
187
|
-
- **Proxy tools**: `mcp__opencode_proxy__bash` -> `bash` (proxy prefix stripped)
|
|
188
|
-
- **Claude CLI internal tools**: `ToolSearch`, `Agent`, `AskFollowupQuestion` are silently skipped
|
|
189
|
-
- **Questions**: `AskUserQuestion` is rendered as text in the stream
|
|
186
|
+
## MCP bridge
|
|
190
187
|
|
|
191
|
-
|
|
188
|
+
If `bridgeOpencodeMcp` is true (the default), the plugin reads your opencode config's `mcp` block, translates it into Claude's MCP schema, writes it to a temp file, and passes that to `claude --mcp-config`. So whatever MCP servers you've already configured in opencode become available to Claude with no extra setup.
|
|
192
189
|
|
|
193
|
-
|
|
190
|
+
### Discovery order (highest to lowest priority)
|
|
194
191
|
|
|
195
|
-
|
|
192
|
+
1. `OPENCODE_CONFIG` env var (file path)
|
|
193
|
+
2. `OPENCODE_CONFIG_DIR` env var
|
|
194
|
+
3. Walk up from the current `cwd` looking for `opencode.jsonc`, `opencode.json`, `config.json`, or a `.opencode/` directory
|
|
195
|
+
4. Global `$XDG_CONFIG_HOME/opencode` or `~/.config/opencode`
|
|
196
196
|
|
|
197
|
-
|
|
197
|
+
Later sources override earlier ones **by server name**, so a project-level MCP server replaces a global one with the same id.
|
|
198
198
|
|
|
199
|
-
|
|
200
|
-
- `controlRequestToolBehaviors` - per-tool allow/deny overrides
|
|
201
|
-
- `controlRequestDenyMessage` - message returned on denied requests
|
|
199
|
+
### Translation
|
|
202
200
|
|
|
203
|
-
|
|
201
|
+
| opencode `type` | Claude `type` |
|
|
202
|
+
|---|---|
|
|
203
|
+
| `local` | `stdio` |
|
|
204
|
+
| `remote` | `http` |
|
|
204
205
|
|
|
205
|
-
|
|
206
|
-
- `text-start` -> `text-delta`* -> `text-end`
|
|
207
|
-
- `reasoning-start` -> `reasoning-delta`* -> `reasoning-end`
|
|
208
|
-
- `tool-input-start` -> `tool-input-delta`* -> `tool-call` -> `tool-result`
|
|
206
|
+
If you want to manage MCP servers only via `~/.claude/settings.json`, set `bridgeOpencodeMcp: false`.
|
|
209
207
|
|
|
210
|
-
|
|
208
|
+
To replace (rather than augment) bridged MCP with your own:
|
|
211
209
|
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
message-builder.ts # AI SDK prompt -> Claude CLI JSON messages
|
|
219
|
-
session-manager.ts # CLI process lifecycle (spawn, reuse, cleanup)
|
|
220
|
-
proxy-mcp.ts # In-process HTTP MCP server for tool proxying
|
|
221
|
-
proxy-broker.ts # Pause/resume broker for proxied tool calls
|
|
222
|
-
mcp-bridge.ts # Opencode MCP config -> Claude CLI translation
|
|
223
|
-
logger.ts # Debug logging
|
|
210
|
+
```json
|
|
211
|
+
"options": {
|
|
212
|
+
"bridgeOpencodeMcp": false,
|
|
213
|
+
"mcpConfig": "/path/to/your/mcp.json",
|
|
214
|
+
"strictMcpConfig": true
|
|
215
|
+
}
|
|
224
216
|
```
|
|
225
217
|
|
|
226
|
-
|
|
218
|
+
---
|
|
227
219
|
|
|
228
|
-
|
|
229
|
-
bun install
|
|
230
|
-
bun run build # Build with tsup
|
|
231
|
-
bun run dev # Build in watch mode
|
|
232
|
-
bun run typecheck # Type check without emitting
|
|
233
|
-
```
|
|
220
|
+
## Sessions
|
|
234
221
|
|
|
235
|
-
|
|
222
|
+
Each chat keeps a long-lived `claude` subprocess so the model retains its native context across turns.
|
|
236
223
|
|
|
237
|
-
|
|
224
|
+
- **Session key**: `(cwd, model, tool-scope, opencode-session-id)`. The opencode session id comes from the `x-session-affinity` header opencode sets on third-party provider calls. Two chats in the same project on the same model run in **separate** CLI processes — they don't race.
|
|
225
|
+
- **Same chat, multiple turns** → process reused, full Claude context retained.
|
|
226
|
+
- **New chat** → fresh process under the new session key.
|
|
227
|
+
- **Resumed chat after restart** → in-memory state is gone; a new process spawns and the conversation history is summarized and prepended.
|
|
228
|
+
- **Abort (Ctrl+C)** → stream closes, process stays alive for the next message in that chat.
|
|
229
|
+
- **Cap**: 16 active processes, LRU eviction.
|
|
238
230
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Plan mode
|
|
234
|
+
|
|
235
|
+
Set `permissionMode: "plan"` to forward `--permission-mode plan` to Claude. The plugin handles `ExitPlanMode` specially — instead of forwarding it as a tool call, it converts it to a confirmation prompt that flows through opencode normally.
|
|
236
|
+
|
|
237
|
+
---
|
|
242
238
|
|
|
243
|
-
|
|
239
|
+
## Quirks worth knowing
|
|
240
|
+
|
|
241
|
+
- **Empty text blocks are dropped.** Claude sometimes opens a `content_block_start` for text but never sends a delta. The plugin no longer emits the empty block (which was triggering Anthropic 400s like `cache_control cannot be set for empty text blocks`).
|
|
242
|
+
- **`AskUserQuestion`** from the CLI is converted into plain text content rather than forwarded as a tool call.
|
|
243
|
+
- **Result fallback timer.** If the CLI finishes a text block but never sends a `result` message, the stream closes gracefully after 5 seconds rather than hanging.
|
|
244
|
+
- **Per-iteration usage.** When the CLI internally retries with tools, the plugin only counts the last iteration's usage so opencode's context accounting stays accurate.
|
|
245
|
+
- **Lazy `cwd`.** The working directory is re-resolved at every request, so opencode's project-aware behavior works without restarting the plugin.
|
|
246
|
+
- **Variants survive merge.** opencode recalculates variant lists after the plugin loads; the plugin re-injects defaults into runtime config so your variants don't disappear.
|
|
247
|
+
|
|
248
|
+
## Debug logging
|
|
244
249
|
|
|
245
250
|
```bash
|
|
246
|
-
|
|
251
|
+
DEBUG=opencode-claude-code opencode
|
|
247
252
|
```
|
|
248
253
|
|
|
249
|
-
|
|
254
|
+
Goes to stderr.
|
|
250
255
|
|
|
251
|
-
##
|
|
256
|
+
## Known limitations
|
|
252
257
|
|
|
253
|
-
|
|
258
|
+
- No streaming of tool inputs as they're being constructed (Anthropic's `input_json_delta`); the plugin emits them once complete.
|
|
259
|
+
- No interleaved thinking — Claude Code CLI doesn't expose reasoning tokens to the SDK.
|
|
260
|
+
- The CLI must be a recent enough version to support `--mcp-config` and `--disallowedTools`. If something breaks after a Claude Code update, that's the first thing to check.
|
|
254
261
|
|
|
255
|
-
|
|
256
|
-
1. Switch to **build mode** using `Tab`
|
|
257
|
-
2. Enter `yes` (or `no` to reject) into the prompt
|
|
262
|
+
---
|
|
258
263
|
|
|
259
|
-
##
|
|
264
|
+
## Development
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
bun install
|
|
268
|
+
bun run typecheck # tsc --noEmit
|
|
269
|
+
bun run build # tsup -> dist/
|
|
270
|
+
```
|
|
260
271
|
|
|
261
|
-
|
|
262
|
-
- **Non-proxied tools bypass opencode permissions**: tools that remain native to Claude CLI (Read, Glob, Grep, etc.) are executed by the CLI directly without opencode permission checks. This is by design for performance, but means those tools are not subject to opencode's permission rules.
|
|
263
|
-
- **Claude upstream bug [#34046](https://github.com/anthropics/claude-code/issues/34046)**: Claude CLI does not reliably emit `can_use_tool` control requests for built-in tools even when `--permission-prompt-tool` is set. The selective proxy approach works around this entirely by disabling the built-in tools and replacing them with MCP equivalents.
|
|
272
|
+
Source layout:
|
|
264
273
|
|
|
265
|
-
|
|
274
|
+
```
|
|
275
|
+
src/
|
|
276
|
+
index.ts # opencode plugin entry, config + provider hooks
|
|
277
|
+
models.ts # default models + variants
|
|
278
|
+
claude-code-language-model.ts # AI-SDK provider that drives `claude`
|
|
279
|
+
proxy-mcp.ts # in-process MCP server for proxied tools
|
|
280
|
+
mcp-bridge.ts # opencode → Claude --mcp-config translator
|
|
281
|
+
session-manager.ts # LRU cache of CLI subprocesses
|
|
282
|
+
logger.ts # DEBUG=opencode-claude-code stderr logger
|
|
283
|
+
types.ts # public option types
|
|
284
|
+
opencode-types.ts # mirrored opencode types
|
|
285
|
+
```
|
|
266
286
|
|
|
267
|
-
|
|
287
|
+
## Publishing (maintainers)
|
|
268
288
|
|
|
269
289
|
```bash
|
|
270
|
-
|
|
271
|
-
git push origin
|
|
290
|
+
npm version patch # or minor/major — bumps package.json + creates the tag
|
|
291
|
+
git push origin master --follow-tags
|
|
272
292
|
```
|
|
273
293
|
|
|
274
|
-
The GitHub Actions workflow
|
|
294
|
+
The GitHub Actions workflow at `.github/workflows/publish.yml` runs `npm publish --access public` on tag push (requires `NPM_TOKEN` secret in the repo settings — use a classic automation token so 2FA isn't required at workflow time).
|
|
275
295
|
|
|
276
296
|
## License
|
|
277
297
|
|
|
278
|
-
MIT
|
|
298
|
+
MIT. See [LICENSE](./LICENSE).
|
|
299
|
+
|
|
300
|
+
Original work © `unixfox`. Fork modifications © Khalil Gharbaoui.
|