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// AIModel.swift3// Zyquo Router4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//89import Foundation1011/// A chat-capable model offered by a provider. Instances come exclusively from12/// `ModelCatalog` (built-in data generated from docs/PROVIDERS.md, dynamic13/// `/models` refreshes, and user-defined custom models) — never hardcode these14/// in views or clients.15struct AIModel: Codable, Identifiable, Hashable {16 /// Exact model ID as sent in API requests (e.g. "gpt-5.6-terra").17 let id: String18 let provider: ProviderID19 /// Human-friendly name shown in the UI (e.g. "GPT-5.6 Terra").20 let displayName: String21 /// Context window in tokens.22 let contextWindow: Int23 /// Maximum output tokens, when documented.24 let maxOutputTokens: Int?25 let capabilities: ModelCapabilities26 let pricing: ModelPricing?27 let parameterSupport: ParameterSupport28 /// Deprecated or superseded models stay selectable but are ranked last and badged.29 var isLegacy: Bool = false30 /// Featured/flagship models surface at the top of pickers.31 var isRecommended: Bool = false32 /// Base URL override for user-defined custom models; nil for built-ins.33 var customBaseURL: URL? = nil3435 /// Short badge text for the model chip (e.g. "1M ctx").36 var contextBadge: String {37 switch contextWindow {38 case 1_000_000...: return "\(contextWindow / 1_000_000)M ctx"39 case 1_000...: return "\(contextWindow / 1_000)K ctx"40 default: return "\(contextWindow) ctx"41 }42 }43}4445/// What a model can do. Drives UI affordances (attach button, thinking section…)46/// and request construction.47struct ModelCapabilities: Codable, Hashable {48 /// Accepts image input.49 var vision: Bool = false50 /// Supports function calling / tools.51 var tools: Bool = false52 /// Produces reasoning/thinking output (shown in the collapsible section).53 var reasoning: Bool = false54 /// Supports SSE streaming (true for every catalog model; custom endpoints may vary).55 var streaming: Bool = true56 /// Supports JSON mode / structured output.57 var jsonMode: Bool = false58 /// Returns web-search citations (Perplexity sonar family).59 var citations: Bool = false60}6162/// USD per 1M tokens. Cached/tiered pricing is intentionally simplified to the63/// base rate — cost figures in the UI are labeled as estimates.64struct ModelPricing: Codable, Hashable {65 var inputPerMTok: Double66 var outputPerMTok: Double6768 /// Estimated cost in USD for a usage record.69 func cost(inputTokens: Int, outputTokens: Int) -> Double {70 (Double(inputTokens) * inputPerMTok + Double(outputTokens) * outputPerMTok) / 1_000_00071 }72}7374/// Which sampling/control parameters a model accepts. Providers reject requests75/// carrying unsupported parameters, so requests only include what's supported —76/// and Settings only shows sliders that apply.77struct ParameterSupport: Codable, Hashable {78 var temperature: Bool = true79 var topP: Bool = true80 var frequencyPenalty: Bool = false81 var presencePenalty: Bool = false82 /// Send "max_completion_tokens" instead of "max_tokens" (OpenAI reasoning models, Cerebras).83 var usesMaxCompletionTokens: Bool = false84 /// Accepts `reasoning_effort` (OpenAI, xAI, Mistral, DeepSeek, Kimi K-series, Cerebras…).85 var reasoningEffort: Bool = false86 /// Anthropic `thinking` / Qwen `enable_thinking` style explicit thinking toggle.87 var thinkingToggle: Bool = false88 /// Model rejects non-streaming calls (Qwen qwq/qvq, DashScope-hosted models89 /// on Together…) — `complete` aggregates a stream instead.90 var requiresStreaming: Bool = false9192 static let openAIDefault = ParameterSupport(frequencyPenalty: true, presencePenalty: true)93}9495/// Token usage reported by a provider for one exchange.96struct TokenUsage: Codable, Hashable {97 var inputTokens: Int = 098 var outputTokens: Int = 099 var reasoningTokens: Int? = nil100101 var totalTokens: Int { inputTokens + outputTokens }102103 static func + (lhs: TokenUsage, rhs: TokenUsage) -> TokenUsage {104 TokenUsage(105 inputTokens: lhs.inputTokens + rhs.inputTokens,106 outputTokens: lhs.outputTokens + rhs.outputTokens,107 reasoningTokens: (lhs.reasoningTokens ?? 0) + (rhs.reasoningTokens ?? 0) == 0108 ? nil : (lhs.reasoningTokens ?? 0) + (rhs.reasoningTokens ?? 0)109 )110 }111}112