| 700 | } |
| 701 | |
| 702 | loadConfigOrDefault(): ProjectsConfig { |
| 703 | try { |
| 704 | if (fs.existsSync(this.configFile)) { |
| 705 | const data = fs.readFileSync(this.configFile, "utf-8"); |
| 706 | const parsed = JSON.parse(data) as Partial<AppConfigOnDisk> & Record<string, unknown>; |
| 707 | let configModified = false; |
| 708 | let shouldInvalidateSessionUsageCaches = false; |
| 709 | |
| 710 | const normalizeNestedModelStrings = (value: unknown): boolean => { |
| 711 | if (!value || typeof value !== "object" || Array.isArray(value)) { |
| 712 | return false; |
| 713 | } |
| 714 | |
| 715 | let modified = false; |
| 716 | for (const entry of Object.values(value as Record<string, unknown>)) { |
| 717 | if (!entry || typeof entry !== "object" || Array.isArray(entry)) { |
| 718 | continue; |
| 719 | } |
| 720 | |
| 721 | const modelString = (entry as { modelString?: unknown }).modelString; |
| 722 | if (typeof modelString !== "string") { |
| 723 | continue; |
| 724 | } |
| 725 | |
| 726 | const normalized = normalizeSelectedModel(modelString.trim()); |
| 727 | if (normalized !== modelString) { |
| 728 | (entry as { modelString?: string }).modelString = normalized; |
| 729 | modified = true; |
| 730 | } |
| 731 | } |
| 732 | |
| 733 | return modified; |
| 734 | }; |
| 735 | |
| 736 | const normalizeLegacyGatewayModel = (value: string): string | undefined => { |
| 737 | const trimmed = value.trim(); |
| 738 | if (!trimmed) { |
| 739 | return undefined; |
| 740 | } |
| 741 | |
| 742 | const legacyModelString = trimmed.includes(":") ? trimmed : trimmed.replace("/", ":"); |
| 743 | const canonicalModel = normalizeToCanonical(legacyModelString); |
| 744 | return isValidModelFormat(canonicalModel) ? canonicalModel : undefined; |
| 745 | }; |
| 746 | |
| 747 | // Migrate legacy gateway settings to the new route-based system. |
| 748 | // Legacy keys are intentionally preserved on disk for downgrade compatibility — |
| 749 | // older versions still read muxGatewayEnabled / muxGatewayModels directly. |
| 750 | if ( |
| 751 | (parsed.muxGatewayModels != null || parsed.muxGatewayEnabled != null) && |
| 752 | !Array.isArray(parsed.routePriority) |
| 753 | ) { |
| 754 | let nextPriority = this.seedRoutePriorityFromProviders() ?? ["direct"]; |
| 755 | if (parsed.muxGatewayEnabled === false) { |
| 756 | nextPriority = nextPriority.filter((route) => route !== "mux-gateway"); |
| 757 | if (nextPriority.length === 0) { |
| 758 | nextPriority = ["direct"]; |
| 759 | } |