@khalilgharbaoui/opencode-claude-code-plugin 0.1.0 → 0.1.5

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 CHANGED
@@ -1,24 +1,48 @@
1
1
  # @khalilgharbaoui/opencode-claude-code-plugin
2
2
 
3
- A standalone [opencode](https://github.com/opencodeco/opencode) provider plugin that uses [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) as a backend. It spawns `claude` as a subprocess with `--output-format stream-json --input-format stream-json`, implements the AI SDK `LanguageModelV2` interface, and streams responses back to opencode.
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), published as `@khalilgharbaoui/opencode-claude-code-plugin` on npm.
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
- This is a **standalone npm package** that opencode loads dynamically via its external provider system -- no modifications to opencode's source code required.
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
- - [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated (`claude` available in your PATH)
12
- - [opencode](https://github.com/opencodeco/opencode) installed
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
- ## Installation
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,156 @@ bun install
28
52
  bun run build
29
53
  ```
30
54
 
31
- Then reference it via `file://` in your `opencode.json`.
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
- Add this to your project's `opencode.json`:
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
+ ### Multiple Claude Code accounts
93
+
94
+ Declare account names once and the plugin expands them into separate opencode providers:
36
95
 
37
96
  ```json
38
97
  {
98
+ "plugin": ["@khalilgharbaoui/opencode-claude-code-plugin"],
39
99
  "provider": {
40
100
  "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
101
  "options": {
63
- "cliPath": "claude",
64
- "proxyTools": ["Bash", "Edit", "Write", "WebFetch"]
102
+ "accounts": ["personal", "work"]
65
103
  }
66
104
  }
67
105
  }
68
106
  }
69
107
  ```
70
108
 
71
- Replace `"@khalilgharbaoui/opencode-claude-code-plugin"` with a `file://` path if you're using a local build.
109
+ `default` is always implicit, so the config above creates:
72
110
 
73
- The model IDs (`haiku`, `sonnet`, `opus`) are passed directly to `claude --model`, which accepts these aliases natively.
111
+ | Provider ID | Display name | Claude config dir |
112
+ |---|---|---|
113
+ | `claude-code-default` | `Claude Code (Default)` | normal `~/.claude` |
114
+ | `claude-code-personal` | `Claude Code (Personal)` | `~/.claude-personal` |
115
+ | `claude-code-work` | `Claude Code (Work)` | `~/.claude-work` |
116
+
117
+ Non-default accounts use `CLAUDE_CONFIG_DIR` through a generated wrapper script, so auth/session state stays isolated per account. Shared capability files and folders are symlinked from `~/.claude` into each account dir when present:
118
+
119
+ ```text
120
+ CLAUDE.md
121
+ settings.json
122
+ skills/
123
+ agents/
124
+ commands/
125
+ plugins/
126
+ ```
74
127
 
75
- ### Options
128
+ Identity/session state is not shared.
76
129
 
77
- - `cliPath` (string, default `"claude"`): path to the Claude Code CLI binary.
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.
130
+ Login each account once:
88
131
 
89
- ## How it works
132
+ ```bash
133
+ CLAUDE_CONFIG_DIR="$HOME/.claude-personal" claude auth login
134
+ CLAUDE_CONFIG_DIR="$HOME/.claude-work" claude auth login
135
+ ```
90
136
 
91
- ### Architecture
137
+ The account model IDs are internally suffixed, for example `claude-sonnet-4-6@work`, so long-lived Claude subprocess sessions do not collide across accounts. The generated wrapper strips the suffix before calling `claude --model`.
92
138
 
93
- ```
94
- opencode --> streamText() --> ClaudeCodeLanguageModel.doStream()
95
- |
96
- v
97
- claude CLI subprocess
98
- (stream-json mode)
99
- |
100
- +-------------+-------------+
101
- | |
102
- native tools proxy MCP server
103
- (Read, Glob, Grep, (127.0.0.1:random)
104
- TodoWrite, etc.) |
105
- | v
106
- executed by CLI opencode tool executor
107
- (bash, edit, write)
108
- |
109
- v
110
- opencode permission UI
111
- ```
139
+ ### Options reference
112
140
 
113
- ### Session management
141
+ ```json
142
+ {
143
+ "plugin": ["@khalilgharbaoui/opencode-claude-code-plugin"],
144
+ "provider": {
145
+ "claude-code": {
146
+ "options": {
147
+ "cliPath": "claude",
148
+ "proxyTools": ["Bash", "Edit", "Write", "WebFetch"],
149
+ "skipPermissions": true,
150
+ "permissionMode": "default",
151
+ "bridgeOpencodeMcp": true,
152
+ "strictMcpConfig": false
153
+ }
154
+ }
155
+ }
156
+ }
157
+ ```
114
158
 
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.
159
+ | Option | Type | Default | Description |
160
+ |---|---|---|---|
161
+ | `cliPath` | string | `process.env.CLAUDE_CLI_PATH ?? "claude"` | Path to the `claude` binary. |
162
+ | `accounts` | string[] | – | Optional account list. `default` is implicit. Expands into `Claude Code (Default)`, `Claude Code (Personal)`, etc. |
163
+ | `cwd` | string | `process.cwd()` | Working directory for the spawned CLI. Resolved **lazily per request**, so opencode's project switching works. |
164
+ | `skipPermissions` | boolean | `true` | Pass `--dangerously-skip-permissions` to `claude`. Ignored when `proxyTools` is set — the proxy handles permissions through opencode instead. |
165
+ | `permissionMode` | `acceptEdits` \| `auto` \| `bypassPermissions` \| `default` \| `dontAsk` \| `plan` | – | Forwarded to `claude --permission-mode`. |
166
+ | `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). |
167
+ | `controlRequestBehavior` | `allow` \| `deny` | `allow` | Default response when `skipPermissions: false` and Claude sends a `can_use_tool` control request. |
168
+ | `controlRequestToolBehaviors` | `Record<string, "allow" \| "deny">` | – | Per-tool override for `can_use_tool`. Example: `{ "Bash": "deny", "Read": "allow" }`. |
169
+ | `controlRequestDenyMessage` | string | built-in message | Message returned to Claude on a deny. |
170
+ | `bridgeOpencodeMcp` | boolean | `true` | Auto-translate your opencode `mcp` block into Claude's `--mcp-config`. See [MCP bridge](#mcp-bridge). |
171
+ | `mcpConfig` | string \| string[] | – | Extra `--mcp-config` paths/JSON passed alongside the bridged config. |
172
+ | `strictMcpConfig` | boolean | `false` | Pass `--strict-mcp-config` so Claude loads **only** the configured servers and ignores `~/.claude/settings.json`. |
173
+
174
+ ### Overriding model metadata
175
+
176
+ To rename a model, change a limit, or add a custom one:
116
177
 
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.
178
+ ```json
179
+ {
180
+ "plugin": ["@khalilgharbaoui/opencode-claude-code-plugin"],
181
+ "provider": {
182
+ "claude-code": {
183
+ "models": {
184
+ "claude-sonnet-4-6": {
185
+ "name": "Sonnet (custom)",
186
+ "limit": { "context": 1000000, "output": 32768 }
187
+ }
188
+ }
189
+ }
190
+ }
191
+ }
192
+ ```
122
193
 
123
- ### Selective Tool Proxy
194
+ Anything you supply is merged on top of the defaults; you don't need to redeclare every model.
124
195
 
125
- The key feature of this plugin is the ability to selectively route Claude's built-in tools through opencode's own tool execution and permission system.
196
+ ---
126
197
 
127
- **Why this exists**: Claude CLI normally executes tools (Bash, Edit, Write, etc.) internally, bypassing opencode's permission UI entirely. By proxying selected tools, you get opencode's native permission prompts, audit trail, and policy rules for dangerous operations while keeping Claude CLI for authentication and model access.
198
+ ## Selective tool proxy
128
199
 
129
- **How it works**:
200
+ This is the core feature.
130
201
 
131
- 1. The plugin starts an in-process HTTP MCP server on `127.0.0.1` (random port).
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.
202
+ 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.
139
203
 
140
- **Supported proxy tools**:
204
+ ### Default proxied tools
141
205
 
142
206
  | `proxyTools` value | Claude built-in disabled | Proxy MCP tool exposed |
143
207
  |---|---|---|
@@ -146,133 +210,139 @@ The key feature of this plugin is the ability to selectively route Claude's buil
146
210
  | `"Write"` | `Write` | `mcp__opencode_proxy__write` |
147
211
  | `"WebFetch"` | `WebFetch` | `mcp__opencode_proxy__webfetch` |
148
212
 
149
- Tools not listed in `proxyTools` remain fully native to Claude CLI (fast, no permission overhead).
213
+ 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
214
 
151
- **Example configuration**:
215
+ To turn off proxying entirely:
152
216
 
153
217
  ```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
- }
218
+ "options": { "proxyTools": [] }
165
219
  ```
166
220
 
167
- **What Claude keeps doing**:
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
221
+ ### What you get with proxying on
172
222
 
173
- **What opencode now handles**:
174
- - Executing the proxied tools (bash commands, file writes, file edits)
175
- - Permission prompts for those tools through opencode's native UI
176
- - Policy enforcement via opencode's permission rules
223
+ - opencode's **permission prompts** for every Bash/Edit/Write/WebFetch call (the default `claude --dangerously-skip-permissions` is NOT applied to proxied tools).
224
+ - opencode's **audit log** captures the calls.
225
+ - Per-tool **policy rules** in opencode apply.
177
226
 
178
- ### Tool handling
227
+ ### What you give up
179
228
 
180
- Claude CLI executes non-proxied tools internally (Read, Glob, Grep, etc.). Tool calls and results are streamed to opencode for UI display with `providerExecuted: true`.
229
+ - A small per-call latency hop through `127.0.0.1:<random>/mcp`.
230
+ - Some Claude-specific tool features stay on the built-in side (notably `MultiEdit` — see the note above).
181
231
 
182
- Proxied tools follow a different path: Claude calls the MCP proxy, the plugin pauses the stream, opencode executes the tool, and the result is fed back to Claude on the next turn.
232
+ ---
183
233
 
184
- Tool name mapping:
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
234
+ ## MCP bridge
190
235
 
191
- ### Permissions
236
+ 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
237
 
193
- When `proxyTools` is configured (recommended), permission handling is straightforward: proxied tools go through opencode's native permission system, and non-proxied tools are handled by Claude CLI directly.
238
+ ### Discovery order (highest to lowest priority)
194
239
 
195
- When `proxyTools` is not set and `skipPermissions` is `false`, the plugin handles Claude stream-json control requests (`type: control_request`, `subtype: can_use_tool`) with auto allow/deny based on config. This prevents stream deadlocks but does not open opencode's permission UI.
240
+ 1. `OPENCODE_CONFIG` env var (file path)
241
+ 2. `OPENCODE_CONFIG_DIR` env var
242
+ 3. Walk up from the current `cwd` looking for `opencode.jsonc`, `opencode.json`, `config.json`, or a `.opencode/` directory
243
+ 4. Global `$XDG_CONFIG_HOME/opencode` or `~/.config/opencode`
196
244
 
197
- Control request behavior is configurable with:
245
+ Later sources override earlier ones **by server name**, so a project-level MCP server replaces a global one with the same id.
198
246
 
199
- - `controlRequestBehavior` - global default allow/deny
200
- - `controlRequestToolBehaviors` - per-tool allow/deny overrides
201
- - `controlRequestDenyMessage` - message returned on denied requests
247
+ ### Translation
202
248
 
203
- ### Stream sequencing
249
+ | opencode `type` | Claude `type` |
250
+ |---|---|
251
+ | `local` | `stdio` |
252
+ | `remote` | `http` |
204
253
 
205
- The plugin ensures proper event ordering for opencode's processor:
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`
254
+ If you want to manage MCP servers only via `~/.claude/settings.json`, set `bridgeOpencodeMcp: false`.
209
255
 
210
- ## Package structure
256
+ To replace (rather than augment) bridged MCP with your own:
211
257
 
212
- ```
213
- src/
214
- index.ts # Factory: createClaudeCode()
215
- claude-code-language-model.ts # LanguageModelV2 impl (doGenerate + doStream)
216
- types.ts # Type definitions
217
- tool-mapping.ts # Tool name/input conversion
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
258
+ ```json
259
+ "options": {
260
+ "bridgeOpencodeMcp": false,
261
+ "mcpConfig": "/path/to/your/mcp.json",
262
+ "strictMcpConfig": true
263
+ }
224
264
  ```
225
265
 
226
- ## Development
266
+ ---
227
267
 
228
- ```bash
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
- ```
268
+ ## Sessions
234
269
 
235
- ### Debug logging
270
+ Each chat keeps a long-lived `claude` subprocess so the model retains its native context across turns.
236
271
 
237
- Set `DEBUG=opencode-claude-code` to enable verbose logging to stderr:
272
+ - **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. In account mode, model IDs are suffixed per account, so account sessions do not collide.
273
+ - **Same chat, multiple turns** → process reused, full Claude context retained.
274
+ - **New chat** → fresh process under the new session key.
275
+ - **Resumed chat after restart** → in-memory state is gone; a new process spawns and the conversation history is summarized and prepended.
276
+ - **Abort (Ctrl+C)** → stream closes, process stays alive for the next message in that chat.
277
+ - **Cap**: 16 active processes, LRU eviction.
238
278
 
239
- ```bash
240
- DEBUG=opencode-claude-code opencode
241
- ```
279
+ ---
242
280
 
243
- ### Running tests
281
+ ## Plan mode
282
+
283
+ 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.
284
+
285
+ ---
286
+
287
+ ## Quirks worth knowing
288
+
289
+ - **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`).
290
+ - **`AskUserQuestion`** from the CLI is converted into plain text content rather than forwarded as a tool call.
291
+ - **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.
292
+ - **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.
293
+ - **Lazy `cwd`.** The working directory is re-resolved at every request, so opencode's project-aware behavior works without restarting the plugin.
294
+ - **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.
295
+
296
+ ## Debug logging
244
297
 
