@bman654/clodex 2.3.0 → 2.5.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,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,65 +562,161 @@ 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
  }
572
+ if (typeof p.preserveModelPricing === "boolean") {
573
+ provider.preserveModelPricing = p.preserveModelPricing;
574
+ }
488
575
  if (p.authType === "api" || p.authType === "oauth" || p.authType === "none") {
489
576
  provider.authType = p.authType;
490
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
+ }
491
587
  if (typeof p.refreshedAt === "string") provider.refreshedAt = p.refreshedAt;
492
- if (p.modelsCache && typeof p.modelsCache === "object") {
493
- const cache = p.modelsCache;
494
- if (typeof cache.fetchedAt === "string" && Array.isArray(cache.models)) {
495
- provider.modelsCache = {
496
- fetchedAt: cache.fetchedAt,
497
- models: cache.models.filter((m) => m && typeof m === "object")
498
- };
499
- }
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}".`);
500
597
  }
501
598
  return provider;
502
599
  }
503
600
  function hasOwn(record, key) {
504
601
  return Object.prototype.hasOwnProperty.call(record, key);
505
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
+ }
506
637
  function hasValidStrictProviderFields(raw) {
507
638
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return false;
508
639
  const provider = raw;
509
640
  if (hasOwn(provider, "subscriptionFilter") && provider.subscriptionFilter !== "free") {
510
641
  return false;
511
642
  }
643
+ if (hasOwn(provider, "preserveModelPricing") && typeof provider.preserveModelPricing !== "boolean") {
644
+ return false;
645
+ }
512
646
  if (hasOwn(provider, "authType") && provider.authType !== "api" && provider.authType !== "oauth" && provider.authType !== "none") {
513
647
  return false;
514
648
  }
515
649
  if (hasOwn(provider, "refreshedAt") && typeof provider.refreshedAt !== "string") {
516
650
  return false;
517
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
+ }
518
664
  if (hasOwn(provider, "modelsCache")) {
519
- const cache = provider.modelsCache;
520
- if (!cache || typeof cache !== "object" || Array.isArray(cache)) return false;
521
- const fields = cache;
522
- if (typeof fields.fetchedAt !== "string" || !Array.isArray(fields.models)) {
523
- return false;
524
- }
525
- if (fields.models.some((model) => !model || typeof model !== "object" || Array.isArray(model))) {
526
- return false;
527
- }
665
+ if (parseModelsCache(provider.modelsCache) === null) return false;
528
666
  }
529
667
  return true;
530
668
  }
531
- 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) {
532
699
  const empty = { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };
533
700
  if (!raw || typeof raw !== "object") return empty;
534
701
  const data = raw;
702
+ const schemaVersion = typeof data.schemaVersion === "number" ? data.schemaVersion : REGISTRY_SCHEMA_VERSION;
535
703
  const providers = [];
536
704
  if (Array.isArray(data.providers)) {
537
- for (const entry of data.providers) {
538
- const parsed = parseProvider(entry);
539
- 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
+ }
540
716
  }
541
717
  }
542
718
  const registry = {
543
- schemaVersion: typeof data.schemaVersion === "number" ? data.schemaVersion : REGISTRY_SCHEMA_VERSION,
719
+ schemaVersion,
544
720
  providers
545
721
  };
546
722
  if (typeof data.importedAt === "string") registry.importedAt = data.importedAt;
@@ -552,14 +728,15 @@ function parseRegistryStrict(raw) {
552
728
  throw new Error("Provider registry must be a JSON object.");
553
729
  }
554
730
  const data = raw;
555
- 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) {
556
732
  throw new Error("Provider registry has an unsupported schema version.");
557
733
  }
558
734
  if (!Array.isArray(data.providers)) {
559
735
  throw new Error("Provider registry is missing its providers list.");
560
736
  }
