@khalilgharbaoui/opencode-claude-code-plugin 0.64.0 → 0.64.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
@@ -18,8 +18,6 @@
18
18
  [![License](https://img.shields.io/github/license/khalilgharbaoui/opencode-claude-code-plugin?style=flat-square&label=license&labelColor=15181E&color=FFC46B)](./LICENSE)
19
19
  [![Buy me a coffee](https://img.shields.io/badge/support-buy%20me%20a%20coffee-FFC46B?style=flat-square&labelColor=15181E&logo=buymeacoffee&logoColor=FFC46B)](https://www.buymeacoffee.com/khalilgharbaoui)
20
20
 
21
- <a href="https://www.buymeacoffee.com/khalilgharbaoui"><img src="site/public/buy-me-a-coffee.png" alt="Buy me a coffee" width="214" height="60"></a>
22
-
23
21
  **Claude Code is the provider.** This opencode plugin runs Anthropic's Claude models through the official **Claude Code CLI** (`claude`) as a subprocess instead of calling the HTTP API. opencode inherits whatever that CLI is logged in as (a Claude subscription, an API key, Bedrock or Vertex), gets Claude's own tools, MCP servers and skills, and keeps running the tools that touch your machine itself, behind its own permission prompts. One package serves opencode 1.x and 2.x.
24
22
 
25
23
  - **Your CLI's login, untouched.** The plugin never reads, stores or replays a token. Lifting the OAuth session out of the official client is what proxy-style plugins do, and Anthropic disallowed that for third-party Claude use in February 2026. This plugin structurally cannot do it.
@@ -46,9 +44,13 @@
46
44
  }
47
45
  ```
48
46
 
49
- `~/.config/opencode/opencode.json` on opencode 1.x. opencode 2 reads the same key; its native spelling is `plugins`.
47
+ `~/.config/opencode/opencode.json`, on opencode 1.x and 2.x alike. opencode 2's native key is `plugins`, and `opencode plugin add @khalilgharbaoui/opencode-claude-code-plugin` writes that entry and installs the package for you.
48
+
49
+ 3. **Quit opencode fully and relaunch.** Plugins load once, at process start. The model picker now has a **Claude Code** provider (opencode 1.x labels it with its id, `claude-code`) with entries such as `Claude Sonnet 5.5 (2×)` and `Claude Opus 5 (5×)`; the suffix is each model's list price relative to Haiku. A model's full id is `claude-code/<model>`, so one line checks it from a terminal:
50
50
 
51
- 3. **Quit opencode fully and relaunch.** Plugins load once, at process start. The model picker now has a **Claude Code (Default)** provider with entries such as `Claude Sonnet 5.5 (2×)` and `Claude Opus 5 (5×)`; the suffix is each model's list price relative to Haiku.
51
+ ```bash
52
+ opencode run -m claude-code/claude-haiku-5-5 "reply with ok"
53
+ ```
52
54
 
53
55
  Nothing in the picker? [Troubleshooting](./docs/troubleshooting/symptoms.md) is keyed on the first thing you see. `/claude-code-doctor` in any session prints what the plugin thinks is happening, without calling a model.
54
56
 
@@ -70,12 +72,14 @@ Nothing in the picker? [Troubleshooting](./docs/troubleshooting/symptoms.md) is
70
72
  | | opencode's native `anthropic` provider | This plugin | Proxy and token-reuse plugins |
71
73
  |---|---|---|---|
72
74
  | **Authentication** | A Platform API key in opencode's auth store. | Whatever the official `claude` CLI holds. No token is read, stored or replayed. | The Claude OAuth session, used outside the official client. |
73
- | **Terms-of-service status** | The ordinary API route. | Sanctioned: the official client does the authenticating. | Disallowed. Anthropic disallowed reusing subscription authentication for third-party Claude use in February 2026, and each of those projects carries its own disclaimer. |
75
+ | **Terms-of-service status** | The ordinary API route. | Uses the official client: Claude Code does the authenticating, the same as running `claude -p` yourself. Anthropic's [legal page](https://code.claude.com/docs/en/legal-and-compliance) allows signing in to the unmodified binary; it has not approved or endorsed this plugin. | Disallowed. Anthropic disallowed reusing subscription authentication for third-party Claude use in February 2026, and each of those projects carries its own disclaimer. |
74
76
  | **Who runs Bash, Edit, Write** | opencode. | opencode, by default; `Read`, `Grep`, `Glob` run in Claude Code. | opencode. |
75
77
  | **What it costs you** | Baseline. | A `claude` child per conversation under a cap of 16; Claude Code may compact its own context behind opencode's back. | One more moving part, plus the account risk above. |
76
78
 
77
79
  The [full comparison](./docs/comparison.md) has nine rows, names the projects in the third column, and quotes their own READMEs.
78
80
 
81
+ On opencode 2.2.3 or later with one Claude login, also look at opencode's own **Claude Agent** provider: it drives your own `claude` too, runs every tool in opencode, and leaves your `~/.claude` setup out. The comparison has [a section on it](./docs/comparison.md#opencodes-built-in-claude-agent-provider), including what this plugin still adds (opencode 1.x, several accounts, your Claude Code setup, fast mode, `/btw`, the sidebar).
82
+
79
83
  ## Built from measurements
80
84
 
81
85
  A dozen test files drive a fake `claude` through real turns: the stream parser, the proxy broker, both watchdogs, abort, respawn, account failover and the model fallback chain are exercised end to end. Every rule in [`AGENTS.md`](./AGENTS.md) names the probe, the version and the number that produced it, and the evidence lives in [`docs/agents-history.md`](./docs/agents-history.md). Two runtime dependencies. The [site](https://opencode-claude-code-plugin.dev/) reports the live numbers: it rebuilds itself after every release, on every push that moves a figure, and every six hours.
package/dist/index.js CHANGED
@@ -4059,6 +4059,15 @@ function forkSiblingSignature(sessionKey2) {
4059
4059
  blanked[3] = "*";
4060
4060
  return blanked.join("::");
4061
4061
  }
4062
+ function withoutSpawnSettings(segment) {
4063
+ if (!segment.startsWith("context=")) return segment;
4064
+ try {
4065
+ const context = JSON.parse(segment.slice("context=".length));
4066
+ if (Array.isArray(context)) return `context=${JSON.stringify(context.slice(0, 2))}`;
4067
+ } catch {
4068
+ }
4069
+ return segment;
4070
+ }
4062
4071
  function modelSiblingSignature(sessionKey2, accountProviders) {
4063
4072
  const parts = sessionKey2.split("::");
4064
4073
  if (parts.length < 5) return null;
@@ -4067,6 +4076,7 @@ function modelSiblingSignature(sessionKey2, accountProviders) {
4067
4076
  if (withoutEffort.length < 5) return null;
4068
4077
  const blanked = [...withoutEffort];
4069
4078
  blanked[1] = "*";
4079
+ blanked[blanked.length - 1] = withoutSpawnSettings(blanked[blanked.length - 1]);
4070
4080
  const signature = blanked.join("::");
4071
4081
  return accountProviders && accountProviders.size > 0 ? blankAccountProvider(signature, accountProviders) : signature;
4072
4082
  }
@@ -8460,9 +8470,9 @@ async function releaseIdleForHandover(sessionID) {
8460
8470
  }
8461
8471
  return released;
8462
8472
  }
8463
- function transferClaudeSession(fromKey, toKey) {
8473
+ function transferClaudeSession(fromKey, toKey, recorded) {
8464
8474
  if (fromKey === toKey) return void 0;
8465
- const claudeSessionId = claudeSessions.get(fromKey);
8475
+ const claudeSessionId = claudeSessions.get(fromKey) ?? recorded;
8466
8476
  if (!claudeSessionId) return void 0;
8467
8477
  if (claudeSessions.has(toKey)) return void 0;
8468
8478
  claudeSessions.delete(fromKey);
@@ -8485,14 +8495,7 @@ function effortSessionKey(baseKey, effort) {
8485
8495
  function spawnIdentity(key) {
8486
8496
  const parts = key.split("::");
8487
8497
  if (parts.length > 1 && parts[parts.length - 1].startsWith("effort=")) parts.pop();
8488
- const last = parts.length - 1;
8489
- if (parts[last].startsWith("context=")) {
8490
- try {
8491
- const context = JSON.parse(parts[last].slice("context=".length));
8492
- if (Array.isArray(context)) parts[last] = `context=${JSON.stringify(context.slice(0, 2))}`;
8493
- } catch {
8494
- }
8495
- }
8498
+ parts[parts.length - 1] = withoutSpawnSettings(parts[parts.length - 1]);
8496
8499
  return parts.join("::");
8497
8500
  }
8498
8501
  function spawnHasPendingWork(key) {
@@ -8548,7 +8551,8 @@ function invalidateOtherSpawnVariants(currentKey, options = {}) {
8548
8551
  clearBackgroundTasks(key);
8549
8552
  continue;
8550
8553
  }
8551
- deleteClaudeSessionId(key);
8554
+ if (carry && !claudeSessions.has(key)) forgetClaudeSessionInMemory(key);
8555
+ else deleteClaudeSessionId(key);
8552
8556
  clearCompression(key);
8553
8557
  clearBackgroundTasks(key);
8554
8558
  }
@@ -19109,7 +19113,7 @@ var ClaudeCodeLanguageModel = class {
19109
19113
  });
19110
19114
  } else {
19111
19115
  deleteActiveProcess(sibling.siblingKey);
19112
- const carried = transferClaudeSession(sibling.siblingKey, sk);
19116
+ const carried = transferClaudeSession(sibling.siblingKey, sk, sibling.claudeSessionId);
19113
19117
  if (carried) {
19114
19118
  if (carriedSessionId && carriedSessionId !== carried) {
19115
19119
  setClaudeSessionId(sk, carriedSessionId);