@cortexkit/opencode-anthropic-auth 1.7.0 → 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 +11 -3
- 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-ykvgffhr.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
|
|
@@ -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
|
|
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()}
|