opencode-multi-account-core 0.2.32 → 0.2.34
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 +50 -50
- package/dist/index.d.ts +140 -4
- package/dist/index.js +405 -33
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
package/dist/index.js
CHANGED
|
@@ -40,11 +40,15 @@ var CredentialRefreshPatchSchema = v.object({
|
|
|
40
40
|
refreshToken: v.optional(v.string()),
|
|
41
41
|
uuid: v.optional(v.string()),
|
|
42
42
|
accountId: v.optional(v.string()),
|
|
43
|
+
accountUuid: v.optional(v.string()),
|
|
44
|
+
deviceId: v.optional(v.string()),
|
|
43
45
|
email: v.optional(v.string())
|
|
44
46
|
});
|
|
45
47
|
var StoredAccountSchema = v.object({
|
|
46
48
|
uuid: v.optional(v.string()),
|
|
47
49
|
accountId: v.optional(v.string()),
|
|
50
|
+
accountUuid: v.optional(v.string()),
|
|
51
|
+
deviceId: v.optional(v.string()),
|
|
48
52
|
label: v.optional(v.string()),
|
|
49
53
|
email: v.optional(v.string()),
|
|
50
54
|
planTier: v.optional(v.string(), ""),
|
|
@@ -259,6 +263,7 @@ function getClearedOAuthBody() {
|
|
|
259
263
|
// src/claims.ts
|
|
260
264
|
var CLAIMS_FILENAME = "multiauth-claims.json";
|
|
261
265
|
var CLAIM_EXPIRY_MS = 6e4;
|
|
266
|
+
var CLAIM_LOCK_STALE_MS = 15e3;
|
|
262
267
|
function getClaimsPath(filename) {
|
|
263
268
|
return join3(getConfigDir2(), filename);
|
|
264
269
|
}
|
|
@@ -321,17 +326,67 @@ async function writeClaimsFile(path, claims) {
|
|
|
321
326
|
throw error;
|
|
322
327
|
}
|
|
323
328
|
}
|
|
329
|
+
async function readClaimsFile(path) {
|
|
330
|
+
try {
|
|
331
|
+
const data = await fs2.readFile(path, "utf-8");
|
|
332
|
+
return parseClaims(data);
|
|
333
|
+
} catch {
|
|
334
|
+
return {};
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function sleep2(ms) {
|
|
338
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
339
|
+
}
|
|
340
|
+
async function acquireClaimsLock(path) {
|
|
341
|
+
const lockPath = `${path}.lock`;
|
|
342
|
+
await fs2.mkdir(dirname2(path), { recursive: true });
|
|
343
|
+
for (; ; ) {
|
|
344
|
+
try {
|
|
345
|
+
await fs2.mkdir(lockPath, { mode: 448 });
|
|
346
|
+
return async () => {
|
|
347
|
+
try {
|
|
348
|
+
await fs2.rm(lockPath, { recursive: true, force: true });
|
|
349
|
+
} catch {
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
} catch (error) {
|
|
353
|
+
const code = error.code;
|
|
354
|
+
if (code !== "EEXIST") throw error;
|
|
355
|
+
try {
|
|
356
|
+
const stat = await fs2.stat(lockPath);
|
|
357
|
+
if (Date.now() - stat.mtimeMs > CLAIM_LOCK_STALE_MS) {
|
|
358
|
+
await fs2.rm(lockPath, { recursive: true, force: true });
|
|
359
|
+
}
|
|
360
|
+
} catch {
|
|
361
|
+
}
|
|
362
|
+
await sleep2(10 + Math.floor(Math.random() * 20));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
async function withClaimsLock(path, fn) {
|
|
367
|
+
const release = await acquireClaimsLock(path);
|
|
368
|
+
try {
|
|
369
|
+
return await fn();
|
|
370
|
+
} finally {
|
|
371
|
+
await release();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
324
374
|
function createClaimsManager(filename = CLAIMS_FILENAME) {
|
|
325
375
|
async function readClaims2() {
|
|
326
376
|
const claimsPath = getClaimsPath(filename);
|
|
327
377
|
try {
|
|
328
|
-
const
|
|
329
|
-
const parsed = parseClaims(data);
|
|
378
|
+
const parsed = await readClaimsFile(claimsPath);
|
|
330
379
|
const now = Date.now();
|
|
331
380
|
const { cleaned, changed } = cleanClaims(parsed, now);
|
|
332
381
|
if (changed) {
|
|
333
382
|
try {
|
|
334
|
-
await
|
|
383
|
+
await withClaimsLock(claimsPath, async () => {
|
|
384
|
+
const current = await readClaimsFile(claimsPath);
|
|
385
|
+
const latest = cleanClaims(current, Date.now());
|
|
386
|
+
if (latest.changed) {
|
|
387
|
+
await writeClaimsFile(claimsPath, latest.cleaned);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
335
390
|
} catch {
|
|
336
391
|
}
|
|
337
392
|
}
|
|
@@ -342,27 +397,31 @@ function createClaimsManager(filename = CLAIMS_FILENAME) {
|
|
|
342
397
|
}
|
|
343
398
|
async function writeClaim2(accountId) {
|
|
344
399
|
const claimsPath = getClaimsPath(filename);
|
|
345
|
-
const now = Date.now();
|
|
346
|
-
const claims = await readClaims2();
|
|
347
|
-
const { cleaned } = cleanClaims(claims, now);
|
|
348
|
-
cleaned[accountId] = { pid: process.pid, at: now };
|
|
349
400
|
try {
|
|
350
|
-
await
|
|
401
|
+
await withClaimsLock(claimsPath, async () => {
|
|
402
|
+
const now = Date.now();
|
|
403
|
+
const claims = await readClaimsFile(claimsPath);
|
|
404
|
+
const { cleaned } = cleanClaims(claims, now);
|
|
405
|
+
cleaned[accountId] = { pid: process.pid, at: now };
|
|
406
|
+
await writeClaimsFile(claimsPath, cleaned);
|
|
407
|
+
});
|
|
351
408
|
} catch {
|
|
352
409
|
}
|
|
353
410
|
}
|
|
354
411
|
async function releaseClaim2(accountId) {
|
|
355
412
|
const claimsPath = getClaimsPath(filename);
|
|
356
|
-
const now = Date.now();
|
|
357
|
-
const claims = await readClaims2();
|
|
358
|
-
const { cleaned } = cleanClaims(claims, now);
|
|
359
|
-
const currentClaim = cleaned[accountId];
|
|
360
|
-
if (!currentClaim || currentClaim.pid !== process.pid) {
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
delete cleaned[accountId];
|
|
364
413
|
try {
|
|
365
|
-
await
|
|
414
|
+
await withClaimsLock(claimsPath, async () => {
|
|
415
|
+
const now = Date.now();
|
|
416
|
+
const claims = await readClaimsFile(claimsPath);
|
|
417
|
+
const { cleaned } = cleanClaims(claims, now);
|
|
418
|
+
const currentClaim = cleaned[accountId];
|
|
419
|
+
if (!currentClaim || currentClaim.pid !== process.pid) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
delete cleaned[accountId];
|
|
423
|
+
await writeClaimsFile(claimsPath, cleaned);
|
|
424
|
+
});
|
|
366
425
|
} catch {
|
|
367
426
|
}
|
|
368
427
|
}
|
|
@@ -459,6 +518,8 @@ function createAccountManagerForProvider(dependencies) {
|
|
|
459
518
|
index,
|
|
460
519
|
uuid: storedAccount.uuid,
|
|
461
520
|
accountId: storedAccount.accountId,
|
|
521
|
+
accountUuid: storedAccount.accountUuid,
|
|
522
|
+
deviceId: storedAccount.deviceId,
|
|
462
523
|
label: storedAccount.label,
|
|
463
524
|
email: storedAccount.email,
|
|
464
525
|
planTier: storedAccount.planTier,
|
|
@@ -477,8 +538,22 @@ function createAccountManagerForProvider(dependencies) {
|
|
|
477
538
|
last429At: storedAccount.uuid ? this.last429Map.get(storedAccount.uuid) : void 0
|
|
478
539
|
};
|
|
479
540
|
}
|
|
480
|
-
|
|
481
|
-
return
|
|
541
|
+
applyAccountMetadata(account, metadata) {
|
|
542
|
+
if (!metadata) return;
|
|
543
|
+
if (metadata.accountId) account.accountId = metadata.accountId;
|
|
544
|
+
if (metadata.accountUuid) account.accountUuid = metadata.accountUuid;
|
|
545
|
+
if (metadata.deviceId) account.deviceId = metadata.deviceId;
|
|
546
|
+
if (metadata.email) account.email = metadata.email;
|
|
547
|
+
if (metadata.label) account.label = metadata.label;
|
|
548
|
+
if (metadata.planTier !== void 0) account.planTier = metadata.planTier;
|
|
549
|
+
}
|
|
550
|
+
applyRefreshIdentityPatch(account, result) {
|
|
551
|
+
if (result.patch.accountId) account.accountId = result.patch.accountId;
|
|
552
|
+
if (result.patch.accountUuid && !account.accountUuid) account.accountUuid = result.patch.accountUuid;
|
|
553
|
+
if (result.patch.deviceId && !account.deviceId) account.deviceId = result.patch.deviceId;
|
|
554
|
+
}
|
|
555
|
+
createNewAccount(auth, now, metadata) {
|
|
556
|
+
const account = {
|
|
482
557
|
uuid: randomUUID(),
|
|
483
558
|
refreshToken: auth.refresh,
|
|
484
559
|
accessToken: auth.access,
|
|
@@ -490,6 +565,8 @@ function createAccountManagerForProvider(dependencies) {
|
|
|
490
565
|
consecutiveAuthFailures: 0,
|
|
491
566
|
isAuthDisabled: false
|
|
492
567
|
};
|
|
568
|
+
this.applyAccountMetadata(account, metadata);
|
|
569
|
+
return account;
|
|
493
570
|
}
|
|
494
571
|
getAccountCount() {
|
|
495
572
|
return this.getEligibleAccounts().length;
|
|
@@ -903,7 +980,7 @@ function createAccountManagerForProvider(dependencies) {
|
|
|
903
980
|
account.expiresAt = result.patch.expiresAt;
|
|
904
981
|
if (result.patch.refreshToken) account.refreshToken = result.patch.refreshToken;
|
|
905
982
|
if (result.patch.uuid && result.patch.uuid !== uuid) account.uuid = result.patch.uuid;
|
|
906
|
-
|
|
983
|
+
this.applyRefreshIdentityPatch(account, result);
|
|
907
984
|
if (result.patch.email) account.email = result.patch.email;
|
|
908
985
|
account.consecutiveAuthFailures = 0;
|
|
909
986
|
account.isAuthDisabled = false;
|
|
@@ -957,7 +1034,7 @@ function createAccountManagerForProvider(dependencies) {
|
|
|
957
1034
|
this.activeAccountUuid = void 0;
|
|
958
1035
|
this.stickyBindings.clear();
|
|
959
1036
|
}
|
|
960
|
-
async addAccount(auth, email) {
|
|
1037
|
+
async addAccount(auth, email, metadata) {
|
|
961
1038
|
if (!auth.refresh) return;
|
|
962
1039
|
const existingByToken = this.cached.find((account) => account.refreshToken === auth.refresh);
|
|
963
1040
|
if (existingByToken) return;
|
|
@@ -966,11 +1043,11 @@ function createAccountManagerForProvider(dependencies) {
|
|
|
966
1043
|
(account) => account.email && account.email === email
|
|
967
1044
|
);
|
|
968
1045
|
if (existingByEmail?.uuid) {
|
|
969
|
-
await this.replaceAccountCredentials(existingByEmail.uuid, auth);
|
|
1046
|
+
await this.replaceAccountCredentials(existingByEmail.uuid, auth, metadata);
|
|
970
1047
|
return;
|
|
971
1048
|
}
|
|
972
1049
|
}
|
|
973
|
-
const newAccount = this.createNewAccount(auth, Date.now());
|
|
1050
|
+
const newAccount = this.createNewAccount(auth, Date.now(), metadata);
|
|
974
1051
|
if (email) newAccount.email = email;
|
|
975
1052
|
await this.store.addAccount(newAccount);
|
|
976
1053
|
this.activeAccountUuid = newAccount.uuid;
|
|
@@ -987,11 +1064,12 @@ function createAccountManagerForProvider(dependencies) {
|
|
|
987
1064
|
}
|
|
988
1065
|
});
|
|
989
1066
|
}
|
|
990
|
-
async replaceAccountCredentials(uuid, auth) {
|
|
1067
|
+
async replaceAccountCredentials(uuid, auth, metadata) {
|
|
991
1068
|
const updated = await this.store.mutateAccount(uuid, (account) => {
|
|
992
1069
|
account.refreshToken = auth.refresh;
|
|
993
1070
|
account.accessToken = auth.access;
|
|
994
1071
|
account.expiresAt = auth.expires;
|
|
1072
|
+
this.applyAccountMetadata(account, metadata);
|
|
995
1073
|
account.lastUsed = Date.now();
|
|
996
1074
|
account.enabled = true;
|
|
997
1075
|
account.isAuthDisabled = false;
|
|
@@ -1020,7 +1098,7 @@ function createAccountManagerForProvider(dependencies) {
|
|
|
1020
1098
|
account.expiresAt = result.patch.expiresAt;
|
|
1021
1099
|
if (result.patch.refreshToken) account.refreshToken = result.patch.refreshToken;
|
|
1022
1100
|
if (result.patch.uuid) account.uuid = result.patch.uuid;
|
|
1023
|
-
|
|
1101
|
+
this.applyRefreshIdentityPatch(account, result);
|
|
1024
1102
|
if (result.patch.email) account.email = result.patch.email;
|
|
1025
1103
|
account.enabled = true;
|
|
1026
1104
|
account.consecutiveAuthFailures = 0;
|
|
@@ -1160,7 +1238,7 @@ import { dirname as dirname4 } from "path";
|
|
|
1160
1238
|
var DEFAULT_STALE_MS = 1e4;
|
|
1161
1239
|
var DEFAULT_RETRY_DELAY_MS = 50;
|
|
1162
1240
|
var DEFAULT_MAX_RETRIES = 10;
|
|
1163
|
-
function
|
|
1241
|
+
function sleep3(ms) {
|
|
1164
1242
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1165
1243
|
}
|
|
1166
1244
|
async function removeIfStale(lockPath, staleMs) {
|
|
@@ -1191,7 +1269,7 @@ async function withDirectoryLock(targetPath, fn, options) {
|
|
|
1191
1269
|
if (attempt >= retries) {
|
|
1192
1270
|
throw new Error(`Failed to acquire lock for ${targetPath}`);
|
|
1193
1271
|
}
|
|
1194
|
-
await
|
|
1272
|
+
await sleep3(retryDelayMs * (attempt + 1));
|
|
1195
1273
|
}
|
|
1196
1274
|
}
|
|
1197
1275
|
try {
|
|
@@ -1269,7 +1347,9 @@ var AccountStore = class {
|
|
|
1269
1347
|
refreshToken: account.refreshToken,
|
|
1270
1348
|
accessToken: account.accessToken,
|
|
1271
1349
|
expiresAt: account.expiresAt,
|
|
1272
|
-
accountId: account.accountId
|
|
1350
|
+
accountId: account.accountId,
|
|
1351
|
+
accountUuid: account.accountUuid,
|
|
1352
|
+
deviceId: account.deviceId
|
|
1273
1353
|
};
|
|
1274
1354
|
}
|
|
1275
1355
|
async mutateAccount(uuid, fn) {
|
|
@@ -1405,7 +1485,7 @@ function createExecutorForProvider(providerName, dependencies) {
|
|
|
1405
1485
|
const {
|
|
1406
1486
|
handleRateLimitResponse,
|
|
1407
1487
|
formatWaitTime: formatWaitTime2,
|
|
1408
|
-
sleep:
|
|
1488
|
+
sleep: sleep4,
|
|
1409
1489
|
showToast: showToast2,
|
|
1410
1490
|
getAccountLabel: getAccountLabel2
|
|
1411
1491
|
} = dependencies;
|
|
@@ -1417,7 +1497,7 @@ function createExecutorForProvider(providerName, dependencies) {
|
|
|
1417
1497
|
for (let attempt = 0; attempt < MAX_SERVER_RETRIES_PER_ATTEMPT; attempt++) {
|
|
1418
1498
|
const backoff = Math.min(SERVER_RETRY_BASE_MS * 2 ** attempt, SERVER_RETRY_MAX_MS);
|
|
1419
1499
|
const jitteredBackoff = backoff * (0.5 + Math.random() * 0.5);
|
|
1420
|
-
await
|
|
1500
|
+
await sleep4(jitteredBackoff);
|
|
1421
1501
|
let retryResponse;
|
|
1422
1502
|
try {
|
|
1423
1503
|
retryResponse = await runtime.fetch(input, init);
|
|
@@ -1573,7 +1653,7 @@ function createExecutorForProvider(providerName, dependencies) {
|
|
|
1573
1653
|
`All ${manager.getAccountCount()} account(s) rate-limited. Waiting ${formatWaitTime2(waitMs)}...`,
|
|
1574
1654
|
"warning"
|
|
1575
1655
|
);
|
|
1576
|
-
await
|
|
1656
|
+
await sleep4(waitMs);
|
|
1577
1657
|
}
|
|
1578
1658
|
}
|
|
1579
1659
|
return {
|
|
@@ -1674,6 +1754,8 @@ function createProactiveRefreshQueueForProvider(dependencies) {
|
|
|
1674
1754
|
if (result.patch.uuid) target.uuid = result.patch.uuid;
|
|
1675
1755
|
if (result.patch.email) target.email = result.patch.email;
|
|
1676
1756
|
if (result.patch.accountId) target.accountId = result.patch.accountId;
|
|
1757
|
+
if (result.patch.accountUuid && !target.accountUuid) target.accountUuid = result.patch.accountUuid;
|
|
1758
|
+
if (result.patch.deviceId && !target.deviceId) target.deviceId = result.patch.deviceId;
|
|
1677
1759
|
target.consecutiveAuthFailures = 0;
|
|
1678
1760
|
target.isAuthDisabled = false;
|
|
1679
1761
|
target.authDisabledReason = void 0;
|
|
@@ -2073,7 +2155,6 @@ var anthropicOAuthAdapter = {
|
|
|
2073
2155
|
id: "anthropic",
|
|
2074
2156
|
authProviderId: "anthropic",
|
|
2075
2157
|
modelDisplayName: "Claude",
|
|
2076
|
-
statusToolName: "claude_multiauth_status",
|
|
2077
2158
|
authMethodLabel: "Claude Pro/Max (Multi-Auth)",
|
|
2078
2159
|
serviceLogName: "claude-multiauth",
|
|
2079
2160
|
oauthClientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
|
@@ -2107,7 +2188,6 @@ var openAIOAuthAdapter = {
|
|
|
2107
2188
|
id: "openai",
|
|
2108
2189
|
authProviderId: "openai",
|
|
2109
2190
|
modelDisplayName: "ChatGPT",
|
|
2110
|
-
statusToolName: "chatgpt_multiauth_status",
|
|
2111
2191
|
authMethodLabel: "ChatGPT Plus/Pro (Multi-Auth)",
|
|
2112
2192
|
serviceLogName: "chatgpt-multiauth",
|
|
2113
2193
|
oauthClientId: "app_EMoamEEZ73f0CkXaXp7hrann",
|
|
@@ -2510,6 +2590,293 @@ var CascadeStateManager = class {
|
|
|
2510
2590
|
};
|
|
2511
2591
|
}
|
|
2512
2592
|
};
|
|
2593
|
+
|
|
2594
|
+
// src/native-plugin-lifecycle.ts
|
|
2595
|
+
var EMPTY_OAUTH_CREDENTIALS = {
|
|
2596
|
+
type: "oauth",
|
|
2597
|
+
refresh: "",
|
|
2598
|
+
access: "",
|
|
2599
|
+
expires: 0
|
|
2600
|
+
};
|
|
2601
|
+
function zeroProviderModelCosts(provider) {
|
|
2602
|
+
const models = provider?.models;
|
|
2603
|
+
if (!models || typeof models !== "object") {
|
|
2604
|
+
return;
|
|
2605
|
+
}
|
|
2606
|
+
for (const model of Object.values(models)) {
|
|
2607
|
+
if (model) {
|
|
2608
|
+
model.cost = { input: 0, output: 0, cache: { read: 0, write: 0 } };
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
function createOpenCodeNativePluginLifecycle(options) {
|
|
2613
|
+
let manager = null;
|
|
2614
|
+
let runtimeFactory = null;
|
|
2615
|
+
let refreshQueue = null;
|
|
2616
|
+
const defaultFetch = async (input, init) => {
|
|
2617
|
+
if (!manager || !runtimeFactory) {
|
|
2618
|
+
return fetch(input, init);
|
|
2619
|
+
}
|
|
2620
|
+
if (manager.getAccountCount() === 0) {
|
|
2621
|
+
throw new Error(options.noAccountsMessage);
|
|
2622
|
+
}
|
|
2623
|
+
return options.executeWithAccountRotation(manager, runtimeFactory, options.client, input, init);
|
|
2624
|
+
};
|
|
2625
|
+
async function createLoaderResult() {
|
|
2626
|
+
const fetchHandler = options.createFetch?.({
|
|
2627
|
+
getManager: () => manager,
|
|
2628
|
+
getRuntimeFactory: () => runtimeFactory,
|
|
2629
|
+
defaultFetch
|
|
2630
|
+
}) ?? defaultFetch;
|
|
2631
|
+
const extras = await options.createLoaderExtras?.(manager);
|
|
2632
|
+
return {
|
|
2633
|
+
apiKey: options.oauthApiKey,
|
|
2634
|
+
fetch: fetchHandler,
|
|
2635
|
+
...extras
|
|
2636
|
+
};
|
|
2637
|
+
}
|
|
2638
|
+
async function createPassthroughResult() {
|
|
2639
|
+
return {
|
|
2640
|
+
apiKey: "",
|
|
2641
|
+
fetch
|
|
2642
|
+
};
|
|
2643
|
+
}
|
|
2644
|
+
async function initializeRuntimeFactory() {
|
|
2645
|
+
if (!manager || !runtimeFactory) {
|
|
2646
|
+
return;
|
|
2647
|
+
}
|
|
2648
|
+
manager.setRuntimeFactory(runtimeFactory);
|
|
2649
|
+
await options.afterManagerInitialized?.(manager, runtimeFactory);
|
|
2650
|
+
}
|
|
2651
|
+
async function startRefreshQueueIfNeeded() {
|
|
2652
|
+
if (!options.createRefreshQueue || !manager || !runtimeFactory || manager.getAccountCount() === 0) {
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
2655
|
+
if (refreshQueue) {
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
refreshQueue = options.createRefreshQueue(
|
|
2659
|
+
options.client,
|
|
2660
|
+
options.store,
|
|
2661
|
+
(uuid) => runtimeFactory?.invalidate(uuid)
|
|
2662
|
+
);
|
|
2663
|
+
refreshQueue.start();
|
|
2664
|
+
}
|
|
2665
|
+
async function initializeManagerFromStore() {
|
|
2666
|
+
if (manager) {
|
|
2667
|
+
return manager.getAccountCount() > 0;
|
|
2668
|
+
}
|
|
2669
|
+
const storage = await options.store.load();
|
|
2670
|
+
if (storage.accounts.length === 0) {
|
|
2671
|
+
return false;
|
|
2672
|
+
}
|
|
2673
|
+
manager = await options.managerClass.create(options.store, EMPTY_OAUTH_CREDENTIALS, options.client);
|
|
2674
|
+
runtimeFactory = options.createRuntimeFactory(options.store, options.client);
|
|
2675
|
+
await initializeRuntimeFactory();
|
|
2676
|
+
await startRefreshQueueIfNeeded();
|
|
2677
|
+
return manager.getAccountCount() > 0;
|
|
2678
|
+
}
|
|
2679
|
+
async function initializeManagerFromAuth(credentials) {
|
|
2680
|
+
manager = await options.managerClass.create(options.store, credentials, options.client);
|
|
2681
|
+
runtimeFactory = options.createRuntimeFactory(options.store, options.client);
|
|
2682
|
+
await initializeRuntimeFactory();
|
|
2683
|
+
}
|
|
2684
|
+
async function restartRefreshQueueIfNeeded() {
|
|
2685
|
+
if (refreshQueue) {
|
|
2686
|
+
await refreshQueue.stop();
|
|
2687
|
+
refreshQueue = null;
|
|
2688
|
+
}
|
|
2689
|
+
await startRefreshQueueIfNeeded();
|
|
2690
|
+
}
|
|
2691
|
+
return {
|
|
2692
|
+
getManager: () => manager,
|
|
2693
|
+
getRuntimeFactory: () => runtimeFactory,
|
|
2694
|
+
async load(auth, provider) {
|
|
2695
|
+
if (auth.type !== "oauth") {
|
|
2696
|
+
await options.migrateFromAuthJson?.(options.authJsonProviderKey, options.store);
|
|
2697
|
+
const recoveredFromStore = await initializeManagerFromStore();
|
|
2698
|
+
return recoveredFromStore ? createLoaderResult() : createPassthroughResult();
|
|
2699
|
+
}
|
|
2700
|
+
zeroProviderModelCosts(provider);
|
|
2701
|
+
const credentials = auth;
|
|
2702
|
+
await options.migrateFromAuthJson?.(options.authJsonProviderKey, options.store);
|
|
2703
|
+
await initializeManagerFromAuth(credentials);
|
|
2704
|
+
const initializedManager = manager;
|
|
2705
|
+
if (!initializedManager) {
|
|
2706
|
+
return createPassthroughResult();
|
|
2707
|
+
}
|
|
2708
|
+
await options.afterOAuthLoad?.(credentials, initializedManager);
|
|
2709
|
+
if (initializedManager.getAccountCount() > 0) {
|
|
2710
|
+
const activeAccount = initializedManager.getActiveAccount?.();
|
|
2711
|
+
const activeLabel = activeAccount ? options.getAccountLabel(activeAccount) : "none";
|
|
2712
|
+
options.client.tui.showToast({
|
|
2713
|
+
body: {
|
|
2714
|
+
message: `Multi-Auth: ${initializedManager.getAccountCount()} account(s) loaded. Active: ${activeLabel}`,
|
|
2715
|
+
variant: "info"
|
|
2716
|
+
}
|
|
2717
|
+
}).catch(() => {
|
|
2718
|
+
});
|
|
2719
|
+
await initializedManager.validateNonActiveTokens?.(options.client);
|
|
2720
|
+
const disabledCount = initializedManager.getAccounts().filter((account) => account.isAuthDisabled).length;
|
|
2721
|
+
if (disabledCount > 0) {
|
|
2722
|
+
options.client.tui.showToast({
|
|
2723
|
+
body: {
|
|
2724
|
+
message: `${disabledCount} account(s) have auth failures.`,
|
|
2725
|
+
variant: "warning"
|
|
2726
|
+
}
|
|
2727
|
+
}).catch(() => {
|
|
2728
|
+
});
|
|
2729
|
+
}
|
|
2730
|
+
await restartRefreshQueueIfNeeded();
|
|
2731
|
+
}
|
|
2732
|
+
return createLoaderResult();
|
|
2733
|
+
}
|
|
2734
|
+
};
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
// src/native-plugin-auth.ts
|
|
2738
|
+
function toInputs(value) {
|
|
2739
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2740
|
+
return void 0;
|
|
2741
|
+
}
|
|
2742
|
+
return value;
|
|
2743
|
+
}
|
|
2744
|
+
function createOpenCodeNativeAuthMethods(options) {
|
|
2745
|
+
return [
|
|
2746
|
+
{
|
|
2747
|
+
label: options.oauthLabel,
|
|
2748
|
+
type: "oauth",
|
|
2749
|
+
authorize: async (...args) => options.authorize(toInputs(args[0]))
|
|
2750
|
+
}
|
|
2751
|
+
];
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
// src/native-plugin-loader.ts
|
|
2755
|
+
function readProviderModels(provider) {
|
|
2756
|
+
return provider.models && typeof provider.models === "object" && !Array.isArray(provider.models) ? provider.models : {};
|
|
2757
|
+
}
|
|
2758
|
+
function createOpenCodeNativeAuthLoader(options) {
|
|
2759
|
+
return async function openCodeNativeAuthLoader(getAuth, provider) {
|
|
2760
|
+
const providerModels = readProviderModels(provider);
|
|
2761
|
+
options.debugLog?.("Auth loader received provider metadata", {
|
|
2762
|
+
providerId: typeof provider.id === "string" ? provider.id : void 0,
|
|
2763
|
+
providerName: typeof provider.name === "string" ? provider.name : void 0,
|
|
2764
|
+
modelCount: Object.keys(providerModels).length,
|
|
2765
|
+
modelIds: Object.keys(providerModels)
|
|
2766
|
+
});
|
|
2767
|
+
await options.beforeAuth?.(provider);
|
|
2768
|
+
const auth = await getAuth();
|
|
2769
|
+
options.debugLog?.("Auth loader resolved auth payload", {
|
|
2770
|
+
authType: typeof auth.type === "string" ? auth.type : void 0,
|
|
2771
|
+
authKeys: Object.keys(auth)
|
|
2772
|
+
});
|
|
2773
|
+
await options.beforeLoad?.({ auth, provider, lifecycle: options.lifecycle });
|
|
2774
|
+
const result = await options.lifecycle.load(auth, provider);
|
|
2775
|
+
const replacement = await options.afterLoad?.({
|
|
2776
|
+
auth,
|
|
2777
|
+
provider,
|
|
2778
|
+
lifecycle: options.lifecycle,
|
|
2779
|
+
manager: options.lifecycle.getManager(),
|
|
2780
|
+
runtimeFactory: options.lifecycle.getRuntimeFactory(),
|
|
2781
|
+
result
|
|
2782
|
+
});
|
|
2783
|
+
if (auth.type !== "oauth") {
|
|
2784
|
+
options.debugLog?.("Auth loader attempted store recovery", {
|
|
2785
|
+
recoveredFromStore: Boolean(options.lifecycle.getManager()?.getAccountCount())
|
|
2786
|
+
});
|
|
2787
|
+
} else {
|
|
2788
|
+
const manager = options.lifecycle.getManager();
|
|
2789
|
+
if (manager) {
|
|
2790
|
+
options.debugLog?.("Auth loader initialized manager state", {
|
|
2791
|
+
accountCount: manager.getAccountCount(),
|
|
2792
|
+
activeAccountUuid: manager.getActiveAccount?.()?.uuid
|
|
2793
|
+
});
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
return replacement ?? result;
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
// src/native-plugin-bootstrap-auth.ts
|
|
2801
|
+
import { promises as fs8 } from "fs";
|
|
2802
|
+
import { join as join8 } from "path";
|
|
2803
|
+
var AUTH_JSON_FILENAME2 = "auth.json";
|
|
2804
|
+
function hasCompleteOAuthCredential(account) {
|
|
2805
|
+
return typeof account.refreshToken === "string" && account.refreshToken.length > 0 && typeof account.accessToken === "string" && account.accessToken.length > 0 && typeof account.expiresAt === "number" && Number.isFinite(account.expiresAt);
|
|
2806
|
+
}
|
|
2807
|
+
function selectBootstrapAccount(accounts, activeAccountUuid) {
|
|
2808
|
+
const completeAccounts = accounts.filter(hasCompleteOAuthCredential);
|
|
2809
|
+
if (completeAccounts.length === 0) {
|
|
2810
|
+
return null;
|
|
2811
|
+
}
|
|
2812
|
+
const activeAccount = activeAccountUuid ? completeAccounts.find((account) => account.uuid === activeAccountUuid) : void 0;
|
|
2813
|
+
if (activeAccount) {
|
|
2814
|
+
return activeAccount;
|
|
2815
|
+
}
|
|
2816
|
+
const firstUsableAccount = completeAccounts.find(
|
|
2817
|
+
(account) => account.enabled !== false && account.isAuthDisabled !== true
|
|
2818
|
+
);
|
|
2819
|
+
return firstUsableAccount ?? completeAccounts[0];
|
|
2820
|
+
}
|
|
2821
|
+
async function readCurrentAuth(providerId) {
|
|
2822
|
+
const authPath = join8(getConfigDir2(), AUTH_JSON_FILENAME2);
|
|
2823
|
+
let raw;
|
|
2824
|
+
try {
|
|
2825
|
+
raw = await fs8.readFile(authPath, "utf-8");
|
|
2826
|
+
} catch {
|
|
2827
|
+
return null;
|
|
2828
|
+
}
|
|
2829
|
+
try {
|
|
2830
|
+
const parsed = JSON.parse(raw);
|
|
2831
|
+
const providerAuth = parsed[providerId];
|
|
2832
|
+
if (providerAuth?.type !== "oauth" || typeof providerAuth.refresh !== "string" || typeof providerAuth.access !== "string" || typeof providerAuth.expires !== "number") {
|
|
2833
|
+
return null;
|
|
2834
|
+
}
|
|
2835
|
+
return {
|
|
2836
|
+
type: "oauth",
|
|
2837
|
+
refresh: providerAuth.refresh,
|
|
2838
|
+
access: providerAuth.access,
|
|
2839
|
+
expires: providerAuth.expires
|
|
2840
|
+
};
|
|
2841
|
+
} catch {
|
|
2842
|
+
return null;
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
function shouldSyncBootstrapAuth(currentAuth, nextAuth) {
|
|
2846
|
+
if (!currentAuth) {
|
|
2847
|
+
return true;
|
|
2848
|
+
}
|
|
2849
|
+
if (currentAuth.refresh === nextAuth.refresh && currentAuth.access === nextAuth.access && currentAuth.expires === nextAuth.expires) {
|
|
2850
|
+
return false;
|
|
2851
|
+
}
|
|
2852
|
+
return currentAuth.expires < nextAuth.expires;
|
|
2853
|
+
}
|
|
2854
|
+
async function syncOpenCodeNativeBootstrapAuth(options) {
|
|
2855
|
+
const storage = await options.store.load();
|
|
2856
|
+
const bootstrapAccount = selectBootstrapAccount(storage.accounts, storage.activeAccountUuid);
|
|
2857
|
+
if (!bootstrapAccount) {
|
|
2858
|
+
return false;
|
|
2859
|
+
}
|
|
2860
|
+
const nextAuth = {
|
|
2861
|
+
type: "oauth",
|
|
2862
|
+
refresh: bootstrapAccount.refreshToken,
|
|
2863
|
+
access: bootstrapAccount.accessToken,
|
|
2864
|
+
expires: bootstrapAccount.expiresAt
|
|
2865
|
+
};
|
|
2866
|
+
const currentAuth = await readCurrentAuth(options.providerId);
|
|
2867
|
+
if (!shouldSyncBootstrapAuth(currentAuth, nextAuth)) {
|
|
2868
|
+
return false;
|
|
2869
|
+
}
|
|
2870
|
+
await options.client.auth.set({
|
|
2871
|
+
path: { id: options.providerId },
|
|
2872
|
+
body: nextAuth
|
|
2873
|
+
});
|
|
2874
|
+
return true;
|
|
2875
|
+
}
|
|
2876
|
+
var __openCodeNativeBootstrapAuthTestUtils = {
|
|
2877
|
+
selectBootstrapAccount,
|
|
2878
|
+
shouldSyncBootstrapAuth
|
|
2879
|
+
};
|
|
2513
2880
|
export {
|
|
2514
2881
|
ACCOUNTS_FILENAME,
|
|
2515
2882
|
ANSI,
|
|
@@ -2529,6 +2896,7 @@ export {
|
|
|
2529
2896
|
TokenRefreshError,
|
|
2530
2897
|
UsageLimitEntrySchema,
|
|
2531
2898
|
UsageLimitsSchema,
|
|
2899
|
+
__openCodeNativeBootstrapAuthTestUtils,
|
|
2532
2900
|
anthropicOAuthAdapter,
|
|
2533
2901
|
confirm,
|
|
2534
2902
|
createAccountManagerForProvider,
|
|
@@ -2536,6 +2904,9 @@ export {
|
|
|
2536
2904
|
createConfigLoader,
|
|
2537
2905
|
createExecutorForProvider,
|
|
2538
2906
|
createMinimalClient,
|
|
2907
|
+
createOpenCodeNativeAuthLoader,
|
|
2908
|
+
createOpenCodeNativeAuthMethods,
|
|
2909
|
+
createOpenCodeNativePluginLifecycle,
|
|
2539
2910
|
createProactiveRefreshQueueForProvider,
|
|
2540
2911
|
createRateLimitHandlers,
|
|
2541
2912
|
debugLog,
|
|
@@ -2566,6 +2937,7 @@ export {
|
|
|
2566
2937
|
setConfigGetter,
|
|
2567
2938
|
showToast,
|
|
2568
2939
|
sleep,
|
|
2940
|
+
syncOpenCodeNativeBootstrapAuth,
|
|
2569
2941
|
updateConfigField,
|
|
2570
2942
|
withDirectoryLock,
|
|
2571
2943
|
writeClaim
|