@bman654/clodex 2.4.0 → 2.5.1

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,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/oauth-account-selection.ts
4
+ var OAUTH_ACCOUNT_ENV = "CLODEX_OAUTH_ACCOUNT";
5
+
3
6
  // src/config.ts
4
7
  import { randomUUID as randomUUID3 } from "crypto";
5
8
  import { readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync3 } from "fs";
@@ -52,9 +55,15 @@ import {
52
55
  writeSync
53
56
  } from "fs";
54
57
  import { dirname as dirname2 } from "path";
58
+ import { isDeepStrictEqual } from "util";
55
59
 
56
60
  // src/registry/types.ts
57
61
  var REGISTRY_SCHEMA_VERSION = 1;
62
+ var REGISTRY_SCHEMA_VERSION_WITH_ACCOUNT_SLOTS = 2;
63
+ var REGISTRY_SCHEMA_VERSION_WITH_ACTIVE_ACCOUNT = 3;
64
+ var REGISTRY_SCHEMA_VERSION_WITH_ACCOUNT_MODEL_CACHES = 4;
65
+ var REGISTRY_SCHEMA_VERSION_WITH_MATERIALIZED_ACTIVE_ACCOUNT = 5;
66
+ var OAUTH_ACCOUNT_NAME_RE = /^[a-z0-9][a-z0-9_-]{0,31}$/;
58
67
 
59
68
  // src/registry/lock.ts
60
69
  import { AsyncLocalStorage } from "async_hooks";
@@ -393,6 +402,69 @@ function withProviderMutationLock(providerSlot, operation) {
393
402
  });
394
403
  }
395
404
 
405
+ // src/registry/oauth-account-storage.ts
406
+ function getOAuthAccountSlot(provider, name) {
407
+ const accounts = provider.authAccounts;
408
+ return accounts && Object.prototype.hasOwnProperty.call(accounts, name) ? accounts[name] : void 0;
409
+ }
410
+ function providerDefaultAuthRef(provider) {
411
+ return provider.defaultAuthRef ?? provider.authRef;
412
+ }
413
+ function storeActiveOAuthAccount(provider, name, selectedAuthRef) {
414
+ const previousAuthRef = provider.authRef;
415
+ const previousDefaultAuthRef = provider.defaultAuthRef;
416
+ const previousDefaultModelsCache = provider.defaultModelsCache;
417
+ const previousAccount = provider.activeAuthAccount;
418
+ if (provider.defaultAuthRef === void 0) {
419
+ provider.defaultAuthRef = provider.authRef;
420
+ if (provider.modelsCache) provider.defaultModelsCache = provider.modelsCache;
421
+ }
422
+ provider.authRef = selectedAuthRef;
423
+ provider.activeAuthAccount = name;
424
+ return previousAuthRef !== provider.authRef || previousDefaultAuthRef !== provider.defaultAuthRef || previousDefaultModelsCache !== provider.defaultModelsCache || previousAccount !== provider.activeAuthAccount;
425
+ }
426
+ function clearActiveOAuthAccount(provider) {
427
+ const previousAuthRef = provider.authRef;
428
+ const previousDefaultAuthRef = provider.defaultAuthRef;
429
+ const previousDefaultModelsCache = provider.defaultModelsCache;
430
+ const previousAccount = provider.activeAuthAccount;
431
+ const hasMaterializedSelection = provider.defaultAuthRef !== void 0;
432
+ provider.authRef = providerDefaultAuthRef(provider);
433
+ if (hasMaterializedSelection) {
434
+ if (provider.defaultModelsCache) {
435
+ provider.modelsCache = provider.defaultModelsCache;
436
+ provider.refreshedAt = provider.defaultModelsCache.fetchedAt;
437
+ } else {
438
+ delete provider.modelsCache;
439
+ delete provider.refreshedAt;
440
+ }
441
+ }
442
+ delete provider.defaultAuthRef;
443
+ delete provider.defaultModelsCache;
444
+ delete provider.activeAuthAccount;
445
+ return previousAuthRef !== provider.authRef || previousDefaultAuthRef !== provider.defaultAuthRef || previousDefaultModelsCache !== provider.defaultModelsCache || previousAccount !== provider.activeAuthAccount;
446
+ }
447
+ function migrateActiveOAuthAccountStorage(registry) {
448
+ let changed = false;
449
+ for (const provider of registry.providers) {
450
+ const name = provider.activeAuthAccount?.trim();
451
+ if (provider.authType !== "oauth" || !name || provider.defaultAuthRef !== void 0) continue;
452
+ const selected = getOAuthAccountSlot(provider, name);
453
+ if (!selected) continue;
454
+ provider.defaultAuthRef = provider.authRef;
455
+ provider.authRef = selected.authRef;
456
+ if (registry.schemaVersion >= 4 && selected.modelsCache) {
457
+ provider.modelsCache = selected.modelsCache;
458
+ provider.refreshedAt = selected.modelsCache.fetchedAt;
459
+ } else {
460
+ delete provider.modelsCache;
461
+ delete provider.refreshedAt;
462
+ }
463
+ changed = true;
464
+ }
465
+ return changed;
466
+ }
467
+
396
468
  // src/registry/migrate.ts