561
737
  for (const entry of data.providers) {
562
- if (!parseProvider(entry) || !hasValidStrictProviderFields(entry)) {
738
+ const provider = parseProvider(entry);
739
+ if (!provider || !hasValidStrictProviderFields(entry) || !hasValidSelectionStorage(provider, data.schemaVersion)) {
563
740
  throw new Error("Provider registry contains an invalid provider entry.");
564
741
  }
565
742
  }
@@ -568,27 +745,185 @@ function parseRegistryStrict(raw) {
568
745
  function readRegistryStrict(path) {
569
746
  return parseRegistryStrict(JSON.parse(readFileSync2(path, "utf8")));
570
747
  }
571
- 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) {
572
801
  if (!existsSync(path)) {
573
802
  return { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };
574
803
  }
804
+ let registry;
575
805
  try {
576
806
  const raw = JSON.parse(readFileSync2(path, "utf8"));
577
- const registry = parseRegistry(raw);
578
- const migrated = migrateOAuthOpenAiProvider(registry);
579
- if (migrated) {
580
- try {
581
- withRegistryWriteLockSync(() => {
582
- if (!existsSync(path)) return;
583
- const current = readRegistryStrict(path);
584
- if (migrateOAuthOpenAiProvider(current)) saveRegistry(current, path);
585
- }, { lockPath: `${path}.lock` });
586
- } 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");
587
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}`);
588
823
  }
589
824
  return registry;
590
- } catch {
591
- 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;
592
927
  }
593
928
  }
594
929
  function loadRegistryStrict(path = getProvidersPath()) {
@@ -596,12 +931,43 @@ function loadRegistryStrict(path = getProvidersPath()) {
596
931
  return { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };
597
932
  }
598
933
  const registry = readRegistryStrict(path);
599
- migrateOAuthOpenAiProvider(registry);
934
+ migrateRegistry(registry);
600
935
  return registry;
601
936
  }
602
937
  function saveRegistry(registry, path = getProvidersPath()) {
603
938
  assertRegistryWriteOwnership(path);
604
- 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)}
605
971
  `;
606
972
  const backup = `${path}.bak`;
607
973
  if (existsSync(path)) {
@@ -616,11 +982,16 @@ function saveRegistry(registry, path = getProvidersPath()) {
616
982
  assertRegistryWriteOwnership(path);
617
983
  renameSync(tmp, path);
618
984
  syncParentDirectory(path);
985
+ } catch (error) {
986
+ if (error instanceof RegistryLockLostError) throw error;
987
+ throw new RegistryPersistenceError(path, error);
619
988
  } finally {
620
989
  try {
621
990
  unlinkSync2(tmp);
622
991
  } catch (err) {
623
- if (err.code !== "ENOENT") throw err;
992
+ if (err.code !== "ENOENT") {
993
+ throw new RegistryPersistenceError(path, err);
994
+ }
624
995
  }
625
996
  }
626
997
  }
@@ -822,9 +1193,9 @@ function setServerListenMode(listenMode) {
822
1193
  });
823
1194
  }
824
1195
 
825
- // src/launch.ts
826
- import { execFileSync as execFileSync2, execSync, spawn } from "child_process";
827
- 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";
828
1199
  import { homedir as homedir2 } from "os";
829
1200
  import { join as join3 } from "path";
830
1201
 
@@ -850,7 +1221,7 @@ function findBinaryOnPath(name, fallbackPaths, options = {}) {
850
1221
  return null;
851
1222
  }
852
1223
 
853
- // src/launch.ts
1224
+ // src/claude-binary.ts
854
1225
  var isWindows = process.platform === "win32";
855
1226
  var FALLBACK_PATHS = isWindows ? [
856
1227
  join3(process.env["APPDATA"] ?? homedir2(), "npm", "claude.cmd"),
@@ -894,57 +1265,6 @@ function getInstalledClaudeVersion() {
894
1265
  if (!claudePath) return FALLBACK_CLAUDE_VERSION;
895
1266
  return getClaudeVersionForBinary(claudePath) ?? FALLBACK_CLAUDE_VERSION;
896
1267
  }
897
- function buildClaudeArgs(model, extraArgs) {
898
- return model ? ["--model", model, ...extraArgs] : [...extraArgs];
899
- }
900
- function launchClaude(env, model, extraArgs) {
901
- return new Promise((resolve) => {
902
- const claudePath = findClaudeBinary();
903
- const args = buildClaudeArgs(model, extraArgs);
904
- const debugFileIdx = extraArgs.indexOf("--debug-file");
905
- const debugLogPath = debugFileIdx !== -1 && extraArgs[debugFileIdx + 1] ? extraArgs[debugFileIdx + 1] : void 0;
906
- const originalStdoutWrite = process.stdout.write;
907
- const originalStderrWrite = process.stderr.write;
908
- const muteWrite = (chunk, encoding, callback) => {
909
- if (typeof encoding === "function") {
910
- callback = encoding;
911
- }
912
- if (debugLogPath) {
913
- try {
914
- const str = typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk);
915
- appendFileSync(debugLogPath, `[parent] ${str}`);
916
- } catch {
917
- }
918
- }
919
- if (callback) callback();
920
- return true;
921
- };
922
- process.stdout.write = muteWrite;
923
- process.stderr.write = muteWrite;
924
- const restore = () => {
925
- process.stdout.write = originalStdoutWrite;
926
- process.stderr.write = originalStderrWrite;
927
- };
928
- const child = spawn(claudePath, args, {
929
- stdio: "inherit",
930
- env,
931
- shell: isWindows
932
- });
933
- const forward = (signal) => {
934
- child.kill(signal);
935
- };
936
- process.once("SIGINT", () => forward("SIGINT"));
937
- process.once("SIGTERM", () => forward("SIGTERM"));
938
- child.on("exit", (code) => {
939
- restore();
940
- resolve(code ?? 0);
941
- });
942
- child.on("error", (err) => {
943
- restore();
944
- resolve(1);
945
- });
946
- });
947
- }
948
1268
 
949
1269
  // src/listener-ready.ts
950
1270
  import { connect } from "net";
@@ -1223,8 +1543,84 @@ function orderWrapperServerCandidates(records) {
1223
1543
  });
