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/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 data = await fs2.readFile(claimsPath, "utf-8");
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 writeClaimsFile(claimsPath, cleaned);
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 writeClaimsFile(claimsPath, cleaned);
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 writeClaimsFile(claimsPath, cleaned);
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
- createNewAccount(auth, now) {
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
- if (result.patch.accountId) account.accountId = result.patch.accountId;
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
- if (result.patch.accountId) account.accountId = result.patch.accountId;
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 sleep2(ms) {
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 sleep2(retryDelayMs * (attempt + 1));
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: sleep3,
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 sleep3(jitteredBackoff);
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 sleep3(waitMs);
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