397
469
  function migrateOAuthOpenAiProvider(registry) {
398
470
  if (registry.providers.some((p) => p.id === "openai-oauth")) return false;
@@ -409,6 +481,14 @@ function migrateOAuthOpenAiProvider(registry) {
409
481
  };
410
482
  return true;
411
483
  }
484
+ function migrateRegistry(registry) {
485
+ const renamed = migrateOAuthOpenAiProvider(registry);
486
+ const materialized = registry.schemaVersion >= REGISTRY_SCHEMA_VERSION_WITH_ACTIVE_ACCOUNT && registry.schemaVersion <= REGISTRY_SCHEMA_VERSION_WITH_ACCOUNT_MODEL_CACHES ? migrateActiveOAuthAccountStorage(registry) : false;
487
+ return {
488
+ changed: renamed || materialized,
489
+ materializedActiveAccount: materialized
490
+ };
491
+ }
412
492
 
413
493
  // src/registry/validate.ts
414
494
  var PROVIDER_ID_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
@@ -462,7 +542,7 @@ function syncParentDirectory(path) {
462
542
  if (fd !== void 0) closeSync2(fd);
463
543
  }
464
544
  }
465
- function parseProvider(raw) {
545
+ function parseProvider(raw, diag) {
466
546
  if (!raw || typeof raw !== "object") return null;
467
547
  const p = raw;
468
548
  if (typeof p.id !== "string" || !isValidProviderId(p.id)) return null;
@@ -482,6 +562,10 @@ function parseProvider(raw) {
482
562
  api,
483
563
  addedAt: p.addedAt
484
564
  };
565
+ if (hasOwn(p, "defaultAuthRef")) {
566
+ if (typeof p.defaultAuthRef !== "string" || !p.defaultAuthRef) return null;
567
+ provider.defaultAuthRef = p.defaultAuthRef;
568
+ }
485
569
  if (p.subscriptionFilter === "free") {
486
570
  provider.subscriptionFilter = p.subscriptionFilter;
487
571
  }
@@ -491,21 +575,65 @@ function parseProvider(raw) {
491
575
  if (p.authType === "api" || p.authType === "oauth" || p.authType === "none") {
492
576
  provider.authType = p.authType;
493
577
  }
578
+ if (hasOwn(p, "authAccounts")) {
579
+ const slots = parseAuthAccounts(p.authAccounts);
580
+ if (slots === null) return null;
581
+ provider.authAccounts = slots;
582
+ }
583
+ if (hasOwn(p, "activeAuthAccount")) {
584
+ if (!isAccountName(p.activeAuthAccount)) return null;
585
+ provider.activeAuthAccount = p.activeAuthAccount;
586
+ }
494
587
  if (typeof p.refreshedAt === "string") provider.refreshedAt = p.refreshedAt;
495
- if (p.modelsCache && typeof p.modelsCache === "object") {
496
- const cache = p.modelsCache;
497
- if (typeof cache.fetchedAt === "string" && Array.isArray(cache.models)) {
498
- provider.modelsCache = {
499
- fetchedAt: cache.fetchedAt,
500
- models: cache.models.filter((m) => m && typeof m === "object")
501
- };
502
- }
588
+ if (hasOwn(p, "defaultModelsCache")) {
589
+ const defaultModelsCache = parseModelsCache(p.defaultModelsCache);
590
+ if (!defaultModelsCache) return null;
591
+ provider.defaultModelsCache = defaultModelsCache;
592
+ }
593
+ const modelsCache = parseModelsCache(p.modelsCache);
594
+ if (modelsCache) provider.modelsCache = modelsCache;
595
+ else if (hasOwn(p, "modelsCache")) {
596
+ diag?.(`Provider registry dropped an invalid model cache for provider "${p.id}".`);
503
597
  }
504
598
  return provider;
505
599
  }
506
600
  function hasOwn(record, key) {
507
601
  return Object.prototype.hasOwnProperty.call(record, key);
508
602
  }
603
+ function isAccountName(raw) {
604
+ return typeof raw === "string" && OAUTH_ACCOUNT_NAME_RE.test(raw);
605
+ }
606
+ function parseModelsCache(raw) {
607
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
608
+ const cache = raw;
609
+ if (typeof cache.fetchedAt !== "string" || !Array.isArray(cache.models)) return null;
610
+ if (cache.models.some((model) => !model || typeof model !== "object" || Array.isArray(model))) {
611
+ return null;
612
+ }
613
+ return {
614
+ fetchedAt: cache.fetchedAt,
615
+ models: cache.models
616
+ };
617
+ }
618
+ function parseAuthAccounts(raw) {
619
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
620
+ const out = {};
621
+ for (const [name, value] of Object.entries(raw)) {
622
+ if (!OAUTH_ACCOUNT_NAME_RE.test(name)) return null;
623
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
624
+ const slot = value;
625
+ if (typeof slot.authRef !== "string" || !slot.authRef) return null;
626
+ if (typeof slot.addedAt !== "string" || !slot.addedAt) return null;
627
+ const modelsCache = hasOwn(slot, "modelsCache") ? parseModelsCache(slot.modelsCache) : void 0;
628
+ if (hasOwn(slot, "modelsCache") && !modelsCache) return null;
629
+ out[name] = {
630
+ authRef: slot.authRef,
631
+ addedAt: slot.addedAt,
632
+ ...modelsCache ? { modelsCache } : {}
633
+ };
634
+ }
635
+ return out;
636
+ }
509
637
  function hasValidStrictProviderFields(raw) {
510
638
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return false;
511
639
  const provider = raw;
@@ -521,32 +649,74 @@ function hasValidStrictProviderFields(raw) {
521
649
  if (hasOwn(provider, "refreshedAt") && typeof provider.refreshedAt !== "string") {
522
650
  return false;
523
651
  }
652
+ if (hasOwn(provider, "authAccounts") && parseAuthAccounts(provider.authAccounts) === null) {
653
+ return false;
654
+ }
655
+ if (hasOwn(provider, "activeAuthAccount") && !isAccountName(provider.activeAuthAccount)) {
656
+ return false;
657
+ }
658
+ if (hasOwn(provider, "defaultAuthRef") && (typeof provider.defaultAuthRef !== "string" || !provider.defaultAuthRef)) {
659
+ return false;
660
+ }
661
+ if (hasOwn(provider, "defaultModelsCache") && parseModelsCache(provider.defaultModelsCache) === null) {
662
+ return false;
663
+ }
524
664
  if (hasOwn(provider, "modelsCache")) {
525
- const cache = provider.modelsCache;
526
- if (!cache || typeof cache !== "object" || Array.isArray(cache)) return false;
527
- const fields = cache;
528
- if (typeof fields.fetchedAt !== "string" || !Array.isArray(fields.models)) {
529
- return false;
530
- }
531
- if (fields.models.some((model) => !model || typeof model !== "object" || Array.isArray(model))) {
532
- return false;
533
- }
665
+ if (parseModelsCache(provider.modelsCache) === null) return false;
534
666
  }
535
667
  return true;
536
668
  }
537
- function parseRegistry(raw) {
669
+ function hasPresentInvalidAuthType(raw) {
670
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return false;
671
+ const provider = raw;
672
+ return hasOwn(provider, "authType") && provider.authType !== "api" && provider.authType !== "oauth" && provider.authType !== "none";
673
+ }
674
+ function hasValidSelectionStorage(provider, schemaVersion) {
675
+ const name = provider.activeAuthAccount?.trim();
676
+ const hasDefault = provider.defaultAuthRef !== void 0;
677
+ const hasDefaultCache = provider.defaultModelsCache !== void 0;
678
+ if (schemaVersion < REGISTRY_SCHEMA_VERSION_WITH_ACCOUNT_MODEL_CACHES && Object.values(provider.authAccounts ?? {}).some((account) => account.modelsCache !== void 0)) {
679
+ return false;
680
+ }
681
+ if (name && schemaVersion < REGISTRY_SCHEMA_VERSION_WITH_ACTIVE_ACCOUNT) {
682
+ return false;
683
+ }
684
+ if (schemaVersion <= REGISTRY_SCHEMA_VERSION_WITH_ACCOUNT_MODEL_CACHES && (hasDefault || hasDefaultCache)) {
685
+ return false;
686
+ }
687
+ if (!name) return !hasDefault && !hasDefaultCache;
688
+ if (provider.authType !== "oauth") return !hasDefault && !hasDefaultCache;
689
+ if (schemaVersion <= REGISTRY_SCHEMA_VERSION_WITH_ACCOUNT_MODEL_CACHES) {
690
+ return true;
691
+ }
692
+ if (schemaVersion !== REGISTRY_SCHEMA_VERSION_WITH_MATERIALIZED_ACTIVE_ACCOUNT) {
693
+ return true;
694
+ }
695
+ const selected = getOAuthAccountSlot(provider, name);
696
+ return hasDefault && selected !== void 0 && provider.authRef === selected.authRef && isDeepStrictEqual(provider.modelsCache, selected.modelsCache);
697
+ }
698
+ function parseRegistry(raw, diag) {
538
699
  const empty = { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };
539
700
  if (!raw || typeof raw !== "object") return empty;
540
701
  const data = raw;
702
+ const schemaVersion = typeof data.schemaVersion === "number" ? data.schemaVersion : REGISTRY_SCHEMA_VERSION;
541
703
  const providers = [];
542
704
  if (Array.isArray(data.providers)) {
543
- for (const entry of data.providers) {
544
- const parsed = parseProvider(entry);
545
- if (parsed) providers.push(parsed);
705
+ for (const [index, entry] of data.providers.entries()) {
706
+ const parsed = parseProvider(entry, diag);
707
+ const invalidKnownSelectionAuthType = schemaVersion >= REGISTRY_SCHEMA_VERSION_WITH_ACTIVE_ACCOUNT && schemaVersion <= REGISTRY_SCHEMA_VERSION_WITH_MATERIALIZED_ACTIVE_ACCOUNT && parsed?.activeAuthAccount !== void 0 && hasPresentInvalidAuthType(entry);
708
+ const validSelectionStorage = parsed ? hasValidSelectionStorage(parsed, schemaVersion) : false;
709
+ if (parsed && !invalidKnownSelectionAuthType && validSelectionStorage) {
710
+ providers.push(parsed);
711
+ } else {
712
+ const id = entry && typeof entry === "object" && typeof entry.id === "string" ? ` "${entry.id}"` : ` at index ${index}`;
713
+ const reason = parsed && !validSelectionStorage ? " because its OAuth account selection storage is inconsistent" : "";
714
+ diag?.(`Provider registry dropped invalid provider${id}${reason}.`);
715
+ }
546
716
  }
547
717
  }
548
718
  const registry = {
549
- schemaVersion: typeof data.schemaVersion === "number" ? data.schemaVersion : REGISTRY_SCHEMA_VERSION,
719
+ schemaVersion,
550
720
  providers
551
721
  };
552
722
  if (typeof data.importedAt === "string") registry.importedAt = data.importedAt;
@@ -558,14 +728,15 @@ function parseRegistryStrict(raw) {
558
728
  throw new Error("Provider registry must be a JSON object.");
559
729
  }
560
730
  const data = raw;
561
- if (data.schemaVersion !== REGISTRY_SCHEMA_VERSION) {
731
+ if (data.schemaVersion !== REGISTRY_SCHEMA_VERSION && data.schemaVersion !== REGISTRY_SCHEMA_VERSION_WITH_ACCOUNT_SLOTS && data.schemaVersion !== REGISTRY_SCHEMA_VERSION_WITH_ACTIVE_ACCOUNT && data.schemaVersion !== REGISTRY_SCHEMA_VERSION_WITH_ACCOUNT_MODEL_CACHES && data.schemaVersion !== REGISTRY_SCHEMA_VERSION_WITH_MATERIALIZED_ACTIVE_ACCOUNT) {
562
732
  throw new Error("Provider registry has an unsupported schema version.");
563
733
  }
564
734
  if (!Array.isArray(data.providers)) {
565
735
  throw new Error("Provider registry is missing its providers list.");
566
736
  }
567
737
  for (const entry of data.providers) {
568
- if (!parseProvider(entry) || !hasValidStrictProviderFields(entry)) {
738
+ const provider = parseProvider(entry);
739
+ if (!provider || !hasValidStrictProviderFields(entry) || !hasValidSelectionStorage(provider, data.schemaVersion)) {
569
740
  throw new Error("Provider registry contains an invalid provider entry.");
570
741
  }
571
742
  }
@@ -574,27 +745,185 @@ function parseRegistryStrict(raw) {
574
745
  function readRegistryStrict(path) {
575
746
  return parseRegistryStrict(JSON.parse(readFileSync2(path, "utf8")));
576
747
  }
577
- function loadRegistry(path = getProvidersPath()) {
748
+ var RegistryMigrationValidationError = class extends Error {
749
+ constructor(registryPath, cause) {
750
+ const detail = cause instanceof Error ? cause.message : String(cause);
751
+ super(detail, { cause });
752
+ this.registryPath = registryPath;
753
+ this.name = "RegistryMigrationValidationError";
754
+ }
755
+ registryPath;
756
+ };
757
+ var RegistryMigrationReadError = class extends Error {
758
+ constructor(registryPath, cause) {
759
+ const detail = cause instanceof Error ? cause.message : String(cause);
760
+ super(detail, { cause });
761
+ this.registryPath = registryPath;
762
+ this.name = "RegistryMigrationReadError";
763
+ }
764
+ registryPath;
765
+ };
766
+ var RegistrySaveValidationError = class extends Error {
767
+ constructor(message, options) {
768
+ super(message, options);
769
+ this.name = "RegistrySaveValidationError";
770
+ }
771
+ };
772
+ var RegistryPersistenceError = class extends Error {
773
+ constructor(registryPath, cause) {
774
+ const detail = cause instanceof Error ? cause.message : String(cause);
775
+ super(detail, { cause });
776
+ this.registryPath = registryPath;
777
+ this.name = "RegistryPersistenceError";
778
+ }
779
+ registryPath;
780
+ };
781
+ var RegistryDurabilityCheckError = class extends Error {
782
+ constructor(registryPath, cause) {
783
+ const detail = cause instanceof Error ? cause.message : String(cause);
784
+ super(detail, { cause });
785
+ this.registryPath = registryPath;
786
+ this.name = "RegistryDurabilityCheckError";
787
+ }
788
+ registryPath;
789
+ };
790
+ function selectedAccountFilesystemError(path, cause, action) {
791
+ const detail = cause instanceof Error ? cause.message : String(cause);
792
+ return new Error(
793
+ `Could not safely persist the selected OAuth account before launch. Could not ${action} the provider registry at ${path}: ${detail} Check filesystem permissions, storage health, and free disk space, then retry.`,
794
+ { cause }
795
+ );
796
+ }
797
+ function hasMaterializedActiveAccount(registry) {
798
+ return registry.schemaVersion === REGISTRY_SCHEMA_VERSION_WITH_MATERIALIZED_ACTIVE_ACCOUNT && registry.providers.some((provider) => provider.defaultAuthRef !== void 0);
799
+ }
800
+ function loadRegistry(path = getProvidersPath(), diag) {
578
801
  if (!existsSync(path)) {
579
802
  return { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };
580
803
  }
804
+ let registry;
581
805
  try {
582
806
  const raw = JSON.parse(readFileSync2(path, "utf8"));
583
- const registry = parseRegistry(raw);
584
- const migrated = migrateOAuthOpenAiProvider(registry);
585
- if (migrated) {
586
- try {
587
- withRegistryWriteLockSync(() => {
588
- if (!existsSync(path)) return;
589
- const current = readRegistryStrict(path);
590
- if (migrateOAuthOpenAiProvider(current)) saveRegistry(current, path);
591
- }, { lockPath: `${path}.lock` });
592
- } catch {
807
+ registry = parseRegistry(raw, diag);
808
+ } catch (error) {
809
+ const detail = error instanceof Error ? error.message : String(error);
810
+ diag?.(`Could not read the provider registry at ${path}; treating it as empty: ${detail}`);
811
+ return { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };
812
+ }
813
+ const migration = migrateRegistry(registry);
814
+ if (!migration.changed) {
815
+ try {
816
+ syncParentDirectory(path);
817
+ } catch (error) {
818
+ if (hasMaterializedActiveAccount(registry)) {
819
+ throw selectedAccountFilesystemError(path, error, "durably sync");
593
820
  }
821
+ const detail = error instanceof Error ? error.message : String(error);
822
+ diag?.(`Could not durably sync the unchanged provider registry at ${path}; continuing read-only: ${detail}`);
594
823
  }
595
824
  return registry;
596
- } catch {
597
- return { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };
825
+ }
826
+ let winner = registry;
827
+ let materializedActiveAccount = migration.materializedActiveAccount;
828
+ try {
829
+ withRegistryWriteLockSync(() => {
830
+ if (!existsSync(path)) {
831
+ if (materializedActiveAccount) {
832
+ throw new Error("the provider registry disappeared during migration");
833
+ }
834
+ return;
835
+ }
836
+ let serialized;
837
+ try {
838
+ serialized = readFileSync2(path, "utf8");
839
+ } catch (error) {
840
+ if (error.code === "ENOENT") {
841
+ throw new Error("the provider registry disappeared during migration", { cause: error });
842
+ }
843
+ throw new RegistryMigrationReadError(path, error);
844
+ }
845
+ let raw;
846
+ try {
847
+ raw = JSON.parse(serialized);
848
+ } catch (error) {
849
+ throw new RegistryMigrationValidationError(path, error);
850
+ }
851
+ const lenientCurrent = parseRegistry(raw);
852
+ const lenientMigration = migrateRegistry(lenientCurrent);
853
+ winner = lenientCurrent;
854
+ materializedActiveAccount = lenientMigration.materializedActiveAccount || hasMaterializedActiveAccount(lenientCurrent);
855
+ let current;
856
+ try {
857
+ current = parseRegistryStrict(raw);
858
+ } catch (error) {
859
+ throw new RegistryMigrationValidationError(path, error);
860
+ }
861
+ const currentMigration = migrateRegistry(current);
862
+ winner = current;
863
+ materializedActiveAccount = currentMigration.materializedActiveAccount || hasMaterializedActiveAccount(current);
864
+ if (currentMigration.changed) {
865
+ try {
866
+ saveRegistry(current, path);
867
+ } catch (error) {
868
+ if (error instanceof RegistrySaveValidationError) {
869
+ throw new RegistryMigrationValidationError(path, error);
870
+ }
871
+ throw error;
872
+ }
873
+ } else {
874
+ try {
875
+ syncParentDirectory(path);
876
+ } catch (error) {
877
+ throw new RegistryDurabilityCheckError(path, error);
878
+ }
879
+ }
880
+ }, { lockPath: `${path}.lock` });
881
+ return winner;
882
+ } catch (error) {
883
+ if (error instanceof RegistryDurabilityCheckError) {
884
+ if (materializedActiveAccount) {
885
+ throw selectedAccountFilesystemError(
886
+ error.registryPath,
887
+ error,
888
+ "durably sync"
889
+ );
890
+ }
891
+ throw new Error(
892
+ `Could not durably sync the provider registry at ${error.registryPath}: ${error.message} Check filesystem permissions, storage health, and free disk space, then retry.`,
893
+ { cause: error }
894
+ );
895
+ }
896
+ if (materializedActiveAccount) {
897
+ if (error instanceof RegistryMigrationValidationError) {
898
+ throw new Error(
899
+ `Could not safely persist the selected OAuth account before launch. The provider registry at ${error.registryPath} is invalid: ${error.message} Repair it or restore ${error.registryPath}.bak, then retry.`,
900
+ { cause: error }
901
+ );
902
+ }
903
+ if (error instanceof RegistryMigrationReadError) {
904
+ throw selectedAccountFilesystemError(
905
+ error.registryPath,
906
+ error,
907
+ "read"
908
+ );
909
+ }
910
+ if (error instanceof RegistryPersistenceError) {
911
+ throw selectedAccountFilesystemError(
912
+ error.registryPath,
913
+ error,
914
+ "durably write"
915
+ );
916
+ }
917
+ if (error && typeof error === "object" && typeof error.code === "string") {
918
+ throw selectedAccountFilesystemError(path, error, "access the lock for");
919
+ }
920
+ const detail = error instanceof Error ? ` ${error.message}` : "";
921
+ throw new Error(
922
+ `Could not safely persist the selected OAuth account before launch.${detail} Stop other Clodex processes and retry.`,
923
+ { cause: error }
924
+ );
925
+ }
926
+ return winner;
598
927
  }
599
928
  }
600
929
  function loadRegistryStrict(path = getProvidersPath()) {
@@ -602,12 +931,43 @@ function loadRegistryStrict(path = getProvidersPath()) {
602
931
  return { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };
603
932
  }
604
933
  const registry = readRegistryStrict(path);
605
- migrateOAuthOpenAiProvider(registry);
934
+ migrateRegistry(registry);
606
935
  return registry;
607
936
  }
608
937
  function saveRegistry(registry, path = getProvidersPath()) {
609
938
  assertRegistryWriteOwnership(path);
610
- const payload = `${JSON.stringify(registry, null, 2)}
939
+ if (registry.schemaVersion >= REGISTRY_SCHEMA_VERSION_WITH_ACTIVE_ACCOUNT && registry.schemaVersion <= REGISTRY_SCHEMA_VERSION_WITH_ACCOUNT_MODEL_CACHES) {
940
+ migrateActiveOAuthAccountStorage(registry);
941
+ }
942
+ for (const provider of registry.providers) {
943
+ if (!hasValidSelectionStorage(
944
+ provider,
945
+ REGISTRY_SCHEMA_VERSION_WITH_MATERIALIZED_ACTIVE_ACCOUNT
946
+ )) {
947
+ throw new RegistrySaveValidationError(
948
+ "Provider registry contains invalid OAuth account selection storage."
949
+ );
950
+ }
951
+ }
952
+ const hasMaterializedSelector = registry.providers.some(
953
+ (provider) => provider.defaultAuthRef !== void 0
954
+ );
955
+ const hasAccountModelCaches = registry.providers.some((provider) => Object.values(provider.authAccounts ?? {}).some((account) => account.modelsCache !== void 0));
956
+ const hasSelector = registry.providers.some((provider) => provider.activeAuthAccount !== void 0);
957
+ const hasSlots = registry.providers.some(
958
+ (provider) => provider.authAccounts && Object.keys(provider.authAccounts).length > 0
959
+ );
960
+ const schemaVersion = hasMaterializedSelector ? REGISTRY_SCHEMA_VERSION_WITH_MATERIALIZED_ACTIVE_ACCOUNT : hasAccountModelCaches ? REGISTRY_SCHEMA_VERSION_WITH_ACCOUNT_MODEL_CACHES : hasSelector ? REGISTRY_SCHEMA_VERSION_WITH_ACTIVE_ACCOUNT : hasSlots ? REGISTRY_SCHEMA_VERSION_WITH_ACCOUNT_SLOTS : REGISTRY_SCHEMA_VERSION;
961
+ const serializedRegistry = { ...registry, schemaVersion };
962
+ try {
963
+ parseRegistryStrict(JSON.parse(JSON.stringify(serializedRegistry)));
964
+ } catch (error) {
965
+ throw new RegistrySaveValidationError(
966
+ error instanceof Error ? error.message : String(error),
967
+ { cause: error }
968
+ );
969
+ }
970
+ const payload = `${JSON.stringify(serializedRegistry, null, 2)}
611
971
  `;
612
972
  const backup = `${path}.bak`;
613
973
  if (existsSync(path)) {
@@ -622,11 +982,16 @@ function saveRegistry(registry, path = getProvidersPath()) {
622
982
  assertRegistryWriteOwnership(path);
623
983
  renameSync(tmp, path);
624
984
  syncParentDirectory(path);
985
+ } catch (error) {
986
+ if (error instanceof RegistryLockLostError) throw error;
987
+ throw new RegistryPersistenceError(path, error);
625
988
  } finally {
626
989
  try {
627
990
  unlinkSync2(tmp);
628
991
  } catch (err) {
629
- if (err.code !== "ENOENT") throw err;
992
+ if (err.code !== "ENOENT") {
993
+ throw new RegistryPersistenceError(path, err);
994
+ }
630
995
  }
631
996
  }
632
997
  }
@@ -828,9 +1193,9 @@ function setServerListenMode(listenMode) {
828
1193
  });
829
1194
  }
830
1195
 
831
- // src/launch.ts
832
- import { execFileSync as execFileSync2, execSync, spawn } from "child_process";
833
- import { existsSync as existsSync3, appendFileSync } from "fs";
1196
+ // src/claude-binary.ts
1197
+ import { execFileSync as execFileSync2, execSync } from "child_process";
1198
+ import { existsSync as existsSync3 } from "fs";
834
1199
  import { homedir as homedir2 } from "os";
835
1200
  import { join as join3 } from "path";
836
1201
 
@@ -856,7 +1221,7 @@ function findBinaryOnPath(name, fallbackPaths, options = {}) {
856
1221
  return null;
857
1222
  }
858
1223
 
859
- // src/launch.ts
1224
+ // src/claude-binary.ts
860
1225
  var isWindows = process.platform === "win32";
861
1226
  var FALLBACK_PATHS = isWindows ? [
862
1227
  join3(process.env["APPDATA"] ?? homedir2(), "npm", "claude.cmd"),
@@ -900,57 +1265,6 @@ function getInstalledClaudeVersion() {
900
1265
  if (!claudePath) return FALLBACK_CLAUDE_VERSION;
901
1266
  return getClaudeVersionForBinary(claudePath) ?? FALLBACK_CLAUDE_VERSION;
902
1267
  }
903
- function buildClaudeArgs(model, extraArgs) {
904
- return model ? ["--model", model, ...extraArgs] : [...extraArgs];
905
- }
906
- function launchClaude(env, model, extraArgs) {
907
- return new Promise((resolve) => {
908
- const claudePath = findClaudeBinary();
909
- const args = buildClaudeArgs(model, extraArgs);
910
- const debugFileIdx = extraArgs.indexOf("--debug-file");
911
- const debugLogPath = debugFileIdx !== -1 && extraArgs[debugFileIdx + 1] ? extraArgs[debugFileIdx + 1] : void 0;
912
- const originalStdoutWrite = process.stdout.write;
913
- const originalStderrWrite = process.stderr.write;
914
- const muteWrite = (chunk, encoding, callback) => {
915
- if (typeof encoding === "function") {
916
- callback = encoding;
917
- }
918
- if (debugLogPath) {
919
- try {
920
- const str = typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk);
921
- appendFileSync(debugLogPath, `[parent] ${str}`);
922
- } catch {
923
- }
924
- }
925
- if (callback) callback();
926
- return true;
927
- };
928
- process.stdout.write = muteWrite;
929
- process.stderr.write = muteWrite;
930
- const restore = () => {
931
- process.stdout.write = originalStdoutWrite;
932
- process.stderr.write = originalStderrWrite;
933
- };
934
- const child = spawn(claudePath, args, {
935
- stdio: "inherit",
936
- env,
937
- shell: isWindows
938
- });
939
- const forward = (signal) => {
940
- child.kill(signal);
941
- };
942
- process.once("SIGINT", () => forward("SIGINT"));
943
- process.once("SIGTERM", () => forward("SIGTERM"));
944
- child.on("exit", (code) => {
945
- restore();
946
- resolve(code ?? 0);
947
- });
948
- child.on("error", (err) => {
949
- restore();
950
- resolve(1);
951
- });
952
- });
953
- }
954
1268
 
955
1269
  // src/listener-ready.ts
956
1270
  import { connect } from "net";
@@ -1229,8 +1543,84 @@ function orderWrapperServerCandidates(records) {
1229
1543
  });
1230
1544
  }
1231
1545
 
1546
+ // src/network-env.ts
1547
+ var PROXY_ENV_VARS = [
1548
+ "HTTPS_PROXY",
1549
+ "HTTP_PROXY",
1550
+ "https_proxy",
1551
+ "http_proxy"
1552
+ ];
1553
+ var CHILD_NETWORK_ENV_VARS = [
1554
+ ...PROXY_ENV_VARS,
1555
+ "NO_PROXY",
1556
+ "no_proxy",
1557
+ "NODE_EXTRA_CA_CERTS"
1558
+ ];
1559
+ var NETWORK_ENV_CONTRACT_VAR = "CLAUDE_CODE_CLODEX_NETWORK_ENV";
1560
+ var childNetworkEnvVarSet = new Set(CHILD_NETWORK_ENV_VARS);
1561
+ function networkEnvValue(env, name) {
1562
+ return typeof env[name] === "string" ? env[name] : null;
1563
+ }
1564
+ function isNetworkValueRecord(value) {
1565
+ return Boolean(value && typeof value === "object" && !Array.isArray(value) && Object.entries(value).every(([key, entry]) => childNetworkEnvVarSet.has(key) && (typeof entry === "string" || entry === null)));
1566
+ }
1567
+ function parseNetworkEnvContract(value) {
1568
+ if (value === void 0) return void 0;
1569
+ try {
1570
+ const parsed = JSON.parse(value);
1571
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
1572
+ const candidate = parsed;
1573
+ const original = candidate.original;
1574
+ const injected = candidate.injected;
1575
+ if (candidate.version !== 1 || !isNetworkValueRecord(original) || !isNetworkValueRecord(injected)) {
1576
+ return void 0;
1577
+ }
1578
+ if (!Object.keys(original).every((key) => key in injected) || !Object.keys(injected).every((key) => key in original)) {
1579
+ return void 0;
1580
+ }
1581
+ return { version: 1, original, injected };
1582
+ } catch {
1583
+ return void 0;
1584
+ }
1585
+ }
1586
+ function setNetworkEnvValue(env, name, value) {
1587
+ if (value === null) delete env[name];
1588
+ else env[name] = value;
1589
+ }
1590
+ function networkEnvBaseline(baseEnv) {
1591
+ const env = { ...baseEnv };
1592
+ const contract = parseNetworkEnvContract(baseEnv[NETWORK_ENV_CONTRACT_VAR]);
1593
+ delete env[NETWORK_ENV_CONTRACT_VAR];
1594
+ if (!contract) return env;
1595
+ for (const name of CHILD_NETWORK_ENV_VARS) {
1596
+ if (!(name in contract.original) || !(name in contract.injected)) continue;
1597
+ if (networkEnvValue(baseEnv, name) !== contract.injected[name]) continue;
1598
+ setNetworkEnvValue(env, name, contract.original[name] ?? null);
1599
+ }
1600
+ return env;
1601
+ }
1602
+ function recordNetworkEnvMutation(baseline, injectedEnv) {
1603
+ const original = {};
1604
+ const injected = {};
1605
+ for (const name of CHILD_NETWORK_ENV_VARS) {
1606
+ const before = networkEnvValue(baseline, name);
1607
+ const after = networkEnvValue(injectedEnv, name);
1608
+ if (before === after) continue;
1609
+ original[name] = before;
1610
+ injected[name] = after;
1611
+ }
1612
+ if (Object.keys(original).length === 0) {
1613
+ delete injectedEnv[NETWORK_ENV_CONTRACT_VAR];
1614
+ return;
1615
+ }
1616
+ injectedEnv[NETWORK_ENV_CONTRACT_VAR] = JSON.stringify({
1617
+ version: 1,
1618
+ original,
1619
+ injected
1620
+ });
1621
+ }
1622
+
1232
1623
  // src/wrapper-env.ts
1233
- var PROXY_ENV_VARS = ["HTTPS_PROXY", "HTTP_PROXY", "https_proxy", "http_proxy"];
1234
1624
  var REQUIRE_SERVER_ENV = "CLODEX_REQUIRE_SERVER";
1235
1625
  function removeAnthropicProxyBypass(env) {
1236
1626
  const noProxyValues = [env["NO_PROXY"], env["no_proxy"]].filter((value) => value !== void 0);
@@ -1256,27 +1646,33 @@ function wrapperRequiresServer(env) {
1256
1646
  return env[REQUIRE_SERVER_ENV] === "1";
1257
1647
  }
1258
1648
  function computeWrapperEnv(baseEnv, state) {
1259
- const env = { ...baseEnv };
1260
- if (!state) return env;
1649
+ if (!state) return { ...baseEnv };
1650
+ const baseline = networkEnvBaseline(baseEnv);
1651
+ const env = { ...baseline };
1261
1652
  if (state.mode === "proxy") {
1262
1653
  const proxyUrl = `http://127.0.0.1:${state.port}`;
1263
1654
  delete env["ANTHROPIC_BASE_URL"];
1264
1655
  for (const name of PROXY_ENV_VARS) env[name] = proxyUrl;
1265
1656
  if (state.caPath) env["NODE_EXTRA_CA_CERTS"] = state.caPath;
1266
1657
  removeAnthropicProxyBypass(env);
1658
+ recordNetworkEnvMutation(baseline, env);
1267
1659
  return env;
1268
1660
  }
1269
1661
  for (const name of PROXY_ENV_VARS) delete env[name];
1270
1662
  env["ANTHROPIC_BASE_URL"] = `http://127.0.0.1:${state.port}/anthropic`;
1271
1663
  env["ANTHROPIC_API_KEY"] = LOCAL_GATEWAY_API_KEY;
1664
+ recordNetworkEnvMutation(baseline, env);
1272
1665
  return env;
1273
1666
  }
1274
1667
 
1275
1668
  export {
1669
+ OAUTH_ACCOUNT_ENV,
1276
1670
  getAppHome,
1277
1671
  getLocalPatchesPath,
1278
1672
  getCredentialCleanupPath,
1279
1673
  getLogsPath,
1674
+ REGISTRY_SCHEMA_VERSION_WITH_MATERIALIZED_ACTIVE_ACCOUNT,
1675
+ OAUTH_ACCOUNT_NAME_RE,
1280
1676
  assertRegistryWriteOwnership,
1281
1677
  withRegistryWriteLock,
1282
1678
  withRegistryWriteLockSync,
@@ -1284,6 +1680,10 @@ export {
1284
1680
  getCredentialStateRoot,
1285
1681
  withCredentialMutationLock,
1286
1682
  withProviderMutationLock,
1683
+ getOAuthAccountSlot,
1684
+ providerDefaultAuthRef,
1685
+ storeActiveOAuthAccount,
1686
+ clearActiveOAuthAccount,
1287
1687
  isValidProviderId,
1288
1688
  ensureSecureAppHome,
1289
1689
  loadRegistry,
@@ -1306,7 +1706,6 @@ export {
1306
1706
  findClaudeBinary,
1307
1707
  getClaudeVersionForBinary,
1308
1708
  getInstalledClaudeVersion,
1309
- launchClaude,
1310
1709
  tcpListenerUrlHost,
1311
1710
  waitForTcpListenerCandidate,
1312
1711
  listenTcpServer,
@@ -1315,8 +1714,12 @@ export {
1315
1714
  unregisterServerRuntimeState,
1316
1715
  readLiveServerRuntimeStates,
1317
1716
  orderWrapperServerCandidates,
1717
+ CHILD_NETWORK_ENV_VARS,
1718
+ NETWORK_ENV_CONTRACT_VAR,
1719
+ networkEnvBaseline,
1720
+ recordNetworkEnvMutation,
1318
1721
  removeAnthropicProxyBypass,
1319
1722
  wrapperRequiresServer,
1320
1723
  computeWrapperEnv
1321
1724
  };
1322
- //# sourceMappingURL=chunk-LBEJOEUY.js.map
1725
+ //# sourceMappingURL=chunk-MRO3KE3P.js.map