@cortexkit/opencode-anthropic-auth 1.6.1 → 1.8.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 +13 -5
- package/dist/cli.js +6 -4
- package/dist/index-gcmgg93d.js +335 -0
- package/dist/index-s5ckbbqq.js +2 -0
- package/dist/index.js +6 -6
- package/dist/sidebar-state.d.ts +5 -0
- package/dist/sidebar-state.js +1 -1
- package/dist/transform.d.ts +8 -2
- package/package.json +2 -2
- package/src/sidebar-state.ts +18 -0
- package/src/tui.tsx +19 -27
- package/dist/index-q0d64q8d.js +0 -2
- package/dist/index-vmrz4qdc.js +0 -335
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ This repo is a Bun workspace monorepo with two user-facing integrations and one
|
|
|
19
19
|
| Primary Claude Pro/Max OAuth | OpenCode `/connect anthropic` | Pi `/login anthropic` |
|
|
20
20
|
| Provider integration point | OpenCode plugin fetch/request transform | Pi `registerProvider("anthropic")` provider override |
|
|
21
21
|
| Sidecar config | `~/.config/opencode/anthropic-auth.json` | `~/.pi/agent/anthropic-auth.json` |
|
|
22
|
+
| Runtime state | `~/.config/opencode/anthropic-auth-state.json` | next to the Pi sidecar as `anthropic-auth-state.json` |
|
|
22
23
|
| Commands | `/claude-cache`, `/claude-cachekeep`, `/claude-routing`, `/claude-fast`, `/claude-quota`, `/claude-dump`, `/claude-killswitch` | `/claude-cache`, `/claude-cachekeep`, `/claude-routing`, `/claude-fast`, `/claude-quota`, `/claude-dump` |
|
|
23
24
|
| Fallback accounts, quota routing, killswitch, relay, dumps, fast mode | Supported | Supported through the same shared core and Pi sidecar |
|
|
24
25
|
|
|
@@ -206,11 +207,13 @@ Example:
|
|
|
206
207
|
}
|
|
207
208
|
```
|
|
208
209
|
|
|
209
|
-
The `routing` block controls `/claude-routing`, `claudeCache` controls `/claude-cache`, `cacheKeep` controls `/claude-cachekeep`, and `claudeFast` controls `/claude-fast`. Set `quota.showToasts` to `true` to opt into OpenCode quota toast notifications after quota refreshes. The `main` field identifies OpenCode's primary auth entry; Pi keeps primary OAuth credentials in Pi's own credential store, but uses the same sidecar shape for CortexKit settings and fallback
|
|
210
|
+
The `routing` block controls `/claude-routing`, `claudeCache` controls `/claude-cache`, `cacheKeep` controls `/claude-cachekeep`, and `claudeFast` controls `/claude-fast`. Set `quota.showToasts` to `true` to opt into OpenCode quota toast notifications after quota refreshes. The `main` field identifies OpenCode's primary auth entry; Pi keeps primary OAuth credentials in Pi's own credential store, but uses the same sidecar shape for CortexKit settings and fallback account labels.
|
|
211
|
+
|
|
212
|
+
Runtime data is stored separately in `anthropic-auth-state.json`: fallback OAuth tokens, API-route keys, token refresh backoff, quota snapshots, and quota API backoff. Background refresh and quota checks write only the state file, so editing `anthropic-auth.json` does not get overwritten by another running plugin instance.
|
|
210
213
|
|
|
211
214
|
## Fallback accounts
|
|
212
215
|
|
|
213
|
-
Fallback accounts are separate Claude OAuth accounts managed by this plugin. By default, the main account is tried first unless quota policy says it is currently unusable. Fallbacks are then tried in sidecar order when the primary request returns a configured fallback status.
|
|
216
|
+
Fallback accounts are separate Claude OAuth accounts or Anthropic-compatible API-key routes managed by this plugin. By default, the main account is tried first unless quota policy says it is currently unusable. Fallbacks are then tried in sidecar order when the primary request returns a configured fallback status.
|
|
214
217
|
|
|
215
218
|
Use `/claude-routing fallback-first` to prefer usable fallback accounts before the main account. Use `/claude-routing main-first` to restore the default. The command persists `routing.mode` and takes effect on the next request without restarting.
|
|
216
219
|
|
|
@@ -224,11 +227,16 @@ Add and inspect OpenCode fallback accounts with the CLI:
|
|
|
224
227
|
|
|
225
228
|
```bash
|
|
226
229
|
bunx @cortexkit/opencode-anthropic-auth login personal-alt
|
|
230
|
+
bunx @cortexkit/opencode-anthropic-auth api add kie-opus
|
|
227
231
|
bunx @cortexkit/opencode-anthropic-auth list
|
|
228
232
|
```
|
|
229
233
|
|
|
230
234
|
Prefer npm? Use `npx -y @cortexkit/opencode-anthropic-auth ...` with the same subcommands.
|
|
231
235
|
|
|
236
|
+
API fallback routes use `Authorization: Bearer <key>` by default and rewrite requests to the configured Anthropic-compatible base URL. For Kie, use `https://api.kie.ai/claude` as the base URL; the plugin appends `/v1/messages` automatically. API-key routes are only eligible after direct evidence that the main OAuth quota is exhausted: a fresh token-bound quota snapshot at 0% remaining, or an actual main OAuth model response with HTTP 429 / streaming rate-limit error followed by a live quota check that confirms 0% remaining. Low-but-nonzero quota, stale cached quota, unconfirmed 429s, 401, or 403 do not trigger API-key routes. API-route keys are stored in `anthropic-auth-state.json`, while `anthropic-auth.json` keeps the route label, type, enabled flag, base URL, and auth-header mode.
|
|
237
|
+
|
|
238
|
+
OpenCode cost accounting stays simple: when the native Anthropic auth entry is OAuth, OpenCode sees zero-cost Claude OAuth models. If a request falls through to an API-key route, token accounting is still recorded, but OpenCode's built-in dollar cost is not route-aware.
|
|
239
|
+
|
|
232
240
|
For Pi fallback accounts, write the same account JSON shape to `~/.pi/agent/anthropic-auth.json`. The CLI helper currently lives in the OpenCode package, so you can also point it at Pi's sidecar path when logging in a fallback account:
|
|
233
241
|
|
|
234
242
|
```bash
|
|
@@ -333,7 +341,7 @@ Modes:
|
|
|
333
341
|
|
|
334
342
|
- `explicit` keeps OpenCode's explicit cache breakpoints and adds `ttl: "1h"` to them.
|
|
335
343
|
- `automatic` removes block-level cache controls and sends a top-level `cache_control` object.
|
|
336
|
-
- `hybrid` (recommended) removes top-level automatic caching and uses explicit anchors
|
|
344
|
+
- `hybrid` (recommended) removes top-level automatic caching and uses explicit anchors for Magic Context's leading history plus a moving latest-user boundary. When the first message has multiple cacheable content blocks, hybrid anchors both the first and last block so a stable leading history block remains cached even if a volatile trailing block changes; otherwise it anchors the first two messages. It keeps the last stable system block in normal turns, and uses that slot for the previous user boundary when a tool-heavy step would exceed Anthropic's 20-block lookback.
|
|
337
345
|
|
|
338
346
|
In OpenCode, subagent requests do not receive 1-hour TTL caching. The plugin detects child sessions through OpenCode's `x-parent-session-id` header, strips that internal header before forwarding to Anthropic, and leaves default ephemeral caching in place for those requests.
|
|
339
347
|
|
|
@@ -363,7 +371,7 @@ Both OpenCode and Pi packages can persistently request Anthropic fast mode for s
|
|
|
363
371
|
/claude-fast off
|
|
364
372
|
```
|
|
365
373
|
|
|
366
|
-
When enabled, supported requests add `speed: "fast"` to the Anthropic JSON body and include the `fast-mode-2026-02-01` beta header. Unsupported models are left at standard speed. Anthropic currently documents fast mode for `claude-opus-4-6`, `claude-opus-4-7`, and `claude-opus-4-8
|
|
374
|
+
When enabled, supported requests add `speed: "fast"` to the Anthropic JSON body and include the `fast-mode-2026-02-01` beta header. Unsupported models are left at standard speed. Anthropic currently documents fast mode for `claude-opus-4-6`, `claude-opus-4-7`, and `claude-opus-4-8`; Claude Fable 5 and Mythos 5 are not fast-mode models.
|
|
367
375
|
|
|
368
376
|
Fast and standard speeds do not share prompt-cache prefixes, so switching this setting can cause cache misses.
|
|
369
377
|
|
|
@@ -477,7 +485,7 @@ When enabled, the plugin writes artifacts under the OS temp directory:
|
|
|
477
485
|
ls "$(node -p 'require("node:os").tmpdir()')/opencode-anthropic-auth-dumps"
|
|
478
486
|
```
|
|
479
487
|
|
|
480
|
-
Each request gets:
|
|
488
|
+
Each filename includes a sanitized session/affinity segment so dumps from different sessions are easier to find. Each request gets:
|
|
481
489
|
|
|
482
490
|
- `*.body.json` — final rewritten Anthropic request body.
|
|
483
491
|
- `*.relay.json` — redacted relay payload/frame metadata.
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{
|
|
2
|
+
import{Ia as I,Ka as $,m as w,n as k,p as A,r as R,s as d,t as l,u as p}from"./index-gcmgg93d.js";import{stdin as E,stdout as U}from"node:process";import{createInterface as _}from"node:readline/promises";function g(){return{version:1,main:{type:"opencode",provider:"anthropic"},fallbackOn:[401,403,429],refresh:{enabled:!0,intervalMinutes:10,refreshBeforeExpiryMinutes:240},quota:{enabled:!0,checkIntervalMinutes:5,minimumRemaining:{five_hour:10,seven_day:20},failClosedOnUnknownQuota:!0},accounts:[]}}function y(){console.log(`Usage:
|
|
3
3
|
opencode-anthropic-auth login [label]
|
|
4
|
+
opencode-anthropic-auth api add [label]
|
|
4
5
|
opencode-anthropic-auth list
|
|
5
6
|
opencode-anthropic-auth relay setup
|
|
6
7
|
|
|
7
8
|
Fallback accounts are stored in:
|
|
8
|
-
${
|
|
9
|
+
${d()}`)}function h(e,t){let a=e?.trim();if(!a)throw Error(`${t} is required`);return a}async function f(e){let t=await fetch(`https://api.cloudflare.com/client/v4${e.path}`,{method:e.method,headers:{authorization:`Bearer ${e.token}`,...e.body instanceof FormData?{}:{"content-type":"application/json"},...e.headers},body:e.body}),a=await t.text(),n;try{n=JSON.parse(a)}catch{throw Error(`Cloudflare API returned ${t.status}: ${a}`)}if(!t.ok||n.success===!1){let r=n.errors?.map((o)=>o.message).filter(Boolean).join("; ");throw Error(r||`Cloudflare API returned ${t.status}`)}return n.result}async function P(e,t,a){return f({token:e,method:"POST",path:`/accounts/${t}/storage/kv/namespaces`,body:JSON.stringify({title:a})})}async function T(e){let t={main_module:"worker.js",compatibility_date:"2026-04-28",bindings:[{type:"kv_namespace",name:"RELAY_STATE",namespace_id:e.kvNamespaceId},{type:"secret_text",name:"RELAY_TOKEN",text:e.relayToken}]},a=new FormData;return a.set("metadata",JSON.stringify(t)),a.set("worker.js",new Blob([$],{type:"application/javascript+module"}),"worker.js"),f({token:e.token,method:"PUT",path:`/accounts/${e.accountId}/workers/scripts/${e.scriptName}`,body:a})}async function C(e,t,a){await f({token:e,method:"POST",path:`/accounts/${t}/workers/scripts/${a}/subdomain`,body:JSON.stringify({enabled:!0,previews_enabled:!1})})}async function O(e,t){return f({token:e,method:"GET",path:`/accounts/${t}/workers/subdomain`}).catch(()=>null)}async function N(){let e=await l()??g(),t=h(process.env.CLOUDFLARE_API_TOKEN?.trim()||await s("Cloudflare API token: "),"Cloudflare API token"),a=h(process.env.CLOUDFLARE_ACCOUNT_ID||await s("Cloudflare account ID: "),"Cloudflare account ID"),n=await s("Worker name [opencode-anthropic-relay]: ")||"opencode-anthropic-relay",r=`${n}-state`,o=I();console.log("Creating Cloudflare KV namespace...");let i=await P(t,a,r);console.log("Uploading relay Worker..."),await T({token:t,accountId:a,scriptName:n,kvNamespaceId:i.id,relayToken:o}),await C(t,a,n).catch((m)=>{console.warn(`Could not enable workers.dev automatically: ${m instanceof Error?m.message:String(m)}`)});let c=await O(t,a),b=(c?.subdomain?`https://${n}.${c.subdomain}.workers.dev`:"")||h(await s("Relay Worker URL: "),"Relay Worker URL");e.relay={enabled:!0,url:b,token:o,fallbackToDirect:!0,transport:"http"},await p(e),console.log(`Relay enabled at ${b}`),console.log(`Config saved to ${d()}.`)}var u=null;async function s(e){return u??=_({input:E,output:U}),(await u.question(e)).trim()}function L(){u?.close(),u=null}function v(e,t){let a=e.accounts.findIndex((n)=>n.id===t.id||t.label&&n.label===t.label);if(a>=0){e.accounts[a]={...e.accounts[a],...t,addedAt:e.accounts[a]?.addedAt??t.addedAt,...t.type==="oauth"&&{quota:t.quota,lastRefreshedAt:t.lastRefreshedAt,lastRefreshError:t.lastRefreshError,lastQuotaRefreshError:t.lastQuotaRefreshError}};return}e.accounts.push(t)}async function S(e){let t=await l()??g(),a=e?.trim()||await s("Fallback account label (optional): "),n=await w("max");console.log(`
|
|
9
10
|
Open this URL in your browser and complete Claude sign-in:
|
|
10
11
|
`),console.log(`${n.url}
|
|
11
|
-
`);let r=await
|
|
12
|
-
Saved fallback account${a?` "${a}"`:""}.`)}async function
|
|
12
|
+
`);let r=await s("Paste the full callback URL or authorization code here: "),o=await k(r,n.verifier,n.redirectUri,n.state);if(o.type==="failed")throw Error("Authentication failed");let i=Date.now();v(t,{id:a||crypto.randomUUID(),label:a||void 0,type:"oauth",access:o.access,refresh:o.refresh,expires:o.expires,enabled:!0,addedAt:i,lastUsed:i}),await p(t),console.log(`
|
|
13
|
+
Saved fallback account${a?` "${a}"`:""}.`)}async function x(e){let t=await l()??g(),a=e?.trim()||await s("API fallback label (optional): "),n=process.env.OPENCODE_ANTHROPIC_AUTH_API_BASE_URL?.trim()||(await s("Anthropic-compatible base URL [https://api.kie.ai/claude]: ")).trim()||"https://api.kie.ai/claude";if(!R(n))throw Error("API fallback base URL must be an http(s) URL without embedded credentials");let r=process.env.OPENCODE_ANTHROPIC_AUTH_API_KEY?.trim()||await s("API key: ");if(!r.trim())throw Error("API key is required");let i=(process.env.OPENCODE_ANTHROPIC_AUTH_API_AUTH_HEADER?.trim()||await s("Auth header [authorization-bearer|x-api-key] (default authorization-bearer): ")).trim().toLowerCase()==="x-api-key"?"x-api-key":"authorization-bearer",c=Date.now();v(t,{id:a||crypto.randomUUID(),label:a||void 0,type:"api",apiKey:r.trim(),baseURL:n,authHeader:i,enabled:!0,addedAt:c,lastUsed:c}),await p(t),console.log(`
|
|
14
|
+
Saved API fallback route${a?` "${a}"`:""} (${n}).`)}async function D(){let e=await l();if(!e?.accounts.length){console.log(`No fallback accounts found at ${d()}.`);return}for(let[t,a]of e.accounts.entries()){let n=a.label||a.id,r=a.enabled===!1?"disabled":"enabled";if(!A(a)){console.log(`${t+1}. ${n} (${r}) — API route ${a.baseURL}`);continue}let o=a.quota?.five_hour?.remainingPercent,i=a.quota?.seven_day?.remainingPercent,c=o===void 0&&i===void 0?"quota unknown":`5h ${o??"?"}%, 1w ${i??"?"}% remaining`;console.log(`${t+1}. ${n} (${r}) — ${c}`)}}async function q(){let[e,t,a]=process.argv.slice(2);if(!e||e==="help"||e==="--help"||e==="-h"){y();return}if(e==="login"){await S(t);return}if(e==="api"&&t==="add"){await x(a);return}if(e==="list"){await D();return}if(e==="relay"&&t==="setup"){await N();return}y(),process.exitCode=1}try{await q()}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exitCode=1}finally{L()}
|