spb/zyquo-router Public MIT
One local endpoint, every AI provider — a private OpenAI-compatible LLM gateway for your Mac (170 models, 12 providers).
Swift 95.7%
Python 2.3%
Shell 1.2%
Makefile 0.9%
1//2// RouterConfigStore.swift3// Zyquo Router4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//8// User routing configuration: aliases (fast → cerebras/…), fallback chains,9// disabled models, favorites. Persisted to router-config.json; the server10// takes a snapshot at Start (restart applies changes to a running server).11//1213import Foundation1415struct RouterConfig: Codable {16 static let fileName = "router-config.json"1718 var aliases: [String: String] = [:]19 var fallbackChains: [String: [String]] = [:]20 var disabledIDs: Set<String> = []21 var favoriteIDs: Set<String> = []22}2324@MainActor25final class RouterConfigStore: ObservableObject {26 static var fileName: String { RouterConfig.fileName }2728 @Published var config: RouterConfig {29 didSet { persistence.save(config, to: Self.fileName) }30 }3132 private let persistence: PersistenceService3334 init(persistence: PersistenceService = .shared) {35 self.persistence = persistence36 config = persistence.load(RouterConfig.self, from: Self.fileName) ?? RouterConfig()37 }3839 /// Snapshot for the server thread (also used headless via `load`).40 static func snapshot() -> RouterConfig {41 PersistenceService.shared.load(RouterConfig.self, from: fileName) ?? RouterConfig()42 }4344 func isDisabled(_ namespacedID: String) -> Bool {45 config.disabledIDs.contains(namespacedID)46 }4748 func setDisabled(_ disabled: Bool, for namespacedID: String) {49 if disabled {50 config.disabledIDs.insert(namespacedID)51 } else {52 config.disabledIDs.remove(namespacedID)53 }54 }5556 func isFavorite(_ namespacedID: String) -> Bool {57 config.favoriteIDs.contains(namespacedID)58 }5960 func toggleFavorite(_ namespacedID: String) {61 if !config.favoriteIDs.insert(namespacedID).inserted {62 config.favoriteIDs.remove(namespacedID)63 }64 }6566 func setAlias(_ alias: String, target: String) {67 let name = alias.trimmingCharacters(in: .whitespaces)68 guard !name.isEmpty else { return }69 config.aliases[name] = target70 }7172 func removeAlias(_ alias: String) {73 config.aliases.removeValue(forKey: alias)74 }7576 func setChain(for namespacedID: String, chain: [String]) {77 if chain.isEmpty {78 config.fallbackChains.removeValue(forKey: namespacedID)79 } else {80 config.fallbackChains[namespacedID] = chain81 }82 }8384 /// Export for Settings → Advanced (never includes keys).85 func exportData() throws -> Data {86 let encoder = JSONEncoder()87 encoder.outputFormatting = [.prettyPrinted, .sortedKeys]88 return try encoder.encode(config)89 }9091 func importData(_ data: Data) throws {92 config = try JSONDecoder().decode(RouterConfig.self, from: data)93 }94}95