opencode-claude-auth 2.1.4 → 2.1.6
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 +20 -10
- package/dist/credentials.d.ts +90 -7
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +557 -59
- package/dist/credentials.js.map +1 -1
- package/dist/http.d.ts +3 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +74 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +183 -68
- package/dist/index.js.map +1 -1
- package/dist/keychain.d.ts +20 -1
- package/dist/keychain.d.ts.map +1 -1
- package/dist/keychain.js +66 -1
- package/dist/keychain.js.map +1 -1
- package/dist/refresh-backoff.d.ts +49 -0
- package/dist/refresh-backoff.d.ts.map +1 -0
- package/dist/refresh-backoff.js +99 -0
- package/dist/refresh-backoff.js.map +1 -0
- package/dist/refresh-lock.d.ts +22 -0
- package/dist/refresh-lock.d.ts.map +1 -0
- package/dist/refresh-lock.js +117 -0
- package/dist/refresh-lock.js.map +1 -0
- package/dist/transforms.d.ts +41 -1
- package/dist/transforms.d.ts.map +1 -1
- package/dist/transforms.js +238 -46
- package/dist/transforms.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -151,14 +151,19 @@ This reads your stored credentials, calls Anthropic's OAuth token endpoint, and
|
|
|
151
151
|
|
|
152
152
|
All configurable parameters can be overridden via environment variables. If Anthropic changes something before we publish an update, set an env var and keep working:
|
|
153
153
|
|
|
154
|
-
| Variable
|
|
155
|
-
|
|
|
156
|
-
| `ANTHROPIC_CLI_VERSION`
|
|
157
|
-
| `ANTHROPIC_USER_AGENT`
|
|
158
|
-
| `ANTHROPIC_BETA_FLAGS`
|
|
159
|
-
| `CLAUDE_AUTH_DEBUG`
|
|
160
|
-
| `CLAUDE_CONFIG_DIR`
|
|
161
|
-
| `OPENCODE_CLAUDE_AUTH_MAX_RETRY_MS`
|
|
154
|
+
| Variable | Description | Default |
|
|
155
|
+
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
|
|
156
|
+
| `ANTHROPIC_CLI_VERSION` | Claude CLI version for user-agent and billing headers | `config.ccVersion` in [`src/model-config.ts`](src/model-config.ts) |
|
|
157
|
+
| `ANTHROPIC_USER_AGENT` | Full User-Agent string (overrides CLI version) | `claude-cli/{version} (external, sdk-cli)` |
|
|
158
|
+
| `ANTHROPIC_BETA_FLAGS` | Comma-separated beta feature flags | `baseBetas` list in [`src/model-config.ts`](src/model-config.ts) |
|
|
159
|
+
| `CLAUDE_AUTH_DEBUG` | Enable diagnostic logging (`1` for default path, or a custom file path) | disabled |
|
|
160
|
+
| `CLAUDE_CONFIG_DIR` | Claude Code config directory used for the credentials-file fallback (reads `$CLAUDE_CONFIG_DIR/.credentials.json`). macOS still checks the Keychain first. | `~/.claude` |
|
|
161
|
+
| `OPENCODE_CLAUDE_AUTH_MAX_RETRY_MS` | Max ms the plugin waits when honouring a 429/529 `retry-after` header. Beyond this cap the response surfaces immediately so OpenCode doesn't appear to hang on hour-long quota resets. | `30000` |
|
|
162
|
+
| `OPENCODE_CLAUDE_AUTH_TOOL_REPAIR` | Strategy for reconciling `tool_use`/`tool_result` adjacency broken by OpenCode auto-compaction. `placeholder` synthesizes a paired result for orphaned `tool_use` blocks (lossless, preserves `thinking` blocks); `drop` removes orphaned blocks (omitting whole thinking turns). | `placeholder` |
|
|
163
|
+
| `OPENCODE_CLAUDE_AUTH_REFRESH_WAIT_MS` | Max ms a single request waits through a transient token-refresh rate-limit (429) before returning a retryable error instead of a hard "run `claude`". | `45000` |
|
|
164
|
+
| `OPENCODE_CLAUDE_AUTH_REFRESH_COOLDOWN_MS` | Base per-account cooldown after a rate-limited refresh, before the plugin retries the token endpoint. Escalates with consecutive failures and is jittered; capped at 60s. | `15000` |
|
|
165
|
+
| `OPENCODE_CLAUDE_AUTH_REFRESH_LOCK_TTL_MS` | TTL for the cross-process refresh lock. A held lock older than this is treated as stale (crashed holder) and taken over. | `20000` |
|
|
166
|
+
| `OPENCODE_CLAUDE_AUTH_REFRESH_LOCK_DIR` | Directory for the advisory cross-process refresh lock files. | OpenCode data dir (`~/.local/share/opencode`) |
|
|
162
167
|
|
|
163
168
|
Example:
|
|
164
169
|
|
|
@@ -176,10 +181,15 @@ export ANTHROPIC_CLI_VERSION=2.2.0
|
|
|
176
181
|
- Sets required API headers (beta flags, billing, user-agent) with model-aware selection
|
|
177
182
|
- On macOS, enumerates all `Claude Code-credentials*` Keychain entries and labels them by subscription tier
|
|
178
183
|
- Provides an account switcher via `opencode auth login` when multiple accounts are found; persists selection to `~/.local/share/opencode/claude-account-source.txt`
|
|
179
|
-
- Syncs credentials to `auth.json` on startup and every 5 minutes as a fallback
|
|
184
|
+
- Syncs credentials to `auth.json` on startup and every 5 minutes as a fallback; that same tick proactively refreshes once the token is within an hour of expiry
|
|
180
185
|
- On Windows, writes to both `%USERPROFILE%\.local\share\opencode\auth.json` and `%LOCALAPPDATA%\opencode\auth.json`
|
|
186
|
+
- Re-reads the credential source on every cache miss, so an account rotated by something other than this plugin — the `claude` CLI in another terminal, a second OpenCode instance, or a switcher like [claude-swap](https://github.com/realiti4/claude-swap) — gets picked up mid-session without a restart. Bounded by the same 30s cache, so it adds at most about two source reads a minute under load. A stored token is adopted whenever it is usable, and when it isn't only if the one already held is also unusable — otherwise a failed write-back would resurrect the pre-refresh token it left behind
|
|
187
|
+
- Guards credential write-back with the access token the refresh started from, so a switch landing mid-refresh can't write one account's rotated tokens into another account's slot
|
|
181
188
|
- Retries API requests on 429 (rate limit) and 529 (overloaded) with exponential backoff, respecting `retry-after` headers
|
|
182
|
-
-
|
|
189
|
+
- On a 429 that outlives those backoff retries, re-reads the source once and retries only if the access token changed, so a rate limit another process has already resolved by switching accounts isn't surfaced. A changed token isn't proof of a switch — a routine refresh of the same account changes it too — so this costs at most one extra request
|
|
190
|
+
- On a 401, recovers in place rather than surfacing it: adopts an externally rotated token if the source now holds one, otherwise forces an OAuth refresh, then retries the request. Bounded at two attempts, so a rejected token costs at most three API calls. A 401 that survives recovery is returned unmodified, without SSE stream transformation, since it carries an error body rather than a stream
|
|
191
|
+
- Refreshes directly via `POST https://claude.ai/v1/oauth/token` using the runtime's own `fetch` (no LLM tokens consumed, no subprocess). Requests are triggered within 60 seconds of expiry on the API request path and within an hour on the background tick; concurrent refreshes of one account share a single request, since each rotation invalidates the previous refresh token
|
|
192
|
+
- Falls back to the `claude` CLI only within the 60-second window, the point at which Claude Code will actually rotate the token — running it earlier costs a real API request and returns the same token. New tokens are written back to Keychain (macOS) or credentials file (Linux/Windows) to keep stored credentials in sync with rotated refresh tokens
|
|
183
193
|
- If credentials aren't OAuth-based, the auth loader returns `{}` and falls through to API key auth
|
|
184
194
|
- If credentials are unavailable or unreadable, the plugin disables itself and OpenCode continues without Claude auth
|
|
185
195
|
|
package/dist/credentials.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ClaudeAccount, type ClaudeCredentials } from "./keychain.ts";
|
|
2
|
+
import { type RefreshFailureKind } from "./refresh-backoff.ts";
|
|
2
3
|
export type { ClaudeAccount } from "./keychain.ts";
|
|
3
4
|
export type { ClaudeCredentials } from "./keychain.ts";
|
|
4
5
|
export declare function initAccounts(accounts: ClaudeAccount[]): void;
|
|
@@ -11,7 +12,59 @@ export declare function syncAuthJson(creds: ClaudeCredentials): void;
|
|
|
11
12
|
export declare const OAUTH_TOKEN_URL = "https://claude.ai/v1/oauth/token";
|
|
12
13
|
export declare const OAUTH_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
13
14
|
export declare function parseOAuthResponse(raw: string, currentRefreshToken: string, now?: number): ClaudeCredentials | null;
|
|
14
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Extract the non-secret failure reason from an OAuth token-endpoint error
|
|
17
|
+
* body so a refresh failure is diagnosable from the debug log. Handles both the
|
|
18
|
+
* OAuth shape (`{ error, error_description }`) and Anthropic's API error
|
|
19
|
+
* envelope (`{ error: { type, message } }`). Values are truncated and never
|
|
20
|
+
* include tokens; the logger additionally redacts anything JWT-shaped.
|
|
21
|
+
*/
|
|
22
|
+
export declare function extractOAuthError(raw: string): {
|
|
23
|
+
oauthError?: string;
|
|
24
|
+
oauthErrorDescription?: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Exchanges a refresh token for fresh credentials using the runtime's own
|
|
28
|
+
* fetch.
|
|
29
|
+
*
|
|
30
|
+
* This previously ran the request inside a child process spawned as
|
|
31
|
+
* `process.execPath -e <script>`. That assumed process.execPath is a
|
|
32
|
+
* JavaScript runtime, which does not hold inside OpenCode: the plugin runs
|
|
33
|
+
* in a compiled single-file executable, so process.execPath is the OpenCode
|
|
34
|
+
* binary itself and `-e` is not a script to evaluate. Every refresh exited
|
|
35
|
+
* non-zero with empty stdout and silently fell through to the claude CLI.
|
|
36
|
+
* Node 18+ and Bun both expose a global fetch, so no subprocess is needed.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Classified result of an OAuth refresh. A `transient` outcome (429/5xx/network
|
|
40
|
+
* /`rate_limit_error`) means the refresh token is still good and the caller
|
|
41
|
+
* should back off and retry rather than surface a hard error; a `terminal`
|
|
42
|
+
* outcome (`invalid_grant`, ...) means the refresh token is dead.
|
|
43
|
+
*/
|
|
44
|
+
export type RefreshOutcome = {
|
|
45
|
+
kind: "ok";
|
|
46
|
+
creds: ClaudeCredentials;
|
|
47
|
+
} | {
|
|
48
|
+
kind: "transient";
|
|
49
|
+
status: number;
|
|
50
|
+
oauthError?: string;
|
|
51
|
+
retryAfterMs?: number;
|
|
52
|
+
} | {
|
|
53
|
+
kind: "terminal";
|
|
54
|
+
status: number;
|
|
55
|
+
oauthError?: string;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Exchange a refresh token for fresh credentials and classify the result.
|
|
59
|
+
* See {@link RefreshOutcome}. Uses the runtime's own fetch (no subprocess).
|
|
60
|
+
*/
|
|
61
|
+
export declare function refreshViaOAuthDetailed(refreshToken: string, timeoutMs?: number): Promise<RefreshOutcome>;
|
|
62
|
+
/**
|
|
63
|
+
* Backward-compatible wrapper: returns credentials on success, else null.
|
|
64
|
+
* Prefer {@link refreshViaOAuthDetailed} when the transient/terminal
|
|
65
|
+
* distinction matters (cooldown, CLI-fallback gating).
|
|
66
|
+
*/
|
|
67
|
+
export declare function refreshViaOAuth(refreshToken: string, timeoutMs?: number): Promise<ClaudeCredentials | null>;
|
|
15
68
|
/**
|
|
16
69
|
* Refreshes the given (or active) account's credentials if they are within
|
|
17
70
|
* `thresholdMs` of expiry. Defaults to 60s, matching the reactive
|
|
@@ -21,13 +74,19 @@ export declare function refreshViaOAuth(refreshToken: string): ClaudeCredentials
|
|
|
21
74
|
* of threshold, so this always operates on the currently active account
|
|
22
75
|
* unless one is explicitly passed in.
|
|
23
76
|
*/
|
|
24
|
-
export declare function refreshIfNeeded(account?: ClaudeAccount, thresholdMs?: number): ClaudeCredentials | null
|
|
77
|
+
export declare function refreshIfNeeded(account?: ClaudeAccount, thresholdMs?: number): Promise<ClaudeCredentials | null>;
|
|
25
78
|
export declare function getCredentialsForSync(): ClaudeCredentials | null;
|
|
26
79
|
/**
|
|
27
80
|
* Re-read only the active account's credentials from its source (single
|
|
28
|
-
* keychain service read or credentials file) and update them in place
|
|
29
|
-
*
|
|
30
|
-
*
|
|
81
|
+
* keychain service read or credentials file) and update them in place,
|
|
82
|
+
* so an externally refreshed token is picked up without a full
|
|
83
|
+
* multi-account keychain rescan.
|
|
84
|
+
*
|
|
85
|
+
* Currently has no call sites: the 401 path uses
|
|
86
|
+
* reloadCredentialsFromSource, which additionally validates the result
|
|
87
|
+
* and refreshes the cache. Wiring this up or deleting it is tracked as a
|
|
88
|
+
* follow-up; until then it must stay consistent with the read paths that
|
|
89
|
+
* are live, hence the configDir below.
|
|
31
90
|
*/
|
|
32
91
|
export declare function reloadActiveAccount(): void;
|
|
33
92
|
/**
|
|
@@ -37,7 +96,7 @@ export declare function reloadActiveAccount(): void;
|
|
|
37
96
|
* On success the account, its source, and the cache are all updated.
|
|
38
97
|
* The refresh function is injectable for tests.
|
|
39
98
|
*/
|
|
40
|
-
export declare function forceRefreshActiveAccount(refresh?: (refreshToken: string) => ClaudeCredentials | null): ClaudeCredentials | null
|
|
99
|
+
export declare function forceRefreshActiveAccount(refresh?: (refreshToken: string) => Promise<ClaudeCredentials | null>): Promise<ClaudeCredentials | null>;
|
|
41
100
|
/**
|
|
42
101
|
* Drop the active account's cached credentials so the next
|
|
43
102
|
* getCachedCredentials() call re-reads from the source, bypassing the
|
|
@@ -45,6 +104,30 @@ export declare function forceRefreshActiveAccount(refresh?: (refreshToken: strin
|
|
|
45
104
|
* valid locally.
|
|
46
105
|
*/
|
|
47
106
|
export declare function invalidateCredentialCache(): void;
|
|
48
|
-
export declare function getCachedCredentials(): ClaudeCredentials | null
|
|
107
|
+
export declare function getCachedCredentials(): Promise<ClaudeCredentials | null>;
|
|
108
|
+
export interface CredentialWaitOptions {
|
|
109
|
+
maxWaitMs?: number;
|
|
110
|
+
pollMs?: number;
|
|
111
|
+
signal?: AbortSignal;
|
|
112
|
+
now?: () => number;
|
|
113
|
+
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
114
|
+
rng?: () => number;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Resolve credentials, waiting through a transient refresh rate-limit rather
|
|
118
|
+
* than failing hard. Returns as soon as a token is available — ours refreshed
|
|
119
|
+
* once the cooldown clears, or a sibling OpenCode instance / the `claude` CLI
|
|
120
|
+
* wrote a fresh one to the shared store. Returns null promptly on a terminal
|
|
121
|
+
* failure (dead refresh token) or when the wait budget is exhausted, so the
|
|
122
|
+
* caller can decide between a retryable response and a hard error.
|
|
123
|
+
*/
|
|
124
|
+
export declare function getCredentialsWithBackoff(opts?: CredentialWaitOptions): Promise<ClaudeCredentials | null>;
|
|
125
|
+
/**
|
|
126
|
+
* Whether the active account's most recent refresh failure was transient
|
|
127
|
+
* (rate-limited/retryable) or terminal (dead refresh token), for callers
|
|
128
|
+
* deciding between a retryable response and a hard "re-authenticate" error.
|
|
129
|
+
* An active cooldown implies a transient failure.
|
|
130
|
+
*/
|
|
131
|
+
export declare function getActiveRefreshFailureKind(): RefreshFailureKind | null;
|
|
49
132
|
export declare function reloadCredentialsFromSource(): ClaudeCredentials | null;
|
|
50
133
|
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAUA,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACvB,MAAM,eAAe,CAAA;AAItB,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAClD,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
|