import Foundation public enum QoderProviderDescriptor { public static let descriptor: ProviderDescriptor = Self.makeDescriptor() static func makeDescriptor() -> ProviderDescriptor { ProviderDescriptor( id: .qoder, metadata: ProviderMetadata( id: .qoder, displayName: "Qoder", sessionLabel: "Credits", weeklyLabel: "Balance", opusLabel: nil, supportsOpus: false, supportsCredits: false, creditsHint: "Big model credits from the Qoder usage dashboard.", toggleTitle: "Show Qoder usage", cliName: "qoder", defaultEnabled: false, isPrimaryProvider: false, usesAccountFallback: false, browserCookieOrder: ProviderBrowserCookieDefaults.qoderCookieImportOrder, dashboardURL: QoderWebSite.international.dashboardURL.absoluteString, statusPageURL: nil, statusLinkURL: nil), branding: ProviderBranding( iconStyle: .qoder, iconResourceName: "ProviderIcon-qoder", color: ProviderColor(red: 16 / 255, green: 185 / 255, blue: 129 / 255)), tokenCost: ProviderTokenCostConfig( supportsTokenCost: false, noDataMessage: { "Qoder cost summary is not supported." }), fetchPlan: ProviderFetchPlan( sourceModes: [.auto, .web], pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [QoderWebFetchStrategy()] })), cli: ProviderCLIConfig( name: "qoder", aliases: [], versionDetector: nil)) } public static func dashboardURL( settings: ProviderSettingsSnapshot.QoderProviderSettings?, sourceLabel: String?) -> URL { guard settings?.cookieSource == .manual else { return self.dashboardURL(forSourceLabel: sourceLabel) } guard let site = QoderWebFetchStrategy.site(forManualCookieHeader: settings?.manualCookieHeader) else { return QoderWebSite.international.dashboardURL } return site.dashboardURL } public static func dashboardURL(forSourceLabel sourceLabel: String?) -> URL { guard let sourceLabel, !sourceLabel.isEmpty else { return QoderWebSite.international.dashboardURL } return QoderWebFetchStrategy.site(for: sourceLabel).dashboardURL } } struct QoderWebFetchStrategy: ProviderFetchStrategy { typealias UsageLoader = @Sendable (String, QoderWebSite, TimeInterval) async throws -> QoderUsageSnapshot typealias CookieResolver = @Sendable (ProviderFetchContext, Bool, Set) throws -> QoderResolvedCookie? let id: String = "qoder.web" let kind: ProviderFetchKind = .web private let usageLoader: UsageLoader private let cookieResolver: CookieResolver init( usageLoader: @escaping UsageLoader = { cookieHeader, site, timeout in try await QoderUsageFetcher.fetchUsage( cookieHeader: cookieHeader, site: site, timeout: timeout) }, cookieResolver: @escaping CookieResolver = QoderWebFetchStrategy.resolveCookieHeader) { self.usageLoader = usageLoader self.cookieResolver = cookieResolver } func isAvailable(_ context: ProviderFetchContext) async -> Bool { let cookieSource = context.settings?.qoder?.cookieSource ?? .auto guard cookieSource != .off else { return false } if cookieSource == .manual { return CookieHeaderNormalizer.normalize(context.settings?.qoder?.manualCookieHeader) != nil } #if os(macOS) return true #else return false #endif } func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult { let cookieSource = context.settings?.qoder?.cookieSource ?? .auto let shouldRetry = cookieSource != .manual || Self.shouldRetryManualCookieHeader( context.settings?.qoder?.manualCookieHeader) var skippedSourceLabels = Set() var allowCached = true var sawInvalidCredentials = false var terminalNonAuthError: Error? while true { let resolvedCookie: QoderResolvedCookie do { guard let candidate = try self.cookieResolver( context, allowCached, skippedSourceLabels) else { if let exhaustionError = Self.errorForExhaustion( terminalNonAuthError: terminalNonAuthError, sawInvalidCredentials: sawInvalidCredentials) { throw exhaustionError } throw QoderUsageError.missingCredentials } resolvedCookie = candidate } catch QoderUsageError.missingCredentials where terminalNonAuthError != nil || sawInvalidCredentials { if let exhaustionError = Self.errorForExhaustion( terminalNonAuthError: terminalNonAuthError, sawInvalidCredentials: sawInvalidCredentials) { throw exhaustionError } throw QoderUsageError.missingCredentials } do { let snapshot = try await self.usageLoader( resolvedCookie.cookieHeader, Self.site(for: resolvedCookie.sourceLabel), context.webTimeout) return self.makeResult( usage: snapshot.toUsageSnapshot(), sourceLabel: resolvedCookie.sourceLabel) } catch is CancellationError { throw CancellationError() } catch let fetchError { guard shouldRetry else { throw fetchError } if case QoderUsageError.invalidCredentials = fetchError { CookieHeaderCache.clear(provider: .qoder) sawInvalidCredentials = true } else { terminalNonAuthError = fetchError } if !resolvedCookie.isFromCache { skippedSourceLabels.insert(resolvedCookie.sourceLabel) } allowCached = false continue } } } private static func errorForExhaustion( terminalNonAuthError: Error?, sawInvalidCredentials: Bool) -> Error? { if let terminalNonAuthError { return terminalNonAuthError } if sawInvalidCredentials { return QoderUsageError.invalidCredentials } return nil } static func resolveCookieHeader( context: ProviderFetchContext, allowCached: Bool, skippingSourceLabels: Set) throws -> QoderResolvedCookie? { if context.settings?.qoder?.cookieSource == .manual { let rawHeader = context.settings?.qoder?.manualCookieHeader let sites = try Self.sites(forManualCookieHeader: rawHeader) guard let manual = CookieHeaderNormalizer.normalize(rawHeader) else { throw QoderUsageError.missingCredentials } for site in sites { let sourceLabel = Self.sourceLabel(browserLabel: "manual", site: site) guard Self.shouldUseSourceLabel(sourceLabel, skipping: skippingSourceLabels) else { continue } return QoderResolvedCookie(cookieHeader: manual, sourceLabel: sourceLabel) } return nil } #if os(macOS) if allowCached, let cached = CookieHeaderCache.load(provider: .qoder), !cached.cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, shouldUseSourceLabel(cached.sourceLabel, skipping: skippingSourceLabels) { return QoderResolvedCookie( cookieHeader: cached.cookieHeader, sourceLabel: cached.sourceLabel, isFromCache: true) } let sessions: [QoderCookieImporter.SessionInfo] do { sessions = try QoderCookieImporter.importSessions(browserDetection: context.browserDetection) } catch { throw QoderUsageError.missingCredentials } guard let session = sessions.first(where: { session in Self.shouldUseSourceLabel( Self.sourceLabel(browserLabel: session.sourceLabel, site: session.site), skipping: skippingSourceLabels) }) else { return nil } guard !session.cookies.isEmpty else { throw QoderUsageError.missingCredentials } let sourceLabel = Self.sourceLabel(browserLabel: session.sourceLabel, site: session.site) CookieHeaderCache.store( provider: .qoder, cookieHeader: session.cookieHeader, sourceLabel: sourceLabel) return QoderResolvedCookie(cookieHeader: session.cookieHeader, sourceLabel: sourceLabel) #else throw QoderUsageError.missingCredentials #endif } static func site(forManualCookieHeader rawHeader: String?) -> QoderWebSite? { guard case let .site(site) = self.manualCookieRoute(rawHeader) else { return nil } return site } private enum ManualCookieRoute { case site(QoderWebSite) case invalid } private enum CurlHeaderHostInspection { case ignored case site(QoderWebSite) case invalid } private static func sites(forManualCookieHeader rawHeader: String?) throws -> [QoderWebSite] { switch self.manualCookieRoute(rawHeader) { case let .site(site): return [site] case .invalid: throw QoderUsageError.invalidCredentials } } private static func manualCookieRoute(_ rawHeader: String?) -> ManualCookieRoute { guard let rawHeader else { return .site(.international) } if let curlRoute = self.curlRequestRoute(rawHeader) { return curlRoute } if let httpRoute = self.httpRequestRoute(rawHeader) { return httpRoute } return self.plainCookieRoute(rawHeader) } private static func plainCookieRoute(_ rawHeader: String) -> ManualCookieRoute { var routedSite: QoderWebSite? for part in rawHeader.split(separator: ";") { let pieces = part.split(separator: "=", maxSplits: 1) guard pieces.count == 2 else { continue } let name = pieces[0].trimmingCharacters(in: .whitespacesAndNewlines).lowercased() guard name == "domain" else { continue } let value = pieces[1] .trimmingCharacters(in: .whitespacesAndNewlines) .trimmingCharacters(in: CharacterSet(charactersIn: "\"'")) guard let site = self.site(forHost: value) else { return .invalid } if let routedSite, routedSite != site { return .invalid } routedSite = site } return .site(routedSite ?? .international) } private static func httpRequestRoute(_ rawHeader: String) -> ManualCookieRoute? { let supportedMethods = ["get", "post", "put", "patch", "delete", "head", "options"] let unsupportedKnownMethods = ["trace", "connect"] var requestSite: QoderWebSite? var sawRequestLine = false for line in rawHeader.split(whereSeparator: \.isNewline) { let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines) let parts = trimmed.split(whereSeparator: { $0 == " " || $0 == "\t" }) guard parts.count >= 2 else { continue } let method = String(parts[0]).lowercased() let isHTTPVersionedLine = parts.count >= 3 && parts[2].lowercased().hasPrefix("http/") if unsupportedKnownMethods.contains(method) || (isHTTPVersionedLine && self.isHTTPRequestMethodToken(String(parts[0]))) { guard supportedMethods.contains(method) else { return .invalid } } else if !supportedMethods.contains(method) { continue } guard !sawRequestLine else { return .invalid } sawRequestLine = true let target = String(parts[1]) if target.hasPrefix("/") { requestSite = nil } else if let site = self.site(forURLText: target) { requestSite = site } else { return .invalid } } guard sawRequestLine else { return nil } let hostHeaderSites = self.hostHeaderSites(rawHeader) guard hostHeaderSites.allSatisfy({ $0 != nil }) else { return .invalid } let concreteHostSites = hostHeaderSites.compactMap(\.self) guard concreteHostSites.dropFirst().allSatisfy({ $0 == concreteHostSites.first }) else { return .invalid } if let requestSite { if let hostSite = concreteHostSites.first, hostSite != requestSite { return .invalid } return .site(requestSite) } guard let hostSite = concreteHostSites.first else { return .invalid } return .site(hostSite) } private static func curlRequestRoute(_ rawHeader: String) -> ManualCookieRoute? { guard let preprocessedHeader = self.preprocessedCurlShellText(rawHeader) else { return self.containsCurlExecutableText(rawHeader) ? .invalid : nil } let tokens = self.shellTokens(preprocessedHeader) guard let curlIndex = self.curlCommandIndex(tokens) else { if tokens.contains(where: self.isCurlExecutableToken) { return .invalid } return nil } guard tokens.allSatisfy(self.isCurlTokenTextSafe) else { return .invalid } guard let explicitTargets = self.explicitCurlURLTargets(tokens, after: curlIndex), let urlTargets = self.urlTokenTargets(tokens, after: curlIndex) else { return .invalid } let targetIndices = Set(explicitTargets.map(\.index)).union(urlTargets.map(\.index)) guard targetIndices.count == 1, let targetIndex = targetIndices.first else { return .invalid } let trustedIndex = tokens.index(after: curlIndex) let targetSite: QoderWebSite if let explicitTarget = explicitTargets.first(where: { $0.index == targetIndex }) { targetSite = explicitTarget.site } else if targetIndex == trustedIndex, let trustedTarget = urlTargets.first(where: { $0.index == trustedIndex }) { targetSite = trustedTarget.site } else { return .invalid } guard let headerSites = self.curlHeaderHostSites(tokens, after: curlIndex), headerSites.allSatisfy({ $0 == targetSite }) else { return .invalid } return .site(targetSite) } private static func curlCommandIndex(_ tokens: [String]) -> Array.Index? { var index = tokens.startIndex while index < tokens.endIndex, self.isShellAssignment(tokens[index]) { index = tokens.index(after: index) } guard index < tokens.endIndex else { return nil } guard self.isCurlExecutableToken(tokens[index]) else { return nil } return index } private static func isCurlExecutableToken(_ token: String) -> Bool { guard !token.contains("="), !token.contains("://") else { return false } let executable = token.split(separator: "/").last.map(String.init) ?? token return executable.lowercased() == "curl" } private static func containsCurlExecutableText(_ text: String) -> Bool { if self.shellTokens(text).contains(where: self.isCurlExecutableToken) { return true } let pattern = #"(^|[\s;])(?:[^\s;=]+/)?curl($|[\s;])"# return text.range(of: pattern, options: [.regularExpression, .caseInsensitive]) != nil } private static func isShellAssignment(_ token: String) -> Bool { guard !token.contains(";") else { return false } guard let equals = token.firstIndex(of: "="), equals != token.startIndex else { return false } let name = token[.. Bool { guard !token.isEmpty else { return false } return token.allSatisfy { character in character.isASCII && character.isLetter } } private static func explicitCurlURLTargets( _ tokens: [String], after curlIndex: Array.Index) -> [(index: Array.Index, site: QoderWebSite)]? { var targets: [(index: Array.Index, site: QoderWebSite)] = [] var index = tokens.index(after: curlIndex) while index < tokens.endIndex { let token = tokens[index] let lowercased = token.lowercased() if lowercased == "--url" { let valueIndex = tokens.index(after: index) guard valueIndex < tokens.endIndex, let site = self.site(forURLText: tokens[valueIndex]) else { return nil } targets.append((valueIndex, site)) index = tokens.index(after: valueIndex) continue } if lowercased.hasPrefix("--url=") { guard let site = self.site(forURLText: String(token.dropFirst("--url=".count))) else { return nil } targets.append((index, site)) } index = tokens.index(after: index) } return targets } private static func urlTokenTargets( _ tokens: [String], after curlIndex: Array.Index) -> [(index: Array.Index, site: QoderWebSite)]? { var targets: [(index: Array.Index, site: QoderWebSite)] = [] var index = tokens.index(after: curlIndex) while index < tokens.endIndex { if let site = self.site(forURLText: tokens[index]) { targets.append((index, site)) } else if self.host(forURLText: tokens[index]) != nil { return nil } index = tokens.index(after: index) } return targets } private static func curlHeaderHostSites( _ tokens: [String], after curlIndex: Array.Index) -> [QoderWebSite]? { var sites: [QoderWebSite] = [] var index = tokens.index(after: curlIndex) while index < tokens.endIndex { let token = tokens[index] let lowercased = token.lowercased() let headerValue: String? if lowercased == "--config" || lowercased.hasPrefix("--config=") || lowercased.hasPrefix("--expand-") || lowercased == "--location-trusted" || self.shortCurlOptions(token, contain: "K") { return nil } if lowercased == "--header" { let valueIndex = tokens.index(after: index) guard valueIndex < tokens.endIndex else { return nil } headerValue = tokens[valueIndex] index = tokens.index(after: valueIndex) } else if lowercased.hasPrefix("--header=") { headerValue = String(token.dropFirst("--header=".count)) index = tokens.index(after: index) } else if let shortHeaderValue = self.shortCurlHeaderValue(token) { switch shortHeaderValue { case let .attached(value): headerValue = value index = tokens.index(after: index) case .nextToken: let valueIndex = tokens.index(after: index) guard valueIndex < tokens.endIndex else { return nil } headerValue = tokens[valueIndex] index = tokens.index(after: valueIndex) case .invalid: return nil } } else { index = tokens.index(after: index) continue } guard let headerValue else { continue } switch self.inspectCurlHeaderHost(headerValue) { case .ignored: continue case let .site(site): sites.append(site) case .invalid: return nil } } return sites } private enum ShortCurlHeaderValue { case attached(String) case nextToken case invalid } private static func shortCurlOptions(_ token: String, contain option: Character) -> Bool { guard token.hasPrefix("-"), !token.hasPrefix("--") else { return false } return token.dropFirst().contains(option) } private static func shortCurlHeaderValue(_ token: String) -> ShortCurlHeaderValue? { guard token.hasPrefix("-"), !token.hasPrefix("--") else { return nil } let options = String(token.dropFirst()) guard let headerOption = options.firstIndex(of: "H") else { return nil } let safeBundledFlags = Set("fsSL") let bundledFlags = options[.. CurlHeaderHostInspection { let trimmed = headerValue.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty, !trimmed.hasPrefix("@") else { return .invalid } let pieces = trimmed.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false) guard pieces.count == 2 else { let lowercased = trimmed.lowercased() if lowercased == "host" || lowercased.hasPrefix("host ") || lowercased.hasPrefix("host\t") || lowercased == "host;" || lowercased.hasPrefix("host;") { return .invalid } return .ignored } let name = pieces[0].trimmingCharacters(in: .whitespacesAndNewlines).lowercased() guard name == "host" else { return .ignored } guard let site = self.site(forHost: String(pieces[1])) else { return .invalid } return .site(site) } private enum ShellQuote: Equatable { case single case double } private static func preprocessedCurlShellText(_ text: String) -> String? { let scalars = Array(text.unicodeScalars) var output = String.UnicodeScalarView() var index = scalars.startIndex var quote: ShellQuote? while index < scalars.endIndex { let scalar = scalars[index] let nextIndex = scalars.index(after: index) if scalar == "\\" { if quote == nil { if nextIndex < scalars.endIndex, scalars[nextIndex] == "\n" { index = scalars.index(after: nextIndex) continue } if nextIndex < scalars.endIndex, scalars[nextIndex] == "\r" { let afterReturn = scalars.index(after: nextIndex) if afterReturn < scalars.endIndex, scalars[afterReturn] == "\n" { index = scalars.index(after: afterReturn) continue } } } if quote != .single, nextIndex < scalars.endIndex, self.isSupportedEscapedShellLiteral(scalars[nextIndex]) { output.append(scalar) output.append(scalars[nextIndex]) index = scalars.index(after: nextIndex) continue } if quote == .double { return nil } } guard self.isShellScalarTextSafe(scalar) else { return nil } switch quote { case .single: if scalar == "'" { quote = nil } case .double: if scalar == "\"" { quote = nil } else if scalar == "`" || self.isUnsupportedShellDollarExpansion(in: scalars, at: index) { return nil } case nil: if self.isUnsupportedShellControlOperator(scalar) { return nil } else if scalar == "'" { quote = .single } else if scalar == "\"" { quote = .double } else if scalar == "`" || self.isUnsupportedShellDollarExpansion(in: scalars, at: index) || self.isProcessSubstitution(in: scalars, at: index) { return nil } } output.append(scalar) index = nextIndex } return quote == nil ? String(output) : nil } private static func isUnsupportedShellControlOperator(_ scalar: UnicodeScalar) -> Bool { ";|&<>".unicodeScalars.contains(scalar) } private static func isSupportedEscapedShellLiteral(_ scalar: UnicodeScalar) -> Bool { scalar == "'" || scalar == "\"" || scalar == "\\" } private static func isShellScalarTextSafe(_ scalar: UnicodeScalar) -> Bool { scalar.value >= 0x20 && scalar.value != 0x7F } private static func isUnsupportedShellDollarExpansion(in scalars: [UnicodeScalar], at index: Int) -> Bool { guard scalars[index] == "$" else { return false } let nextIndex = scalars.index(after: index) guard nextIndex < scalars.endIndex else { return false } let next = scalars[nextIndex] if next == "'" || next == "\"" || next == "(" || next == "{" || next == "[" { return true } if next == "_" || next.properties.isAlphabetic || (48...57).contains(Int(next.value)) { return true } return "*@#?$!-".unicodeScalars.contains(next) } private static func isProcessSubstitution(in scalars: [UnicodeScalar], at index: Int) -> Bool { guard scalars[index] == "<" || scalars[index] == ">" else { return false } let nextIndex = scalars.index(after: index) return nextIndex < scalars.endIndex && scalars[nextIndex] == "(" } private static func isCurlTokenTextSafe(_ token: String) -> Bool { token.unicodeScalars.allSatisfy { scalar in scalar.value >= 0x20 && scalar.value != 0x7F } } private static func hostHeaderSites(_ rawHeader: String) -> [QoderWebSite?] { var sites: [QoderWebSite?] = [] for line in rawHeader.split(whereSeparator: \.isNewline) { let pieces = line.split(separator: ":", maxSplits: 1) guard pieces.count == 2 else { continue } let name = pieces[0].trimmingCharacters(in: .whitespacesAndNewlines).lowercased() guard name == "host" else { continue } sites.append(self.site(forHost: String(pieces[1]))) } return sites } private static func host(forURLText text: String) -> String? { let trimmed = text .trimmingCharacters(in: .whitespacesAndNewlines) .trimmingCharacters(in: CharacterSet(charactersIn: "\"'")) let lowercased = trimmed.lowercased() guard lowercased.hasPrefix("https://") || lowercased.hasPrefix("http://") else { return nil } return URL(string: trimmed)?.host(percentEncoded: false)?.lowercased() } private static func site(forURLText text: String) -> QoderWebSite? { guard let host = self.host(forURLText: text) else { return nil } return self.site(forHost: host) } private static func site(forHost host: String) -> QoderWebSite? { var normalized = String(host .trimmingCharacters(in: .whitespacesAndNewlines) .trimmingCharacters(in: CharacterSet(charactersIn: "\"'")) .lowercased() .trimmingPrefix(".")) if let portSeparator = normalized.lastIndex(of: ":") { let port = normalized[normalized.index(after: portSeparator)...] let hostname = normalized[.. [String] { var tokens: [String] = [] var current = "" var quote: Character? var isEscaped = false for character in text { if isEscaped { current.append(character) isEscaped = false continue } if character == "\\" { isEscaped = true continue } if let activeQuote = quote { if character == activeQuote { quote = nil } else { current.append(character) } continue } if character == "'" || character == "\"" { quote = character continue } if character.isWhitespace { if !current.isEmpty { tokens.append(current) current = "" } continue } current.append(character) } if isEscaped { current.append("\\") } if !current.isEmpty { tokens.append(current) } return tokens } private static func shouldRetryManualCookieHeader(_ rawHeader: String?) -> Bool { ((try? self.sites(forManualCookieHeader: rawHeader).count) ?? 0) > 1 } private static func sourceLabel(browserLabel: String, site: QoderWebSite) -> String { switch site { case .international: "\(browserLabel) / qoder.com" case .china: "\(browserLabel) / qoder.com.cn" } } static func site(for sourceLabel: String) -> QoderWebSite { if sourceLabel == "qoder.com.cn" || sourceLabel.hasSuffix(" / qoder.com.cn") { return .china } if sourceLabel == "qoder.com" || sourceLabel.hasSuffix(" / qoder.com") { return .international } return .international } private static func shouldUseSourceLabel(_ sourceLabel: String, skipping skippedLabels: Set) -> Bool { !skippedLabels.contains(sourceLabel) } func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool { false } } struct QoderResolvedCookie { let cookieHeader: String let sourceLabel: String let isFromCache: Bool init(cookieHeader: String, sourceLabel: String, isFromCache: Bool = false) { self.cookieHeader = cookieHeader self.sourceLabel = sourceLabel self.isFromCache = isFromCache } }