245
298
  ```bash
246
- bun run test.ts
299
+ DEBUG=opencode-claude-code opencode
247
300
  ```
248
301
 
249
- Requires the `claude` CLI to be installed and authenticated.
302
+ Goes to stderr.
250
303
 
251
- ## Plan mode
304
+ ## Known limitations
252
305
 
253
- When Claude finishes planning, the plugin does **not** automatically exit plan mode (since a plugin cannot switch opencode's mode). Instead, the plan is displayed as text with a confirmation prompt.
306
+ - No streaming of tool inputs as they're being constructed (Anthropic's `input_json_delta`); the plugin emits them once complete.
307
+ - No interleaved thinking — Claude Code CLI doesn't expose reasoning tokens to the SDK.
308
+ - 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
309
 
255
- To proceed after reviewing the plan:
256
- 1. Switch to **build mode** using `Tab`
257
- 2. Enter `yes` (or `no` to reject) into the prompt
310
+ ---
258
311
 
259
- ## Known limitations
312
+ ## Development
313
+
314
+ ```bash
315
+ bun install
316
+ bun run typecheck # tsc --noEmit
317
+ bun run build # tsup -> dist/
318
+ ```
260
319
 
261
- - **Proxy tool set is currently limited**: only `Bash`, `Edit`, `Write`, and `WebFetch` are supported as proxy targets. More tools can be added when opencode gains matching built-in executors (e.g. `NotebookEdit`).
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.
320
+ Source layout:
264
321
 
