opencode-anthropic-fix 0.0.18 → 0.0.19
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/index.mjs +48 -14
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -103,7 +103,7 @@ async function promptManageAccounts(accountManager) {
|
|
|
103
103
|
// Request building helpers (extracted from original fetch interceptor)
|
|
104
104
|
// ---------------------------------------------------------------------------
|
|
105
105
|
|
|
106
|
-
const FALLBACK_CLAUDE_CLI_VERSION = "2.1.
|
|
106
|
+
const FALLBACK_CLAUDE_CLI_VERSION = "2.1.76";
|
|
107
107
|
const CLAUDE_CODE_NPM_LATEST_URL = "https://registry.npmjs.org/@anthropic-ai/claude-code/latest";
|
|
108
108
|
const CLAUDE_CODE_BETA_FLAG = "claude-code-20250219";
|
|
109
109
|
const BILLING_HASH_SALT = "59cf53e54c78";
|
|
@@ -292,7 +292,10 @@ function supportsThinking(model) {
|
|
|
292
292
|
*/
|
|
293
293
|
function isOpus46Model(model) {
|
|
294
294
|
if (!model) return false;
|
|
295
|
-
|
|
295
|
+
// Match standard IDs (claude-opus-4-6, claude-opus-4.6) and Bedrock ARNs
|
|
296
|
+
// (arn:aws:bedrock:...anthropic.claude-opus-4-6-...).
|
|
297
|
+
// Also match bare "opus-4-6" / "opus-4.6" fragments for non-standard strings.
|
|
298
|
+
return /claude-opus-4[._-]6|opus[._-]4[._-]6/i.test(model);
|
|
296
299
|
}
|
|
297
300
|
|
|
298
301
|
/**
|
|
@@ -300,7 +303,8 @@ function isOpus46Model(model) {
|
|
|
300
303
|
* @returns {boolean}
|
|
301
304
|
*/
|
|
302
305
|
function hasOneMillionContext(model) {
|
|
303
|
-
|
|
306
|
+
// Models with explicit 1m suffix, or Opus 4.6 (1M by default since v2.1.75).
|
|
307
|
+
return /(^|[-_ ])1m($|[-_ ])|context[-_]?1m/i.test(model) || isOpus46Model(model);
|
|
304
308
|
}
|
|
305
309
|
|
|
306
310
|
/**
|
|
@@ -732,7 +736,7 @@ function buildAnthropicBetaHeader(incomingBeta, signatureEnabled, model, provide
|
|
|
732
736
|
return mergedBetas.join(",");
|
|
733
737
|
}
|
|
734
738
|
|
|
735
|
-
/** @typedef {'low' | 'medium' | 'high'
|
|
739
|
+
/** @typedef {'low' | 'medium' | 'high'} ThinkingEffort */
|
|
736
740
|
|
|
737
741
|
/**
|
|
738
742
|
* Map budgetTokens to an adaptive thinking effort level.
|
|
@@ -743,8 +747,7 @@ function buildAnthropicBetaHeader(incomingBeta, signatureEnabled, model, provide
|
|
|
743
747
|
function budgetTokensToEffort(budgetTokens) {
|
|
744
748
|
if (budgetTokens <= 1024) return "low";
|
|
745
749
|
if (budgetTokens <= 8000) return "medium";
|
|
746
|
-
|
|
747
|
-
return "max";
|
|
750
|
+
return "high";
|
|
748
751
|
}
|
|
749
752
|
|
|
750
753
|
/**
|
|
@@ -753,7 +756,7 @@ function budgetTokensToEffort(budgetTokens) {
|
|
|
753
756
|
* @returns {value is ThinkingEffort}
|
|
754
757
|
*/
|
|
755
758
|
function isValidEffort(value) {
|
|
756
|
-
return value === "low" || value === "medium" || value === "high"
|
|
759
|
+
return value === "low" || value === "medium" || value === "high";
|
|
757
760
|
}
|
|
758
761
|
|
|
759
762
|
/**
|
|
@@ -1427,10 +1430,14 @@ function applyDiskAuthIfFresher(account, diskAuth, options = {}) {
|
|
|
1427
1430
|
* @param {import('./lib/accounts.mjs').ManagedAccount} account
|
|
1428
1431
|
* @param {ReturnType<typeof import('@opencode-ai/sdk').createOpencodeClient>} client
|
|
1429
1432
|
* @param {"foreground" | "idle"} [source]
|
|
1433
|
+
* @param {{ onTokensUpdated?: () => Promise<void> }} [options] - If provided,
|
|
1434
|
+
* called under the cross-process lock after token update to persist rotated
|
|
1435
|
+
* tokens before the lock is released. Omitting means tokens won't be saved
|
|
1436
|
+
* to disk until the caller arranges it (risking the rotation race).
|
|
1430
1437
|
* @returns {Promise<string>} The new access token
|
|
1431
1438
|
* @throws {Error} If refresh fails
|
|
1432
1439
|
*/
|
|
1433
|
-
async function refreshAccountToken(account, client, source = "foreground") {
|
|
1440
|
+
async function refreshAccountToken(account, client, source = "foreground", { onTokensUpdated } = {}) {
|
|
1434
1441
|
const lockResult = await acquireRefreshLock(account.id, {
|
|
1435
1442
|
timeoutMs: 2_000,
|
|
1436
1443
|
backoffMs: 60,
|
|
@@ -1471,6 +1478,22 @@ async function refreshAccountToken(account, client, source = "foreground") {
|
|
|
1471
1478
|
}
|
|
1472
1479
|
markTokenStateUpdated(account);
|
|
1473
1480
|
|
|
1481
|
+
// Persist new tokens to disk BEFORE releasing the cross-process lock.
|
|
1482
|
+
// This is critical: if we release the lock first, another process can
|
|
1483
|
+
// acquire it and read the old (now-rotated) refresh token from disk,
|
|
1484
|
+
// leading to an invalid_grant failure. The debounced requestSaveToDisk()
|
|
1485
|
+
// that callers used previously left a ~1 s window where this race could
|
|
1486
|
+
// (and did) happen.
|
|
1487
|
+
if (onTokensUpdated) {
|
|
1488
|
+
try {
|
|
1489
|
+
await onTokensUpdated();
|
|
1490
|
+
} catch {
|
|
1491
|
+
// Best-effort: in-memory tokens remain valid for this process.
|
|
1492
|
+
// The callback is responsible for scheduling its own fallback
|
|
1493
|
+
// (e.g. a debounced retry) if the synchronous save fails.
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1474
1497
|
// Also persist to OpenCode's auth.json for compatibility.
|
|
1475
1498
|
// This should be best-effort: a persistence hiccup should not invalidate an
|
|
1476
1499
|
// otherwise successful refresh token exchange.
|
|
@@ -2011,7 +2034,21 @@ export async function AnthropicAuthPlugin({ client }) {
|
|
|
2011
2034
|
const entry = { source, promise: Promise.resolve("") };
|
|
2012
2035
|
const p = (async () => {
|
|
2013
2036
|
try {
|
|
2014
|
-
return await refreshAccountToken(account, client, source
|
|
2037
|
+
return await refreshAccountToken(account, client, source, {
|
|
2038
|
+
onTokensUpdated: async () => {
|
|
2039
|
+
try {
|
|
2040
|
+
await accountManager.saveToDisk();
|
|
2041
|
+
} catch {
|
|
2042
|
+
// Synchronous save failed (disk full, permissions, etc.).
|
|
2043
|
+
// Schedule a debounced retry so the rotated token eventually
|
|
2044
|
+
// reaches disk. Another process may hit invalid_grant in the
|
|
2045
|
+
// interim, but its retry-from-disk logic can recover once this
|
|
2046
|
+
// save lands.
|
|
2047
|
+
accountManager.requestSaveToDisk();
|
|
2048
|
+
throw new Error("save failed, debounced retry scheduled");
|
|
2049
|
+
}
|
|
2050
|
+
},
|
|
2051
|
+
});
|
|
2015
2052
|
} finally {
|
|
2016
2053
|
if (refreshInFlight.get(key) === entry) {
|
|
2017
2054
|
refreshInFlight.delete(key);
|
|
@@ -2040,7 +2077,6 @@ export async function AnthropicAuthPlugin({ client }) {
|
|
|
2040
2077
|
try {
|
|
2041
2078
|
try {
|
|
2042
2079
|
await refreshAccountTokenSingleFlight(account, "idle");
|
|
2043
|
-
accountManager.requestSaveToDisk();
|
|
2044
2080
|
return;
|
|
2045
2081
|
} catch (err) {
|
|
2046
2082
|
let details = parseRefreshFailure(err);
|
|
@@ -2068,7 +2104,6 @@ export async function AnthropicAuthPlugin({ client }) {
|
|
|
2068
2104
|
|
|
2069
2105
|
try {
|
|
2070
2106
|
await refreshAccountTokenSingleFlight(account, "idle");
|
|
2071
|
-
accountManager.requestSaveToDisk();
|
|
2072
2107
|
return;
|
|
2073
2108
|
} catch (retryErr) {
|
|
2074
2109
|
details = parseRefreshFailure(retryErr);
|
|
@@ -2252,8 +2287,8 @@ export async function AnthropicAuthPlugin({ client }) {
|
|
|
2252
2287
|
const attemptedRefreshToken = account.refreshToken;
|
|
2253
2288
|
try {
|
|
2254
2289
|
accessToken = await refreshAccountTokenSingleFlight(account);
|
|
2255
|
-
//
|
|
2256
|
-
|
|
2290
|
+
// Tokens are now saved under the refresh lock (inside
|
|
2291
|
+
// refreshAccountToken) so no debounced save needed here.
|
|
2257
2292
|
} catch (err) {
|
|
2258
2293
|
// Token refresh failed — check if another instance rotated the
|
|
2259
2294
|
// refresh token and persisted it between attempts.
|
|
@@ -2287,7 +2322,6 @@ export async function AnthropicAuthPlugin({ client }) {
|
|
|
2287
2322
|
|
|
2288
2323
|
try {
|
|
2289
2324
|
accessToken = await refreshAccountTokenSingleFlight(account);
|
|
2290
|
-
accountManager.requestSaveToDisk();
|
|
2291
2325
|
} catch (retryErr) {
|
|
2292
2326
|
finalError = retryErr;
|
|
2293
2327
|
details = parseRefreshFailure(retryErr);
|