1224
1544
  }
1225
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
+
1226
1623
  // src/wrapper-env.ts
1227
- var PROXY_ENV_VARS = ["HTTPS_PROXY", "HTTP_PROXY", "https_proxy", "http_proxy"];
1228
1624
  var REQUIRE_SERVER_ENV = "CLODEX_REQUIRE_SERVER";
1229
1625
  function removeAnthropicProxyBypass(env) {
1230
1626
  const noProxyValues = [env["NO_PROXY"], env["no_proxy"]].filter((value) => value !== void 0);
@@ -1250,27 +1646,33 @@ function wrapperRequiresServer(env) {
1250
1646
  return env[REQUIRE_SERVER_ENV] === "1";
1251
1647
  }
1252
1648
  function computeWrapperEnv(baseEnv, state) {
1253
- const env = { ...baseEnv };
1254
- if (!state) return env;
1649
+ if (!state) return { ...baseEnv };
1650
+ const baseline = networkEnvBaseline(baseEnv);
1651
+ const env = { ...baseline };
1255
1652
  if (state.mode === "proxy") {
1256
1653
  const proxyUrl = `http://127.0.0.1:${state.port}`;
1257
1654
  delete env["ANTHROPIC_BASE_URL"];
1258
1655
  for (const name of PROXY_ENV_VARS) env[name] = proxyUrl;
1259
1656
  if (state.caPath) env["NODE_EXTRA_CA_CERTS"] = state.caPath;
1260
1657
  removeAnthropicProxyBypass(env);
1658
+ recordNetworkEnvMutation(baseline, env);
1261
1659
  return env;
1262
1660
  }
1263
1661
  for (const name of PROXY_ENV_VARS) delete env[name];
1264
1662
  env["ANTHROPIC_BASE_URL"] = `http://127.0.0.1:${state.port}/anthropic`;
1265
1663
  env["ANTHROPIC_API_KEY"] = LOCAL_GATEWAY_API_KEY;
1664
+ recordNetworkEnvMutation(baseline, env);
1266
1665
  return env;
1267
1666
  }
1268
1667
 
1269
1668
  export {
1669
+ OAUTH_ACCOUNT_ENV,
1270
1670
  getAppHome,
1271
1671
  getLocalPatchesPath,
1272
1672
  getCredentialCleanupPath,
1273
1673
  getLogsPath,
1674
+ REGISTRY_SCHEMA_VERSION_WITH_MATERIALIZED_ACTIVE_ACCOUNT,
1675
+ OAUTH_ACCOUNT_NAME_RE,
1274
1676
  assertRegistryWriteOwnership,
1275
1677
  withRegistryWriteLock,
1276
1678
  withRegistryWriteLockSync,
@@ -1278,6 +1680,10 @@ export {
1278
1680
  getCredentialStateRoot,
1279
1681
  withCredentialMutationLock,
1280
1682
  withProviderMutationLock,
1683
+ getOAuthAccountSlot,
1684
+ providerDefaultAuthRef,
1685
+ storeActiveOAuthAccount,
1686
+ clearActiveOAuthAccount,
1281
1687
  isValidProviderId,
1282
1688
  ensureSecureAppHome,
1283
1689
  loadRegistry,
@@ -1300,7 +1706,6 @@ export {
1300
1706
  findClaudeBinary,
1301
1707
  getClaudeVersionForBinary,
1302
1708
  getInstalledClaudeVersion,
1303
- launchClaude,
1304
1709
  tcpListenerUrlHost,
1305
1710
  waitForTcpListenerCandidate,
1306
1711
  listenTcpServer,
@@ -1309,8 +1714,12 @@ export {
1309
1714
  unregisterServerRuntimeState,
1310
1715
  readLiveServerRuntimeStates,
1311
1716
  orderWrapperServerCandidates,
1717
+ CHILD_NETWORK_ENV_VARS,
1718
+ NETWORK_ENV_CONTRACT_VAR,
1719
+ networkEnvBaseline,
1720
+ recordNetworkEnvMutation,
1312
1721
  removeAnthropicProxyBypass,
1313
1722
  wrapperRequiresServer,
1314
1723
  computeWrapperEnv
1315
1724
  };
1316
- //# sourceMappingURL=chunk-W4SVDQCZ.js.map
1725
+ //# sourceMappingURL=chunk-MRO3KE3P.js.map