oc-chatgpt-multi-auth 5.3.4 → 5.4.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.
@@ -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;AA+J/D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MA8kK/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;AA+J/D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MA8yK/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAoB,CAAC;AAElD,eAAe,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -350,6 +350,93 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
350
350
  cooldownReason: mergedCooldownReason,
351
351
  };
352
352
  };
353
+ const normalizeStoredAccountId = (account) => {
354
+ const accountId = account?.accountId?.trim();
355
+ return accountId && accountId.length > 0 ? accountId : undefined;
356
+ };
357
+ const hasDistinctNonEmptyAccountIds = (left, right) => {
358
+ const leftId = normalizeStoredAccountId(left);
359
+ const rightId = normalizeStoredAccountId(right);
360
+ return !!leftId && !!rightId && leftId !== rightId;
361
+ };
362
+ const canCollapseWithCandidateAccountId = (existing, candidateAccountId) => {
363
+ const existingAccountId = normalizeStoredAccountId(existing);
364
+ const normalizedCandidate = candidateAccountId?.trim() || undefined;
365
+ if (!existingAccountId || !normalizedCandidate) {
366
+ return true;
367
+ }
368
+ return existingAccountId === normalizedCandidate;
369
+ };
370
+ const resolveOrganizationMatch = (indexes, organizationId, candidateAccountId) => {
371
+ const matches = indexes.byOrganizationId.get(organizationId);
372
+ if (!matches || matches.length === 0)
373
+ return undefined;
374
+ const candidateId = candidateAccountId?.trim() || undefined;
375
+ let newestNoAccountId;
376
+ let newestExactAccountId;
377
+ let newestAnyNonEmptyAccountId;
378
+ const distinctNonEmptyAccountIds = new Set();
379
+ for (const index of matches) {
380
+ const existing = accounts[index];
381
+ if (!existing)
382
+ continue;
383
+ const existingAccountId = normalizeStoredAccountId(existing);
384
+ if (!existingAccountId) {
385
+ newestNoAccountId =
386
+ typeof newestNoAccountId === "number"
387
+ ? pickNewestAccountIndex(newestNoAccountId, index)
388
+ : index;
389
+ continue;
390
+ }
391
+ distinctNonEmptyAccountIds.add(existingAccountId);
392
+ newestAnyNonEmptyAccountId =
393
+ typeof newestAnyNonEmptyAccountId === "number"
394
+ ? pickNewestAccountIndex(newestAnyNonEmptyAccountId, index)
395
+ : index;
396
+ if (candidateId && existingAccountId === candidateId) {
397
+ newestExactAccountId =
398
+ typeof newestExactAccountId === "number"
399
+ ? pickNewestAccountIndex(newestExactAccountId, index)
400
+ : index;
401
+ }
402
+ }
403
+ if (candidateId) {
404
+ return newestExactAccountId ?? newestNoAccountId;
405
+ }
406
+ if (typeof newestNoAccountId === "number") {
407
+ return newestNoAccountId;
408
+ }
409
+ if (distinctNonEmptyAccountIds.size === 1) {
410
+ return newestAnyNonEmptyAccountId;
411
+ }
412
+ return undefined;
413
+ };
414
+ const resolveNoOrgRefreshMatch = (indexes, refreshToken, candidateAccountId) => {
415
+ const candidateId = candidateAccountId?.trim() || undefined;
416
+ const matches = indexes.byRefreshTokenNoOrg.get(refreshToken);
417
+ if (!matches || matches.length === 0)
418
+ return undefined;
419
+ let newestNoAccountId;
420
+ let newestExactAccountId;
421
+ for (const index of matches) {
422
+ const existing = accounts[index];
423
+ const existingAccountId = normalizeStoredAccountId(existing);
424
+ if (!existingAccountId) {
425
+ newestNoAccountId =
426
+ typeof newestNoAccountId === "number"
427
+ ? pickNewestAccountIndex(newestNoAccountId, index)
428
+ : index;
429
+ continue;
430
+ }
431
+ if (candidateId && existingAccountId === candidateId) {
432
+ newestExactAccountId =
433
+ typeof newestExactAccountId === "number"
434
+ ? pickNewestAccountIndex(newestExactAccountId, index)
435
+ : index;
436
+ }
437
+ }
438
+ return newestExactAccountId ?? newestNoAccountId;
439
+ };
353
440
  const resolveUniqueOrgScopedMatch = (indexes, accountId, refreshToken) => {
354
441
  const byAccountId = accountId
355
442
  ? asUniqueIndex(indexes.byAccountIdOrgScoped.get(accountId))
@@ -378,12 +465,13 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
378
465
  const accountId = account.accountId?.trim();
379
466
  const refreshToken = account.refreshToken?.trim();
380
467
  const email = account.email?.trim();
381
- // Global refresh token index: first entry wins (keeps earliest/primary).
382
- if (refreshToken && !byRefreshTokenGlobal.has(refreshToken)) {
383
- byRefreshTokenGlobal.set(refreshToken, i);
468
+ // Track all refresh-token matches. Callers can require uniqueness
469
+ // so org variants that share a token do not collapse accidentally.
470
+ if (refreshToken) {
471
+ pushIndex(byRefreshTokenGlobal, refreshToken, i);
384
472
  }
385
473
  if (organizationId) {
386
- byOrganizationId.set(organizationId, i);
474
+ pushIndex(byOrganizationId, organizationId, i);
387
475
  if (accountId) {
388
476
  pushIndex(byAccountIdOrgScoped, accountId, i);
389
477
  }
@@ -396,7 +484,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
396
484
  byAccountIdNoOrg.set(accountId, i);
397
485
  }
398
486
  if (refreshToken) {
399
- byRefreshTokenNoOrg.set(refreshToken, i);
487
+ pushIndex(byRefreshTokenNoOrg, refreshToken, i);
400
488
  }
401
489
  if (email) {
402
490
  byEmailNoOrg.set(email, i);
@@ -425,7 +513,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
425
513
  const accountEmail = sanitizeEmail(extractAccountEmail(result.access, result.idToken));
426
514
  const existingIndex = (() => {
427
515
  if (organizationId) {
428
- return identityIndexes.byOrganizationId.get(organizationId);
516
+ return resolveOrganizationMatch(identityIndexes, organizationId, normalizedAccountId);
429
517
  }
430
518
  if (normalizedAccountId) {
431
519
  const byAccountId = identityIndexes.byAccountIdNoOrg.get(normalizedAccountId);
@@ -433,11 +521,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
433
521
  return byAccountId;
434
522
  }
435
523
  }
436
- const byRefreshToken = identityIndexes.byRefreshTokenNoOrg.get(result.refresh);
524
+ const byRefreshToken = resolveNoOrgRefreshMatch(identityIndexes, result.refresh, normalizedAccountId);
437
525
  if (byRefreshToken !== undefined) {
438
526
  return byRefreshToken;
439
527
  }
440
- if (accountEmail) {
528
+ if (accountEmail && !normalizedAccountId) {
441
529
  const byEmail = identityIndexes.byEmailNoOrg.get(accountEmail);
442
530
  if (byEmail !== undefined) {
443
531
  return byEmail;
@@ -446,9 +534,17 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
446
534
  const orgScoped = resolveUniqueOrgScopedMatch(identityIndexes, normalizedAccountId, result.refresh);
447
535
  if (orgScoped !== undefined)
448
536
  return orgScoped;
449
- // Absolute last resort: same refresh token = same human account.
450
- // Catches org-scoped entries invisible to no-org lookups.
451
- return identityIndexes.byRefreshTokenGlobal.get(result.refresh);
537
+ // Absolute last resort: only collapse when refresh token maps to a
538
+ // single compatible account. Avoids merging distinct workspace variants.
539
+ const globalRefreshMatch = asUniqueIndex(identityIndexes.byRefreshTokenGlobal.get(result.refresh));
540
+ if (globalRefreshMatch === undefined) {
541
+ return undefined;
542
+ }
543
+ const existing = accounts[globalRefreshMatch];
544
+ if (!canCollapseWithCandidateAccountId(existing, normalizedAccountId)) {
545
+ return undefined;
546
+ }
547
+ return globalRefreshMatch;
452
548
  })();
453
549
  if (existingIndex === undefined) {
454
550
  accounts.push({
@@ -502,6 +598,50 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
502
598
  const pruneRefreshTokenCollisions = () => {
503
599
  const indicesToRemove = new Set();
504
600
  const refreshMap = new Map();
601
+ const pickPreferredOrgIndex = (existingIndex, candidateIndex) => {
602
+ if (existingIndex === undefined)
603
+ return candidateIndex;
604
+ return pickNewestAccountIndex(existingIndex, candidateIndex);
605
+ };
606
+ const collapseFallbackIntoPreferredOrg = (entry) => {
607
+ if (entry.preferredOrgIndex === undefined) {
608
+ return;
609
+ }
610
+ const preferredOrgIndex = entry.preferredOrgIndex;
611
+ const collapseFallbackIndex = (fallbackIndex) => {
612
+ if (preferredOrgIndex === fallbackIndex)
613
+ return true;
614
+ const target = accounts[preferredOrgIndex];
615
+ const source = accounts[fallbackIndex];
616
+ if (!target || !source)
617
+ return true;
618
+ const targetAccountId = normalizeStoredAccountId(target);
619
+ const sourceAccountId = normalizeStoredAccountId(source);
620
+ if (!targetAccountId && sourceAccountId) {
621
+ return false;
622
+ }
623
+ if (hasDistinctNonEmptyAccountIds(target, source)) {
624
+ return false;
625
+ }
626
+ mergeAccountRecords(preferredOrgIndex, fallbackIndex);
627
+ indicesToRemove.add(fallbackIndex);
628
+ return true;
629
+ };
630
+ if (typeof entry.fallbackNoAccountIdIndex === "number") {
631
+ if (collapseFallbackIndex(entry.fallbackNoAccountIdIndex)) {
632
+ entry.fallbackNoAccountIdIndex = undefined;
633
+ }
634
+ }
635
+ const fallbackAccountIdsToDelete = [];
636
+ for (const [accountId, fallbackIndex] of entry.fallbackByAccountId) {
637
+ if (collapseFallbackIndex(fallbackIndex)) {
638
+ fallbackAccountIdsToDelete.push(accountId);
639
+ }
640
+ }
641
+ for (const accountId of fallbackAccountIdsToDelete) {
642
+ entry.fallbackByAccountId.delete(accountId);
643
+ }
644
+ };
505
645
  for (let i = 0; i < accounts.length; i += 1) {
506
646
  const account = accounts[i];
507
647
  if (!account)
@@ -512,49 +652,70 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
512
652
  const orgKey = account.organizationId?.trim() ?? "";
513
653
  let entry = refreshMap.get(refreshToken);
514
654
  if (!entry) {
515
- entry = { byOrg: new Map(), fallbackIndex: undefined };
655
+ entry = {
656
+ byOrg: new Map(),
657
+ preferredOrgIndex: undefined,
658
+ fallbackNoAccountIdIndex: undefined,
659
+ fallbackByAccountId: new Map(),
660
+ };
516
661
  refreshMap.set(refreshToken, entry);
517
662
  }
518
663
  if (orgKey) {
519
- const existingIndex = entry.byOrg.get(orgKey);
664
+ const orgMatches = entry.byOrg.get(orgKey) ?? [];
665
+ const existingIndex = resolveOrganizationMatch({
666
+ byOrganizationId: new Map([[orgKey, orgMatches]]),
667
+ byAccountIdNoOrg: new Map(),
668
+ byRefreshTokenNoOrg: new Map(),
669
+ byEmailNoOrg: new Map(),
670
+ byAccountIdOrgScoped: new Map(),
671
+ byRefreshTokenOrgScoped: new Map(),
672
+ byRefreshTokenGlobal: new Map(),
673
+ }, orgKey, normalizeStoredAccountId(account));
520
674
  if (existingIndex !== undefined) {
521
675
  const newestIndex = pickNewestAccountIndex(existingIndex, i);
522
676
  const obsoleteIndex = newestIndex === existingIndex ? i : existingIndex;
523
677
  mergeAccountRecords(newestIndex, obsoleteIndex);
524
678
  indicesToRemove.add(obsoleteIndex);
525
- entry.byOrg.set(orgKey, newestIndex);
679
+ const nextOrgMatches = orgMatches.filter((index) => index !== obsoleteIndex && index !== newestIndex);
680
+ nextOrgMatches.push(newestIndex);
681
+ entry.byOrg.set(orgKey, nextOrgMatches);
682
+ entry.preferredOrgIndex = pickPreferredOrgIndex(entry.preferredOrgIndex, newestIndex);
683
+ collapseFallbackIntoPreferredOrg(entry);
526
684
  continue;
527
685
  }
528
- entry.byOrg.set(orgKey, i);
686
+ entry.byOrg.set(orgKey, [...orgMatches, i]);
687
+ entry.preferredOrgIndex = pickPreferredOrgIndex(entry.preferredOrgIndex, i);
688
+ collapseFallbackIntoPreferredOrg(entry);
529
689
  continue;
530
690
  }
531
- const existingFallback = entry.fallbackIndex;
691
+ const fallbackAccountId = normalizeStoredAccountId(account);
692
+ if (fallbackAccountId) {
693
+ const existingFallback = entry.fallbackByAccountId.get(fallbackAccountId);
694
+ if (typeof existingFallback === "number") {
695
+ const newestIndex = pickNewestAccountIndex(existingFallback, i);
696
+ const obsoleteIndex = newestIndex === existingFallback ? i : existingFallback;
697
+ mergeAccountRecords(newestIndex, obsoleteIndex);
698
+ indicesToRemove.add(obsoleteIndex);
699
+ entry.fallbackByAccountId.set(fallbackAccountId, newestIndex);
700
+ collapseFallbackIntoPreferredOrg(entry);
701
+ continue;
702
+ }
703
+ entry.fallbackByAccountId.set(fallbackAccountId, i);
704
+ collapseFallbackIntoPreferredOrg(entry);
705
+ continue;
706
+ }
707
+ const existingFallback = entry.fallbackNoAccountIdIndex;
532
708
  if (typeof existingFallback === "number") {
533
709
  const newestIndex = pickNewestAccountIndex(existingFallback, i);
534
710
  const obsoleteIndex = newestIndex === existingFallback ? i : existingFallback;
535
711
  mergeAccountRecords(newestIndex, obsoleteIndex);
536
712
  indicesToRemove.add(obsoleteIndex);
537
- entry.fallbackIndex = newestIndex;
713
+ entry.fallbackNoAccountIdIndex = newestIndex;
714
+ collapseFallbackIntoPreferredOrg(entry);
538
715
  continue;
539
716
  }
540
- entry.fallbackIndex = i;
541
- }
542
- for (const entry of refreshMap.values()) {
543
- const fallbackIndex = entry.fallbackIndex;
544
- if (typeof fallbackIndex !== "number")
545
- continue;
546
- const orgIndices = Array.from(entry.byOrg.values());
547
- if (orgIndices.length === 0)
548
- continue;
549
- const [firstOrgIndex, ...otherOrgIndices] = orgIndices;
550
- if (typeof firstOrgIndex !== "number")
551
- continue;
552
- let preferredOrgIndex = firstOrgIndex;
553
- for (const orgIndex of otherOrgIndices) {
554
- preferredOrgIndex = pickNewestAccountIndex(preferredOrgIndex, orgIndex);
555
- }
556
- mergeAccountRecords(preferredOrgIndex, fallbackIndex);
557
- indicesToRemove.add(fallbackIndex);
717
+ entry.fallbackNoAccountIdIndex = i;
718
+ collapseFallbackIntoPreferredOrg(entry);
558
719
  }
559
720
  if (indicesToRemove.size > 0) {
560
721
  accounts = accounts.filter((_, index) => !indicesToRemove.has(index));
@@ -621,7 +782,13 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
621
782
  const activeIndex = remappedActiveIndex ?? fallbackActiveIndex;
622
783
  const clampedActiveIndex = Math.max(0, Math.min(Math.floor(activeIndex), accounts.length - 1));
623
784
  const activeIndexByFamily = {};
624
- for (const family of MODEL_FAMILIES) {
785
+ const familiesToPersist = replaceAll
786
+ ? []
787
+ : MODEL_FAMILIES.filter((family) => {
788
+ const storedFamilyIndex = stored?.activeIndexByFamily?.[family];
789
+ return typeof storedFamilyIndex === "number" && Number.isFinite(storedFamilyIndex);
790
+ });
791
+ for (const family of familiesToPersist) {
625
792
  const storedFamilyIndex = stored?.activeIndexByFamily?.[family];
626
793
  const remappedFamilyIndex = replaceAll
627
794
  ? undefined