265
- ## Publishing
322
+ ```
323
+ src/
324
+ index.ts # opencode plugin entry, config + provider hooks
325
+ models.ts # default models + variants
326
+ claude-code-language-model.ts # AI-SDK provider that drives `claude`
327
+ proxy-mcp.ts # in-process MCP server for proxied tools
328
+ mcp-bridge.ts # opencode → Claude --mcp-config translator
329
+ session-manager.ts # LRU cache of CLI subprocesses
330
+ logger.ts # DEBUG=opencode-claude-code stderr logger
331
+ types.ts # public option types
332
+ opencode-types.ts # mirrored opencode types
333
+ ```
266
334
 
267
- To publish a new version to npm, bump the version in `package.json` and push a tag:
335
+ ## Publishing (maintainers)
268
336
 
269
337
  ```bash
270
- git tag v0.1.1
271
- git push origin v0.1.1
338
+ npm version patch # or minor/major — bumps package.json + creates the tag
339
+ git push origin master --follow-tags
272
340
  ```
273
341
 
274
- The GitHub Actions workflow will automatically build and publish to npm on any `v*` tag.
342
+ 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
343
 
276
344
  ## License
277
345
 
278
- MIT
346
+ MIT. See [LICENSE](./LICENSE).
347
+
348
+ Original work © `unixfox`. Fork modifications © Khalil Gharbaoui.
package/dist/index.d.ts CHANGED
@@ -83,6 +83,9 @@ interface ClaudeCodeConfig {
83
83
  provider: string;
84
84
  cliPath: string;
85
85
  cwd?: string;
86
+ account?: string;
87
+ configDir?: string;
88
+ providerID?: string;
86
89
  skipPermissions?: boolean;
87
90
  permissionMode?: PermissionMode;
88
91
  mcpConfig?: string | string[];
@@ -97,6 +100,10 @@ interface ClaudeCodeProviderSettings {
97
100
  cliPath?: string;
98
101
  cwd?: string;
99
102
  name?: string;
103
+ providerID?: string;
104
+ account?: string;
105
+ configDir?: string;
106
+ accounts?: string[];
100
107
  skipPermissions?: boolean;
101
108
  permissionMode?: PermissionMode;
102
109
  mcpConfig?: string | string[];