@cortexkit/opencode-anthropic-auth 1.19.1 → 1.20.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 +113 -2
- package/dist/cache-diagnostics.d.ts +79 -0
- package/dist/cli.js +2 -2
- package/dist/index-afyr2g77.js +2 -0
- package/dist/index-ebq5g321.js +427 -0
- package/dist/{index-075t8qec.js → index-hmebes3f.js} +1 -1
- package/dist/index-j3323dyj.js +2 -0
- package/dist/{index-2k47hc8b.js → index-jjx6dbj3.js} +1 -1
- package/dist/{index-dzs7w297.js → index-nqs7chg0.js} +1 -1
- package/dist/{index-dex8fs0m.js → index-vkecgsm3.js} +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +22 -18
- package/dist/lane-start.d.ts +17 -0
- package/dist/{logger-n49md99d.js → logger-8b9bz0g8.js} +1 -1
- package/dist/prime-manager-registry.d.ts +7 -0
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +2 -1
- package/dist/rpc/protocol.js +1 -0
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/server-fallback.d.ts +2 -0
- package/dist/sidebar-state.d.ts +26 -1
- package/dist/sidebar-state.js +1 -1
- package/dist/transform.d.ts +9 -0
- package/dist/tui/command-dialogs.d.ts +16 -1
- package/dist/tui-preferences.js +1 -1
- package/package.json +4 -4
- package/src/rpc/protocol.ts +15 -10
- package/src/sidebar-state.ts +154 -17
- package/src/tui/command-dialogs.tsx +112 -0
- package/src/tui-compiled/rpc/protocol.ts +15 -10
- package/src/tui-compiled/sidebar-state.ts +154 -17
- package/src/tui-compiled/tui/command-dialogs.tsx +134 -18
- package/src/tui-compiled/tui.tsx +43 -1
- package/src/tui.tsx +28 -0
- package/dist/index-a13w4gm9.js +0 -2
- package/dist/index-vwgzgh5x.js +0 -422
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ This repo is a Bun workspace monorepo with two user-facing integrations and one
|
|
|
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
22
|
| Runtime state | `~/.config/opencode/anthropic-auth-state.json` | next to the Pi sidecar as `anthropic-auth-state.json` |
|
|
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
|
+
| Commands | `/claude-cache`, `/claude-cachekeep`, `/claude-prime`, `/claude-start`, `/claude-routing`, `/claude-fast`, `/claude-quota`, `/claude-dump`, `/claude-killswitch` | `/claude-cache`, `/claude-cachekeep`, `/claude-prime` (status only), `/claude-routing`, `/claude-fast`, `/claude-quota`, `/claude-dump` |
|
|
24
24
|
| Fallback accounts, quota routing, killswitch, relay, dumps, fast mode | Supported | Supported through the same shared core and Pi sidecar |
|
|
25
25
|
|
|
26
26
|
## What CortexKit adds over the original plugin
|
|
@@ -30,6 +30,8 @@ This repo is a Bun workspace monorepo with two user-facing integrations and one
|
|
|
30
30
|
- **Quota-aware routing**: skip main or fallback accounts when their 5-hour or 7-day Claude quota falls below your configured minimum.
|
|
31
31
|
- **Persistent Claude cache controls**: manage Anthropic 1-hour prompt caching from `/claude-cache` with explicit, automatic, or hybrid modes.
|
|
32
32
|
- **Cache keepalive**: use `/claude-cachekeep always` or `/claude-cachekeep HH-HH` to pre-warm hybrid cache anchors for active sessions before the 1-hour TTL expires.
|
|
33
|
+
- **Quota window priming**: opt in with `/claude-prime on` to start each 5-hour quota window about one minute after it resets instead of waiting for the next normal prompt.
|
|
34
|
+
- **Lane start (OpenCode only)**: use `/claude-start` to fire one synthetic, one-token turn through the current session's normal model, agent, variant, quota, routing, cache, and request pipeline.
|
|
33
35
|
- **Fast mode toggle**: use `/claude-fast on|off` to request Anthropic fast mode for supported Opus models.
|
|
34
36
|
- **Adaptive reasoning visibility**: request summarized adaptive thinking for Claude Fable 5, Mythos 5, and Opus 5. OpenCode receives native `low`, `medium`, `high`, `xhigh`, and `max` Opus 5 effort variants rather than legacy manual-thinking budgets.
|
|
35
37
|
- **Fable/Opus 5 safety fallback**: eligible OAuth requests try Anthropic's server-side safety fallback first. The plugin preserves Anthropic's fallback conversation boundary across OpenCode history and automatically starts its deterministic 10-response Opus 4.8 recovery if the response still ends in refusal. The TUI sidebar and OpenCode Desktop report the active target model and restoration. Set `OPENCODE_ANTHROPIC_AUTH_FALLBACK_MODE=legacy` to bypass the server policy and use client-side recovery exclusively.
|
|
@@ -47,7 +49,7 @@ This repo is a Bun workspace monorepo with two user-facing integrations and one
|
|
|
47
49
|
- Support fallback Claude accounts stored in a local per-agent sidecar file.
|
|
48
50
|
- Keep fallback OAuth tokens fresh in the background.
|
|
49
51
|
- Apply quota thresholds before routing to main or fallback accounts.
|
|
50
|
-
- Add `/claude-cache`, `/claude-cachekeep`, `/claude-fast`, `/claude-quota`, and `/claude-dump` commands.
|
|
52
|
+
- Add `/claude-cache`, `/claude-cachekeep`, `/claude-prime`, `/claude-start`, `/claude-fast`, `/claude-quota`, and `/claude-dump` commands.
|
|
51
53
|
- Optionally relay large requests through a Cloudflare Worker owned by the user.
|
|
52
54
|
|
|
53
55
|
## Install
|
|
@@ -217,6 +219,9 @@ The `routing` block controls `/claude-routing`, `claudeCache` controls `/claude-
|
|
|
217
219
|
|
|
218
220
|
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. Sticky session assignments use `anthropic-auth-routing-state.json` and store only SHA-256 hashes of session IDs. Background refresh and quota checks write only runtime state, so editing `anthropic-auth.json` does not get overwritten by another running plugin instance.
|
|
219
221
|
|
|
222
|
+
## OpenCode lane-start setting
|
|
223
|
+
|
|
224
|
+
|
|
220
225
|
## Fallback accounts
|
|
221
226
|
|
|
222
227
|
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.
|
|
@@ -393,6 +398,112 @@ Request bodies, headers, and tokens remain in memory. A lease-backed file under
|
|
|
393
398
|
|
|
394
399
|
Pre-warm requests preserve explicit cache anchors but remove response-only fields that Anthropic rejects with `max_tokens: 0`, such as streaming, enabled thinking, structured output format, and forced/any tool choice. The feature works only while OpenCode or Pi is running and the machine is awake, and cache writes are still billed when the cache entry is no longer warm.
|
|
395
400
|
|
|
401
|
+
## Claude quota window priming
|
|
402
|
+
|
|
403
|
+
Quota-window priming is off by default. OpenCode controls it with:
|
|
404
|
+
|
|
405
|
+
```text
|
|
406
|
+
/claude-prime
|
|
407
|
+
/claude-prime on
|
|
408
|
+
/claude-prime off
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
When enabled, the plugin watches each OAuth account's 5-hour quota reset. About one minute after a confirmed reset, it sends one minimal `claude-haiku-4-5` request so the next window starts without waiting for a normal prompt. Usage is measured from response accounting.
|
|
412
|
+
|
|
413
|
+
Scheduled fires happen only after the quota window has reset. If an idle account has no cached reset time, the plugin sends one bootstrap request to establish its first observed window. Atomic temporary-file claims ensure that multiple OpenCode processes sharing the same account config send only one request per account and reset.
|
|
414
|
+
|
|
415
|
+
Prime marker identities live in `anthropic-auth-state.json`. Plugin-owned refresh rotations preserve the main account's lineage, while a host credential replacement creates a new lineage. An existing main lineage without a refresh-token binding attaches to the current credential on its first check without changing identity. On upgrade, an existing fallback account receives an identity during its first prime check; that one-time marker change can send one extra request in the current window.
|
|
416
|
+
|
|
417
|
+
Pi exposes `/claude-prime` as a status-only command. Its `on` and `off` arguments are ignored; enable or disable priming from OpenCode.
|
|
418
|
+
|
|
419
|
+
## OpenCode lane start
|
|
420
|
+
|
|
421
|
+
`/claude-start` is an OpenCode-only command. It queues one synthetic turn for the current session:
|
|
422
|
+
|
|
423
|
+
```text
|
|
424
|
+
/claude-start
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
The bare command fires immediately. The synthetic prompt uses the session's current model, agent, and variant, then travels through the ordinary quota, routing, cache, relay, signing, and response pipeline. OpenCode shapes that OAuth request to `max_tokens: 1` while keeping streaming enabled, and correlates the request by its synthetic message ID. A queued modal is a request to start the turn, not a provider-success claim.
|
|
428
|
+
|
|
429
|
+
> [!IMPORTANT]
|
|
430
|
+
> This is a real billed request. The one-token limit reduces generated output only; Anthropic still charges for prompt-cache reads and any new cache writes. Use it when you plan to resume the session before the refreshed cache expires.
|
|
431
|
+
|
|
432
|
+
Pi does not expose this command.
|
|
433
|
+
|
|
434
|
+
### Cache diagnostics (beta)
|
|
435
|
+
|
|
436
|
+
The `cache-diagnosis-2026-04-07` beta is measure-only. It asks Anthropic to report prompt-cache diagnostics; it does not change cache controls or routing. OpenCode captures the provider's top-level response ID as an opaque string and sends it as `diagnostics.previous_message_id` on the next request in the same session. The first request sends `null`.
|
|
437
|
+
|
|
438
|
+
The `MC-CACHE-DIAG ` line is a versioned, one-line JSON record. Records and their beta side-channel are emitted only at debug level; use `/claude-logging debug` to capture them. Because emission is gated on the runtime log level, an empty record stream is ambiguous between a quiet system and a level below debug — liveness checks must cross-reference an independent activity witness (billing, usage rows, or dumps) instead of reading silence as absence of cache traffic. Version `2` contains these fields:
|
|
439
|
+
|
|
440
|
+
| Field | Type | Source |
|
|
441
|
+
| --- | --- | --- |
|
|
442
|
+
| `v` | `2` | Capture schema |
|
|
443
|
+
| `source` | string | Observation path; known values are `"turn"`, `"start"`, and `"prewarm_cachekeep"`, but consumers must tolerate future values |
|
|
444
|
+
| `synthetic` | boolean | Whether this observation was generated by plugin machinery rather than a real turn |
|
|
445
|
+
| `account_id` | string | Persisted plugin-internal OAuth account identifier used by routing and the sidebar; stable across restarts and token refreshes, so consumers may key timelines on it. Opaque mixed key space (the main account is a sentinel string, fallbacks are UUIDs) — never validate its shape |
|
|
446
|
+
| `betas_hash` | 16-character lowercase hex | xxHash64 (seed `0`) of the sorted `anthropic-beta` list actually sent, truncated to its first 16 hexadecimal characters |
|
|
447
|
+
| `requested_model` | string, optional | Sent request-body model, present only when it differs from the served Anthropic response model |
|
|
448
|
+
| `session_id` | string | OpenCode session affinity |
|
|
449
|
+
| `ts_ms_received` | integer | Local response receipt time |
|
|
450
|
+
| `model` | string | Anthropic response |
|
|
451
|
+
| `is_subagent` | boolean | Original request context |
|
|
452
|
+
| `ttl_sent` | `"1h" \| "5m" \| null` | Last valid cache breakpoint in the sent body |
|
|
453
|
+
| `cache_read` | number | `usage.cache_read_input_tokens` |
|
|
454
|
+
| `cache_creation` | number | `usage.cache_creation_input_tokens` |
|
|
455
|
+
| `input_tokens` | number | `usage.input_tokens` |
|
|
456
|
+
| `ephemeral_5m_tokens` | number | `usage.cache_creation.ephemeral_5m_input_tokens` |
|
|
457
|
+
| `ephemeral_1h_tokens` | number | `usage.cache_creation.ephemeral_1h_input_tokens` |
|
|
458
|
+
| `message_id` | string | Anthropic response top-level `id` |
|
|
459
|
+
| `previous_message_id` | string \| null | Sent `diagnostics.previous_message_id` |
|
|
460
|
+
| `diag_state` | `"absent" \| "server_null" \| "pending" \| "populated"` | Anthropic response envelope |
|
|
461
|
+
| `miss_reason` | string, optional | `diagnostics.cache_miss_reason.type` |
|
|
462
|
+
| `cache_missed_input_tokens` | number, optional | `diagnostics.cache_miss_reason.cache_missed_input_tokens` |
|
|
463
|
+
|
|
464
|
+
`cache_missed_input_tokens` is a byte-derived, pre-tokenization magnitude indicator, not a token count: it can differ from and occasionally exceed `input_tokens`, so never difference it against usage fields.
|
|
465
|
+
|
|
466
|
+
All required usage counters must be finite, non-negative numbers. A malformed required counter rejects the whole record rather than emitting a partial receipt.
|
|
467
|
+
|
|
468
|
+
Consumers classifying these records should check `cache_read` before `miss_reason`: `cache_read > 0` is a fact and `miss_reason` is an interpretation, and facts win. On multi-turn agent traffic a populated `*_changed` reason routinely coexists with a full prefix hit (each turn appends messages), so a populated-first classifier inverts every such record into a false miss.
|
|
469
|
+
|
|
470
|
+
Known sources map to `synthetic` as follows:
|
|
471
|
+
|
|
472
|
+
| `source` | `synthetic` |
|
|
473
|
+
| --- | --- |
|
|
474
|
+
| `turn` | `false` |
|
|
475
|
+
| `start` | `true` |
|
|
476
|
+
| `prewarm_cachekeep` | `true` |
|
|
477
|
+
|
|
478
|
+
Lane-start cache observations therefore appear as ordinary `MC-CACHE-DIAG ` records with `source: "start"`; the row above is the diagnostics-side companion to the `/claude-start` pipeline described earlier.
|
|
479
|
+
|
|
480
|
+
`synthetic` wins on conflict. A disagreement for a known source is an emitter defect: OpenCode writes one warning and still emits the record with the supplied `synthetic` value. Unknown future sources have no mapping and must not be rejected by consumers.
|
|
481
|
+
|
|
482
|
+
The first observation of each `betas_hash` in a process also writes `MC-CACHE-DIAG-BETAS {"hash":"…","betas":[…]}` at debug level on the `cache-diagnostics` logger channel. Its sorted beta list makes an observed hash interpretable without reconstructing headers; repeated hashes do not emit another side-channel line.
|
|
483
|
+
|
|
484
|
+
The trailing space in `MC-CACHE-DIAG ` is load-bearing: a record line has a space after `MC-CACHE-DIAG`, while the beta side-channel line has a hyphen. Do not trim the delimiter or write a trimming consumer. `grep -c "MC-CACHE-DIAG"` over-counts because it includes beta lines; count records with `grep -c "MC-CACHE-DIAG "` or an anchored equivalent.
|
|
485
|
+
|
|
486
|
+
The four diagnostic states are:
|
|
487
|
+
|
|
488
|
+
| State | Response shape |
|
|
489
|
+
| --- | --- |
|
|
490
|
+
| `absent` | No `diagnostics` property |
|
|
491
|
+
| `server_null` | `diagnostics: null` |
|
|
492
|
+
| `pending` | `diagnostics.cache_miss_reason: null` |
|
|
493
|
+
| `populated` | An object `diagnostics.cache_miss_reason` with a non-empty string `type`, including `"unavailable"` |
|
|
494
|
+
|
|
495
|
+
Only valid Message envelopes produce an `MC-CACHE-DIAG ` record. Malformed or error responses are not mapped to `absent`; they retain ordinary response handling and dumps. `ttl_sent` is reduced from the last valid breakpoint in the actual body sent. A short-gap `previous_message_not_found` result is also recorded as a canary when the previous provider ID was captured less than five minutes earlier.
|
|
496
|
+
|
|
497
|
+
`unavailable` is expected when any prompt-affecting parameter changes between requests. This includes the active Anthropic beta-header set; structured-output requests use a different beta set and can therefore produce `unavailable`. Diagnostics are scoped to the provider's cache fingerprint, organization, and workspace. Fingerprints expire, so a later request can miss after a long gap even when the body is unchanged. These records measure the result; they do not guarantee a cache hit.
|
|
498
|
+
|
|
499
|
+
Diagnostics comparison requires a cacheable prefix. Requests below the model's cacheable minimum or without cache breakpoints return `diagnostics: null` even when the request genuinely changed; consumers must gate interpretation of `null` on observed cache activity.
|
|
500
|
+
|
|
501
|
+
`diag_state` is always a string and never JSON null: `absent | server_null | pending | populated`.
|
|
502
|
+
|
|
503
|
+
Version 1 records come from the unversioned-source era and cannot distinguish prewarms from turns; consumers must treat their source as unknown and cannot split machinery from traffic retroactively. Version 2 always states the source.
|
|
504
|
+
|
|
505
|
+
When request dumps are enabled, each response gets a `.response.json` artifact containing status and parsed response metadata, but no response content. Lane-start requests are tagged `-start-`; CacheKeep prewarms retain `-prewarm-cachekeep-`. If Prime prewarming is enabled in a build that supports it, those artifacts use `-prewarm-prime`. Treat request bodies and related dump files as sensitive local debugging data.
|
|
506
|
+
|
|
396
507
|
## Claude fast mode
|
|
397
508
|
|
|
398
509
|
Both OpenCode and Pi packages can persistently request Anthropic fast mode for supported Opus models:
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export declare const CACHE_DIAGNOSTICS_BETA = "cache-diagnosis-2026-04-07";
|
|
2
|
+
export declare const CACHE_DIAGNOSTICS_LOG_PREFIX = "MC-CACHE-DIAG ";
|
|
3
|
+
export declare const CACHE_DIAGNOSTICS_SESSION_LIMIT = 1000;
|
|
4
|
+
export declare const CACHE_DIAGNOSTICS_SHORT_GAP_MS: number;
|
|
5
|
+
export type CacheDiagnosticsState = 'absent' | 'server_null' | 'pending' | 'populated';
|
|
6
|
+
export type CacheDiagnosticsSource = string;
|
|
7
|
+
export declare const CACHE_DIAGNOSTICS_SOURCE_SYNTHETIC: {
|
|
8
|
+
readonly turn: false;
|
|
9
|
+
readonly start: true;
|
|
10
|
+
readonly prewarm_cachekeep: true;
|
|
11
|
+
};
|
|
12
|
+
export declare const CACHE_DIAGNOSTICS_BETAS_LIMIT = 1000;
|
|
13
|
+
export type CacheDiagnosticsRecord = {
|
|
14
|
+
v: 2;
|
|
15
|
+
source: CacheDiagnosticsSource;
|
|
16
|
+
account_id: string;
|
|
17
|
+
synthetic: boolean;
|
|
18
|
+
betas_hash: string;
|
|
19
|
+
requested_model?: string;
|
|
20
|
+
session_id: string;
|
|
21
|
+
ts_ms_received: number;
|
|
22
|
+
model: string;
|
|
23
|
+
is_subagent: boolean;
|
|
24
|
+
ttl_sent: '1h' | '5m' | null;
|
|
25
|
+
cache_read: number;
|
|
26
|
+
cache_creation: number;
|
|
27
|
+
input_tokens: number;
|
|
28
|
+
ephemeral_5m_tokens: number;
|
|
29
|
+
ephemeral_1h_tokens: number;
|
|
30
|
+
message_id: string;
|
|
31
|
+
previous_message_id: string | null;
|
|
32
|
+
diag_state: CacheDiagnosticsState;
|
|
33
|
+
miss_reason?: string;
|
|
34
|
+
cache_missed_input_tokens?: number;
|
|
35
|
+
};
|
|
36
|
+
export type CacheDiagnosticsRequestContext = {
|
|
37
|
+
sessionId: string;
|
|
38
|
+
previousMessageId: string | null;
|
|
39
|
+
previousMessageReceivedAt?: number;
|
|
40
|
+
isSubagent: boolean;
|
|
41
|
+
ttlSent: '1h' | '5m' | null;
|
|
42
|
+
};
|
|
43
|
+
type CapturedMessage = {
|
|
44
|
+
messageId: string;
|
|
45
|
+
receivedAt: number;
|
|
46
|
+
};
|
|
47
|
+
export declare class CacheDiagnosticsTracker {
|
|
48
|
+
private readonly entries;
|
|
49
|
+
previousFor(sessionId: string): CapturedMessage | null;
|
|
50
|
+
capture(sessionId: string, messageId: string, receivedAt: number): void;
|
|
51
|
+
}
|
|
52
|
+
export declare class CacheDiagnosticsBetaTracker {
|
|
53
|
+
private readonly hashes;
|
|
54
|
+
capture(hash: string, betas: string[]): string | null;
|
|
55
|
+
}
|
|
56
|
+
export declare function copyCacheDiagnosticsContext<T>(contexts: WeakMap<Response, T>, source: Response, destination: Response): void;
|
|
57
|
+
export declare function withStickyRetryAfter(response: Response, sessionId: string, retryAfterSeconds: number, streamingRateLimit: boolean, contexts: WeakMap<Response, unknown>): Promise<Response>;
|
|
58
|
+
export declare function applyCacheDiagnosticsOptIn(body: Record<string, unknown>, previousMessageId: string | null): void;
|
|
59
|
+
export declare function summarizeCacheTtl(body: unknown): '1h' | '5m' | null;
|
|
60
|
+
export declare function buildCacheDiagnosticsRecord(input: {
|
|
61
|
+
request: CacheDiagnosticsRequestContext;
|
|
62
|
+
source: CacheDiagnosticsSource;
|
|
63
|
+
accountId: string;
|
|
64
|
+
synthetic: boolean;
|
|
65
|
+
betasHash: string;
|
|
66
|
+
requestedModel?: string;
|
|
67
|
+
onWarning?: (message: string) => void;
|
|
68
|
+
message: unknown;
|
|
69
|
+
receivedAt: number;
|
|
70
|
+
}): {
|
|
71
|
+
record?: CacheDiagnosticsRecord;
|
|
72
|
+
messageId?: string;
|
|
73
|
+
canary?: {
|
|
74
|
+
messageId: string;
|
|
75
|
+
previousMessageId: string;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
export declare function formatCacheDiagnosticsLogLine(record: CacheDiagnosticsRecord): string;
|
|
79
|
+
export {};
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{
|
|
2
|
+
import{G as z,H as N,J as P,Ja as U,L,N as E,P as K,R as S,hc as T,jc as q}from"./index-ebq5g321.js";import"./index-vkecgsm3.js";import{Uc as w}from"./index-nqs7chg0.js";import{stdin as x,stdout as v}from"node:process";import{createInterface as y}from"node:readline/promises";function R(){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 _(){console.log(`Usage:
|
|
3
3
|
opencode-anthropic-auth login [label]
|
|
4
4
|
opencode-anthropic-auth api add [label]
|
|
5
5
|
opencode-anthropic-auth list
|
|
@@ -9,6 +9,6 @@ Fallback accounts are stored in:
|
|
|
9
9
|
${E()}`)}function H(j,G){let F=j?.trim();if(!F)throw Error(`${G} is required`);return F}async function V(j){let F=await(j.fetchImpl??fetch)(`https://api.cloudflare.com/client/v4${j.path}`,{method:j.method,headers:{authorization:`Bearer ${j.token}`,...j.body instanceof FormData?{}:{"content-type":"application/json"},...j.headers},body:j.body}),J=await F.text(),M;try{M=JSON.parse(J)}catch{throw Error(`Cloudflare API returned ${F.status}: ${J}`)}if(!F.ok||M.success===!1){let Q=M.errors?.map((X)=>X.message).filter(Boolean).join("; ");throw Error(Q||`Cloudflare API returned ${F.status}`)}return M.result}async function A(j,G,F,J){return V({token:j,method:"POST",path:`/accounts/${G}/storage/kv/namespaces`,body:JSON.stringify({title:F}),fetchImpl:J})}async function g(j){let G={main_module:"worker.js",compatibility_date:"2026-04-28",bindings:[{type:"kv_namespace",name:"RELAY_STATE",namespace_id:j.kvNamespaceId},{type:"secret_text",name:"RELAY_TOKEN",text:j.relayToken}]},F=new FormData;return F.set("metadata",JSON.stringify(G)),F.set("worker.js",new Blob([q],{type:"application/javascript+module"}),"worker.js"),V({token:j.token,method:"PUT",path:`/accounts/${j.accountId}/workers/scripts/${j.scriptName}`,body:F,fetchImpl:j.fetchImpl})}async function h(j,G,F,J){await V({token:j,method:"POST",path:`/accounts/${G}/workers/scripts/${F}/subdomain`,body:JSON.stringify({enabled:!0,previews_enabled:!1}),fetchImpl:J})}async function k(j,G,F){return V({token:j,method:"GET",path:`/accounts/${G}/workers/subdomain`,fetchImpl:F}).catch(()=>null)}async function f(j={}){let G=j.fetchImpl??fetch,F=j.prompt??C,J=H(process.env.CLOUDFLARE_API_TOKEN?.trim()||await F("Cloudflare API token: "),"Cloudflare API token"),M=H(process.env.CLOUDFLARE_ACCOUNT_ID||await F("Cloudflare account ID: "),"Cloudflare account ID"),Q=await F("Worker name [opencode-anthropic-relay]: ")||"opencode-anthropic-relay",X=`${Q}-state`,Z=T();console.log("Creating Cloudflare KV namespace...");let Y=await A(J,M,X,G);console.log("Uploading relay Worker..."),await g({token:J,accountId:M,scriptName:Q,kvNamespaceId:Y.id,relayToken:Z,fetchImpl:G}),await h(J,M,Q,G).catch((D)=>{console.warn(`Could not enable workers.dev automatically: ${D instanceof Error?D.message:String(D)}`)});let $=await k(J,M,G),W=($?.subdomain?`https://${Q}.${$.subdomain}.workers.dev`:"")||H(await C("Relay Worker URL: "),"Relay Worker URL"),O=await K()??R();O.relay={enabled:!0,url:W,token:Z,fallbackToDirect:!0,transport:"http"},await S(O),console.log(`Relay enabled at ${W}`),console.log(`Config saved to ${E()}.`)}var B=null;async function C(j){return B??=y({input:x,output:v}),(await B.question(j)).trim()}function I(){B?.close(),B=null}async function b(j,G={}){let F=G.prompt??C,J=G.authorize??z,M=G.exchange??N,Q=j?.trim()||await F("Fallback account label (optional): "),X=await J("max");console.log(`
|
|
10
10
|
Open this URL in your browser and complete Claude sign-in:
|
|
11
11
|
`),console.log(`${X.url}
|
|
12
|
-
`);let Z=await F("Paste the full callback URL or authorization code here: "),Y=await M(Z,X.verifier,X.redirectUri,X.state);if(Y.type==="failed")throw Error("Authentication failed");let $=Date.now();await U({id:Q||crypto.randomUUID(),label:Q||void 0,type:"oauth",access:Y.access,refresh:Y.refresh,expires:Y.expires,enabled:!0,addedAt:$,lastUsed:$,lastRefreshedAt:$}),console.log(`
|
|
12
|
+
`);let Z=await F("Paste the full callback URL or authorization code here: "),Y=await M(Z,X.verifier,X.redirectUri,X.state);if(Y.type==="failed")throw Error("Authentication failed");let $=Date.now();await U({id:Q||crypto.randomUUID(),label:Q||void 0,type:"oauth",authLineageId:crypto.randomUUID(),access:Y.access,refresh:Y.refresh,expires:Y.expires,enabled:!0,addedAt:$,lastUsed:$,lastRefreshedAt:$}),console.log(`
|
|
13
13
|
Saved fallback account${Q?` "${Q}"`:""}.`)}async function d(j,G={}){let F=G.prompt??C,J=j?.trim()||await F("API fallback label (optional): "),M=process.env.OPENCODE_ANTHROPIC_AUTH_API_BASE_URL?.trim()||(await F("Anthropic-compatible base URL [https://api.kie.ai/claude]: ")).trim()||"https://api.kie.ai/claude";if(!L(M))throw Error("API fallback base URL must be an http(s) URL without embedded credentials");let Q=process.env.OPENCODE_ANTHROPIC_AUTH_API_KEY?.trim()||await F("API key: ");if(!Q.trim())throw Error("API key is required");let Z=(process.env.OPENCODE_ANTHROPIC_AUTH_API_AUTH_HEADER?.trim()||await F("Auth header [authorization-bearer|x-api-key] (default authorization-bearer): ")).trim().toLowerCase()==="x-api-key"?"x-api-key":"authorization-bearer",Y=Date.now();await U({id:J||crypto.randomUUID(),label:J||void 0,type:"api",apiKey:Q.trim(),baseURL:M,authHeader:Z,enabled:!0,addedAt:Y,lastUsed:Y}),console.log(`
|
|
14
14
|
Saved API fallback route${J?` "${J}"`:""} (${M}).`)}async function m(){let j=await K();if(!j?.accounts.length){console.log(`No fallback accounts found at ${E()}.`);return}for(let[G,F]of j.accounts.entries()){let J=F.label||F.id,M=F.enabled===!1?"disabled":"enabled";if(!P(F)){console.log(`${G+1}. ${J} (${M}) — API route ${F.baseURL}`);continue}let Q=F.quota?.five_hour?.remainingPercent,X=F.quota?.seven_day?.remainingPercent,Z=Q===void 0&&X===void 0?"quota unknown":`5h ${Q??"?"}%, 1w ${X??"?"}% remaining`;console.log(`${G+1}. ${J} (${M}) — ${Z}`)}}async function u(){let[j,G,F]=process.argv.slice(2);if(!j||j==="help"||j==="--help"||j==="-h"){_();return}if(j==="login"){await b(G);return}if(j==="api"&&G==="add"){await d(F);return}if(j==="list"){await m();return}if(j==="relay"&&G==="setup"){await f();return}_(),process.exitCode=1}if(w.main==w.module)try{await u()}catch(j){console.error(j instanceof Error?j.message:String(j)),process.exitCode=1}finally{I()}export{f as relaySetup,b as login,d as addApiRoute};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{Kc as O}from"./index-vkecgsm3.js";import{randomUUID as H}from"node:crypto";import{mkdir as I,readFile as k,rename as g,rm as N,rmdir as F,stat as P,unlink as w,writeFile as T}from"node:fs/promises";import{tmpdir as m}from"node:os";import{dirname as y,join as L}from"node:path";var d="OPENCODE_ANTHROPIC_AUTH_SIDEBAR_STATE_FILE",v=L(m(),"opencode-anthropic-auth"),c=L(v,"sidebar-state.json");function E(){return process.env[d]||c}var C={main:{quota:null},fallbacks:[],activeId:void 0,route:"main",relay:null,fastMode:!1,lastUpdated:0};function K(J){return J!==null&&typeof J==="object"&&!Array.isArray(J)}function q(J){return typeof J==="number"&&Number.isFinite(J)}function u(J){if(!K(J))return;let Z=Number(J.count),V=Number(J.inputTokens),x=Number(J.outputTokens),Y=Number(J.since);if(![Z,V,x,Y].every(Number.isFinite)||Z<0||V<0||x<0||Y<0)return;return{count:Math.floor(Z),inputTokens:Math.floor(V),outputTokens:Math.floor(x),since:Math.floor(Y)}}function n(J){if(!K(J))return;if(typeof J.id!=="string"||!J.id.trim())return;if(typeof J.label!=="string"||!J.label.trim())return;let Z={id:J.id.trim(),label:J.label.trim()};if(J.nextDueAt===null)Z.nextDueAt=null;else if(q(J.nextDueAt))Z.nextDueAt=J.nextDueAt;if(J.lastPrimedAt===null)Z.lastPrimedAt=null;else if(q(J.lastPrimedAt))Z.lastPrimedAt=J.lastPrimedAt;if(J.lastResult==="ok"||J.lastResult==="error")Z.lastResult=J.lastResult;let V=u(J.usage);if(V)Z.usage=V;if(q(J.estimatedCostUsd))Z.estimatedCostUsd=J.estimatedCostUsd;return Z}function i(J){if(!K(J))return;if(typeof J.enabled!=="boolean")return;let Z=Array.isArray(J.accounts)?J.accounts.map(n).filter((V)=>V!=null):[];return{enabled:J.enabled,accounts:Z}}function h(J){if(!K(J))return;let Z=Number(J.usedPercent),V=Number(J.remainingPercent);if(!Number.isFinite(Z)||!Number.isFinite(V))return;return{usedPercent:Z,remainingPercent:V,resetsAt:typeof J.resetsAt==="string"?J.resetsAt:void 0}}function b(J){if(!K(J))return null;let Z={},V=h(J.five_hour),x=h(J.seven_day);if(V)Z.five_hour=V;if(x)Z.seven_day=x;if(Array.isArray(J.scoped)){let Y=J.scoped.map((X)=>{if(!K(X))return;let $=h(X);if(!$)return;if(typeof X.id!=="string"||!X.id.trim())return;if(typeof X.title!=="string"||!X.title.trim())return;if(typeof X.modelName!=="string"||!X.modelName.trim())return;let j=typeof X.modelId==="string"&&X.modelId.trim()?X.modelId.trim():void 0;return{...$,id:X.id.trim(),title:X.title.trim(),...j&&{modelId:j},modelName:X.modelName.trim()}}).filter((X)=>X!=null);Z.scoped=Y}if(K(J.extraUsage)){let Y=A(J.extraUsage.used),X=A(J.extraUsage.limit);if(Y&&X&&typeof J.extraUsage.exhausted==="boolean")Z.extraUsage={used:Y,limit:X,...typeof J.extraUsage.utilizationPercent==="number"&&Number.isFinite(J.extraUsage.utilizationPercent)&&{utilizationPercent:J.extraUsage.utilizationPercent},...typeof J.extraUsage.severity==="string"&&{severity:J.extraUsage.severity},exhausted:J.extraUsage.exhausted}}if(typeof J.bindingWindow==="string"&&J.bindingWindow.trim())Z.bindingWindow=J.bindingWindow.trim();if(typeof J.fallbackAdvised==="boolean")Z.fallbackAdvised=J.fallbackAdvised;return Object.keys(Z).length?Z:null}function A(J){if(!K(J))return;if(!Number.isInteger(J.amountMinor)||typeof J.currency!=="string"||!/^[A-Za-z]{3}$/.test(J.currency.trim())||!Number.isInteger(J.exponent)||J.exponent<0||J.exponent>20)return;return{amountMinor:J.amountMinor,currency:J.currency.trim(),exponent:J.exponent}}function s(J){if(!K(J))return{...C};let Z={quota:null};if(K(J.main)){let $=J.main;if(Z.quota=b($.quota),typeof $.tierLabel==="string"&&$.tierLabel.trim())Z.tierLabel=$.tierLabel.trim();if(typeof $.quotaBackedOff==="boolean")Z.quotaBackedOff=$.quotaBackedOff;if(typeof $.quotaBackoffUntil==="number")Z.quotaBackoffUntil=$.quotaBackoffUntil;if(typeof $.refreshBackedOff==="boolean")Z.refreshBackedOff=$.refreshBackedOff;if(typeof $.refreshBackoffUntil==="number")Z.refreshBackoffUntil=$.refreshBackoffUntil}let V=Array.isArray(J.fallbacks)?J.fallbacks.filter(K).filter(($)=>typeof $.id==="string").map(($)=>({id:$.id,label:typeof $.label==="string"?$.label:void 0,quota:b($.quota),enabled:typeof $.enabled==="boolean"?$.enabled:!1,needsReauth:typeof $.needsReauth==="boolean"?$.needsReauth:!1,tierLabel:typeof $.tierLabel==="string"&&$.tierLabel.trim()?$.tierLabel.trim():void 0})):[],x=null;if(K(J.relay)){let $=J.relay;if(typeof $.enabled==="boolean"&&typeof $.transport==="string")x={enabled:$.enabled,transport:$.transport}}let Y;if(K(J.cacheKeep)){let $=J.cacheKeep;if(typeof $.enabled==="boolean")Y={enabled:$.enabled,window:typeof $.window==="string"?$.window:void 0,trackedSessions:typeof $.trackedSessions==="number"?$.trackedSessions:void 0}}let X=Array.isArray(J.fableRecoveries)?J.fableRecoveries.filter(K).flatMap(($)=>{if(typeof $.sessionId!=="string"||$.mode!=="opus"&&$.mode!=="fable"&&$.mode!=="server"||typeof $.remaining!=="number"||!Number.isFinite($.remaining)||typeof $.changedAt!=="number"||!Number.isFinite($.changedAt))return[];return[{sessionId:$.sessionId,mode:$.mode,remaining:Math.max(0,Math.floor($.remaining)),changedAt:$.changedAt,requestedModelId:typeof $.requestedModelId==="string"?$.requestedModelId:void 0,targetModelId:typeof $.targetModelId==="string"?$.targetModelId:void 0}]}):[];return{main:Z,fallbacks:V,activeId:typeof J.activeId==="string"?J.activeId:void 0,route:typeof J.route==="string"?J.route:C.route,relay:x,fastMode:typeof J.fastMode==="boolean"?J.fastMode:C.fastMode,cacheKeep:Y,prime:i(J.prime),fableRecoveries:X.length>0?X:void 0,lastUpdated:typeof J.lastUpdated==="number"?J.lastUpdated:0}}var W=Promise.resolve(),l=1000,o=2000,r=5,a=15,U=new Map;function QJ(J,Z=E()){if(J)U.set(Z,J);else U.delete(Z)}function M(J){return U.get(J)}var S=null;function BJ(J){S=J}function OJ(){return S}function t(J){return new Promise((Z)=>setTimeout(Z,J))}async function p(J){let Z=`${J}.lock`,V=M(J),x=V?.lockBudgetMs??l,Y=V?.lockRetryMinMs??r,X=V?.lockRetryMaxMs??a,$=Date.now()+x;while(!0){try{await I(Z);let G=H(),Q=L(Z,G);return await T(Q,"",{encoding:"utf8",mode:384,flag:"wx"}),await V?.onLockAcquired?.(Z),{ownsLock:async()=>{try{return await P(Q),!0}catch{return!1}},release:async()=>{try{await w(Q)}catch{return}await V?.beforeReleaseDirectoryRemoval?.(Z),await F(Z).catch(()=>{})}}}catch(G){if(G.code!=="EEXIST")return O.trace("sidebar","state lock unavailable; write skipped",{stateFile:J,error:G instanceof Error?G.message:String(G)}),null}try{let G=await P(Z);if(Date.now()-G.mtimeMs>o){await V?.afterStaleLockStat?.(Z);let Q=`${Z}.evict-${process.pid}-${H()}`;try{await g(Z,Q)}catch(z){if(z.code==="ENOENT")continue;throw z}await V?.onStaleLockClaimed?.(Z),await N(Q,{recursive:!0,force:!0}).catch(()=>{});continue}}catch(G){if(G.code==="ENOENT")continue;return O.trace("sidebar","state lock inspection failed; write skipped",{stateFile:J,error:G instanceof Error?G.message:String(G)}),null}let j=$-Date.now();if(j<=0)return O.warn("sidebar","lock budget exhausted, write skipped",{stateFile:J,budgetMs:x}),null;let B=Y+Math.floor(Math.random()*(X-Y+1));await t(Math.min(B,j))}}async function zJ(J){return(await p(J))?.release??null}async function e(J,Z,V){let x=M(J),Y=`${J}.${process.pid}.${H()}.tmp`;await T(Y,JSON.stringify(Z),{encoding:"utf8",mode:384});try{if(!await V())return O.trace("sidebar","state lock lost before rename; write aborted",{stateFile:J}),await N(Y,{force:!0}).catch(()=>{}),"lock-lost-before-rename";if(await x?.beforeRename?.(J,Y),!await V())return O.trace("sidebar","state lock lost before rename; write aborted",{stateFile:J}),await N(Y,{force:!0}).catch(()=>{}),"lock-lost-before-rename";if(await g(Y,J),await x?.afterRename?.(J),!await V())return"lock-lost-after-rename";return"written"}catch(X){throw await N(Y,{force:!0}).catch(()=>{}),X}}async function R(J=E()){try{let Z=await k(J,"utf8");return s(JSON.parse(Z))}catch{return C}}async function NJ(J,Z=E(),V={}){return W=W.then(async()=>{await I(y(Z),{recursive:!0});for(let x=0;x<2;x++){let Y=x===1,X=await p(Z);if(!X){if(Y)O.warn("sidebar","post-rename repair lock unavailable; write skipped",{stateFile:Z});return}let $=J,j;try{if(V.routingAuthoritative===!1){let B=await R(Z),G=await V.resolvePreservedRouting?.(B);if(G){let Q=G.state??J;$={...Q,activeId:G.activeId,route:G.route,lastUpdated:Math.max(Q.lastUpdated,B.lastUpdated)}}await M(Z)?.afterMergeRead?.(Z)}else if(Y){let B=await R(Z);$={...B,activeId:J.activeId,route:J.route,relay:J.relay,fastMode:J.fastMode,cacheKeep:J.cacheKeep,lastUpdated:Math.max(B.lastUpdated,J.lastUpdated)}}j=await e(Z,$,X.ownsLock)}finally{await X.release()}if(j==="lock-lost-after-rename"){if(Y){O.warn("sidebar","post-rename repair lost lock; write skipped",{stateFile:Z});return}O.warn("sidebar","state lock lost after rename; repairing write",{stateFile:Z});continue}if(j==="lock-lost-before-rename")return;V.onRoutingResolved?.({activeId:$.activeId,route:$.route});return}}).catch(()=>{}),W}async function CJ(){return W}function WJ(J){let Z=J.activeId;if(Z&&Z!=="main"){let V=J.fallbacks?.find((x)=>x.enabled&&x.id===Z);if(V)return{id:V.id,name:V.label??V.id,quota:V.quota}}return{id:"main",name:"main",quota:J.main?.quota??null}}function JJ(J){let Z=J.replace(/\s+only$/i,"").trim();return/^fable$/i.test(Z)?"Fa":Z}function f(J){if(J==="claude-fable-5"||J?.startsWith("claude-fable-5-"))return"Fable 5";if(J==="claude-opus-5"||J?.startsWith("claude-opus-5-"))return"Opus 5";if(J==="claude-opus-4-8"||J?.startsWith("claude-opus-4-8-"))return"Opus 4.8";return J??"Fable 5"}function D(J){return new Date(J).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}function ZJ(J){if(J===0)return"0";if(J<0.0001)return J.toExponential(2);return J.toFixed(Math.min(6,Math.max(0,4)))}function qJ(J){if(J.lastResult==="error")return{text:"err",hasError:!0};if(J.nextDueAt&&J.nextDueAt>Date.now())return{text:D(J.nextDueAt),hasError:!1};if(J.lastPrimedAt)return{text:`primed ${D(J.lastPrimedAt)} ✓`,hasError:!1};if(J.usage?.count)return{text:`✓ ${J.usage.count} ≈ $${ZJ(J.estimatedCostUsd??0)}`,hasError:!1};return{text:"—",hasError:!1}}function hJ(J,Z){let V=J.fableRecoveries?.find((x)=>x.sessionId===Z);if(!V)return;if(V.mode==="fable")return`${f(V.requestedModelId)} · restored`;if(V.mode==="server")return`${f(V.requestedModelId)}→${f(V.targetModelId)} · safety`;return`Opus 4.8 · ${V.remaining} left`}function fJ(J){let Z=J?.five_hour?.usedPercent??null,V=J?.seven_day?.usedPercent??null,x=J?.scoped??[],Y=x.map((j)=>j.usedPercent);if(Z==null&&V==null&&x.length===0)return{fiveHourUsedPercent:Z,sevenDayUsedPercent:V,scopedUsedPercents:Y,text:null};let X=x.map((j)=>`${JJ(j.title)}: ${Math.round(j.usedPercent)}%`),$=Z==null&&V==null?[]:[`5h: ${Z==null?"—":`${Math.round(Z)}%`}`,`7d: ${V==null?"—":`${Math.round(V)}%`}`];return{fiveHourUsedPercent:Z,sevenDayUsedPercent:V,scopedUsedPercents:Y,text:[...$,...X].join(" ")}}var HJ=18000000,UJ=604800000,$J=300000,VJ=0.01,XJ=1;function LJ(J,Z,V){if(!J.resetsAt)return null;let x=new Date(J.resetsAt).getTime();if(!Number.isFinite(x))return null;let Y=x-Z,X=V-Y;if(X<$J)return null;if(X<Z*VJ)return null;if(X>=Z)return null;let $=J.usedPercent,j=Math.min(Math.max(X/Z*100,0),100),B=$-j,G=Math.abs(B)<XJ?"on-pace":B>0?"deficit":"reserve",Q=null;if($>0){let z=X*100/$,_=Y+z;if(_<x)Q=new Date(_).toISOString()}return{pacePercent:j,deltaPercent:B,state:G,runsOutAt:Q}}
|
|
2
|
+
export{E as a,C as b,s as c,QJ as d,BJ as e,OJ as f,zJ as g,R as h,NJ as i,CJ as j,WJ as k,JJ as l,f as m,D as n,ZJ as o,qJ as p,hJ as q,fJ as r,HJ as s,UJ as t,LJ as u};
|