@khalilgharbaoui/opencode-claude-code-plugin 0.1.0
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 +278 -0
- package/dist/index.d.ts +303 -0
- package/dist/index.js +2631 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# @khalilgharbaoui/opencode-claude-code-plugin
|
|
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.
|
|
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.
|
|
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.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
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
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
### From npm
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @khalilgharbaoui/opencode-claude-code-plugin
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Local development
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
git clone https://github.com/khalilgharbaoui/opencode-claude-code-plugin
|
|
26
|
+
cd opencode-claude-code-plugin
|
|
27
|
+
bun install
|
|
28
|
+
bun run build
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Then reference it via `file://` in your `opencode.json`.
|
|
32
|
+
|
|
33
|
+
## Configuration
|
|
34
|
+
|
|
35
|
+
Add this to your project's `opencode.json`:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"provider": {
|
|
40
|
+
"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
|
+
"options": {
|
|
63
|
+
"cliPath": "claude",
|
|
64
|
+
"proxyTools": ["Bash", "Edit", "Write", "WebFetch"]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Replace `"@khalilgharbaoui/opencode-claude-code-plugin"` with a `file://` path if you're using a local build.
|
|
72
|
+
|
|
73
|
+
The model IDs (`haiku`, `sonnet`, `opus`) are passed directly to `claude --model`, which accepts these aliases natively.
|
|
74
|
+
|
|
75
|
+
### Options
|
|
76
|
+
|
|
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.
|
|
88
|
+
|
|
89
|
+
## How it works
|
|
90
|
+
|
|
91
|
+
### Architecture
|
|
92
|
+
|
|
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
|
+
```
|
|
112
|
+
|
|
113
|
+
### Session management
|
|
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.
|
|
122
|
+
|
|
123
|
+
### Selective Tool Proxy
|
|
124
|
+
|
|
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.
|
|
126
|
+
|
|
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.
|
|
128
|
+
|
|
129
|
+
**How it works**:
|
|
130
|
+
|
|
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.
|
|
139
|
+
|
|
140
|
+
**Supported proxy tools**:
|
|
141
|
+
|
|
142
|
+
| `proxyTools` value | Claude built-in disabled | Proxy MCP tool exposed |
|
|
143
|
+
|---|---|---|
|
|
144
|
+
| `"Bash"` | `Bash` | `mcp__opencode_proxy__bash` |
|
|
145
|
+
| `"Edit"` | `Edit` | `mcp__opencode_proxy__edit` |
|
|
146
|
+
| `"Write"` | `Write` | `mcp__opencode_proxy__write` |
|
|
147
|
+
| `"WebFetch"` | `WebFetch` | `mcp__opencode_proxy__webfetch` |
|
|
148
|
+
|
|
149
|
+
Tools not listed in `proxyTools` remain fully native to Claude CLI (fast, no permission overhead).
|
|
150
|
+
|
|
151
|
+
**Example configuration**:
|
|
152
|
+
|
|
153
|
+
```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
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
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
|
|
172
|
+
|
|
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
|
|
177
|
+
|
|
178
|
+
### Tool handling
|
|
179
|
+
|
|
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`.
|
|
181
|
+
|
|
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.
|
|
183
|
+
|
|
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
|
|
190
|
+
|
|
191
|
+
### Permissions
|
|
192
|
+
|
|
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.
|
|
194
|
+
|
|
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.
|
|
196
|
+
|
|
197
|
+
Control request behavior is configurable with:
|
|
198
|
+
|
|
199
|
+
- `controlRequestBehavior` - global default allow/deny
|
|
200
|
+
- `controlRequestToolBehaviors` - per-tool allow/deny overrides
|
|
201
|
+
- `controlRequestDenyMessage` - message returned on denied requests
|
|
202
|
+
|
|
203
|
+
### Stream sequencing
|
|
204
|
+
|
|
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`
|
|
209
|
+
|
|
210
|
+
## Package structure
|
|
211
|
+
|
|
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
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Development
|
|
227
|
+
|
|
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
|
+
```
|
|
234
|
+
|
|
235
|
+
### Debug logging
|
|
236
|
+
|
|
237
|
+
Set `DEBUG=opencode-claude-code` to enable verbose logging to stderr:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
DEBUG=opencode-claude-code opencode
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Running tests
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
bun run test.ts
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Requires the `claude` CLI to be installed and authenticated.
|
|
250
|
+
|
|
251
|
+
## Plan mode
|
|
252
|
+
|
|
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.
|
|
254
|
+
|
|
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
|
|
258
|
+
|
|
259
|
+
## Known limitations
|
|
260
|
+
|
|
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.
|
|
264
|
+
|
|
265
|
+
## Publishing
|
|
266
|
+
|
|
267
|
+
To publish a new version to npm, bump the version in `package.json` and push a tag:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
git tag v0.1.1
|
|
271
|
+
git push origin v0.1.1
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
The GitHub Actions workflow will automatically build and publish to npm on any `v*` tag.
|
|
275
|
+
|
|
276
|
+
## License
|
|
277
|
+
|
|
278
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { LanguageModelV3, LanguageModelV3CallOptions } from '@ai-sdk/provider';
|
|
2
|
+
|
|
3
|
+
type ModelID = string;
|
|
4
|
+
type ProviderID = string;
|
|
5
|
+
type OpenCodeModel = {
|
|
6
|
+
id: ModelID;
|
|
7
|
+
providerID: ProviderID;
|
|
8
|
+
api: {
|
|
9
|
+
id: string;
|
|
10
|
+
url: string;
|
|
11
|
+
npm: string;
|
|
12
|
+
};
|
|
13
|
+
name: string;
|
|
14
|
+
family?: string;
|
|
15
|
+
capabilities: {
|
|
16
|
+
temperature: boolean;
|
|
17
|
+
reasoning: boolean;
|
|
18
|
+
attachment: boolean;
|
|
19
|
+
toolcall: boolean;
|
|
20
|
+
input: {
|
|
21
|
+
text: boolean;
|
|
22
|
+
audio: boolean;
|
|
23
|
+
image: boolean;
|
|
24
|
+
video: boolean;
|
|
25
|
+
pdf: boolean;
|
|
26
|
+
};
|
|
27
|
+
output: {
|
|
28
|
+
text: boolean;
|
|
29
|
+
audio: boolean;
|
|
30
|
+
image: boolean;
|
|
31
|
+
video: boolean;
|
|
32
|
+
pdf: boolean;
|
|
33
|
+
};
|
|
34
|
+
interleaved: boolean | {
|
|
35
|
+
field: "reasoning_content" | "reasoning_details";
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
cost: {
|
|
39
|
+
input: number;
|
|
40
|
+
output: number;
|
|
41
|
+
cache: {
|
|
42
|
+
read: number;
|
|
43
|
+
write: number;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
limit: {
|
|
47
|
+
context: number;
|
|
48
|
+
input?: number;
|
|
49
|
+
output: number;
|
|
50
|
+
};
|
|
51
|
+
status: "alpha" | "beta" | "deprecated" | "active";
|
|
52
|
+
options: Record<string, unknown>;
|
|
53
|
+
headers: Record<string, string>;
|
|
54
|
+
release_date: string;
|
|
55
|
+
variants?: Record<string, Record<string, unknown>>;
|
|
56
|
+
};
|
|
57
|
+
type OpenCodeProvider = {
|
|
58
|
+
id: ProviderID;
|
|
59
|
+
name?: string;
|
|
60
|
+
source?: string;
|
|
61
|
+
options?: Record<string, unknown>;
|
|
62
|
+
models: Record<string, OpenCodeModel>;
|
|
63
|
+
};
|
|
64
|
+
type OpenCodeConfig = {
|
|
65
|
+
provider?: Record<string, {
|
|
66
|
+
name?: string;
|
|
67
|
+
npm?: string;
|
|
68
|
+
env?: string[];
|
|
69
|
+
options?: Record<string, unknown>;
|
|
70
|
+
models?: Record<string, unknown>;
|
|
71
|
+
}>;
|
|
72
|
+
};
|
|
73
|
+
type OpenCodeHooks = {
|
|
74
|
+
config?: (input: OpenCodeConfig) => Promise<void>;
|
|
75
|
+
provider?: {
|
|
76
|
+
id: string;
|
|
77
|
+
models?: (provider: OpenCodeProvider) => Promise<Record<string, OpenCodeModel>>;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
type OpenCodePlugin = (input: unknown, options?: Record<string, unknown>) => Promise<OpenCodeHooks>;
|
|
81
|
+
|
|
82
|
+
interface ClaudeCodeConfig {
|
|
83
|
+
provider: string;
|
|
84
|
+
cliPath: string;
|
|
85
|
+
cwd?: string;
|
|
86
|
+
skipPermissions?: boolean;
|
|
87
|
+
permissionMode?: PermissionMode;
|
|
88
|
+
mcpConfig?: string | string[];
|
|
89
|
+
strictMcpConfig?: boolean;
|
|
90
|
+
bridgeOpencodeMcp?: boolean;
|
|
91
|
+
controlRequestBehavior?: ControlRequestBehavior;
|
|
92
|
+
controlRequestToolBehaviors?: Record<string, ControlRequestBehavior>;
|
|
93
|
+
controlRequestDenyMessage?: string;
|
|
94
|
+
proxyTools?: string[];
|
|
95
|
+
}
|
|
96
|
+
interface ClaudeCodeProviderSettings {
|
|
97
|
+
cliPath?: string;
|
|
98
|
+
cwd?: string;
|
|
99
|
+
name?: string;
|
|
100
|
+
skipPermissions?: boolean;
|
|
101
|
+
permissionMode?: PermissionMode;
|
|
102
|
+
mcpConfig?: string | string[];
|
|
103
|
+
strictMcpConfig?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Auto-translate opencode's `mcp` config block (from opencode.json/jsonc
|
|
106
|
+
* discovered via cwd/OPENCODE_CONFIG/XDG) into a Claude CLI `--mcp-config`
|
|
107
|
+
* file and pass it through on spawn. Defaults to `true` so the CLI sees
|
|
108
|
+
* the same MCP servers opencode is configured with.
|
|
109
|
+
*/
|
|
110
|
+
bridgeOpencodeMcp?: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Behavior for Claude CLI `control_request` permission checks
|
|
113
|
+
* (`subtype: can_use_tool`) when `skipPermissions` is false.
|
|
114
|
+
*
|
|
115
|
+
* - allow: approve tool use requests automatically.
|
|
116
|
+
* - deny: reject tool use requests automatically.
|
|
117
|
+
*
|
|
118
|
+
* Defaults to `allow`.
|
|
119
|
+
*/
|
|
120
|
+
controlRequestBehavior?: ControlRequestBehavior;
|
|
121
|
+
/**
|
|
122
|
+
* Optional per-tool overrides for control-request behavior.
|
|
123
|
+
* Keys are Claude tool names (eg. `Bash`, `Read`, `mcp__github__list_prs`) and
|
|
124
|
+
* values are `allow` or `deny`.
|
|
125
|
+
*/
|
|
126
|
+
controlRequestToolBehaviors?: Record<string, ControlRequestBehavior>;
|
|
127
|
+
/**
|
|
128
|
+
* Custom deny message sent back to Claude CLI when behavior resolves to deny.
|
|
129
|
+
*/
|
|
130
|
+
controlRequestDenyMessage?: string;
|
|
131
|
+
/**
|
|
132
|
+
* Proxy these Claude built-in tools through opencode instead of letting the
|
|
133
|
+
* CLI execute them directly. When a tool is listed here, the plugin:
|
|
134
|
+
* - passes `--disallowedTools <ClaudeName>` to the CLI, and
|
|
135
|
+
* - exposes an equivalent tool via an in-process HTTP MCP server named
|
|
136
|
+
* `opencode_proxy`. Claude calls the MCP tool, which blocks on
|
|
137
|
+
* opencode's tool executor (with its native permission UI) and returns
|
|
138
|
+
* the result.
|
|
139
|
+
*
|
|
140
|
+
* Supported: `bash`, `write`, `edit`, `webfetch`. Leave empty or unset to disable proxying.
|
|
141
|
+
*/
|
|
142
|
+
proxyTools?: string[];
|
|
143
|
+
}
|
|
144
|
+
type PermissionMode = "acceptEdits" | "auto" | "bypassPermissions" | "default" | "dontAsk" | "plan";
|
|
145
|
+
type ControlRequestBehavior = "allow" | "deny";
|
|
146
|
+
/**
|
|
147
|
+
* Claude CLI stream-json message types.
|
|
148
|
+
*/
|
|
149
|
+
interface ClaudeStreamMessage {
|
|
150
|
+
type: string;
|
|
151
|
+
subtype?: string;
|
|
152
|
+
request_id?: string;
|
|
153
|
+
request?: {
|
|
154
|
+
subtype?: string;
|
|
155
|
+
tool_name?: string;
|
|
156
|
+
input?: Record<string, unknown>;
|
|
157
|
+
tool_use_id?: string;
|
|
158
|
+
permission_suggestions?: unknown[];
|
|
159
|
+
blocked_path?: string;
|
|
160
|
+
decision_reason?: string;
|
|
161
|
+
title?: string;
|
|
162
|
+
display_name?: string;
|
|
163
|
+
agent_id?: string;
|
|
164
|
+
description?: string;
|
|
165
|
+
};
|
|
166
|
+
message?: {
|
|
167
|
+
role?: string;
|
|
168
|
+
model?: string;
|
|
169
|
+
content?: Array<{
|
|
170
|
+
type: string;
|
|
171
|
+
text?: string;
|
|
172
|
+
name?: string;
|
|
173
|
+
input?: unknown;
|
|
174
|
+
id?: string;
|
|
175
|
+
tool_use_id?: string;
|
|
176
|
+
content?: string | Array<{
|
|
177
|
+
type: string;
|
|
178
|
+
text?: string;
|
|
179
|
+
}>;
|
|
180
|
+
thinking?: string;
|
|
181
|
+
}>;
|
|
182
|
+
};
|
|
183
|
+
tool?: {
|
|
184
|
+
name?: string;
|
|
185
|
+
id?: string;
|
|
186
|
+
input?: unknown;
|
|
187
|
+
};
|
|
188
|
+
tool_result?: {
|
|
189
|
+
tool_use_id?: string;
|
|
190
|
+
content?: string | Array<{
|
|
191
|
+
type: string;
|
|
192
|
+
text?: string;
|
|
193
|
+
}>;
|
|
194
|
+
is_error?: boolean;
|
|
195
|
+
};
|
|
196
|
+
session_id?: string;
|
|
197
|
+
total_cost_usd?: number;
|
|
198
|
+
duration_ms?: number;
|
|
199
|
+
duration_api_ms?: number;
|
|
200
|
+
id?: string;
|
|
201
|
+
result?: string;
|
|
202
|
+
is_error?: boolean;
|
|
203
|
+
num_turns?: number;
|
|
204
|
+
usage?: {
|
|
205
|
+
input_tokens?: number;
|
|
206
|
+
output_tokens?: number;
|
|
207
|
+
cache_read_input_tokens?: number;
|
|
208
|
+
cache_creation_input_tokens?: number;
|
|
209
|
+
iterations?: Array<{
|
|
210
|
+
input_tokens?: number;
|
|
211
|
+
output_tokens?: number;
|
|
212
|
+
cache_read_input_tokens?: number;
|
|
213
|
+
cache_creation_input_tokens?: number;
|
|
214
|
+
}>;
|
|
215
|
+
};
|
|
216
|
+
content_block?: {
|
|
217
|
+
type: string;
|
|
218
|
+
text?: string;
|
|
219
|
+
id?: string;
|
|
220
|
+
name?: string;
|
|
221
|
+
input?: string;
|
|
222
|
+
thinking?: string;
|
|
223
|
+
};
|
|
224
|
+
delta?: {
|
|
225
|
+
type: string;
|
|
226
|
+
text?: string;
|
|
227
|
+
partial_json?: string;
|
|
228
|
+
thinking?: string;
|
|
229
|
+
};
|
|
230
|
+
index?: number;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
|
|
234
|
+
readonly specificationVersion = "v3";
|
|
235
|
+
readonly modelId: string;
|
|
236
|
+
private readonly config;
|
|
237
|
+
constructor(modelId: string, config: ClaudeCodeConfig);
|
|
238
|
+
readonly supportedUrls: Record<string, RegExp[]>;
|
|
239
|
+
get provider(): string;
|
|
240
|
+
private toUsage;
|
|
241
|
+
private toFinishReason;
|
|
242
|
+
private requestScope;
|
|
243
|
+
/**
|
|
244
|
+
* Build the combined `--mcp-config` list: user-configured paths plus the
|
|
245
|
+
* auto-bridged opencode MCP config (when enabled and present) and the
|
|
246
|
+
* proxy MCP scratch file (when proxyTools are enabled).
|
|
247
|
+
*/
|
|
248
|
+
private effectiveMcpConfig;
|
|
249
|
+
/** Resolve ProxyToolDef[] for the configured proxyTools names. */
|
|
250
|
+
private resolvedProxyTools;
|
|
251
|
+
/**
|
|
252
|
+
* Create a proxy MCP server for a single active Claude process/session.
|
|
253
|
+
* The process lifecycle owns the server lifecycle via session-manager.
|
|
254
|
+
*/
|
|
255
|
+
private ensureProxyServer;
|
|
256
|
+
private extractPendingProxyResult;
|
|
257
|
+
/**
|
|
258
|
+
* Opencode sets `x-session-affinity: <sessionID>` on LLM calls for
|
|
259
|
+
* third-party providers (packages/opencode/src/session/llm.ts). Use it so
|
|
260
|
+
* two chats in the same cwd+model get separate CLI processes instead of
|
|
261
|
+
* stomping on each other. Falls back to "default" when absent (older
|
|
262
|
+
* opencode, direct AI-SDK use, title synthesis paths, etc).
|
|
263
|
+
*/
|
|
264
|
+
private sessionAffinity;
|
|
265
|
+
private controlRequestBehaviorForTool;
|
|
266
|
+
private writeControlResponse;
|
|
267
|
+
/**
|
|
268
|
+
* Handle Claude stream-json control requests (`can_use_tool`, etc.) and
|
|
269
|
+
* respond via stdin with a matching `control_response`.
|
|
270
|
+
*/
|
|
271
|
+
private handleControlRequest;
|
|
272
|
+
private getReasoningEffort;
|
|
273
|
+
private latestUserText;
|
|
274
|
+
private synthesizeTitle;
|
|
275
|
+
private doGenerateViaStream;
|
|
276
|
+
doGenerate(options: LanguageModelV3CallOptions): Promise<Awaited<ReturnType<LanguageModelV3["doGenerate"]>>>;
|
|
277
|
+
doStream(options: LanguageModelV3CallOptions): Promise<Awaited<ReturnType<LanguageModelV3["doStream"]>>>;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Read opencode config file(s), translate their `mcp` block to Claude CLI
|
|
282
|
+
* format, write a scratch file, and return its path. Later files override
|
|
283
|
+
* earlier files per server-name (matching opencode's own merge semantics).
|
|
284
|
+
*
|
|
285
|
+
* Returns null when no opencode config with MCP servers is found — callers
|
|
286
|
+
* should treat that as "nothing to bridge" and carry on.
|
|
287
|
+
*/
|
|
288
|
+
declare function bridgeOpencodeMcp(cwd: string): string | null;
|
|
289
|
+
|
|
290
|
+
declare const defaultModels: Record<string, OpenCodeModel>;
|
|
291
|
+
|
|
292
|
+
interface ClaudeCodeProvider {
|
|
293
|
+
specificationVersion: "v3";
|
|
294
|
+
(modelId: string): LanguageModelV3;
|
|
295
|
+
languageModel(modelId: string): LanguageModelV3;
|
|
296
|
+
}
|
|
297
|
+
declare function createClaudeCode(settings?: ClaudeCodeProviderSettings): ClaudeCodeProvider;
|
|
298
|
+
declare const _default: {
|
|
299
|
+
id: string;
|
|
300
|
+
server: OpenCodePlugin;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
export { type ClaudeCodeConfig, ClaudeCodeLanguageModel, type ClaudeCodeProvider, type ClaudeCodeProviderSettings, type ClaudeStreamMessage, type OpenCodeHooks, type OpenCodeModel, type OpenCodePlugin, bridgeOpencodeMcp, createClaudeCode, _default as default, defaultModels };
|