@cortexkit/opencode-anthropic-auth 1.7.0 → 1.9.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 +17 -6
- package/dist/cli.js +6 -4
- package/dist/index-taa0ezxk.js +409 -0
- package/dist/index-zf02be7y.js +2 -0
- package/dist/index.js +6 -6
- package/dist/sanitize-memo.d.ts +23 -0
- package/dist/sidebar-state.d.ts +5 -0
- package/dist/sidebar-state.js +1 -1
- package/dist/transform.d.ts +11 -14
- 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
|
|
|
@@ -195,6 +196,9 @@ Example:
|
|
|
195
196
|
"claudeFast": {
|
|
196
197
|
"enabled": false
|
|
197
198
|
},
|
|
199
|
+
"costZeroing": {
|
|
200
|
+
"enabled": true
|
|
201
|
+
},
|
|
198
202
|
"relay": {
|
|
199
203
|
"enabled": false,
|
|
200
204
|
"url": "https://opencode-anthropic-relay.example.workers.dev",
|
|
@@ -206,11 +210,13 @@ Example:
|
|
|
206
210
|
}
|
|
207
211
|
```
|
|
208
212
|
|
|
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
|
|
213
|
+
The `routing` block controls `/claude-routing`, `claudeCache` controls `/claude-cache`, `cacheKeep` controls `/claude-cachekeep`, and `claudeFast` controls `/claude-fast`. OpenCode zeroes Anthropic OAuth model costs by default because OAuth usage is quota-based; set `costZeroing.enabled` to `false` only if you want OpenCode to display the provider's model pricing instead. 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.
|
|
214
|
+
|
|
215
|
+
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
216
|
|
|
211
217
|
## Fallback accounts
|
|
212
218
|
|
|
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.
|
|
219
|
+
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
220
|
|
|
215
221
|
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
222
|
|
|
@@ -224,11 +230,16 @@ Add and inspect OpenCode fallback accounts with the CLI:
|
|
|
224
230
|
|
|
225
231
|
```bash
|
|
226
232
|
bunx @cortexkit/opencode-anthropic-auth login personal-alt
|
|
233
|
+
bunx @cortexkit/opencode-anthropic-auth api add kie-opus
|
|
227
234
|
bunx @cortexkit/opencode-anthropic-auth list
|
|
228
235
|
```
|
|
229
236
|
|
|
230
237
|
Prefer npm? Use `npx -y @cortexkit/opencode-anthropic-auth ...` with the same subcommands.
|
|
231
238
|
|
|
239
|
+
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.
|
|
240
|
+
|
|
241
|
+
OpenCode cost accounting stays simple: when the native Anthropic auth entry is OAuth, OpenCode sees zero-cost Claude OAuth models by default. 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. Advanced users can set `costZeroing.enabled` to `false` to show Anthropic model pricing for OAuth sessions too.
|
|
242
|
+
|
|
232
243
|
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
244
|
|
|
234
245
|
```bash
|
|
@@ -363,7 +374,7 @@ Both OpenCode and Pi packages can persistently request Anthropic fast mode for s
|
|
|
363
374
|
/claude-fast off
|
|
364
375
|
```
|
|
365
376
|
|
|
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
|
|
377
|
+
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
378
|
|
|
368
379
|
Fast and standard speeds do not share prompt-cache prefixes, so switching this setting can cause cache misses.
|
|
369
380
|
|
|
@@ -585,19 +596,19 @@ This repo uses CortexKit's tag-driven release workflow.
|
|
|
585
596
|
Preview a release:
|
|
586
597
|
|
|
587
598
|
```bash
|
|
588
|
-
./scripts/release.sh 1.
|
|
599
|
+
./scripts/release.sh 1.9.0 --dry
|
|
589
600
|
```
|
|
590
601
|
|
|
591
602
|
Create and push the release tag:
|
|
592
603
|
|
|
593
604
|
```bash
|
|
594
|
-
./scripts/release.sh 1.
|
|
605
|
+
./scripts/release.sh 1.9.0
|
|
595
606
|
```
|
|
596
607
|
|
|
597
608
|
Wait for GitHub Actions:
|
|
598
609
|
|
|
599
610
|
```bash
|
|
600
|
-
./scripts/wait-release.sh v1.
|
|
611
|
+
./scripts/wait-release.sh v1.9.0
|
|
601
612
|
```
|
|
602
613
|
|
|
603
614
|
The release workflow runs checks, publishes the core, OpenCode, and Pi packages to npm with provenance, and creates the GitHub release.
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{
|
|
2
|
+
import{Ja as I,La as $,m as w,n as k,p as A,r as R,t as d,u as l,v as p}from"./index-taa0ezxk.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()}
|