opencode-multi-account-core 0.2.126 → 0.3.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/dist/index.d.ts +13 -3
- package/dist/index.js +173 -98
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -52,6 +52,7 @@ declare const CredentialRefreshPatchSchema: v.ObjectSchema<{
|
|
|
52
52
|
}, undefined>;
|
|
53
53
|
declare const StoredAccountSchema: v.ObjectSchema<{
|
|
54
54
|
readonly uuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
55
|
+
readonly credentialOwnerId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
55
56
|
readonly accountId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
56
57
|
readonly accountUuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
57
58
|
readonly deviceId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -88,6 +89,7 @@ declare const AccountStorageSchema: v.ObjectSchema<{
|
|
|
88
89
|
readonly version: v.LiteralSchema<1, undefined>;
|
|
89
90
|
readonly accounts: v.OptionalSchema<v.ArraySchema<v.ObjectSchema<{
|
|
90
91
|
readonly uuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
92
|
+
readonly credentialOwnerId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
91
93
|
readonly accountId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
92
94
|
readonly accountUuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
93
95
|
readonly deviceId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -222,18 +224,26 @@ interface DiskCredentials {
|
|
|
222
224
|
refreshToken: string;
|
|
223
225
|
accessToken?: string;
|
|
224
226
|
expiresAt?: number;
|
|
227
|
+
credentialOwnerId?: string;
|
|
225
228
|
accountId?: string;
|
|
226
229
|
accountUuid?: string;
|
|
227
230
|
deviceId?: string;
|
|
228
231
|
}
|
|
232
|
+
interface AccountCredentialRefreshResult {
|
|
233
|
+
result: TokenRefreshResult;
|
|
234
|
+
account: StoredAccount | null;
|
|
235
|
+
}
|
|
229
236
|
declare class AccountStore {
|
|
230
237
|
private readonly storagePath;
|
|
231
238
|
constructor(filename?: string);
|
|
232
239
|
private withLock;
|
|
240
|
+
private refreshLockPath;
|
|
233
241
|
load(): Promise<AccountStorage>;
|
|
234
242
|
readCredentials(uuid: string): Promise<DiskCredentials | null>;
|
|
243
|
+
refreshAccountCredentials(uuid: string, expected: DiskCredentials, refresh: (refreshToken: string) => Promise<TokenRefreshResult>): Promise<AccountCredentialRefreshResult>;
|
|
235
244
|
mutateAccount(uuid: string, fn: (account: StoredAccount) => void): Promise<StoredAccount | null>;
|
|
236
245
|
mutateStorage(fn: (storage: AccountStorage) => void): Promise<void>;
|
|
246
|
+
mutateStorageIfCredentialsMatch(uuid: string, expected: DiskCredentials, fn: (account: StoredAccount, storage: AccountStorage) => void): Promise<boolean>;
|
|
237
247
|
addAccount(account: StoredAccount): Promise<void>;
|
|
238
248
|
removeAccount(uuid: string): Promise<boolean>;
|
|
239
249
|
setActiveUuid(uuid: string | undefined): Promise<void>;
|
|
@@ -272,7 +282,7 @@ interface AccountManagerInstance {
|
|
|
272
282
|
markRateLimited(uuid: string, backoffMs?: number): Promise<void>;
|
|
273
283
|
markRevoked(uuid: string): Promise<void>;
|
|
274
284
|
markSuccess(uuid: string): Promise<void>;
|
|
275
|
-
markAuthFailure(uuid: string, result: TokenRefreshResult): Promise<void>;
|
|
285
|
+
markAuthFailure(uuid: string, result: TokenRefreshResult, expected?: DiskCredentials): Promise<void>;
|
|
276
286
|
applyUsageCache(uuid: string, usage: UsageLimits): Promise<void>;
|
|
277
287
|
applyProfileCache(uuid: string, profile: ProfileData): Promise<void>;
|
|
278
288
|
ensureValidToken(uuid: string, client: PluginClient): Promise<TokenRefreshResult>;
|
|
@@ -355,7 +365,7 @@ interface ExecutorAccountManager {
|
|
|
355
365
|
refresh(): Promise<void>;
|
|
356
366
|
selectAccount(stickyKey?: string): Promise<ManagedAccount | null>;
|
|
357
367
|
markSuccess(uuid: string): Promise<void>;
|
|
358
|
-
markAuthFailure(uuid: string, result: TokenRefreshResult): Promise<void>;
|
|
368
|
+
markAuthFailure(uuid: string, result: TokenRefreshResult, expected?: DiskCredentials): Promise<void>;
|
|
359
369
|
markRevoked(uuid: string): Promise<void>;
|
|
360
370
|
hasAnyUsableAccount(): boolean;
|
|
361
371
|
getMinWaitTime(): number;
|
|
@@ -778,4 +788,4 @@ declare const __openCodeNativeBootstrapAuthTestUtils: {
|
|
|
778
788
|
|
|
779
789
|
declare function zeroCostProviderModels(provider: unknown): Promise<Record<string, any>>;
|
|
780
790
|
|
|
781
|
-
export { ACCOUNTS_FILENAME, ANSI, type AccountManagerClass, type AccountManagerDependencies, type AccountManagerInstance, type AccountMetadataPatch, type AccountSelectionStrategy, AccountSelectionStrategySchema, type AccountStorage, AccountStorageSchema, AccountStore, type BuildFailoverPlanOptions, CODEX_STARTUP_PROBE_MAX_BYTES, CODEX_UNKNOWN_RATE_LIMIT_BACKOFF_MS, type CascadeState, CascadeStateManager, type ChainConfig, ChainConfigSchema, type ChainEntryConfig, ChainEntryConfigSchema, type ClaimsManager, type ClaimsMap, type ConfigLoader, type CoreConfig, type CredentialRefreshPatch, CredentialRefreshPatchSchema, type DiskCredentials, type ExecutorAccountManager, type ExecutorDependencies, type ExecutorRuntimeFactory, type FailoverCandidate, type FailoverPlan, type FailoverSkip, type KeyAction, type ManagedAccount, type MenuItem, type NativePluginLifecycle, type NativePluginLifecycleOptions, type NativePluginLoaderResult, type NativePluginManagedAccount, type NativePluginManagerClass, type NativePluginManagerLike, type NativePluginRefreshQueueLike, type NativePluginRuntimeFactoryLike, type NativePluginStoreLike, type OAuthAdapter, type OAuthAdapterPlanLabels, type OAuthAdapterTransformConfig, type OAuthCredentials, OAuthCredentialsSchema, type OpenCodeNativeAuthLoaderOptions, type OpenCodeNativeAuthMethod, type OpenCodeNativeAuthMethodsOptions, type OpenCodeNativeBootstrapAccount, type OpenCodeNativeBootstrapAuthOptions, type OpenCodeNativeBootstrapStore, type OpenCodeNativeLoaderContext, type PluginClient, type PluginConfig, PluginConfigSchema, type PoolChainConfig, PoolChainConfigSchema, type PoolConfig, PoolConfigSchema, PoolManager, type ProactiveRefreshDependencies, type ProactiveRefreshQueueClass, type ProactiveRefreshQueueInstance, type ProfileData, type QuotaResetPaceOptions, type QuotaRoutingWindow, type RateLimitAccountManager, type RateLimitDependencies, type RuntimeFactoryLike, type SelectOptions, type SseStartupSupervisorOptions, type StoredAccount, StoredAccountSchema, type SupervisedTurnResponse, TokenRefreshError, type TokenRefreshResult, type TurnFailureClass, type TurnFailurePhase, type TurnFailureSignal, type TurnResponseSupervisor, type UsageLimitEntry, UsageLimitEntrySchema, type UsageLimits, UsageLimitsSchema, __openCodeNativeBootstrapAuthTestUtils, anthropicOAuthAdapter, classifyCodexFailure, classifyCodexJsonEventFailure, classifyCodexSseStartupFailure, confirm, createAccountManagerForProvider, createClaimsManager, createConfigLoader, createExecutorForProvider, createMinimalClient, createOpenCodeNativeAuthLoader, createOpenCodeNativeAuthMethods, createOpenCodeNativePluginLifecycle, createProactiveRefreshQueueForProvider, createRateLimitHandlers, debugLog, deduplicateAccounts, drainSseFrames, formatWaitTime, getAccountLabel, getClearedOAuthBody, getConfig, getConfigDir, getErrorCode, initCoreConfig, isClaimedByOther, isCodexStartupOutputEvent, isCodexStartupOutputFrame, isTTY, isTokenRefreshError, loadAccounts, loadConfig, loadPoolChainConfig, migrateFromAuthJson, openAIOAuthAdapter, parseCodexRetryAfterSeconds, parseKey, readClaims, readStorageFromDisk, releaseClaim, resetConfigCache, savePoolChainConfig, scoreQuotaResetPace, select, setAccountsFilename, setConfigGetter, showToast, sleep, superviseCodexResponseStartup, superviseSseResponseStartup, syncOpenCodeNativeBootstrapAuth, updateConfigField, withDirectoryLock, writeClaim, zeroCostProviderModels };
|
|
791
|
+
export { ACCOUNTS_FILENAME, ANSI, type AccountCredentialRefreshResult, type AccountManagerClass, type AccountManagerDependencies, type AccountManagerInstance, type AccountMetadataPatch, type AccountSelectionStrategy, AccountSelectionStrategySchema, type AccountStorage, AccountStorageSchema, AccountStore, type BuildFailoverPlanOptions, CODEX_STARTUP_PROBE_MAX_BYTES, CODEX_UNKNOWN_RATE_LIMIT_BACKOFF_MS, type CascadeState, CascadeStateManager, type ChainConfig, ChainConfigSchema, type ChainEntryConfig, ChainEntryConfigSchema, type ClaimsManager, type ClaimsMap, type ConfigLoader, type CoreConfig, type CredentialRefreshPatch, CredentialRefreshPatchSchema, type DiskCredentials, type ExecutorAccountManager, type ExecutorDependencies, type ExecutorRuntimeFactory, type FailoverCandidate, type FailoverPlan, type FailoverSkip, type KeyAction, type ManagedAccount, type MenuItem, type NativePluginLifecycle, type NativePluginLifecycleOptions, type NativePluginLoaderResult, type NativePluginManagedAccount, type NativePluginManagerClass, type NativePluginManagerLike, type NativePluginRefreshQueueLike, type NativePluginRuntimeFactoryLike, type NativePluginStoreLike, type OAuthAdapter, type OAuthAdapterPlanLabels, type OAuthAdapterTransformConfig, type OAuthCredentials, OAuthCredentialsSchema, type OpenCodeNativeAuthLoaderOptions, type OpenCodeNativeAuthMethod, type OpenCodeNativeAuthMethodsOptions, type OpenCodeNativeBootstrapAccount, type OpenCodeNativeBootstrapAuthOptions, type OpenCodeNativeBootstrapStore, type OpenCodeNativeLoaderContext, type PluginClient, type PluginConfig, PluginConfigSchema, type PoolChainConfig, PoolChainConfigSchema, type PoolConfig, PoolConfigSchema, PoolManager, type ProactiveRefreshDependencies, type ProactiveRefreshQueueClass, type ProactiveRefreshQueueInstance, type ProfileData, type QuotaResetPaceOptions, type QuotaRoutingWindow, type RateLimitAccountManager, type RateLimitDependencies, type RuntimeFactoryLike, type SelectOptions, type SseStartupSupervisorOptions, type StoredAccount, StoredAccountSchema, type SupervisedTurnResponse, TokenRefreshError, type TokenRefreshResult, type TurnFailureClass, type TurnFailurePhase, type TurnFailureSignal, type TurnResponseSupervisor, type UsageLimitEntry, UsageLimitEntrySchema, type UsageLimits, UsageLimitsSchema, __openCodeNativeBootstrapAuthTestUtils, anthropicOAuthAdapter, classifyCodexFailure, classifyCodexJsonEventFailure, classifyCodexSseStartupFailure, confirm, createAccountManagerForProvider, createClaimsManager, createConfigLoader, createExecutorForProvider, createMinimalClient, createOpenCodeNativeAuthLoader, createOpenCodeNativeAuthMethods, createOpenCodeNativePluginLifecycle, createProactiveRefreshQueueForProvider, createRateLimitHandlers, debugLog, deduplicateAccounts, drainSseFrames, formatWaitTime, getAccountLabel, getClearedOAuthBody, getConfig, getConfigDir, getErrorCode, initCoreConfig, isClaimedByOther, isCodexStartupOutputEvent, isCodexStartupOutputFrame, isTTY, isTokenRefreshError, loadAccounts, loadConfig, loadPoolChainConfig, migrateFromAuthJson, openAIOAuthAdapter, parseCodexRetryAfterSeconds, parseKey, readClaims, readStorageFromDisk, releaseClaim, resetConfigCache, savePoolChainConfig, scoreQuotaResetPace, select, setAccountsFilename, setConfigGetter, showToast, sleep, superviseCodexResponseStartup, superviseSseResponseStartup, syncOpenCodeNativeBootstrapAuth, updateConfigField, withDirectoryLock, writeClaim, zeroCostProviderModels };
|
package/dist/index.js
CHANGED
|
@@ -46,6 +46,7 @@ var CredentialRefreshPatchSchema = v.object({
|
|
|
46
46
|
});
|
|
47
47
|
var StoredAccountSchema = v.object({
|
|
48
48
|
uuid: v.optional(v.string()),
|
|
49
|
+
credentialOwnerId: v.optional(v.string()),
|
|
49
50
|
accountId: v.optional(v.string()),
|
|
50
51
|
accountUuid: v.optional(v.string()),
|
|
51
52
|
deviceId: v.optional(v.string()),
|
|
@@ -581,11 +582,6 @@ function createAccountManagerForProvider(dependencies) {
|
|
|
581
582
|
if (metadata.label) account.label = metadata.label;
|
|
582
583
|
if (metadata.planTier !== void 0) account.planTier = metadata.planTier;
|
|
583
584
|
}
|
|
584
|
-
applyRefreshIdentityPatch(account, result) {
|
|
585
|
-
if (result.patch.accountId) account.accountId = result.patch.accountId;
|
|
586
|
-
if (result.patch.accountUuid && !account.accountUuid) account.accountUuid = result.patch.accountUuid;
|
|
587
|
-
if (result.patch.deviceId && !account.deviceId) account.deviceId = result.patch.deviceId;
|
|
588
|
-
}
|
|
589
585
|
createNewAccount(auth, now, metadata) {
|
|
590
586
|
const account = {
|
|
591
587
|
uuid: randomUUID(),
|
|
@@ -947,32 +943,35 @@ function createAccountManagerForProvider(dependencies) {
|
|
|
947
943
|
await this.refresh();
|
|
948
944
|
await this.clearOpenCodeAuthIfNoAccountsRemain();
|
|
949
945
|
}
|
|
950
|
-
async markAuthFailure(uuid, result) {
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
const account = storage.accounts.find((entry) => entry.uuid === uuid);
|
|
954
|
-
if (!account) return;
|
|
946
|
+
async markAuthFailure(uuid, result, expected) {
|
|
947
|
+
const applyFailure = (account, storage) => {
|
|
948
|
+
if (!result.ok && result.permanent) {
|
|
955
949
|
account.consecutiveAuthFailures = Math.max(
|
|
956
950
|
(account.consecutiveAuthFailures ?? 0) + 1,
|
|
957
951
|
getProviderConfig().max_consecutive_auth_failures
|
|
958
952
|
);
|
|
959
953
|
account.isAuthDisabled = true;
|
|
960
954
|
account.authDisabledReason = "refresh failed permanently";
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
}
|
|
964
|
-
await this.store.mutateStorage((storage) => {
|
|
965
|
-
const account = storage.accounts.find((entry) => entry.uuid === uuid);
|
|
966
|
-
if (!account) return;
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
967
957
|
account.consecutiveAuthFailures = (account.consecutiveAuthFailures ?? 0) + 1;
|
|
968
958
|
const maxFailures = getProviderConfig().max_consecutive_auth_failures;
|
|
969
959
|
const usableCount = storage.accounts.filter(
|
|
970
|
-
(entry) => entry.enabled && !entry.isAuthDisabled && entry.uuid !== uuid
|
|
960
|
+
(entry) => entry.enabled && !entry.isAuthDisabled && entry.uuid !== account.uuid
|
|
971
961
|
).length;
|
|
972
962
|
if (account.consecutiveAuthFailures >= maxFailures && usableCount > 0) {
|
|
973
963
|
account.isAuthDisabled = true;
|
|
974
964
|
account.authDisabledReason = `${maxFailures} consecutive auth failures`;
|
|
975
965
|
}
|
|
966
|
+
};
|
|
967
|
+
if (expected) {
|
|
968
|
+
await this.store.mutateStorageIfCredentialsMatch(uuid, expected, applyFailure);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
await this.store.mutateStorage((storage) => {
|
|
972
|
+
const account = storage.accounts.find((entry) => entry.uuid === uuid);
|
|
973
|
+
if (!account) return;
|
|
974
|
+
applyFailure(account, storage);
|
|
976
975
|
});
|
|
977
976
|
}
|
|
978
977
|
async applyUsageCache(uuid, usage) {
|
|
@@ -1004,26 +1003,20 @@ function createAccountManagerForProvider(dependencies) {
|
|
|
1004
1003
|
patch: { accessToken: credentials.accessToken, expiresAt: credentials.expiresAt }
|
|
1005
1004
|
};
|
|
1006
1005
|
}
|
|
1007
|
-
const result = await
|
|
1006
|
+
const { result, account: updated } = await this.store.refreshAccountCredentials(
|
|
1007
|
+
uuid,
|
|
1008
|
+
credentials,
|
|
1009
|
+
(refreshTokenValue) => refreshToken(refreshTokenValue, uuid, client)
|
|
1010
|
+
);
|
|
1008
1011
|
if (!result.ok) return result;
|
|
1009
|
-
const
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
if (result.patch.uuid && result.patch.uuid !== uuid) account.uuid = result.patch.uuid;
|
|
1014
|
-
this.applyRefreshIdentityPatch(account, result);
|
|
1015
|
-
if (result.patch.email) account.email = result.patch.email;
|
|
1016
|
-
account.consecutiveAuthFailures = 0;
|
|
1017
|
-
account.isAuthDisabled = false;
|
|
1018
|
-
account.authDisabledReason = void 0;
|
|
1019
|
-
});
|
|
1020
|
-
if (result.patch.uuid && result.patch.uuid !== uuid && this.activeAccountUuid === uuid) {
|
|
1021
|
-
this.activeAccountUuid = result.patch.uuid;
|
|
1022
|
-
this.store.setActiveUuid(result.patch.uuid).catch(() => {
|
|
1012
|
+
const nextUuid = updated?.uuid ?? uuid;
|
|
1013
|
+
if (nextUuid !== uuid && this.activeAccountUuid === uuid) {
|
|
1014
|
+
this.activeAccountUuid = nextUuid;
|
|
1015
|
+
this.store.setActiveUuid(nextUuid).catch(() => {
|
|
1023
1016
|
});
|
|
1024
1017
|
}
|
|
1025
|
-
if (
|
|
1026
|
-
this.replaceStickyBindingAccountUuid(uuid,
|
|
1018
|
+
if (nextUuid !== uuid) {
|
|
1019
|
+
this.replaceStickyBindingAccountUuid(uuid, nextUuid);
|
|
1027
1020
|
}
|
|
1028
1021
|
if (updated && (uuid === this.activeAccountUuid || updated.uuid === this.activeAccountUuid)) {
|
|
1029
1022
|
this.syncToOpenCode(updated);
|
|
@@ -1043,7 +1036,7 @@ function createAccountManagerForProvider(dependencies) {
|
|
|
1043
1036
|
if (!account.uuid || !isTokenExpired(account)) return;
|
|
1044
1037
|
const result = await this.ensureValidToken(account.uuid, client);
|
|
1045
1038
|
if (!result.ok) {
|
|
1046
|
-
await this.markAuthFailure(account.uuid, result);
|
|
1039
|
+
await this.markAuthFailure(account.uuid, result, account);
|
|
1047
1040
|
}
|
|
1048
1041
|
})
|
|
1049
1042
|
);
|
|
@@ -1122,29 +1115,27 @@ function createAccountManagerForProvider(dependencies) {
|
|
|
1122
1115
|
this.runtimeFactory?.invalidate(uuid);
|
|
1123
1116
|
const credentials = await this.store.readCredentials(uuid);
|
|
1124
1117
|
if (!credentials) return { ok: false, permanent: true };
|
|
1125
|
-
const result = await
|
|
1118
|
+
const { result, account: refreshedAccount } = await this.store.refreshAccountCredentials(
|
|
1119
|
+
uuid,
|
|
1120
|
+
credentials,
|
|
1121
|
+
(refreshTokenValue) => refreshToken(refreshTokenValue, uuid, client)
|
|
1122
|
+
);
|
|
1126
1123
|
if (result.ok) {
|
|
1127
|
-
const
|
|
1128
|
-
|
|
1129
|
-
account.expiresAt = result.patch.expiresAt;
|
|
1130
|
-
if (result.patch.refreshToken) account.refreshToken = result.patch.refreshToken;
|
|
1131
|
-
if (result.patch.uuid) account.uuid = result.patch.uuid;
|
|
1132
|
-
this.applyRefreshIdentityPatch(account, result);
|
|
1133
|
-
if (result.patch.email) account.email = result.patch.email;
|
|
1124
|
+
const nextUuid = refreshedAccount?.uuid ?? uuid;
|
|
1125
|
+
const updated = await this.store.mutateAccount(nextUuid, (account) => {
|
|
1134
1126
|
account.enabled = true;
|
|
1135
1127
|
account.consecutiveAuthFailures = 0;
|
|
1136
1128
|
});
|
|
1137
1129
|
this.runtimeFactory?.invalidate(uuid);
|
|
1138
|
-
if (
|
|
1139
|
-
this.runtimeFactory?.invalidate(
|
|
1130
|
+
if (nextUuid !== uuid) {
|
|
1131
|
+
this.runtimeFactory?.invalidate(nextUuid);
|
|
1140
1132
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
this.
|
|
1144
|
-
await this.store.setActiveUuid(result.patch.uuid);
|
|
1133
|
+
if (this.activeAccountUuid === uuid && nextUuid !== uuid) {
|
|
1134
|
+
this.activeAccountUuid = nextUuid;
|
|
1135
|
+
await this.store.setActiveUuid(nextUuid);
|
|
1145
1136
|
}
|
|
1146
|
-
if (
|
|
1147
|
-
this.replaceStickyBindingAccountUuid(uuid,
|
|
1137
|
+
if (nextUuid !== uuid) {
|
|
1138
|
+
this.replaceStickyBindingAccountUuid(uuid, nextUuid);
|
|
1148
1139
|
}
|
|
1149
1140
|
if (updated && (uuid === this.activeAccountUuid || nextUuid === this.activeAccountUuid)) {
|
|
1150
1141
|
const freshCredentials = await this.store.readCredentials(nextUuid);
|
|
@@ -1157,7 +1148,7 @@ function createAccountManagerForProvider(dependencies) {
|
|
|
1157
1148
|
}
|
|
1158
1149
|
}
|
|
1159
1150
|
} else {
|
|
1160
|
-
await this.markAuthFailure(uuid, result);
|
|
1151
|
+
await this.markAuthFailure(uuid, result, credentials);
|
|
1161
1152
|
this.runtimeFactory?.invalidate(uuid);
|
|
1162
1153
|
}
|
|
1163
1154
|
return result;
|
|
@@ -1185,7 +1176,7 @@ function readRoutingUsageTiers(usage) {
|
|
|
1185
1176
|
|
|
1186
1177
|
// src/account-store.ts
|
|
1187
1178
|
import { promises as fs5 } from "fs";
|
|
1188
|
-
import { randomBytes as randomBytes3 } from "crypto";
|
|
1179
|
+
import { createHash, randomBytes as randomBytes3 } from "crypto";
|
|
1189
1180
|
import { dirname as dirname5, join as join5 } from "path";
|
|
1190
1181
|
import * as v4 from "valibot";
|
|
1191
1182
|
|
|
@@ -1331,6 +1322,9 @@ async function withDirectoryLock(targetPath, fn, options) {
|
|
|
1331
1322
|
|
|
1332
1323
|
// src/account-store.ts
|
|
1333
1324
|
var FILE_MODE = 384;
|
|
1325
|
+
var REFRESH_LOCK_STALE_MS = 45e3;
|
|
1326
|
+
var REFRESH_LOCK_RETRY_DELAY_MS = 100;
|
|
1327
|
+
var REFRESH_LOCK_RETRIES = 31;
|
|
1334
1328
|
function resolveStoragePath(filename) {
|
|
1335
1329
|
return join5(getConfigDir2(), filename);
|
|
1336
1330
|
}
|
|
@@ -1374,6 +1368,49 @@ async function ensureStorageFileExists(targetPath) {
|
|
|
1374
1368
|
if (getErrorCode(error) !== "EEXIST") throw error;
|
|
1375
1369
|
}
|
|
1376
1370
|
}
|
|
1371
|
+
function sameCredentialSnapshot(account, expected) {
|
|
1372
|
+
return account.refreshToken === expected.refreshToken && account.accessToken === expected.accessToken && account.expiresAt === expected.expiresAt;
|
|
1373
|
+
}
|
|
1374
|
+
function findCredentialAccount(accounts, uuid, expected) {
|
|
1375
|
+
const exact = accounts.find((account) => account.uuid === uuid);
|
|
1376
|
+
if (exact) return exact;
|
|
1377
|
+
const ownerId = expected.credentialOwnerId ?? uuid;
|
|
1378
|
+
const ownerMatches = accounts.filter((account) => account.credentialOwnerId === ownerId);
|
|
1379
|
+
return ownerMatches.length === 1 ? ownerMatches[0] : void 0;
|
|
1380
|
+
}
|
|
1381
|
+
function refreshedCredentialPatch(account, previous) {
|
|
1382
|
+
if (!account.accessToken || !account.expiresAt) return null;
|
|
1383
|
+
const rotatedGrant = account.refreshToken !== previous.refreshToken;
|
|
1384
|
+
const newerExpiry = account.expiresAt > (previous.expiresAt ?? 0);
|
|
1385
|
+
const changedAccess = account.accessToken !== previous.accessToken;
|
|
1386
|
+
if (account.expiresAt <= Date.now() || !rotatedGrant && !newerExpiry && !changedAccess) return null;
|
|
1387
|
+
return {
|
|
1388
|
+
accessToken: account.accessToken,
|
|
1389
|
+
expiresAt: account.expiresAt,
|
|
1390
|
+
refreshToken: account.refreshToken,
|
|
1391
|
+
uuid: account.uuid,
|
|
1392
|
+
accountId: account.accountId,
|
|
1393
|
+
accountUuid: account.accountUuid,
|
|
1394
|
+
deviceId: account.deviceId,
|
|
1395
|
+
email: account.email
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
function applyCredentialPatch(account, patch, credentialOwnerId) {
|
|
1399
|
+
account.accessToken = patch.accessToken;
|
|
1400
|
+
account.expiresAt = patch.expiresAt;
|
|
1401
|
+
if (patch.refreshToken) account.refreshToken = patch.refreshToken;
|
|
1402
|
+
if (patch.uuid && patch.uuid !== account.uuid) {
|
|
1403
|
+
account.credentialOwnerId ??= credentialOwnerId;
|
|
1404
|
+
account.uuid = patch.uuid;
|
|
1405
|
+
}
|
|
1406
|
+
if (patch.accountId) account.accountId = patch.accountId;
|
|
1407
|
+
if (patch.accountUuid && !account.accountUuid) account.accountUuid = patch.accountUuid;
|
|
1408
|
+
if (patch.deviceId && !account.deviceId) account.deviceId = patch.deviceId;
|
|
1409
|
+
if (patch.email) account.email = patch.email;
|
|
1410
|
+
account.consecutiveAuthFailures = 0;
|
|
1411
|
+
account.isAuthDisabled = false;
|
|
1412
|
+
account.authDisabledReason = void 0;
|
|
1413
|
+
}
|
|
1377
1414
|
var AccountStore = class {
|
|
1378
1415
|
storagePath;
|
|
1379
1416
|
constructor(filename) {
|
|
@@ -1383,6 +1420,10 @@ var AccountStore = class {
|
|
|
1383
1420
|
await ensureStorageFileExists(this.storagePath);
|
|
1384
1421
|
return await withDirectoryLock(this.storagePath, () => fn(this.storagePath));
|
|
1385
1422
|
}
|
|
1423
|
+
refreshLockPath(uuid, credentials) {
|
|
1424
|
+
const accountKey = createHash("sha256").update(credentials.credentialOwnerId ?? uuid).digest("hex");
|
|
1425
|
+
return `${this.storagePath}.refresh-${accountKey}`;
|
|
1426
|
+
}
|
|
1386
1427
|
async load() {
|
|
1387
1428
|
const storage = await loadAccounts(this.storagePath);
|
|
1388
1429
|
return storage ?? createEmptyStorage();
|
|
@@ -1396,11 +1437,45 @@ var AccountStore = class {
|
|
|
1396
1437
|
refreshToken: account.refreshToken,
|
|
1397
1438
|
accessToken: account.accessToken,
|
|
1398
1439
|
expiresAt: account.expiresAt,
|
|
1440
|
+
credentialOwnerId: account.credentialOwnerId,
|
|
1399
1441
|
accountId: account.accountId,
|
|
1400
1442
|
accountUuid: account.accountUuid,
|
|
1401
1443
|
deviceId: account.deviceId
|
|
1402
1444
|
};
|
|
1403
1445
|
}
|
|
1446
|
+
async refreshAccountCredentials(uuid, expected, refresh) {
|
|
1447
|
+
const credentialOwnerId = expected.credentialOwnerId ?? uuid;
|
|
1448
|
+
return await withDirectoryLock(this.refreshLockPath(uuid, expected), async () => {
|
|
1449
|
+
const currentStorage = await readStorageFromDisk(this.storagePath, false);
|
|
1450
|
+
const current = currentStorage ? findCredentialAccount(currentStorage.accounts, uuid, expected) : void 0;
|
|
1451
|
+
if (!current) {
|
|
1452
|
+
return { result: { ok: false, permanent: true }, account: null };
|
|
1453
|
+
}
|
|
1454
|
+
let refreshSnapshot = expected;
|
|
1455
|
+
if (!sameCredentialSnapshot(current, expected)) {
|
|
1456
|
+
const patch = refreshedCredentialPatch(current, expected);
|
|
1457
|
+
if (patch) return { result: { ok: true, patch }, account: { ...current } };
|
|
1458
|
+
refreshSnapshot = current;
|
|
1459
|
+
}
|
|
1460
|
+
const result = await refresh(current.refreshToken);
|
|
1461
|
+
if (!result.ok) return { result, account: { ...current } };
|
|
1462
|
+
let persistedResult = result;
|
|
1463
|
+
const updated = await this.mutateAccount(current.uuid ?? uuid, (account) => {
|
|
1464
|
+
if (!sameCredentialSnapshot(account, refreshSnapshot)) {
|
|
1465
|
+
const patch = refreshedCredentialPatch(account, refreshSnapshot);
|
|
1466
|
+
persistedResult = patch ? { ok: true, patch } : { ok: false, permanent: false };
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
applyCredentialPatch(account, result.patch, credentialOwnerId);
|
|
1470
|
+
});
|
|
1471
|
+
if (!updated) return { result: { ok: false, permanent: false }, account: null };
|
|
1472
|
+
return { result: persistedResult, account: updated };
|
|
1473
|
+
}, {
|
|
1474
|
+
staleMs: REFRESH_LOCK_STALE_MS,
|
|
1475
|
+
retryDelayMs: REFRESH_LOCK_RETRY_DELAY_MS,
|
|
1476
|
+
retries: REFRESH_LOCK_RETRIES
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1404
1479
|
async mutateAccount(uuid, fn) {
|
|
1405
1480
|
return await this.withLock(async (storagePath) => {
|
|
1406
1481
|
const current = await readStorageFromDisk(storagePath, false);
|
|
@@ -1419,6 +1494,17 @@ var AccountStore = class {
|
|
|
1419
1494
|
await writeStorageAtomic(storagePath, current);
|
|
1420
1495
|
});
|
|
1421
1496
|
}
|
|
1497
|
+
async mutateStorageIfCredentialsMatch(uuid, expected, fn) {
|
|
1498
|
+
return await this.withLock(async (storagePath) => {
|
|
1499
|
+
const current = await readStorageFromDisk(storagePath, false);
|
|
1500
|
+
if (!current) return false;
|
|
1501
|
+
const account = findCredentialAccount(current.accounts, uuid, expected);
|
|
1502
|
+
if (!account || !sameCredentialSnapshot(account, expected)) return false;
|
|
1503
|
+
fn(account, current);
|
|
1504
|
+
await writeStorageAtomic(storagePath, current);
|
|
1505
|
+
return true;
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1422
1508
|
async addAccount(account) {
|
|
1423
1509
|
await this.withLock(async (storagePath) => {
|
|
1424
1510
|
const current = await readStorageFromDisk(storagePath, false) ?? createEmptyStorage();
|
|
@@ -1457,7 +1543,7 @@ var AccountStore = class {
|
|
|
1457
1543
|
};
|
|
1458
1544
|
|
|
1459
1545
|
// src/executor.ts
|
|
1460
|
-
import { createHash } from "crypto";
|
|
1546
|
+
import { createHash as createHash2 } from "crypto";
|
|
1461
1547
|
var MIN_MAX_RETRIES = 6;
|
|
1462
1548
|
var RETRIES_PER_ACCOUNT = 3;
|
|
1463
1549
|
var MAX_SERVER_RETRIES_PER_ATTEMPT = 2;
|
|
@@ -1519,7 +1605,7 @@ function buildStickyKeyFromFirstUserText(text) {
|
|
|
1519
1605
|
if (!text) {
|
|
1520
1606
|
return void 0;
|
|
1521
1607
|
}
|
|
1522
|
-
return
|
|
1608
|
+
return createHash2("sha256").update(text).digest("hex").slice(0, 16);
|
|
1523
1609
|
}
|
|
1524
1610
|
function extractStickyKey(input, init) {
|
|
1525
1611
|
const bodyText = init?.body ?? (input instanceof Request ? input.body : void 0);
|
|
@@ -1614,7 +1700,7 @@ function createExecutorForProvider(providerName, dependencies) {
|
|
|
1614
1700
|
return { type: "retryOuter" };
|
|
1615
1701
|
}
|
|
1616
1702
|
}
|
|
1617
|
-
await manager.markAuthFailure(accountUuid, { ok: false, permanent: false });
|
|
1703
|
+
await manager.markAuthFailure(accountUuid, { ok: false, permanent: false }, account);
|
|
1618
1704
|
await manager.refresh();
|
|
1619
1705
|
if (!manager.hasAnyUsableAccount()) {
|
|
1620
1706
|
void showToast2(client, "All accounts have auth failures.", "error");
|
|
@@ -1704,7 +1790,7 @@ function createExecutorForProvider(providerName, dependencies) {
|
|
|
1704
1790
|
await manager.markAuthFailure(accountUuid, {
|
|
1705
1791
|
ok: false,
|
|
1706
1792
|
permanent: error.permanent
|
|
1707
|
-
});
|
|
1793
|
+
}, account);
|
|
1708
1794
|
await manager.refresh();
|
|
1709
1795
|
if (!manager.hasAnyUsableAccount()) {
|
|
1710
1796
|
void showToast2(client, "All accounts have auth failures.", "error");
|
|
@@ -1833,24 +1919,15 @@ function createProactiveRefreshQueueForProvider(dependencies) {
|
|
|
1833
1919
|
if (token !== this.runToken) return;
|
|
1834
1920
|
const credentials = await this.store.readCredentials(account.uuid);
|
|
1835
1921
|
if (!credentials || !this.needsProactiveRefresh(credentials)) continue;
|
|
1836
|
-
const result = await
|
|
1922
|
+
const { result } = await this.store.refreshAccountCredentials(
|
|
1923
|
+
account.uuid,
|
|
1924
|
+
credentials,
|
|
1925
|
+
(refreshTokenValue) => refreshToken(refreshTokenValue, account.uuid, this.client)
|
|
1926
|
+
);
|
|
1837
1927
|
if (result.ok) {
|
|
1838
|
-
await this.store.mutateAccount(account.uuid, (target) => {
|
|
1839
|
-
target.accessToken = result.patch.accessToken;
|
|
1840
|
-
target.expiresAt = result.patch.expiresAt;
|
|
1841
|
-
if (result.patch.refreshToken) target.refreshToken = result.patch.refreshToken;
|
|
1842
|
-
if (result.patch.uuid) target.uuid = result.patch.uuid;
|
|
1843
|
-
if (result.patch.email) target.email = result.patch.email;
|
|
1844
|
-
if (result.patch.accountId) target.accountId = result.patch.accountId;
|
|
1845
|
-
if (result.patch.accountUuid && !target.accountUuid) target.accountUuid = result.patch.accountUuid;
|
|
1846
|
-
if (result.patch.deviceId && !target.deviceId) target.deviceId = result.patch.deviceId;
|
|
1847
|
-
target.consecutiveAuthFailures = 0;
|
|
1848
|
-
target.isAuthDisabled = false;
|
|
1849
|
-
target.authDisabledReason = void 0;
|
|
1850
|
-
});
|
|
1851
1928
|
this.onInvalidate?.(account.uuid);
|
|
1852
1929
|
} else {
|
|
1853
|
-
await this.persistFailure(account, result.permanent);
|
|
1930
|
+
await this.persistFailure(account, result.permanent, credentials);
|
|
1854
1931
|
}
|
|
1855
1932
|
}
|
|
1856
1933
|
} catch (error) {
|
|
@@ -1862,36 +1939,34 @@ function createProactiveRefreshQueueForProvider(dependencies) {
|
|
|
1862
1939
|
}
|
|
1863
1940
|
}
|
|
1864
1941
|
}
|
|
1865
|
-
async persistFailure(account, permanent) {
|
|
1942
|
+
async persistFailure(account, permanent, expected) {
|
|
1866
1943
|
try {
|
|
1867
1944
|
const accountUuid = account.uuid;
|
|
1868
1945
|
if (!accountUuid) return;
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
target.isAuthDisabled = true;
|
|
1892
|
-
target.authDisabledReason = `${maxFailures} consecutive auth failures (proactive refresh)`;
|
|
1946
|
+
await this.store.mutateStorageIfCredentialsMatch(
|
|
1947
|
+
accountUuid,
|
|
1948
|
+
expected,
|
|
1949
|
+
(target, storage) => {
|
|
1950
|
+
if (permanent) {
|
|
1951
|
+
target.consecutiveAuthFailures = Math.max(
|
|
1952
|
+
(target.consecutiveAuthFailures ?? 0) + 1,
|
|
1953
|
+
getConfig2().max_consecutive_auth_failures
|
|
1954
|
+
);
|
|
1955
|
+
target.isAuthDisabled = true;
|
|
1956
|
+
target.authDisabledReason = "refresh failed permanently (proactive refresh)";
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
target.consecutiveAuthFailures = (target.consecutiveAuthFailures ?? 0) + 1;
|
|
1960
|
+
const maxFailures = getConfig2().max_consecutive_auth_failures;
|
|
1961
|
+
const usableCount = storage.accounts.filter(
|
|
1962
|
+
(entry) => entry.enabled && !entry.isAuthDisabled && entry.uuid !== target.uuid
|
|
1963
|
+
).length;
|
|
1964
|
+
if (target.consecutiveAuthFailures >= maxFailures && usableCount > 0) {
|
|
1965
|
+
target.isAuthDisabled = true;
|
|
1966
|
+
target.authDisabledReason = `${maxFailures} consecutive auth failures (proactive refresh)`;
|
|
1967
|
+
}
|
|
1893
1968
|
}
|
|
1894
|
-
|
|
1969
|
+
);
|
|
1895
1970
|
} catch {
|
|
1896
1971
|
debugLog2(this.client, `Failed to persist auth failure for ${account.uuid}`);
|
|
1897
1972
|
}
|