SPB Git

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%
4.4 KB · 112 lines swift
Raw Blame History
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