// // RouterConfigStore.swift // Zyquo Router // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // User routing configuration: aliases (fast → cerebras/…), fallback chains, // disabled models, favorites. Persisted to router-config.json; the server // takes a snapshot at Start (restart applies changes to a running server). // import Foundation struct RouterConfig: Codable { static let fileName = "router-config.json" var aliases: [String: String] = [:] var fallbackChains: [String: [String]] = [:] var disabledIDs: Set = [] var favoriteIDs: Set = [] } @MainActor final class RouterConfigStore: ObservableObject { static var fileName: String { RouterConfig.fileName } @Published var config: RouterConfig { didSet { persistence.save(config, to: Self.fileName) } } private let persistence: PersistenceService init(persistence: PersistenceService = .shared) { self.persistence = persistence config = persistence.load(RouterConfig.self, from: Self.fileName) ?? RouterConfig() } /// Snapshot for the server thread (also used headless via `load`). static func snapshot() -> RouterConfig { PersistenceService.shared.load(RouterConfig.self, from: fileName) ?? RouterConfig() } func isDisabled(_ namespacedID: String) -> Bool { config.disabledIDs.contains(namespacedID) } func setDisabled(_ disabled: Bool, for namespacedID: String) { if disabled { config.disabledIDs.insert(namespacedID) } else { config.disabledIDs.remove(namespacedID) } } func isFavorite(_ namespacedID: String) -> Bool { config.favoriteIDs.contains(namespacedID) } func toggleFavorite(_ namespacedID: String) { if !config.favoriteIDs.insert(namespacedID).inserted { config.favoriteIDs.remove(namespacedID) } } func setAlias(_ alias: String, target: String) { let name = alias.trimmingCharacters(in: .whitespaces) guard !name.isEmpty else { return } config.aliases[name] = target } func removeAlias(_ alias: String) { config.aliases.removeValue(forKey: alias) } func setChain(for namespacedID: String, chain: [String]) { if chain.isEmpty { config.fallbackChains.removeValue(forKey: namespacedID) } else { config.fallbackChains[namespacedID] = chain } } /// Export for Settings → Advanced (never includes keys). func exportData() throws -> Data { let encoder = JSONEncoder() encoder.outputFormatting = [.prettyPrinted, .sortedKeys] return try encoder.encode(config) } func importData(_ data: Data) throws { config = try JSONDecoder().decode(RouterConfig.self, from: data) } }