oc-chatgpt-multi-auth 5.3.2 → 5.3.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AA0I/D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MAosG/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAoB,CAAC;AAElD,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AA0I/D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MAu7G/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAoB,CAAC;AAElD,eAAe,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -226,7 +226,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
226
226
  await withAccountStorageTransaction(async (loadedStorage, persist) => {
227
227
  const now = Date.now();
228
228
  const stored = replaceAll ? null : loadedStorage;
229
- const accounts = stored?.accounts ? [...stored.accounts] : [];
229
+ let accounts = stored?.accounts ? [...stored.accounts] : [];
230
230
  const pushIndex = (map, key, index) => {
231
231
  const existing = map.get(key);
232
232
  if (existing) {
@@ -241,6 +241,93 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
241
241
  const [onlyIndex] = indices;
242
242
  return typeof onlyIndex === "number" ? onlyIndex : undefined;
243
243
  };
244
+ const pickNewestAccountIndex = (existingIndex, candidateIndex) => {
245
+ const existing = accounts[existingIndex];
246
+ const candidate = accounts[candidateIndex];
247
+ if (!existing)
248
+ return candidateIndex;
249
+ if (!candidate)
250
+ return existingIndex;
251
+ const existingLastUsed = existing.lastUsed ?? 0;
252
+ const candidateLastUsed = candidate.lastUsed ?? 0;
253
+ if (candidateLastUsed > existingLastUsed)
254
+ return candidateIndex;
255
+ if (candidateLastUsed < existingLastUsed)
256
+ return existingIndex;
257
+ const existingAddedAt = existing.addedAt ?? 0;
258
+ const candidateAddedAt = candidate.addedAt ?? 0;
259
+ return candidateAddedAt >= existingAddedAt ? candidateIndex : existingIndex;
260
+ };
261
+ const mergeAccountRecords = (targetIndex, sourceIndex) => {
262
+ const target = accounts[targetIndex];
263
+ const source = accounts[sourceIndex];
264
+ if (!target || !source)
265
+ return;
266
+ const targetLastUsed = target.lastUsed ?? 0;
267
+ const sourceLastUsed = source.lastUsed ?? 0;
268
+ const targetAddedAt = target.addedAt ?? 0;
269
+ const sourceAddedAt = source.addedAt ?? 0;
270
+ const sourceIsNewer = sourceLastUsed > targetLastUsed ||
271
+ (sourceLastUsed === targetLastUsed && sourceAddedAt > targetAddedAt);
272
+ const newer = sourceIsNewer ? source : target;
273
+ const older = sourceIsNewer ? target : source;
274
+ const mergedRateLimitResetTimes = {};
275
+ const rateLimitResetKeys = new Set([
276
+ ...Object.keys(older.rateLimitResetTimes ?? {}),
277
+ ...Object.keys(newer.rateLimitResetTimes ?? {}),
278
+ ]);
279
+ for (const key of rateLimitResetKeys) {
280
+ const olderRaw = older.rateLimitResetTimes?.[key];
281
+ const newerRaw = newer.rateLimitResetTimes?.[key];
282
+ const olderValue = typeof olderRaw === "number" && Number.isFinite(olderRaw) ? olderRaw : 0;
283
+ const newerValue = typeof newerRaw === "number" && Number.isFinite(newerRaw) ? newerRaw : 0;
284
+ const resolved = Math.max(olderValue, newerValue);
285
+ if (resolved > 0) {
286
+ mergedRateLimitResetTimes[key] = resolved;
287
+ }
288
+ }
289
+ const mergedEnabled = target.enabled === false || source.enabled === false
290
+ ? false
291
+ : target.enabled ?? source.enabled;
292
+ const targetCoolingDownUntil = typeof target.coolingDownUntil === "number" && Number.isFinite(target.coolingDownUntil)
293
+ ? target.coolingDownUntil
294
+ : 0;
295
+ const sourceCoolingDownUntil = typeof source.coolingDownUntil === "number" && Number.isFinite(source.coolingDownUntil)
296
+ ? source.coolingDownUntil
297
+ : 0;
298
+ const mergedCoolingDownUntilValue = Math.max(targetCoolingDownUntil, sourceCoolingDownUntil);
299
+ const mergedCoolingDownUntil = mergedCoolingDownUntilValue > 0 ? mergedCoolingDownUntilValue : undefined;
300
+ const mergedCooldownReason = (() => {
301
+ if (mergedCoolingDownUntilValue <= 0) {
302
+ return target.cooldownReason ?? source.cooldownReason;
303
+ }
304
+ if (sourceCoolingDownUntil > targetCoolingDownUntil) {
305
+ return source.cooldownReason ?? target.cooldownReason;
306
+ }
307
+ if (targetCoolingDownUntil > sourceCoolingDownUntil) {
308
+ return target.cooldownReason ?? source.cooldownReason;
309
+ }
310
+ return source.cooldownReason ?? target.cooldownReason;
311
+ })();
312
+ accounts[targetIndex] = {
313
+ ...target,
314
+ accountId: target.accountId ?? source.accountId,
315
+ organizationId: target.organizationId ?? source.organizationId,
316
+ accountIdSource: target.accountIdSource ?? source.accountIdSource,
317
+ accountLabel: target.accountLabel ?? source.accountLabel,
318
+ email: target.email ?? source.email,
319
+ refreshToken: newer.refreshToken || older.refreshToken,
320
+ accessToken: newer.accessToken || older.accessToken,
321
+ expiresAt: newer.expiresAt ?? older.expiresAt,
322
+ enabled: mergedEnabled,
323
+ addedAt: Math.max(target.addedAt ?? 0, source.addedAt ?? 0),
324
+ lastUsed: Math.max(target.lastUsed ?? 0, source.lastUsed ?? 0),
325
+ lastSwitchReason: target.lastSwitchReason ?? source.lastSwitchReason,
326
+ rateLimitResetTimes: mergedRateLimitResetTimes,
327
+ coolingDownUntil: mergedCoolingDownUntil,
328
+ cooldownReason: mergedCooldownReason,
329
+ };
330
+ };
244
331
  const resolveUniqueOrgScopedMatch = (indexes, accountId, refreshToken) => {
245
332
  const byAccountId = accountId
246
333
  ? asUniqueIndex(indexes.byAccountIdOrgScoped.get(accountId))
@@ -260,6 +347,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
260
347
  const byEmailNoOrg = new Map();
261
348
  const byAccountIdOrgScoped = new Map();
262
349
  const byRefreshTokenOrgScoped = new Map();
350
+ const byRefreshTokenGlobal = new Map();
263
351
  for (let i = 0; i < accounts.length; i += 1) {
264
352
  const account = accounts[i];
265
353
  if (!account)
@@ -268,6 +356,10 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
268
356
  const accountId = account.accountId?.trim();
269
357
  const refreshToken = account.refreshToken?.trim();
270
358
  const email = account.email?.trim();
359
+ // Global refresh token index: first entry wins (keeps earliest/primary).
360
+ if (refreshToken && !byRefreshTokenGlobal.has(refreshToken)) {
361
+ byRefreshTokenGlobal.set(refreshToken, i);
362
+ }
271
363
  if (organizationId) {
272
364
  byOrganizationId.set(organizationId, i);
273
365
  if (accountId) {
@@ -295,6 +387,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
295
387
  byEmailNoOrg,
296
388
  byAccountIdOrgScoped,
297
389
  byRefreshTokenOrgScoped,
390
+ byRefreshTokenGlobal,
298
391
  };
299
392
  };
300
393
  let identityIndexes = buildIdentityIndexes();
@@ -328,7 +421,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
328
421
  return byEmail;
329
422
  }
330
423
  }
331
- return resolveUniqueOrgScopedMatch(identityIndexes, normalizedAccountId, result.refresh);
424
+ const orgScoped = resolveUniqueOrgScopedMatch(identityIndexes, normalizedAccountId, result.refresh);
425
+ if (orgScoped !== undefined)
426
+ return orgScoped;
427
+ // Absolute last resort: same refresh token = same human account.
428
+ // Catches org-scoped entries invisible to no-org lookups.
429
+ return identityIndexes.byRefreshTokenGlobal.get(result.refresh);
332
430
  })();
333
431
  if (existingIndex === undefined) {
334
432
  accounts.push({
@@ -351,11 +449,20 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
351
449
  continue;
352
450
  const nextEmail = accountEmail ?? existing.email;
353
451
  const nextOrganizationId = organizationId ?? existing.organizationId;
354
- const nextAccountId = normalizedAccountId ?? existing.accountId;
355
- const nextAccountIdSource = normalizedAccountId
356
- ? accountIdSource ?? existing.accountIdSource
357
- : existing.accountIdSource;
358
- const nextAccountLabel = accountLabel ?? existing.accountLabel;
452
+ const preserveOrgIdentity = typeof existing.organizationId === "string" &&
453
+ existing.organizationId.trim().length > 0 &&
454
+ !organizationId;
455
+ const nextAccountId = preserveOrgIdentity
456
+ ? existing.accountId ?? normalizedAccountId
457
+ : normalizedAccountId ?? existing.accountId;
458
+ const nextAccountIdSource = preserveOrgIdentity
459
+ ? existing.accountIdSource ?? accountIdSource
460
+ : normalizedAccountId
461
+ ? accountIdSource ?? existing.accountIdSource
462
+ : existing.accountIdSource;
463
+ const nextAccountLabel = preserveOrgIdentity
464
+ ? existing.accountLabel ?? accountLabel
465
+ : accountLabel ?? existing.accountLabel;
359
466
  accounts[existingIndex] = {
360
467
  ...existing,
361
468
  accountId: nextAccountId,
@@ -370,22 +477,140 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
370
477
  };
371
478
  identityIndexes = buildIdentityIndexes();
372
479
  }
480
+ const pruneRefreshTokenCollisions = () => {
481
+ const indicesToRemove = new Set();
482
+ const refreshMap = new Map();
483
+ for (let i = 0; i < accounts.length; i += 1) {
484
+ const account = accounts[i];
485
+ if (!account)
486
+ continue;
487
+ const refreshToken = account.refreshToken?.trim();
488
+ if (!refreshToken)
489
+ continue;
490
+ const orgKey = account.organizationId?.trim() ?? "";
491
+ let entry = refreshMap.get(refreshToken);
492
+ if (!entry) {
493
+ entry = { byOrg: new Map(), fallbackIndex: undefined };
494
+ refreshMap.set(refreshToken, entry);
495
+ }
496
+ if (orgKey) {
497
+ const existingIndex = entry.byOrg.get(orgKey);
498
+ if (existingIndex !== undefined) {
499
+ const newestIndex = pickNewestAccountIndex(existingIndex, i);
500
+ const obsoleteIndex = newestIndex === existingIndex ? i : existingIndex;
501
+ mergeAccountRecords(newestIndex, obsoleteIndex);
502
+ indicesToRemove.add(obsoleteIndex);
503
+ entry.byOrg.set(orgKey, newestIndex);
504
+ continue;
505
+ }
506
+ entry.byOrg.set(orgKey, i);
507
+ continue;
508
+ }
509
+ const existingFallback = entry.fallbackIndex;
510
+ if (typeof existingFallback === "number") {
511
+ const newestIndex = pickNewestAccountIndex(existingFallback, i);
512
+ const obsoleteIndex = newestIndex === existingFallback ? i : existingFallback;
513
+ mergeAccountRecords(newestIndex, obsoleteIndex);
514
+ indicesToRemove.add(obsoleteIndex);
515
+ entry.fallbackIndex = newestIndex;
516
+ continue;
517
+ }
518
+ entry.fallbackIndex = i;
519
+ }
520
+ for (const entry of refreshMap.values()) {
521
+ const fallbackIndex = entry.fallbackIndex;
522
+ if (typeof fallbackIndex !== "number")
523
+ continue;
524
+ const orgIndices = Array.from(entry.byOrg.values());
525
+ if (orgIndices.length === 0)
526
+ continue;
527
+ const [firstOrgIndex, ...otherOrgIndices] = orgIndices;
528
+ if (typeof firstOrgIndex !== "number")
529
+ continue;
530
+ let preferredOrgIndex = firstOrgIndex;
531
+ for (const orgIndex of otherOrgIndices) {
532
+ preferredOrgIndex = pickNewestAccountIndex(preferredOrgIndex, orgIndex);
533
+ }
534
+ mergeAccountRecords(preferredOrgIndex, fallbackIndex);
535
+ indicesToRemove.add(fallbackIndex);
536
+ }
537
+ if (indicesToRemove.size > 0) {
538
+ accounts = accounts.filter((_, index) => !indicesToRemove.has(index));
539
+ }
540
+ };
541
+ const collectIdentityKeys = (account) => {
542
+ const keys = [];
543
+ const organizationId = account?.organizationId?.trim();
544
+ if (organizationId)
545
+ keys.push(`org:${organizationId}`);
546
+ const accountId = account?.accountId?.trim();
547
+ if (accountId)
548
+ keys.push(`account:${accountId}`);
549
+ const refreshToken = account?.refreshToken?.trim();
550
+ if (refreshToken)
551
+ keys.push(`refresh:${refreshToken}`);
552
+ return keys;
553
+ };
554
+ const getStoredAccountAtIndex = (rawIndex) => {
555
+ const storedAccounts = stored?.accounts;
556
+ if (!storedAccounts)
557
+ return undefined;
558
+ if (typeof rawIndex !== "number" || !Number.isFinite(rawIndex))
559
+ return undefined;
560
+ const candidate = Math.floor(rawIndex);
561
+ if (candidate < 0 || candidate >= storedAccounts.length)
562
+ return undefined;
563
+ return storedAccounts[candidate];
564
+ };
565
+ const storedActiveKeys = replaceAll
566
+ ? []
567
+ : collectIdentityKeys(getStoredAccountAtIndex(stored?.activeIndex));
568
+ const storedActiveKeysByFamily = {};
569
+ if (!replaceAll) {
570
+ for (const family of MODEL_FAMILIES) {
571
+ const familyKeys = collectIdentityKeys(getStoredAccountAtIndex(stored?.activeIndexByFamily?.[family]));
572
+ if (familyKeys.length > 0) {
573
+ storedActiveKeysByFamily[family] = familyKeys;
574
+ }
575
+ }
576
+ }
577
+ pruneRefreshTokenCollisions();
373
578
  if (accounts.length === 0)
374
579
  return;
375
- const activeIndex = replaceAll
580
+ const resolveIndexByIdentityKeys = (identityKeys) => {
581
+ if (!identityKeys || identityKeys.length === 0)
582
+ return undefined;
583
+ for (const identityKey of identityKeys) {
584
+ const index = accounts.findIndex((account) => collectIdentityKeys(account).includes(identityKey));
585
+ if (index >= 0) {
586
+ return index;
587
+ }
588
+ }
589
+ return undefined;
590
+ };
591
+ const fallbackActiveIndex = replaceAll
376
592
  ? 0
377
593
  : typeof stored?.activeIndex === "number" && Number.isFinite(stored.activeIndex)
378
594
  ? stored.activeIndex
379
595
  : 0;
380
- const clampedActiveIndex = Math.max(0, Math.min(activeIndex, accounts.length - 1));
596
+ const remappedActiveIndex = replaceAll
597
+ ? undefined
598
+ : resolveIndexByIdentityKeys(storedActiveKeys);
599
+ const activeIndex = remappedActiveIndex ?? fallbackActiveIndex;
600
+ const clampedActiveIndex = Math.max(0, Math.min(Math.floor(activeIndex), accounts.length - 1));
381
601
  const activeIndexByFamily = {};
382
602
  for (const family of MODEL_FAMILIES) {
383
603
  const storedFamilyIndex = stored?.activeIndexByFamily?.[family];
604
+ const remappedFamilyIndex = replaceAll
605
+ ? undefined
606
+ : resolveIndexByIdentityKeys(storedActiveKeysByFamily[family]);
384
607
  const rawFamilyIndex = replaceAll
385
608
  ? 0
386
- : typeof storedFamilyIndex === "number" && Number.isFinite(storedFamilyIndex)
387
- ? storedFamilyIndex
388
- : clampedActiveIndex;
609
+ : typeof remappedFamilyIndex === "number"
610
+ ? remappedFamilyIndex
611
+ : typeof storedFamilyIndex === "number" && Number.isFinite(storedFamilyIndex)
612
+ ? storedFamilyIndex
613
+ : clampedActiveIndex;
389
614
  activeIndexByFamily[family] = Math.max(0, Math.min(Math.floor(rawFamilyIndex), accounts.length - 1));
390
615
  }
391
616
  await persist({
@@ -1414,7 +1639,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1414
1639
  const headers = createCodexHeaders(undefined, params.accountId, params.accessToken, {
1415
1640
  model,
1416
1641
  });
1417
- headers.set("content-type", "application/json; charset=utf-8");
1642
+ headers.set("content-type", "application/json");
1418
1643
  const controller = new AbortController();
1419
1644
  const timeout = setTimeout(() => controller.abort(), 15_000);
1420
1645
  let response;