opencode-auth-load-balancer 0.8.0 → 0.9.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/v2-entry.js CHANGED
@@ -41438,6 +41438,15 @@ var init_tool = __esm(() => {
41438
41438
  tool2.schema = exports_external2;
41439
41439
  });
41440
41440
 
41441
+ // src/login-rev.ts
41442
+ function bumpLoginRev(row, now = Date.now()) {
41443
+ row.loginRev = (row.loginRev ?? 1) + 1;
41444
+ row.loginAt = now;
41445
+ }
41446
+ function loginVersion(row) {
41447
+ return { rev: row.loginRev ?? 1, at: row.loginAt ?? 0 };
41448
+ }
41449
+
41441
41450
  // src/types.ts
41442
41451
  function emptyUsage() {
41443
41452
  return { hourly: null, weekly: null, capturedAt: 0 };
@@ -41868,6 +41877,10 @@ function normalizeAccounts(rows) {
41868
41877
  delete row.orgId;
41869
41878
  if (!isFiniteNumber(row.refreshExpires) || row.refreshExpires <= 0)
41870
41879
  delete row.refreshExpires;
41880
+ if (typeof row.loginRev !== "number" || !Number.isSafeInteger(row.loginRev) || row.loginRev < 1)
41881
+ delete row.loginRev;
41882
+ if (!isFiniteNumber(row.loginAt) || row.loginAt < 0)
41883
+ delete row.loginAt;
41871
41884
  if (row.inferenceToken === undefined || !isFiniteNumber(row.inferenceExpires) || row.inferenceExpires <= 0)
41872
41885
  delete row.inferenceExpires;
41873
41886
  if (row.lostLogins !== undefined) {
@@ -42175,6 +42188,10 @@ function makeAccount(providerID, label, tokens) {
42175
42188
  account.orgId = tokens.orgId;
42176
42189
  if (tokens.refreshExpires)
42177
42190
  account.refreshExpires = tokens.refreshExpires;
42191
+ if (tokens.refresh && !tokens.inferenceOnly) {
42192
+ account.loginRev = 1;
42193
+ account.loginAt = Date.now();
42194
+ }
42178
42195
  return account;
42179
42196
  }
42180
42197
  function recordLostLogin(row, login, reason) {
@@ -42197,8 +42214,10 @@ function mergeLoginUsage(row, usage) {
42197
42214
  };
42198
42215
  }
42199
42216
  function applyTokens(row, tokens) {
42200
- if (tokens.refresh)
42217
+ if (tokens.refresh) {
42201
42218
  row.refresh = tokens.refresh;
42219
+ bumpLoginRev(row);
42220
+ }
42202
42221
  row.access = tokens.access;
42203
42222
  row.expires = tokens.expires;
42204
42223
  row.disabledReason = null;
@@ -42408,6 +42427,15 @@ function mergeHeaders(input, init) {
42408
42427
  return headers;
42409
42428
  }
42410
42429
 
42430
+ // src/refresh-hooks.ts
42431
+ function setRefreshHooks(hooks) {
42432
+ current = hooks;
42433
+ }
42434
+ function getRefreshHooks() {
42435
+ return current;
42436
+ }
42437
+ var current = null;
42438
+
42411
42439
  // src/refresh.ts
42412
42440
  function needsRefresh(account, now) {
42413
42441
  return !account.access || account.expires - REFRESH_SKEW_MS <= now;
@@ -42466,6 +42494,7 @@ async function commitRefresh(accountId, attempt, next) {
42466
42494
  if (!sameGeneration(stored, attempt))
42467
42495
  return tokensOf(stored);
42468
42496
  applyTokensTo(stored, next);
42497
+ bumpLoginRev(stored);
42469
42498
  stored.tokenGen = attempt.gen + 1;
42470
42499
  if (stored.disabledReason !== MANUAL_DISABLED_REASON)
42471
42500
  stored.disabledReason = null;
@@ -42492,11 +42521,31 @@ async function resolveInvalidGrant(adapter, accountId, attempt, reason) {
42492
42521
  return null;
42493
42522
  });
42494
42523
  }
42524
+ async function pullNewer(account, recovery) {
42525
+ try {
42526
+ const adopted = await getRefreshHooks()?.pull?.(account, recovery);
42527
+ return adopted ? await readPoolAccount(account.id) : undefined;
42528
+ } catch {
42529
+ return;
42530
+ }
42531
+ }
42532
+ function announceRefresh() {
42533
+ try {
42534
+ getRefreshHooks()?.afterRefresh?.();
42535
+ } catch {
42536
+ ignore();
42537
+ }
42538
+ }
42495
42539
  async function runRefresh(adapter, account, now, rejectedAccess) {
42496
42540
  return withLock(refreshLockDir(adapter.id, account.id), REFRESH_LOCK, async () => {
42497
- const latest = await readPoolAccount(account.id) ?? account;
42498
- if (latest.access !== rejectedAccess && !needsRefresh(latest, Math.max(now, Date.now())))
42499
- return tokensOf(latest);
42541
+ const read = await readPoolAccount(account.id) ?? account;
42542
+ const usable = (row) => row.access !== rejectedAccess && !needsRefresh(row, Math.max(now, Date.now()));
42543
+ if (usable(read))
42544
+ return tokensOf(read);
42545
+ const pulled = await pullNewer(read, false);
42546
+ if (pulled && usable(pulled))
42547
+ return tokensOf(pulled);
42548
+ const latest = pulled ?? read;
42500
42549
  const attempt = {
42501
42550
  refresh: latest.refresh,
42502
42551
  gen: genOf(latest)
@@ -42507,6 +42556,9 @@ async function runRefresh(adapter, account, now, rejectedAccess) {
42507
42556
  } catch (error) {
42508
42557
  if (!(error instanceof Error) || !isInvalidGrant(error))
42509
42558
  throw error;
42559
+ const recovered = await pullNewer(latest, true);
42560
+ if (recovered)
42561
+ return tokensOf(recovered);
42510
42562
  const adopted = await resolveInvalidGrant(adapter, account.id, attempt, refusalReason(error));
42511
42563
  if (adopted)
42512
42564
  return adopted;
@@ -42534,7 +42586,9 @@ async function ensureAccessToken(adapter, account, now, rejectedAccess) {
42534
42586
  const job = runRefresh(adapter, account, now, rejectedAccess);
42535
42587
  inflight.set(account.id, job);
42536
42588
  try {
42537
- return await settleRefresh(adapter, account, job);
42589
+ const access = await settleRefresh(adapter, account, job);
42590
+ announceRefresh();
42591
+ return access;
42538
42592
  } finally {
42539
42593
  inflight.delete(account.id);
42540
42594
  }
@@ -42733,7 +42787,7 @@ var init_session = __esm(() => {
42733
42787
  // src/token-mint.ts
42734
42788
  function autoTokenEnabled(env = process.env) {
42735
42789
  const raw = env.OPENCODE_AUTH_LB_ANTHROPIC_AUTO_TOKEN?.trim().toLowerCase();
42736
- return raw !== "0" && raw !== "false" && raw !== "no" && raw !== "off";
42790
+ return raw === "1" || raw === "true" || raw === "yes" || raw === "on";
42737
42791
  }
42738
42792
  function tokenDue(account, now) {
42739
42793
  if (!account.refresh || account.disabledReason)
@@ -42762,6 +42816,7 @@ async function mintToken(adapter, accountId) {
42762
42816
  if (minted.refresh !== row.refresh) {
42763
42817
  row.refresh = minted.refresh;
42764
42818
  row.tokenGen = attempt.gen + 1;
42819
+ bumpLoginRev(row);
42765
42820
  }
42766
42821
  if (minted.refreshExpires)
42767
42822
  row.refreshExpires = minted.refreshExpires;
@@ -43227,16 +43282,16 @@ function adopt(candidate) {
43227
43282
  const trimmed = candidate.trim();
43228
43283
  if (!VERSION_RE.test(trimmed))
43229
43284
  return;
43230
- if (compareVersions2(trimmed, current) <= 0)
43285
+ if (compareVersions2(trimmed, current2) <= 0)
43231
43286
  return;
43232
- current = trimmed;
43287
+ current2 = trimmed;
43233
43288
  }
43234
43289
  function envOverride() {
43235
43290
  const raw = process.env[CLAUDE_CODE_VERSION_ENV]?.trim();
43236
43291
  return raw && VERSION_RE.test(raw) ? raw : undefined;
43237
43292
  }
43238
43293
  function claudeCodeVersion() {
43239
- return envOverride() ?? current;
43294
+ return envOverride() ?? current2;
43240
43295
  }
43241
43296
  function userAgent() {
43242
43297
  return `claude-cli/${claudeCodeVersion()} (external, cli)`;
@@ -43271,7 +43326,7 @@ async function refreshClaudeCodeVersion(now) {
43271
43326
  await persist(now);
43272
43327
  }
43273
43328
  async function persist(now) {
43274
- await writeJsonAtomic(versionCacheFilePath(), JSON.stringify({ version: current, fetchedAt: now })).catch(ignore);
43329
+ await writeJsonAtomic(versionCacheFilePath(), JSON.stringify({ version: current2, fetchedAt: now })).catch(ignore);
43275
43330
  }
43276
43331
  async function recoverClaudeCodeVersion(res, now) {
43277
43332
  if (envOverride())
@@ -43284,12 +43339,12 @@ async function recoverClaudeCodeVersion(res, now) {
43284
43339
  }
43285
43340
  if (!text.includes(CLAUDE_CODE_VERSION_GATE_CODE))
43286
43341
  return false;
43287
- const before = current;
43342
+ const before = current2;
43288
43343
  const required = CLAUDE_CODE_REQUIRED_VERSION_RE.exec(text)?.[1];
43289
43344
  if (required !== undefined)
43290
43345
  adopt(required);
43291
43346
  await refreshClaudeCodeVersion(now).catch(ignore);
43292
- if (current === before)
43347
+ if (current2 === before)
43293
43348
  return false;
43294
43349
  await persist(now);
43295
43350
  return true;
@@ -43305,7 +43360,7 @@ async function primeClaudeCodeVersion(now) {
43305
43360
  }
43306
43361
  refreshClaudeCodeVersion(now).catch(ignore);
43307
43362
  }
43308
- var VERSION_RE, current;
43363
+ var VERSION_RE, current2;
43309
43364
  var init_version = __esm(() => {
43310
43365
  init_paths();
43311
43366
  init_store();
@@ -43313,7 +43368,7 @@ var init_version = __esm(() => {
43313
43368
  init_usage_http();
43314
43369
  init_constants();
43315
43370
  VERSION_RE = /^\d+\.\d+\.\d+$/;
43316
- current = FALLBACK_CLAUDE_CODE_VERSION;
43371
+ current2 = FALLBACK_CLAUDE_CODE_VERSION;
43317
43372
  });
43318
43373
 
43319
43374
  // src/providers/anthropic/cch.ts
@@ -45625,6 +45680,12 @@ var init_prime = __esm(() => {
45625
45680
  init_select();
45626
45681
  });
45627
45682
 
45683
+ // src/sync/enabled.ts
45684
+ function syncEnabled(env = process.env) {
45685
+ const raw = env.OPENCODE_AUTH_LB_SYNC?.trim().toLowerCase();
45686
+ return raw !== "0" && raw !== "false" && raw !== "no" && raw !== "off";
45687
+ }
45688
+
45628
45689
  // src/sync/errors.ts
45629
45690
  function describeSyncError(error) {
45630
45691
  return error instanceof SyncError ? error.message : "Sync failed.";
@@ -45732,10 +45793,7 @@ var init_crypto = __esm(() => {
45732
45793
  B64URL_RE = /^[\w-]+$/;
45733
45794
  });
45734
45795
 
45735
- // src/sync/payload.ts
45736
- function entryKey(entry) {
45737
- return `${entry.origin}/${entry.id}`;
45738
- }
45796
+ // src/sync/text.ts
45739
45797
  function isControl(code) {
45740
45798
  return code < 32 || code >= 127 && code <= 159 || code === 1564 || code >= 8203 && code <= 8207 || code >= 8232 && code <= 8238 || code >= 8288 && code <= 8297 || code === 65279;
45741
45799
  }
@@ -45745,6 +45803,106 @@ function printable(text) {
45745
45803
  function boundedString(value, max) {
45746
45804
  return typeof value === "string" && value.length > 0 && value.length <= max ? value : undefined;
45747
45805
  }
45806
+ var MAX_LABEL = 80;
45807
+
45808
+ // src/sync/login-payload.ts
45809
+ function loginKey(providerID, accountId) {
45810
+ return `${providerID}:${accountId}`;
45811
+ }
45812
+ function word(value, max) {
45813
+ const text = boundedString(value, max);
45814
+ return text !== undefined && NO_SPACE_RE.test(text) ? text : undefined;
45815
+ }
45816
+ function positive(value) {
45817
+ return isFiniteNumber(value) && value > 0 ? value : undefined;
45818
+ }
45819
+ function parseLogin(raw) {
45820
+ if (!isPlainObject4(raw))
45821
+ return null;
45822
+ const providerID = word(raw.providerID, MAX_ID);
45823
+ const accountId = word(raw.accountId, MAX_ID);
45824
+ const label = printable(boundedString(raw.label, MAX_LABEL) ?? "");
45825
+ const [access, refresh] = [
45826
+ word(raw.access, MAX_TOKEN),
45827
+ word(raw.refresh, MAX_TOKEN)
45828
+ ];
45829
+ const { rev, at } = raw;
45830
+ if (!providerID || !LOGIN_PROVIDERS.has(providerID) || !accountId)
45831
+ return null;
45832
+ if (raw.key !== loginKey(providerID, accountId))
45833
+ return null;
45834
+ const expires = positive(raw.expires);
45835
+ if (!label || !access || !refresh || !expires)
45836
+ return null;
45837
+ if (typeof rev !== "number" || !Number.isSafeInteger(rev) || rev < 1)
45838
+ return null;
45839
+ if (!isFiniteNumber(at) || at < 0)
45840
+ return null;
45841
+ const orgId = word(raw.orgId, MAX_ID);
45842
+ const refreshExpires = positive(raw.refreshExpires);
45843
+ return {
45844
+ key: loginKey(providerID, accountId),
45845
+ providerID,
45846
+ label,
45847
+ access,
45848
+ refresh,
45849
+ expires,
45850
+ accountId,
45851
+ ...orgId ? { orgId } : {},
45852
+ ...refreshExpires ? { refreshExpires } : {},
45853
+ rev,
45854
+ at
45855
+ };
45856
+ }
45857
+ function sortLogins(logins) {
45858
+ return [...logins].sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
45859
+ }
45860
+ function loginsDigest(logins) {
45861
+ return fingerprint(JSON.stringify(sortLogins(logins)));
45862
+ }
45863
+ function compareLogins(a, b) {
45864
+ if (a.rev !== b.rev)
45865
+ return a.rev < b.rev ? -1 : 1;
45866
+ if (a.at !== b.at)
45867
+ return a.at < b.at ? -1 : 1;
45868
+ const [x, y] = [fingerprint(a.refresh), fingerprint(b.refresh)];
45869
+ return x < y ? -1 : x > y ? 1 : 0;
45870
+ }
45871
+ function parseLoginList(rawList) {
45872
+ const parsed = {
45873
+ logins: [],
45874
+ loginsListed: new Set,
45875
+ loginsUnreadable: 0
45876
+ };
45877
+ for (const raw of rawList) {
45878
+ const key = isPlainObject4(raw) ? boundedString(raw.key, MAX_KEY) : undefined;
45879
+ if (key)
45880
+ parsed.loginsListed.add(key);
45881
+ const login = parseLogin(raw);
45882
+ if (!login)
45883
+ parsed.loginsUnreadable += 1;
45884
+ else if (!parsed.logins.some((l) => l.key === login.key))
45885
+ parsed.logins.push(login);
45886
+ }
45887
+ return parsed;
45888
+ }
45889
+ var MAX_LOGINS = 64, MAX_TOKEN = 8192, MAX_ID = 128, MAX_KEY = 300, NO_SPACE_RE, LOGIN_PROVIDERS;
45890
+ var init_login_payload = __esm(() => {
45891
+ init_util4();
45892
+ init_crypto();
45893
+ NO_SPACE_RE = /^\S+$/;
45894
+ LOGIN_PROVIDERS = new Set([
45895
+ "anthropic",
45896
+ "openai",
45897
+ "kimi-code-plan-cn",
45898
+ "kimi-code-plan-global"
45899
+ ]);
45900
+ });
45901
+
45902
+ // src/sync/payload.ts
45903
+ function entryKey(entry) {
45904
+ return `${entry.origin}/${entry.id}`;
45905
+ }
45748
45906
  function keyOf(raw) {
45749
45907
  if (!isPlainObject4(raw))
45750
45908
  return null;
@@ -45785,11 +45943,12 @@ function sortEntries(entries) {
45785
45943
  function entriesDigest(entries) {
45786
45944
  return fingerprint(JSON.stringify(sortEntries(entries)));
45787
45945
  }
45788
- function buildPayload(entries, at) {
45946
+ function buildPayload(entries, at, logins = []) {
45789
45947
  return JSON.stringify({
45790
45948
  v: PAYLOAD_VERSION,
45791
45949
  at,
45792
- entries: sortEntries(entries)
45950
+ entries: sortEntries(entries),
45951
+ logins: sortLogins(logins)
45793
45952
  });
45794
45953
  }
45795
45954
  function parsePayload(text) {
@@ -45801,11 +45960,12 @@ function parsePayload(text) {
45801
45960
  }
45802
45961
  if (!isPlainObject4(json) || !Array.isArray(json.entries))
45803
45962
  throw new SyncError("bad-blob");
45804
- if (json.v !== PAYLOAD_VERSION)
45963
+ if (json.v !== PAYLOAD_VERSION && json.v !== ENTRIES_ONLY_VERSION)
45805
45964
  throw new SyncError("bad-version");
45806
- if (!isFiniteNumber(json.at))
45965
+ const rawLogins = json.v === PAYLOAD_VERSION ? json.logins ?? [] : [];
45966
+ if (!isFiniteNumber(json.at) || !Array.isArray(rawLogins))
45807
45967
  throw new SyncError("bad-blob");
45808
- if (json.entries.length > MAX_ENTRIES)
45968
+ if (json.entries.length > MAX_ENTRIES || rawLogins.length > MAX_LOGINS)
45809
45969
  throw new SyncError("too-large");
45810
45970
  const listed = new Set;
45811
45971
  const entries = [];
@@ -45824,13 +45984,20 @@ function parsePayload(text) {
45824
45984
  else
45825
45985
  unreadable += 1;
45826
45986
  }
45827
- return { at: json.at, entries, listed, unreadable };
45987
+ return {
45988
+ at: json.at,
45989
+ entries,
45990
+ listed,
45991
+ unreadable,
45992
+ ...parseLoginList(rawLogins)
45993
+ };
45828
45994
  }
45829
- var PAYLOAD_VERSION = 2, MAX_ENTRIES = 64, MAX_LABEL = 80, MAX_SECRET = 512, MAX_FIELD = 128, ID_RE, ORIGIN_RE, CLAUDE_TOKEN_RE, API_KEY_RE, SECRET_SHAPES;
45995
+ var PAYLOAD_VERSION = 3, ENTRIES_ONLY_VERSION = 2, MAX_ENTRIES = 64, MAX_SECRET = 512, MAX_FIELD = 128, ID_RE, ORIGIN_RE, CLAUDE_TOKEN_RE, API_KEY_RE, SECRET_SHAPES;
45830
45996
  var init_payload = __esm(() => {
45831
45997
  init_util4();
45832
45998
  init_crypto();
45833
45999
  init_errors6();
46000
+ init_login_payload();
45834
46001
  ID_RE = /^[\w-]{1,64}$/;
45835
46002
  ORIGIN_RE = /^[\da-f]{32}$/;
45836
46003
  CLAUDE_TOKEN_RE = /^sk-ant-oat\d+-[\w-]+$/;
@@ -46076,6 +46243,12 @@ function isRef(value) {
46076
46243
  function isSkip(value) {
46077
46244
  return isPlainObject4(value) && typeof value.fingerprint === "string" && typeof value.blocker === "string" && typeof value.holds === "string";
46078
46245
  }
46246
+ function isMark(value) {
46247
+ return value === true;
46248
+ }
46249
+ function marks(from) {
46250
+ return from && Object.keys(from).length > 0 ? newMap(from) : undefined;
46251
+ }
46079
46252
  function readMap(raw, accept) {
46080
46253
  const map = newMap();
46081
46254
  if (isPlainObject4(raw)) {
@@ -46099,6 +46272,8 @@ function parseState(text) {
46099
46272
  return null;
46100
46273
  const { owner, etag, syncedAt, uploadedDigest, write } = json;
46101
46274
  const { uploadedAt, appliedAt, retryAt, pollAt, writeCheckAt } = json;
46275
+ const loginKnown = marks(readMap(json.loginKnown, isMark));
46276
+ const loginSuppressed = marks(readMap(json.loginSuppressed, isMark));
46102
46277
  return {
46103
46278
  v: 1,
46104
46279
  gistId: json.gistId,
@@ -46106,6 +46281,8 @@ function parseState(text) {
46106
46281
  creator: json.creator === true,
46107
46282
  imported: readMap(json.imported, isRef),
46108
46283
  skipped: readMap(json.skipped, isSkip),
46284
+ ...loginKnown ? { loginKnown } : {},
46285
+ ...loginSuppressed ? { loginSuppressed } : {},
46109
46286
  ...typeof owner === "string" ? { owner } : {},
46110
46287
  ...isWrite(write) ? { write } : {},
46111
46288
  ...typeof etag === "string" ? { etag } : {},
@@ -46335,6 +46512,179 @@ async function discoverGithubToken(run = ghAuthToken, env = process.env) {
46335
46512
  var GH_TIMEOUT_MS = 5000;
46336
46513
  var init_github_auth = () => {};
46337
46514
 
46515
+ // src/sync/logins.ts
46516
+ function loginsSyncEnabled(env = process.env) {
46517
+ const raw = env.OPENCODE_AUTH_LB_SYNC_LOGINS?.trim().toLowerCase();
46518
+ return raw !== "0" && raw !== "false" && raw !== "no" && raw !== "off";
46519
+ }
46520
+ function rowKey(row) {
46521
+ const live = row.refresh !== "" || row.inferenceToken !== undefined && row.lostLogins?.oauth !== undefined;
46522
+ return LOGIN_PROVIDERS.has(row.providerID) && row.accountId && live ? loginKey(row.providerID, row.accountId) : null;
46523
+ }
46524
+ function heldMarks(accounts) {
46525
+ const held = newMap();
46526
+ for (const row of accounts) {
46527
+ const key = rowKey(row);
46528
+ if (key)
46529
+ held[key] = true;
46530
+ }
46531
+ return held;
46532
+ }
46533
+ function ownLogins(pool) {
46534
+ const best = new Map;
46535
+ for (const row of pool.accounts) {
46536
+ if (!row.refresh || !row.accountId)
46537
+ continue;
46538
+ if (row.disabledReason || row.lostLogins?.oauth)
46539
+ continue;
46540
+ const login = parseLogin({
46541
+ key: loginKey(row.providerID, row.accountId),
46542
+ providerID: row.providerID,
46543
+ label: printable(row.label).slice(0, MAX_LABEL).trim() || row.providerID,
46544
+ access: row.access,
46545
+ refresh: row.refresh,
46546
+ expires: row.expires,
46547
+ accountId: row.accountId,
46548
+ orgId: row.orgId,
46549
+ refreshExpires: row.refreshExpires,
46550
+ ...loginVersion(row)
46551
+ });
46552
+ const prior = login ? best.get(login.key) : undefined;
46553
+ if (login && (!prior || compareLogins(login, prior) > 0))
46554
+ best.set(login.key, login);
46555
+ }
46556
+ return sortLogins([...best.values()]).slice(0, MAX_LOGINS);
46557
+ }
46558
+ function mergeLogins(gist, own) {
46559
+ const merged = new Map(gist.map((login) => [login.key, login]));
46560
+ for (const login of own) {
46561
+ const prior = merged.get(login.key);
46562
+ if (prior ? compareLogins(login, prior) > 0 : merged.size < MAX_LOGINS)
46563
+ merged.set(login.key, login);
46564
+ }
46565
+ return sortLogins([...merged.values()]);
46566
+ }
46567
+ function ownCopies(next, own) {
46568
+ return own.filter((login) => next.some((n) => n.key === login.key && n.refresh === login.refresh)).length;
46569
+ }
46570
+ function publishDigest(entries, logins) {
46571
+ return logins.length === 0 ? entriesDigest(entries) : fingerprint(`${entriesDigest(entries)}/${loginsDigest(logins)}`);
46572
+ }
46573
+ function sameLogin(row, login) {
46574
+ return rowKey(row) === login.key && (row.orgId === undefined || login.orgId === undefined || row.orgId === login.orgId);
46575
+ }
46576
+ function versionOf(row) {
46577
+ return { ...loginVersion(row), refresh: row.refresh };
46578
+ }
46579
+ function overwriteLocked(row, login) {
46580
+ const attempt = { refresh: row.refresh, gen: genOf(row) };
46581
+ return mutatePool((pool) => {
46582
+ const cur = findAccount(pool, row.id);
46583
+ if (!cur || !sameGeneration(cur, attempt))
46584
+ return false;
46585
+ cur.access = login.access;
46586
+ cur.refresh = login.refresh;
46587
+ cur.expires = login.expires;
46588
+ if (login.refreshExpires)
46589
+ cur.refreshExpires = login.refreshExpires;
46590
+ else
46591
+ delete cur.refreshExpires;
46592
+ if (login.orgId)
46593
+ cur.orgId = login.orgId;
46594
+ cur.loginRev = login.rev;
46595
+ cur.loginAt = login.at;
46596
+ cur.tokenGen = genOf(cur) + 1;
46597
+ clearLostLogin(cur, "oauth");
46598
+ if (cur.disabledReason?.startsWith(AUTO_DISABLED_PREFIX))
46599
+ cur.disabledReason = null;
46600
+ return true;
46601
+ });
46602
+ }
46603
+ function overwrite(row, login) {
46604
+ return withRefreshLock(row.providerID, row.id, () => overwriteLocked(row, login));
46605
+ }
46606
+ async function create(login) {
46607
+ const tokens = {
46608
+ access: login.access,
46609
+ refresh: login.refresh,
46610
+ expires: login.expires,
46611
+ accountId: login.accountId,
46612
+ ...login.orgId ? { orgId: login.orgId } : {},
46613
+ ...login.refreshExpires ? { refreshExpires: login.refreshExpires } : {}
46614
+ };
46615
+ return mutatePool((pool) => {
46616
+ if (pool.accounts.some((row) => sameLogin(row, login)))
46617
+ return false;
46618
+ const manual = new Set(pool.accounts.filter((row) => row.disabledReason === MANUAL_DISABLED_REASON).map((row) => row.id));
46619
+ const row = placeTokens(pool, login.providerID, tokens, uniqueLabel(pool, login.label), undefined);
46620
+ row.loginRev = login.rev;
46621
+ row.loginAt = login.at;
46622
+ row.tokenGen = genOf(row) + 1;
46623
+ if (manual.has(row.id))
46624
+ row.disabledReason = MANUAL_DISABLED_REASON;
46625
+ return true;
46626
+ });
46627
+ }
46628
+ async function adopt2(login) {
46629
+ const row = (await readPool()).accounts.find((a) => sameLogin(a, login));
46630
+ if (!row)
46631
+ return create(login);
46632
+ return compareLogins(login, versionOf(row)) > 0 ? overwrite(row, login) : false;
46633
+ }
46634
+ async function outrank(login, now) {
46635
+ const row = (await readPool()).accounts.find((a) => sameLogin(a, login));
46636
+ if (!row || compareLogins(versionOf(row), login) > 0)
46637
+ return;
46638
+ const attempt = { refresh: row.refresh, gen: genOf(row) };
46639
+ await mutatePool((pool) => {
46640
+ const cur = findAccount(pool, row.id);
46641
+ if (!cur || !sameGeneration(cur, attempt))
46642
+ return;
46643
+ cur.loginRev = login.rev + 1;
46644
+ cur.loginAt = now;
46645
+ });
46646
+ }
46647
+ async function applyLogins(gist, marks, now) {
46648
+ const known = newMap(marks.known);
46649
+ const suppressed = newMap(marks.suppressed);
46650
+ const held = heldMarks((await readPool()).accounts);
46651
+ for (const key of Object.keys(known)) {
46652
+ if (held[key])
46653
+ continue;
46654
+ suppressed[key] = true;
46655
+ delete known[key];
46656
+ }
46657
+ for (const key of Object.keys(suppressed)) {
46658
+ if (!held[key])
46659
+ continue;
46660
+ delete suppressed[key];
46661
+ const login = gist.find((l) => l.key === key);
46662
+ if (login)
46663
+ await outrank(login, now);
46664
+ }
46665
+ let adopted = 0;
46666
+ for (const login of gist)
46667
+ if (!suppressed[login.key] && await adopt2(login))
46668
+ adopted += 1;
46669
+ return {
46670
+ adopted,
46671
+ known: heldMarks((await readPool()).accounts),
46672
+ suppressed
46673
+ };
46674
+ }
46675
+ var AUTO_DISABLED_PREFIX = "invalid_grant: re-login required";
46676
+ var init_logins = __esm(() => {
46677
+ init_accounts();
46678
+ init_store();
46679
+ init_refresh();
46680
+ init_types2();
46681
+ init_apply();
46682
+ init_crypto();
46683
+ init_login_payload();
46684
+ init_payload();
46685
+ init_state();
46686
+ });
46687
+
46338
46688
  // src/sync/origin.ts
46339
46689
  import { randomBytes as randomBytes2 } from "node:crypto";
46340
46690
  import { readFile as readFile6 } from "node:fs/promises";
@@ -46435,6 +46785,10 @@ var init_timing = __esm(() => {
46435
46785
  function count(n, one, many) {
46436
46786
  return `${n} ${n === 1 ? one : many}`;
46437
46787
  }
46788
+ function uploadedMessage(entries, logins) {
46789
+ const credentials = count(entries, "credential", "credentials");
46790
+ return logins === 0 ? `Uploaded ${credentials}.` : `Uploaded ${credentials} and ${count(logins, "login", "logins")}.`;
46791
+ }
46438
46792
  function recordApplied(gistId, at, applied, syncedAt) {
46439
46793
  return updateSyncState((cur) => cur?.gistId === gistId ? {
46440
46794
  ...cur,
@@ -46458,7 +46812,8 @@ async function runCycle(deps, force, report) {
46458
46812
  const pool = await readPool();
46459
46813
  const backedOff = state.write === "denied" && !force && now() < (state.writeCheckAt ?? 0);
46460
46814
  const mayWrite = token !== null && !backedOff;
46461
- const before = entriesDigest(ownEntries(pool, origin, state.imported));
46815
+ const sharing = loginsSyncEnabled();
46816
+ const before = publishDigest(ownEntries(pool, origin, state.imported), sharing ? ownLogins(pool) : []);
46462
46817
  const dirty = mayWrite && before !== state.uploadedDigest;
46463
46818
  report.retryMs = dirty ? PUBLISH_RETRY_MS : POLL_MS;
46464
46819
  const reread = force || dirty || hasFreedSkips(pool.accounts, state.skipped);
@@ -46480,8 +46835,17 @@ async function runCycle(deps, force, report) {
46480
46835
  ...deps.adapters ? { adapters: deps.adapters } : {},
46481
46836
  afterPass: (soFar) => recordApplied(state.gistId, snapshot.at, soFar, now())
46482
46837
  });
46483
- const own = ownEntries(await readPool(), origin, applied?.imported ?? state.imported);
46484
- const unreadable = snapshot.unreadable > 0;
46838
+ const adoptedLogins = stale || !sharing ? null : await applyLogins(snapshot.logins, { known: state.loginKnown, suppressed: state.loginSuppressed }, now());
46839
+ if (adoptedLogins)
46840
+ await updateSyncState((cur) => cur?.gistId === state.gistId ? {
46841
+ ...cur,
46842
+ loginKnown: marks(adoptedLogins.known),
46843
+ loginSuppressed: marks(adoptedLogins.suppressed)
46844
+ } : cur);
46845
+ const settled = await readPool();
46846
+ const own = ownEntries(settled, origin, applied?.imported ?? state.imported);
46847
+ const ownShared = sharing ? ownLogins(settled) : [];
46848
+ const unreadable = snapshot.unreadable > 0 || snapshot.loginsUnreadable > 0;
46485
46849
  let write = standing;
46486
46850
  let wrote = null;
46487
46851
  let stamped = 0;
@@ -46491,12 +46855,16 @@ async function runCycle(deps, force, report) {
46491
46855
  if (mayWrite && !unreadable) {
46492
46856
  const others = snapshot.entries.filter((e) => e.origin !== origin);
46493
46857
  const next = sortEntries([...others, ...listable(own, others, origin)]);
46494
- const same = JSON.stringify(next) === JSON.stringify(sortEntries(snapshot.entries));
46858
+ const nextLogins = sharing ? mergeLogins(snapshot.logins, ownShared) : snapshot.logins;
46859
+ const same = JSON.stringify(next) === JSON.stringify(sortEntries(snapshot.entries)) && loginsDigest(nextLogins) === loginsDigest(snapshot.logins);
46495
46860
  if (!same || stale) {
46496
46861
  stamped = Math.max(now(), snapshot.at + 1, (state.uploadedAt ?? 0) + 1, (state.appliedAt ?? 0) + 1);
46497
46862
  try {
46498
- await updateGist(token, state.gistId, seal(buildPayload(next, stamped), key), now());
46499
- wrote = next.filter((e) => e.origin === origin);
46863
+ await updateGist(token, state.gistId, seal(buildPayload(next, stamped, nextLogins), key), now());
46864
+ wrote = {
46865
+ entries: next.filter((e) => e.origin === origin).length,
46866
+ logins: ownCopies(nextLogins, ownShared)
46867
+ };
46500
46868
  } catch (error) {
46501
46869
  if (!(error instanceof SyncError) || error.code !== "write-denied")
46502
46870
  throw error;
@@ -46512,18 +46880,22 @@ async function runCycle(deps, force, report) {
46512
46880
  etag: wrote || stale || deferred > 0 || denied ? undefined : read.etag,
46513
46881
  write,
46514
46882
  writeCheckAt: denied ? now() + WRITE_BACKOFF_MS : backedOff ? cur.writeCheckAt : undefined,
46515
- uploadedDigest: mayWrite && !unreadable && !denied ? entriesDigest(own) : cur.uploadedDigest,
46883
+ uploadedDigest: mayWrite && !unreadable && !denied ? publishDigest(own, ownShared) : cur.uploadedDigest,
46516
46884
  ...wrote ? { uploadedAt: stamped } : {}
46517
46885
  } : cur);
46518
46886
  const parts = [];
46519
46887
  const moved = applied ? applied.added + applied.updated + applied.removed + deferred : 0;
46888
+ const adopted = adoptedLogins?.adopted ?? 0;
46520
46889
  if (applied && moved > 0) {
46521
46890
  const tail = deferred > 0 ? `; ${count(deferred, "entry", "entries")} could not be verified yet` : "";
46522
- parts.push(`Synced: ${applied.added} added, ${applied.updated} updated, ${applied.removed} removed${tail}.`);
46523
- } else if (!wrote)
46891
+ const login = adopted > 0 ? `; ${count(adopted, "login", "logins")} adopted` : "";
46892
+ parts.push(`Synced: ${applied.added} added, ${applied.updated} updated, ${applied.removed} removed${tail}${login}.`);
46893
+ } else if (adopted > 0)
46894
+ parts.push(`Synced: ${count(adopted, "login", "logins")} adopted.`);
46895
+ else if (!wrote)
46524
46896
  parts.push("Up to date.");
46525
46897
  if (wrote)
46526
- parts.push(`Uploaded ${count(wrote.length, "credential", "credentials")}.`);
46898
+ parts.push(uploadedMessage(wrote.entries, wrote.logins));
46527
46899
  if (denied)
46528
46900
  parts.push(new SyncError("write-denied").message);
46529
46901
  if (mayWrite && unreadable && state.write !== "unreadable")
@@ -46538,6 +46910,8 @@ var init_cycle = __esm(() => {
46538
46910
  init_errors6();
46539
46911
  init_gist();
46540
46912
  init_github_auth();
46913
+ init_login_payload();
46914
+ init_logins();
46541
46915
  init_merge();
46542
46916
  init_origin();
46543
46917
  init_own();
@@ -46547,9 +46921,6 @@ var init_cycle = __esm(() => {
46547
46921
  });
46548
46922
 
46549
46923
  // src/sync/engine.ts
46550
- function count2(n, one, many) {
46551
- return `${n} ${n === 1 ? one : many}`;
46552
- }
46553
46924
  function createEngine(deps) {
46554
46925
  const { now } = deps;
46555
46926
  async function settle(work, reqAt, scheduled) {
@@ -46582,10 +46953,14 @@ function createEngine(deps) {
46582
46953
  if (!token)
46583
46954
  throw new SyncError("no-auth");
46584
46955
  const origin = await machineOrigin();
46585
- const own = ownEntries(await readPool(), origin, newRefs());
46956
+ const pool = await readPool();
46957
+ const own = ownEntries(pool, origin, newRefs());
46958
+ const sharing = loginsSyncEnabled();
46959
+ const shared = sharing ? ownLogins(pool) : [];
46586
46960
  const key = generateKey();
46587
46961
  const at = now();
46588
- const gist = await createGist(token, seal(buildPayload(own, at), key), now());
46962
+ const gist = await createGist(token, seal(buildPayload(own, at, shared), key), now());
46963
+ const loginKnown = sharing ? marks(heldMarks(pool.accounts)) : undefined;
46589
46964
  await updateSyncState(() => ({
46590
46965
  v: 1,
46591
46966
  gistId: gist.id,
@@ -46594,21 +46969,23 @@ function createEngine(deps) {
46594
46969
  ...gist.owner ? { owner: gist.owner } : {},
46595
46970
  write: "ok",
46596
46971
  syncedAt: now(),
46597
- uploadedDigest: entriesDigest(own),
46972
+ uploadedDigest: publishDigest(own, shared),
46598
46973
  uploadedAt: at,
46599
46974
  appliedAt: at,
46600
46975
  pollAt: now() + POLL_MS,
46601
46976
  imported: newRefs(),
46602
- skipped: newMap()
46977
+ skipped: newMap(),
46978
+ ...loginKnown ? { loginKnown } : {}
46603
46979
  }));
46604
- return `Uploaded ${count2(own.length, "credential", "credentials")}.`;
46980
+ const message = uploadedMessage(own.length, shared.length);
46981
+ return own.length + shared.length === 0 ? `${message} ${NOTHING_TO_SHARE}` : message;
46605
46982
  }
46606
46983
  async function unpublishedDigest() {
46607
46984
  const state = await readSyncState();
46608
46985
  if (!state || state.write === "no-token" || state.write === "unreadable" || state.write === "denied" && now() < (state.writeCheckAt ?? 0))
46609
46986
  return null;
46610
- const own = ownEntries(await readPool(), await machineOrigin(), state.imported);
46611
- const digest = entriesDigest(own);
46987
+ const pool = await readPool();
46988
+ const digest = publishDigest(ownEntries(pool, await machineOrigin(), state.imported), loginsSyncEnabled() ? ownLogins(pool) : []);
46612
46989
  return digest === state.uploadedDigest ? null : digest;
46613
46990
  }
46614
46991
  const engine = {
@@ -46630,7 +47007,9 @@ function createEngine(deps) {
46630
47007
  creator: prior?.creator ?? false,
46631
47008
  ...prior?.owner ? { owner: prior.owner } : {},
46632
47009
  imported: prior?.imported ?? newRefs(),
46633
- skipped: prior?.skipped ?? newMap()
47010
+ skipped: prior?.skipped ?? newMap(),
47011
+ ...prior?.loginKnown ? { loginKnown: prior.loginKnown } : {},
47012
+ ...prior?.loginSuppressed ? { loginSuppressed: prior.loginSuppressed } : {}
46634
47013
  };
46635
47014
  });
46636
47015
  return settle(cycle(false), reqAt, true);
@@ -46657,6 +47036,7 @@ function createEngine(deps) {
46657
47036
  };
46658
47037
  return engine;
46659
47038
  }
47039
+ var NOTHING_TO_SHARE = "No static credential or shareable OAuth login on this machine.";
46660
47040
  var init_engine = __esm(() => {
46661
47041
  init_store();
46662
47042
  init_crypto();
@@ -46664,6 +47044,7 @@ var init_engine = __esm(() => {
46664
47044
  init_errors6();
46665
47045
  init_gist();
46666
47046
  init_github_auth();
47047
+ init_logins();
46667
47048
  init_origin();
46668
47049
  init_own();
46669
47050
  init_payload();
@@ -46671,6 +47052,47 @@ var init_engine = __esm(() => {
46671
47052
  init_timing();
46672
47053
  });
46673
47054
 
47055
+ // src/sync/login-hooks.ts
47056
+ function createPullLogin(runGh) {
47057
+ return async function pullLogin(account, _recovery) {
47058
+ const { providerID, accountId } = account;
47059
+ if (!syncEnabled() || !loginsSyncEnabled())
47060
+ return false;
47061
+ if (!LOGIN_PROVIDERS.has(providerID) || !accountId || !account.refresh)
47062
+ return false;
47063
+ try {
47064
+ const state = await readSyncState();
47065
+ if (!state)
47066
+ return false;
47067
+ const token = await discoverGithubToken(runGh) ?? undefined;
47068
+ const read = await readGist(state.gistId, undefined, Date.now(), token);
47069
+ if (!read.changed)
47070
+ return false;
47071
+ const key = decodeKey(state.key) ?? Buffer.alloc(32);
47072
+ const wanted = loginKey(providerID, accountId);
47073
+ const login = parsePayload(open2(read.content, key)).logins.find((l) => l.key === wanted);
47074
+ if (!login || !sameLogin(account, login))
47075
+ return false;
47076
+ if (compareLogins(login, versionOf(account)) <= 0)
47077
+ return false;
47078
+ return await overwriteLocked(account, login);
47079
+ } catch {
47080
+ return false;
47081
+ }
47082
+ };
47083
+ }
47084
+ var pullLogin;
47085
+ var init_login_hooks = __esm(() => {
47086
+ init_crypto();
47087
+ init_gist();
47088
+ init_github_auth();
47089
+ init_login_payload();
47090
+ init_logins();
47091
+ init_payload();
47092
+ init_state();
47093
+ pullLogin = createPullLogin();
47094
+ });
47095
+
46674
47096
  // src/sync/intent.ts
46675
47097
  import { randomUUID as randomUUID3 } from "node:crypto";
46676
47098
  import { access, readFile as readFile7, rename as rename2, rm as rm3 } from "node:fs/promises";
@@ -46872,10 +47294,6 @@ var init_loop = __esm(() => {
46872
47294
  });
46873
47295
 
46874
47296
  // src/sync/start.ts
46875
- function syncEnabled(env = process.env) {
46876
- const raw = env.OPENCODE_AUTH_LB_SYNC?.trim().toLowerCase();
46877
- return raw !== "0" && raw !== "false" && raw !== "no" && raw !== "off";
46878
- }
46879
47297
  function startSync() {
46880
47298
  if (!syncEnabled())
46881
47299
  return null;
@@ -46884,11 +47302,45 @@ function startSync() {
46884
47302
  now: Date.now
46885
47303
  });
46886
47304
  loop.start();
46887
- return loop;
47305
+ if (!loginsSyncEnabled())
47306
+ return loop;
47307
+ setRefreshHooks({
47308
+ pull: pullLogin,
47309
+ afterRefresh: () => void publishAfterLogin().catch(ignore)
47310
+ });
47311
+ return {
47312
+ ...loop,
47313
+ dispose() {
47314
+ setRefreshHooks(null);
47315
+ return loop.dispose();
47316
+ }
47317
+ };
46888
47318
  }
47319
+ async function publishAfterLogin(options = {}) {
47320
+ const { engine = createEngine({ now: Date.now }) } = options;
47321
+ const { timeoutMs = LOGIN_PUBLISH_MS } = options;
47322
+ let timer;
47323
+ const publish = async () => {
47324
+ if (!syncEnabled() || !loginsSyncEnabled() || !await readSyncState())
47325
+ return;
47326
+ await withCycleLock(() => engine.sync(), timeoutMs);
47327
+ };
47328
+ await Promise.race([
47329
+ publish().catch(ignore),
47330
+ new Promise((resolve) => {
47331
+ timer = setTimeout(resolve, timeoutMs);
47332
+ })
47333
+ ]);
47334
+ clearTimeout(timer);
47335
+ }
47336
+ var LOGIN_PUBLISH_MS = 20000;
46889
47337
  var init_start = __esm(() => {
47338
+ init_util4();
46890
47339
  init_engine();
47340
+ init_login_hooks();
47341
+ init_logins();
46891
47342
  init_loop();
47343
+ init_state();
46892
47344
  });
46893
47345
 
46894
47346
  // src/legacy.ts
@@ -46916,6 +47368,8 @@ function buildAuthHook(adapter, client, pending) {
46916
47368
  if (minted || account.inferenceToken !== undefined && account.refresh)
46917
47369
  notifyPaired(client, adapter.id, account);
46918
47370
  await refreshUsageInBackground(adapter, Date.now()).catch(ignore);
47371
+ if (tokens.refresh && !tokens.inferenceOnly)
47372
+ await publishAfterLogin();
46919
47373
  };
46920
47374
  const oauthSuccess = (tokens) => ({
46921
47375
  type: "success",