@cortexkit/anthropic-auth-core 1.2.3 → 1.2.5
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/dist/accounts.d.ts +1 -0
- package/dist/accounts.js +146 -30
- package/package.json +1 -1
package/dist/accounts.d.ts
CHANGED
|
@@ -153,6 +153,7 @@ export declare class FallbackAccountManager {
|
|
|
153
153
|
refreshAccount(account: OAuthAccount, storage: AccountStorage, options?: {
|
|
154
154
|
force?: boolean;
|
|
155
155
|
}): Promise<OAuthAccount>;
|
|
156
|
+
private waitForConcurrentFallbackRefresh;
|
|
156
157
|
private refreshAccountNow;
|
|
157
158
|
refreshAccountQuota(account: OAuthAccount, storage: AccountStorage): Promise<OAuthAccount>;
|
|
158
159
|
}
|
package/dist/accounts.js
CHANGED
|
@@ -22,6 +22,9 @@ const DEFAULT_MINIMUM_REMAINING = {
|
|
|
22
22
|
const DEFAULT_FAIL_CLOSED_ON_UNKNOWN_QUOTA = true;
|
|
23
23
|
const BACKGROUND_TICK_MS = 60_000;
|
|
24
24
|
const BACKGROUND_TICK_JITTER_MS = 60_000;
|
|
25
|
+
const FALLBACK_REFRESH_LOCK_TTL_MS = 2 * 60_000;
|
|
26
|
+
const FALLBACK_REFRESH_JOIN_WAIT_MS = 10_000;
|
|
27
|
+
const FALLBACK_REFRESH_JOIN_POLL_MS = 100;
|
|
25
28
|
function getConfigDir() {
|
|
26
29
|
if (process.env.OPENCODE_CONFIG_DIR?.trim()) {
|
|
27
30
|
return process.env.OPENCODE_CONFIG_DIR.trim();
|
|
@@ -466,6 +469,35 @@ function quotaIsStale(account, storage, now) {
|
|
|
466
469
|
return !window || now - window.checkedAt >= maxAge;
|
|
467
470
|
});
|
|
468
471
|
}
|
|
472
|
+
function cachedQuotaWindowStillRelevant(window, now) {
|
|
473
|
+
if (!window)
|
|
474
|
+
return false;
|
|
475
|
+
if (!window.resetsAt)
|
|
476
|
+
return true;
|
|
477
|
+
const resetTime = Date.parse(window.resetsAt);
|
|
478
|
+
return !Number.isFinite(resetTime) || resetTime > now;
|
|
479
|
+
}
|
|
480
|
+
function cachedQuotaSnapshotStillRelevant(quota, now) {
|
|
481
|
+
return ['five_hour', 'seven_day'].every((key) => cachedQuotaWindowStillRelevant(quota?.[key], now));
|
|
482
|
+
}
|
|
483
|
+
function isTransientQuotaError(error) {
|
|
484
|
+
const message = formatErrorMessage(error);
|
|
485
|
+
if (/Claude quota check failed: (429|5\d\d)\b/.test(message))
|
|
486
|
+
return true;
|
|
487
|
+
if (!(error instanceof Error))
|
|
488
|
+
return false;
|
|
489
|
+
const code = error.code;
|
|
490
|
+
return (message.includes('fetch failed') ||
|
|
491
|
+
code === 'ECONNRESET' ||
|
|
492
|
+
code === 'ECONNREFUSED' ||
|
|
493
|
+
code === 'ETIMEDOUT' ||
|
|
494
|
+
code === 'UND_ERR_CONNECT_TIMEOUT');
|
|
495
|
+
}
|
|
496
|
+
function canUseCachedQuotaAfterRefreshError(account, storage, error, now) {
|
|
497
|
+
return (isTransientQuotaError(error) &&
|
|
498
|
+
quotaSnapshotPassesPolicy(account.quota, storage) &&
|
|
499
|
+
cachedQuotaSnapshotStillRelevant(account.quota, now));
|
|
500
|
+
}
|
|
469
501
|
function clampPercent(value) {
|
|
470
502
|
if (!Number.isFinite(value))
|
|
471
503
|
return 0;
|
|
@@ -534,6 +566,12 @@ function recordQuotaRefreshError(account, error, now) {
|
|
|
534
566
|
recordRefreshError(account, error, now);
|
|
535
567
|
}
|
|
536
568
|
}
|
|
569
|
+
function fallbackRefreshLockName(accountId) {
|
|
570
|
+
return `fallback-oauth-refresh-${createHash('sha256')
|
|
571
|
+
.update(accountId)
|
|
572
|
+
.digest('hex')
|
|
573
|
+
.slice(0, 16)}`;
|
|
574
|
+
}
|
|
537
575
|
export class FallbackAccountManager {
|
|
538
576
|
now;
|
|
539
577
|
fetchImpl;
|
|
@@ -601,9 +639,17 @@ export class FallbackAccountManager {
|
|
|
601
639
|
if (this.accountPassesQuotaPolicy(next, storage))
|
|
602
640
|
usable.push(next);
|
|
603
641
|
}
|
|
604
|
-
catch {
|
|
605
|
-
if (
|
|
642
|
+
catch (error) {
|
|
643
|
+
if (canUseCachedQuotaAfterRefreshError(account, storage, error, this.now())) {
|
|
644
|
+
log('[refresh] fallback quota using cached quota after refresh error', {
|
|
645
|
+
accountId: account.id,
|
|
646
|
+
error: formatErrorMessage(error),
|
|
647
|
+
});
|
|
648
|
+
usable.push(account);
|
|
649
|
+
}
|
|
650
|
+
else if (!failClosedOnUnknownQuota(storage)) {
|
|
606
651
|
usable.push(account);
|
|
652
|
+
}
|
|
607
653
|
}
|
|
608
654
|
}
|
|
609
655
|
if (changed)
|
|
@@ -717,6 +763,14 @@ export class FallbackAccountManager {
|
|
|
717
763
|
next = await this.refreshAccount(next, storage);
|
|
718
764
|
changed = true;
|
|
719
765
|
}
|
|
766
|
+
if (!quotaIsStale(next, storage, this.now())) {
|
|
767
|
+
if (next.lastQuotaRefreshError) {
|
|
768
|
+
next.lastQuotaRefreshError = undefined;
|
|
769
|
+
updateStoredAccount(storage, next);
|
|
770
|
+
changed = true;
|
|
771
|
+
}
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
720
774
|
await this.refreshAccountQuota(next, storage);
|
|
721
775
|
changed = true;
|
|
722
776
|
}
|
|
@@ -749,43 +803,105 @@ export class FallbackAccountManager {
|
|
|
749
803
|
updateStoredAccount(storage, refreshed);
|
|
750
804
|
return refreshed;
|
|
751
805
|
}
|
|
806
|
+
async waitForConcurrentFallbackRefresh(account, storage, previous, options) {
|
|
807
|
+
const deadline = Date.now() + FALLBACK_REFRESH_JOIN_WAIT_MS;
|
|
808
|
+
while (Date.now() < deadline) {
|
|
809
|
+
await new Promise((resolve) => setTimeout(resolve, FALLBACK_REFRESH_JOIN_POLL_MS));
|
|
810
|
+
const latestStorage = await this.load();
|
|
811
|
+
const latestAccount = latestStorage?.accounts.find((candidate) => candidate.id === account.id);
|
|
812
|
+
if (!latestAccount)
|
|
813
|
+
continue;
|
|
814
|
+
const changed = latestAccount.access !== previous.access ||
|
|
815
|
+
latestAccount.refresh !== previous.refresh ||
|
|
816
|
+
(latestAccount.expires ?? 0) > (previous.expires ?? 0) + 60_000;
|
|
817
|
+
if (changed &&
|
|
818
|
+
(options.force ||
|
|
819
|
+
!tokenNeedsRefresh(latestAccount, latestStorage, this.now()))) {
|
|
820
|
+
updateStoredAccount(storage, latestAccount);
|
|
821
|
+
log('[refresh] fallback oauth joined concurrent refresh', {
|
|
822
|
+
accountId: latestAccount.id,
|
|
823
|
+
expiresInMs: latestAccount.expires
|
|
824
|
+
? latestAccount.expires - this.now()
|
|
825
|
+
: undefined,
|
|
826
|
+
});
|
|
827
|
+
return latestAccount;
|
|
828
|
+
}
|
|
829
|
+
const refreshError = latestAccount.lastRefreshError;
|
|
830
|
+
if (refreshError &&
|
|
831
|
+
refreshBackoffActive(refreshError, latestAccount.refresh, this.now())) {
|
|
832
|
+
updateStoredAccount(storage, latestAccount);
|
|
833
|
+
throw new Error(formatRefreshBackoffMessage(refreshError, this.now()));
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
752
838
|
async refreshAccountNow(account, storage, options) {
|
|
753
|
-
|
|
754
|
-
|
|
839
|
+
let latestStorage = await this.load();
|
|
840
|
+
let latestAccount = latestStorage?.accounts.find((candidate) => candidate.id === account.id);
|
|
755
841
|
if (latestAccount &&
|
|
756
842
|
!options.force &&
|
|
757
843
|
!tokenNeedsRefresh(latestAccount, latestStorage, this.now())) {
|
|
758
844
|
updateStoredAccount(storage, latestAccount);
|
|
759
845
|
return latestAccount;
|
|
760
846
|
}
|
|
761
|
-
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
expiresInMs: sourceAccount.expires
|
|
767
|
-
? sourceAccount.expires - this.now()
|
|
768
|
-
: undefined,
|
|
769
|
-
});
|
|
770
|
-
const refreshed = await refreshClaudeOAuthToken({
|
|
771
|
-
refreshToken,
|
|
772
|
-
fetchImpl: this.fetchImpl,
|
|
847
|
+
let sourceAccount = latestAccount ?? account;
|
|
848
|
+
const fileLock = await acquireRefreshFileLock({
|
|
849
|
+
name: fallbackRefreshLockName(sourceAccount.id),
|
|
850
|
+
ttlMs: FALLBACK_REFRESH_LOCK_TTL_MS,
|
|
851
|
+
path: this.configPath,
|
|
773
852
|
now: this.now,
|
|
774
853
|
});
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
854
|
+
if (!fileLock) {
|
|
855
|
+
log('[refresh] fallback oauth refresh skipped file lock', {
|
|
856
|
+
accountId: sourceAccount.id,
|
|
857
|
+
});
|
|
858
|
+
const concurrent = await this.waitForConcurrentFallbackRefresh(account, storage, sourceAccount, options);
|
|
859
|
+
if (concurrent)
|
|
860
|
+
return concurrent;
|
|
861
|
+
throw new Error('Fallback OAuth refresh is already in progress');
|
|
862
|
+
}
|
|
863
|
+
try {
|
|
864
|
+
latestStorage = await this.load();
|
|
865
|
+
latestAccount = latestStorage?.accounts.find((candidate) => candidate.id === account.id);
|
|
866
|
+
if (latestAccount &&
|
|
867
|
+
!options.force &&
|
|
868
|
+
!tokenNeedsRefresh(latestAccount, latestStorage, this.now())) {
|
|
869
|
+
updateStoredAccount(storage, latestAccount);
|
|
870
|
+
return latestAccount;
|
|
871
|
+
}
|
|
872
|
+
sourceAccount = latestAccount ?? sourceAccount;
|
|
873
|
+
const refreshToken = sourceAccount.refresh;
|
|
874
|
+
log('[refresh] fallback oauth refresh request start', {
|
|
875
|
+
accountId: sourceAccount.id,
|
|
876
|
+
force: options.force === true,
|
|
877
|
+
expiresInMs: sourceAccount.expires
|
|
878
|
+
? sourceAccount.expires - this.now()
|
|
879
|
+
: undefined,
|
|
880
|
+
});
|
|
881
|
+
const refreshed = await refreshClaudeOAuthToken({
|
|
882
|
+
refreshToken,
|
|
883
|
+
fetchImpl: this.fetchImpl,
|
|
884
|
+
now: this.now,
|
|
885
|
+
});
|
|
886
|
+
sourceAccount.access = refreshed.access;
|
|
887
|
+
sourceAccount.refresh = refreshed.refresh;
|
|
888
|
+
sourceAccount.expires = refreshed.expires;
|
|
889
|
+
sourceAccount.lastRefreshedAt =
|
|
890
|
+
refreshed.expires - refreshed.expiresIn * 1000;
|
|
891
|
+
sourceAccount.lastRefreshError = undefined;
|
|
892
|
+
updateStoredAccount(storage, sourceAccount);
|
|
893
|
+
await this.save(storage);
|
|
894
|
+
log('[refresh] fallback oauth refresh succeeded', {
|
|
895
|
+
accountId: sourceAccount.id,
|
|
896
|
+
expiresInMs: sourceAccount.expires
|
|
897
|
+
? sourceAccount.expires - this.now()
|
|
898
|
+
: undefined,
|
|
899
|
+
});
|
|
900
|
+
return sourceAccount;
|
|
901
|
+
}
|
|
902
|
+
finally {
|
|
903
|
+
await fileLock.release();
|
|
904
|
+
}
|
|
789
905
|
}
|
|
790
906
|
async refreshAccountQuota(account, storage) {
|
|
791
907
|
let target = account;
|