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// RequestLogStore.swift3// Zyquo Router4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//8// Live traffic log: a bounded in-memory ring of RequestLogEntry. Bodies are9// carried in memory but the UI redacts them by default (explicit per-session10// reveal). Never persisted to disk unless the user exports; provider keys11// never enter an entry.12//1314import Foundation1516/// One routed request as shown in the Requests screen.17struct RequestLogEntry: Identifiable, Sendable {18 let id: UUID19 var date: Date20 var method: String21 var path: String22 var namespacedModelID: String23 var provider: ProviderID24 var status: Int25 var streamed: Bool26 var latency: TimeInterval27 /// Time to first upstream byte (streaming) — nil for buffered calls.28 var upstreamTTFB: TimeInterval?29 var usage: TokenUsage30 var usageEstimated: Bool31 var estimatedCost: Double?32 var localKeyName: String?33 var errorMessage: String?34 /// Pretty-printed client request JSON (redacted in UI by default).35 var requestBody: String36 /// Pretty-printed response JSON, or the accumulated stream preview.37 var responseBody: String3839 init(40 id: UUID = UUID(),41 date: Date = Date(),42 method: String = "POST",43 path: String = "/v1/chat/completions",44 namespacedModelID: String,45 provider: ProviderID,46 status: Int,47 streamed: Bool,48 latency: TimeInterval,49 upstreamTTFB: TimeInterval? = nil,50 usage: TokenUsage = TokenUsage(),51 usageEstimated: Bool = false,52 estimatedCost: Double? = nil,53 localKeyName: String? = nil,54 errorMessage: String? = nil,55 requestBody: String = "",56 responseBody: String = ""57 ) {58 self.id = id59 self.date = date60 self.method = method61 self.path = path62 self.namespacedModelID = namespacedModelID63 self.provider = provider64 self.status = status65 self.streamed = streamed66 self.latency = latency67 self.upstreamTTFB = upstreamTTFB68 self.usage = usage69 self.usageEstimated = usageEstimated70 self.estimatedCost = estimatedCost71 self.localKeyName = localKeyName72 self.errorMessage = errorMessage73 self.requestBody = requestBody74 self.responseBody = responseBody75 }76}7778actor RequestLogStore {79 /// Ring capacity — old entries fall off the front.80 static let capacity = 5008182 private(set) var entries: [RequestLogEntry] = []83 /// Monotonic revision so the UI can cheaply detect changes.84 private(set) var revision = 08586 func append(_ entry: RequestLogEntry) {87 entries.append(entry)88 if entries.count > Self.capacity {89 entries.removeFirst(entries.count - Self.capacity)90 }91 revision += 192 }9394 func clear() {95 entries.removeAll()96 revision += 197 }9899 /// Metadata-only JSON export (bodies excluded by design).100 func exportJSON() -> Data {101 let rows = entries.map { entry -> [String: Any] in102 [103 "date": ISO8601DateFormatter().string(from: entry.date),104 "model": entry.namespacedModelID,105 "provider": entry.provider.rawValue,106 "status": entry.status,107 "streamed": entry.streamed,108 "latency_ms": Int(entry.latency * 1000),109 "ttfb_ms": entry.upstreamTTFB.map { Int($0 * 1000) } as Any,110 "input_tokens": entry.usage.inputTokens,111 "output_tokens": entry.usage.outputTokens,112 "usage_estimated": entry.usageEstimated,113 "cost_usd": entry.estimatedCost as Any,114 "key": entry.localKeyName as Any,115 "error": entry.errorMessage as Any,116 ]117 }118 return (try? JSONSerialization.data(withJSONObject: rows, options: [.prettyPrinted, .sortedKeys])) ?? Data("[]".utf8)119 }120}121