@khalilgharbaoui/opencode-claude-code-plugin 0.1.6 → 0.2.1

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
@@ -170,6 +170,7 @@ The account model IDs are internally suffixed, for example `claude-sonnet-4-6@wo
170
170
  | `bridgeOpencodeMcp` | boolean | `true` | Auto-translate your opencode `mcp` block into Claude's `--mcp-config`. See [MCP bridge](#mcp-bridge). |
171
171
  | `mcpConfig` | string \| string[] | – | Extra `--mcp-config` paths/JSON passed alongside the bridged config. |
172
172
  | `strictMcpConfig` | boolean | `false` | Pass `--strict-mcp-config` so Claude loads **only** the configured servers and ignores `~/.claude/settings.json`. |
173
+ | `webSearch` | `"claude"` \| `"disabled"` \| `<tool>` | `"claude"` | Routing for Claude's built-in `WebSearch`. See [WebSearch routing](#websearch-routing). |
173
174
 
174
175
  ### Overriding model metadata
175
176
 
@@ -227,6 +228,27 @@ To turn off proxying entirely:
227
228
  ### What you give up
228
229
 
229
230
  - A small per-call latency hop through `127.0.0.1:<random>/mcp`.
231
+
232
+ ---
233
+
234
+ ## WebSearch routing
235
+
236
+ Claude Code ships a built-in `WebSearch` tool. The `webSearch` option controls who actually executes those calls:
237
+
238
+ | `webSearch` value | Behavior | When to use |
239
+ |---|---|---|
240
+ | `"claude"` (default) | Claude CLI runs WebSearch internally via Anthropic. Zero setup, no extra cost, no API key. | Most users. |
241
+ | `"<opencode-tool-name>"` (e.g. `"websearch_web_search_exa"`) | Forward to that opencode-side tool with `executed:false`. Requires the corresponding MCP server to be configured in opencode (e.g. [exa-mcp-server](https://github.com/exa-labs/exa-mcp-server)). | You want a specific search backend (Exa, Tavily, Brave) and have the MCP wired up in opencode. |
242
+ | `"disabled"` | `WebSearch` is added to `--disallowedTools` so the model can't call it. | Compliance/security scenarios where outbound search isn't allowed. |
243
+
244
+ ```json
245
+ "options": { "webSearch": "websearch_web_search_exa" }
246
+ ```
247
+
248
+ **Trade-offs**
249
+
250
+ - Claude-side execution: free with your Claude usage, no API key, but no opencode visibility into queries/results, no caching/rate-limit hooks.
251
+ - opencode-side execution: choose any backend, queries flow through opencode's audit/policy/cache, but costs money (search APIs are paid) and adds a network hop.
230
252
  - Some Claude-specific tool features stay on the built-in side (notably `MultiEdit` — see the note above).
231
253
 
232
254
  ---
package/dist/index.d.ts CHANGED
@@ -70,12 +70,29 @@ type OpenCodeConfig = {
70
70
  models?: Record<string, unknown>;
71
71
  }>;
72
72
  };
73
+ /**
74
+ * Bus events surface to plugins. Shape mirrors what opencode core publishes
75
+ * via `GlobalBus.emit("event", { directory, payload: { type, properties } })`
76
+ * but kept loose since opencode adds events over time and this plugin only
77
+ * reacts to a small subset (currently just `global.disposed`).
78
+ */
79
+ type OpenCodeEvent = {
80
+ type?: string;
81
+ payload?: {
82
+ type?: string;
83
+ properties?: Record<string, unknown>;
84
+ };
85
+ [key: string]: unknown;
86
+ };
73
87
  type OpenCodeHooks = {
74
88
  config?: (input: OpenCodeConfig) => Promise<void>;
75
89
  provider?: {
76
90
  id: string;
77
91
  models?: (provider: OpenCodeProvider) => Promise<Record<string, OpenCodeModel>>;
78
92
  };
93
+ event?: (input: {
94
+ event: OpenCodeEvent;
95
+ }) => Promise<void>;
79
96
  };
80
97
  type OpenCodePlugin = (input: unknown, options?: Record<string, unknown>) => Promise<OpenCodeHooks>;
81
98
 
@@ -95,7 +112,10 @@ interface ClaudeCodeConfig {
95
112
  controlRequestToolBehaviors?: Record<string, ControlRequestBehavior>;
96
113
  controlRequestDenyMessage?: string;
97
114
  proxyTools?: string[];
115
+ webSearch?: WebSearchRouting;
116
+ hotReloadMcp?: boolean;
98
117
  }
118
+ type WebSearchRouting = "claude" | "disabled" | (string & {});
99
119
  interface ClaudeCodeProviderSettings {
100
120
  cliPath?: string;
101
121
  cwd?: string;
@@ -147,6 +167,30 @@ interface ClaudeCodeProviderSettings {
147
167
  * Supported: `bash`, `write`, `edit`, `webfetch`. Leave empty or unset to disable proxying.
148
168
  */
149
169
  proxyTools?: string[];
170
+ /**
171
+ * Routing for Claude's built-in `WebSearch` tool.
172
+ *
173
+ * - `"claude"` (default): Claude CLI runs WebSearch internally via
174
+ * Anthropic's web search. No MCP setup required, no extra cost.
175
+ * - `"<opencode-tool-name>"` (e.g. `"websearch_web_search_exa"`): forward
176
+ * the call to that opencode-side tool with `executed:false`. Requires
177
+ * the corresponding MCP server to be configured in opencode.
178
+ * - `"disabled"`: prevent the model from calling WebSearch entirely
179
+ * (passes `WebSearch` via `--disallowedTools`).
180
+ */
181
+ webSearch?: WebSearchRouting;
182
+ /**
183
+ * Detect mid-session opencode MCP config changes and respawn the
184
+ * underlying claude process so newly enabled / disabled MCPs become
185
+ * visible to the model without restarting opencode or starting a new
186
+ * chat. Eviction happens at the start of the next user turn (never mid
187
+ * tool-call) and `--session-id` is preserved so the conversation
188
+ * continues seamlessly. Defaults to `true`.
189
+ *
190
+ * Set to `false` to keep the previous behavior (cached subprocess
191
+ * survives MCP changes until the chat is reset).
192
+ */
193
+ hotReloadMcp?: boolean;
150
194
  }
151
195
  type PermissionMode = "acceptEdits" | "auto" | "bypassPermissions" | "default" | "dontAsk" | "plan";
152
196
  type ControlRequestBehavior = "allow" | "deny";
@@ -248,9 +292,14 @@ declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
248
292
  private toFinishReason;
249
293
  private requestScope;
250
294
  /**
251
- * Build the combined `--mcp-config` list: user-configured paths plus the
252
- * auto-bridged opencode MCP config (when enabled and present) and the
253
- * proxy MCP scratch file (when proxyTools are enabled).
295
+ * Build the combined `--mcp-config` list and return both the list and the
296
+ * hash of the bridged opencode MCP block (or null when bridging is off /
297
+ * yields nothing). The hash is used to detect mid-session config changes
298
+ * and respawn the underlying claude process.
299
+ *
300
+ * `runtimeStatus` is a snapshot of opencode's `client.mcp.status()`. When
301
+ * provided it overlays opencode's UI-toggled state on top of disk config
302
+ * so `/mcps` toggles propagate without a config file write.
254
303
  */
255
304
  private effectiveMcpConfig;
256
305
  /** Resolve ProxyToolDef[] for the configured proxyTools names. */
@@ -284,15 +333,35 @@ declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
284
333
  doStream(options: LanguageModelV3CallOptions): Promise<Awaited<ReturnType<LanguageModelV3["doStream"]>>>;
285
334
  }
286
335
 
336
+ interface BridgedMcp {
337
+ /** Path to the temp file containing the translated `--mcp-config`. */
338
+ path: string;
339
+ /** Stable hash of the merged opencode mcp block (pre-translation). */
340
+ hash: string;
341
+ }
287
342
  /**
288
- * Read opencode config file(s), translate their `mcp` block to Claude CLI
289
- * format, write a scratch file, and return its path. Later files override
290
- * earlier files per server-name (matching opencode's own merge semantics).
343
+ * Per-server runtime status from opencode's `client.mcp.status()`. Used as
344
+ * an overlay on top of the on-disk merged config so opencode's UI-toggled
345
+ * state which lives only in-memory; `connect()`/`disconnect()` never
346
+ * touch disk — propagates to the bridged claude subprocess.
291
347
  *
292
- * Returns null when no opencode config with MCP servers is found — callers
293
- * should treat that as "nothing to bridge" and carry on.
348
+ * Treatment per server:
349
+ * - "connected" → force `enabled: true` (mirror opencode)
350
+ * - any other status → force `enabled: false` (don't ship a server
351
+ * opencode can't run; user fixes it in opencode first)
352
+ * - missing entry → leave disk value
353
+ *
354
+ * Omit the overlay and the bridge falls back to disk-only.
355
+ */
356
+ type RuntimeMcpStatus = Record<string, string>;
357
+ /**
358
+ * Read opencode config layers, deep-merge their `mcp` blocks per opencode's
359
+ * own semantics, optionally apply an opencode runtime-status overlay, then
360
+ * translate each server to Claude CLI format, write a scratch file, and
361
+ * return its path + a stable hash. Returns null when no enabled MCP servers
362
+ * remain after the merge + overlay.
294
363
  */
295
- declare function bridgeOpencodeMcp(cwd: string): string | null;
364
+ declare function bridgeOpencodeMcp(cwd: string, runtimeStatus?: RuntimeMcpStatus): BridgedMcp | null;
296
365
 
297
366
  declare const defaultModels: Record<string, OpenCodeModel>;
298
367