// // Routes.swift // Zyquo Router // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // Maps inbound requests to endpoints: /health, /v1/models, /v1/models/{id}; // /v1/chat/completions arrives in Phase 3. All errors are OpenAI-shaped. // import Foundation import NIOHTTP1 struct Routes: Sendable { let router: RequestRouter let auth: AuthMiddleware let cors: CORS let version: String let startedAt: Date let chat: ChatCompletionsRoute private static let encoder: JSONEncoder = { let encoder = JSONEncoder() encoder.outputFormatting = [.withoutEscapingSlashes] return encoder }() init( router: RequestRouter, auth: AuthMiddleware = AuthMiddleware(), cors: CORS = CORS(), version: String = "1.0.0", startedAt: Date = Date(), providerKey: @escaping @Sendable (ProviderID) -> String? = { provider in try? SecureKeyStore().key(for: provider) }, usageMeter: UsageMeter = UsageMeter(), requestLog: RequestLogStore = RequestLogStore() ) { self.router = router self.auth = auth self.cors = cors self.version = version self.startedAt = startedAt self.chat = ChatCompletionsRoute( router: router, providerKey: providerKey, usageMeter: usageMeter, requestLog: requestLog, retryPolicy: RetryPolicy() ) } func handle(_ request: RouteRequest) async -> RouteResult { if request.method == .OPTIONS { return cors.preflightResponse() } let path = request.path // /health is deliberately unauthenticated (readiness probes). if request.method == .GET, path == "/health" { return withCORS(health()) } let localKey: APIKeyRecord? switch auth.authorize(request) { case .unauthorized(let message): return withCORS(OpenAIError.response( status: .unauthorized, message: message, type: "authentication_error", code: "invalid_api_key" )) case .allowed(let record): localKey = record } switch (request.method, path) { case (.POST, "/v1/chat/completions"): return withCORS(await chat.handle(request, localKey: localKey)) case (.GET, "/v1/models"): return withCORS(modelList()) case (.GET, _) where path.hasPrefix("/v1/models/"): let id = String(path.dropFirst("/v1/models/".count)) .removingPercentEncoding ?? String(path.dropFirst("/v1/models/".count)) return withCORS(model(id: id)) default: return withCORS(OpenAIError.response( status: .notFound, message: "Unknown request URL: \(request.method) \(path). The router serves /v1/chat/completions, /v1/models, and /health.", type: "invalid_request_error" )) } } // MARK: - Endpoints private func health() -> RouteResult { struct Health: Codable { var status: String var version: String var uptime: Int var models: Int } let payload = Health( status: "ok", version: version, uptime: Int(Date().timeIntervalSince(startedAt)), models: router.exposedModels.filter { !$0.disabled }.count ) return json(payload) } private func modelList() -> RouteResult { let created = Int(startedAt.timeIntervalSince1970) var entries: [OpenAIModelEntry] = [] var aliasTargets: [String: String] = [:] for (alias, target) in router.aliases { aliasTargets[target] = alias } for (namespacedID, model, disabled) in router.exposedModels where !disabled { entries.append(OpenAIModelEntry( id: namespacedID, created: created, ownedBy: model.provider.rawValue, xZyquo: XZyquoModelInfo( displayName: model.displayName, contextWindow: model.contextWindow, maxOutputTokens: model.maxOutputTokens, vision: model.capabilities.vision, tools: model.capabilities.tools, reasoning: model.capabilities.reasoning, inputPerMTok: model.pricing?.inputPerMTok, outputPerMTok: model.pricing?.outputPerMTok, alias: aliasTargets[namespacedID] ) )) } return json(OpenAIModelList(data: entries)) } private func model(id: String) -> RouteResult { do { let resolution = try router.resolve(id) let entry = OpenAIModelEntry( id: resolution.namespacedID, created: Int(startedAt.timeIntervalSince1970), ownedBy: resolution.model.provider.rawValue, xZyquo: XZyquoModelInfo( displayName: resolution.model.displayName, contextWindow: resolution.model.contextWindow, maxOutputTokens: resolution.model.maxOutputTokens, vision: resolution.model.capabilities.vision, tools: resolution.model.capabilities.tools, reasoning: resolution.model.capabilities.reasoning, inputPerMTok: resolution.model.pricing?.inputPerMTok, outputPerMTok: resolution.model.pricing?.outputPerMTok, alias: nil ) ) return json(entry) } catch let error as RequestRouter.RoutingError { return Self.routingErrorResponse(error) } catch { return OpenAIError.response(status: .internalServerError, message: "Internal error.", type: "server_error") } } /// Shared mapping used by every route that resolves a model. static func routingErrorResponse(_ error: RequestRouter.RoutingError) -> RouteResult { switch error { case .unknownModel(let name), .disabledModel(let name): return OpenAIError.modelNotFound(name) case .ambiguousModel(let name, let candidates): return OpenAIError.response( status: .notFound, message: "The model `\(name)` is ambiguous — use a namespaced ID: \(candidates.joined(separator: ", ")).", type: "invalid_request_error", param: "model", code: "model_not_found" ) } } // MARK: - Helpers private func json(_ payload: T, status: HTTPResponseStatus = .ok) -> RouteResult { guard let body = try? Self.encoder.encode(payload) else { return OpenAIError.response(status: .internalServerError, message: "Encoding failure.", type: "server_error") } return .complete(status: status, headers: [("Content-Type", "application/json")], body: body) } private func withCORS(_ result: RouteResult) -> RouteResult { switch result { case .complete(let status, let headers, let body): return .complete(status: status, headers: headers + cors.headers, body: body) case .stream(let status, let headers, let body): return .stream(status: status, headers: headers + cors.headers, body: body) } } }