@khalilgharbaoui/opencode-claude-code-plugin 0.13.0 → 0.13.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
@@ -182,6 +182,7 @@ The account model IDs are internally suffixed, for example `claude-sonnet-4-6@wo
182
182
  | `skipPermissions` | boolean | `true` | Pass `--dangerously-skip-permissions` to `claude`. Ignored when `proxyTools` is set — the proxy handles permissions through opencode instead. |
183
183
  | `permissionMode` | `acceptEdits` \| `auto` \| `bypassPermissions` \| `default` \| `dontAsk` \| `plan` | – | Forwarded to `claude --permission-mode`. |
184
184
  | `proxyTools` | string[] | `["Bash", "Edit", "Write", "WebFetch", "Task"]` | Claude built-in tools to route through opencode's executor + permission UI. Opt-in extras: `"Question"`, `"Compress"`. See [Selective tool proxy](#selective-tool-proxy). |
185
+ | `extraDisallowedTools` | string[] | – | Extra Claude built-ins to switch off with `--disallowedTools`, on top of what `proxyTools` implies. Claude's names, e.g. `["NotebookEdit"]`. See [Closing a tool with no proxy](#closing-a-tool-with-no-proxy). |
185
186
  | `proxyToolTimeoutMs` | `Record<string, number>` | – | Per-tool proxy call deadline in ms, keyed by proxy tool name (`bash`, `task`, …). Defaults: 10 min flat, `task` → 60 min. For `bash`, the call's own `input.timeout` is honoured on top (`max(resolved, input.timeout)`). See [Selective tool proxy](#selective-tool-proxy). |
186
187
  | `planModeQuestion` | boolean | `false` | Route `ExitPlanMode` approval through opencode's native `question` tool instead of a text "(yes/no)" prompt. Off because opencode's question form is currently broken upstream. See [Plan mode](#plan-mode). |
187
188
  | `controlRequestBehavior` | `allow` \| `deny` | `allow` | Default response when `skipPermissions: false` and Claude sends a `can_use_tool` control request. |
@@ -298,6 +299,20 @@ recovery step for harnesses that defer MCP tool schemas. Both apply per Claude
298
299
  process at spawn, and provider options are read once at opencode startup, so
299
300
  `proxyTools` changes need a full opencode restart.
300
301
 
302
+ ### Closing a tool with no proxy
303
+
304
+ `proxyTools` only reaches built-ins the plugin can replace. A built-in with no opencode equivalent, `NotebookEdit` today and whatever Claude Code ships next, stays enabled and unmediated no matter what you put in that list. `extraDisallowedTools` names them directly:
305
+
306
+ ```json
307
+ "options": {
308
+ "extraDisallowedTools": ["NotebookEdit"]
309
+ }
310
+ ```
311
+
312
+ These go straight to `claude --disallowedTools`, so use Claude's tool names rather than opencode's. There is no replacement: the capability goes away rather than being routed through opencode, which is the point, but the model then has to work without it.
313
+
314
+ Unknown entries in `proxyTools` are logged as a warning at spawn rather than passing silently, so a typo shows up as "ignoring unknown proxyTools entries" in the plugin log instead of quietly leaving the matching built-in unmediated.
315
+
301
316
  ### Context compression
302
317
 
303
318
  `"Compress"` is off by default. Add it when you run a harness that expects the model to manage its own context (opencode-dcp injects exactly those instructions), and the plugin exposes `mcp__opencode_proxy__compress`:
package/dist/index.d.ts CHANGED
@@ -161,6 +161,7 @@ interface ClaudeCodeConfig {
161
161
  controlRequestToolBehaviors?: Record<string, ControlRequestBehavior>;
162
162
  controlRequestDenyMessage?: string;
163
163
  proxyTools?: string[];
164
+ extraDisallowedTools?: string[];
164
165
  proxyToolTimeoutMs?: Record<string, number>;
165
166
  /**
166
167
  * Route `ExitPlanMode` through opencode's native `question` tool so plan
@@ -283,6 +284,20 @@ interface ClaudeCodeProviderSettings {
283
284
  * entry, in which case the deny/markdown fallback applies.
284
285
  */
285
286
  proxyTools?: string[];
287
+ /**
288
+ * Extra Claude Code built-ins to switch off with `--disallowedTools`,
289
+ * on top of the ones implied by `proxyTools`.
290
+ *
291
+ * `proxyTools` can only disable built-ins the plugin knows how to
292
+ * replace, so a built-in with no proxy equivalent (`NotebookEdit`, and
293
+ * anything Claude Code adds after this release) has no off switch
294
+ * otherwise. Names are Claude's, not opencode's: `["NotebookEdit"]`.
295
+ *
296
+ * Disabling a tool with no replacement removes the capability rather
297
+ * than routing it through opencode — that is the point, but it does mean
298
+ * the model has to work without it.
299
+ */
300
+ extraDisallowedTools?: string[];
286
301
  /**
287
302
  * Per-tool proxy call timeouts in milliseconds, keyed by the proxy tool
288
303
  * name (`bash`, `edit`, `write`, `webfetch`, `task`, `question` —
package/dist/index.js CHANGED
@@ -294,6 +294,9 @@ var CLAUDE_INTERNAL_TOOLS = /* @__PURE__ */ new Set([
294
294
  "TaskGet",
295
295
  "TaskStop"
296
296
  ]);
297
+ function singleQuoteForShell(value) {
298
+ return `'${value.replace(/'/g, `'\\''`)}'`;
299
+ }
297
300
  function emitTodoWrite(todos) {
298
301
  return {
299
302
  name: "todowrite",
@@ -348,7 +351,7 @@ function mapTool(name, input, opts) {
348
351
  return {
349
352
  name: "bash",
350
353
  input: {
351
- command: `echo "TASK OUTPUT: ${String(output).replace(/"/g, '\\"')}"`,
354
+ command: `printf '%s\\n' ${singleQuoteForShell(`TASK OUTPUT: ${String(output)}`)}`,
352
355
  description: "Displaying task output"
353
356
  },
354
357
  executed: false
@@ -2810,6 +2813,20 @@ function disallowedToolFlags(tools) {
2810
2813
  }
2811
2814
  return out;
2812
2815
  }
2816
+ function resolveDisallowedTools(options) {
2817
+ const out = [];
2818
+ const seen = /* @__PURE__ */ new Set();
2819
+ const push = (name) => {
2820
+ const trimmed = name.trim();
2821
+ if (!trimmed || seen.has(trimmed)) return;
2822
+ seen.add(trimmed);
2823
+ out.push(trimmed);
2824
+ };
2825
+ for (const name of disallowedToolFlags(options.proxyTools ?? [])) push(name);
2826
+ for (const name of options.extraDisallowedTools ?? []) push(String(name));
2827
+ if (options.disableWebSearch) push("WebSearch");
2828
+ return out;
2829
+ }
2813
2830
  function readBody(req) {
2814
2831
  return new Promise((resolve4, reject) => {
2815
2832
  const chunks = [];
@@ -3353,9 +3370,22 @@ var ClaudeCodeLanguageModel = class {
3353
3370
  DEFAULT_PROXY_TOOLS.map((t) => [t.name.toLowerCase(), t])
3354
3371
  );
3355
3372
  const picked = [];
3373
+ const unknown = [];
3356
3374
  for (const n of names) {
3357
3375
  const def = defsByName.get(String(n).toLowerCase());
3358
3376
  if (def) picked.push(def);
3377
+ else unknown.push(String(n));
3378
+ }
3379
+ if (unknown.length > 0) {
3380
+ const known = [...defsByName.keys()].join(", ");
3381
+ if (picked.length === 0) {
3382
+ log.warn(
3383
+ "no proxyTools entry was recognised; nothing will be proxied this turn",
3384
+ { unknown, known }
3385
+ );
3386
+ } else {
3387
+ log.warn("ignoring unknown proxyTools entries", { unknown, known });
3388
+ }
3359
3389
  }
3360
3390
  return picked.length > 0 ? picked : null;
3361
3391
  }
@@ -4453,10 +4483,11 @@ ${plan}
4453
4483
  proxyServer = await self.ensureProxyServer(combinedProxyTools, sk);
4454
4484
  }
4455
4485
  const questionProxyActive = enrichedProxy?.some((t) => t.name === "question") ?? false;
4456
- const proxyDisallowed = enrichedProxy ? disallowedToolFlags(enrichedProxy) : [];
4457
- const extraDisallowed = [];
4458
- if (self.config.webSearch === "disabled") extraDisallowed.push("WebSearch");
4459
- const allDisallowed = [...proxyDisallowed, ...extraDisallowed];
4486
+ const allDisallowed = resolveDisallowedTools({
4487
+ proxyTools: enrichedProxy,
4488
+ extraDisallowedTools: self.config.extraDisallowedTools,
4489
+ disableWebSearch: self.config.webSearch === "disabled"
4490
+ });
4460
4491
  const mcp = self.effectiveMcpConfig(
4461
4492
  cwd,
4462
4493
  proxyServer?.configPath(),
@@ -5707,11 +5738,11 @@ function defineModel(opts) {
5707
5738
  variants: opts.reasoning ? reasoningVariants : void 0
5708
5739
  };
5709
5740
  }
5710
- var haikuCost = { input: 1e-6, output: 5e-6, cacheRead: 1e-7, cacheWrite: 125e-8 };
5711
- var sonnetCost = { input: 3e-6, output: 15e-6, cacheRead: 3e-7, cacheWrite: 375e-8 };
5712
- var sonnet5Cost = { input: 2e-6, output: 1e-5, cacheRead: 2e-7, cacheWrite: 25e-7 };
5713
- var opusCost = { input: 5e-6, output: 25e-6, cacheRead: 5e-7, cacheWrite: 625e-8 };
5714
- var fableCost = { input: 1e-5, output: 5e-5, cacheRead: 1e-6, cacheWrite: 125e-7 };
5741
+ var haikuCost = { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 };
5742
+ var sonnetCost = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 };
5743
+ var sonnet5Cost = { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 };
5744
+ var opusCost = { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 };
5745
+ var fableCost = { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 };
5715
5746
  function toConfigModel(model) {
5716
5747
  const inputMods = [];
5717
5748
  const outputMods = [];
@@ -6310,6 +6341,7 @@ function createClaudeCode(settings = {}) {
6310
6341
  controlRequestToolBehaviors: settings.controlRequestToolBehaviors,
6311
6342
  controlRequestDenyMessage: settings.controlRequestDenyMessage,
6312
6343
  proxyTools,
6344
+ extraDisallowedTools: settings.extraDisallowedTools,
6313
6345
  proxyToolTimeoutMs: settings.proxyToolTimeoutMs,
6314
6346
  planModeQuestion: settings.planModeQuestion ?? false,
6315
6347
  webSearch: settings.webSearch,