import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif public struct CrofUsageResponse: Decodable, Sendable { public let credits: Double public let requestsPlan: Double public let usableRequests: Double enum CodingKeys: String, CodingKey { case credits case requestsPlan = "requests_plan" case usableRequests = "usable_requests" } } public enum CrofUsageError: LocalizedError, Sendable, Equatable { case missingCredentials case networkError(String) case apiError(Int) case parseFailed(String) public var errorDescription: String? { switch self { case .missingCredentials: "Missing Crof API key." case let .networkError(message): "Crof network error: \(message)" case let .apiError(statusCode): "Crof API error: HTTP \(statusCode)" case let .parseFailed(message): "Failed to parse Crof response: \(message)" } } } public enum CrofUsageFetcher { public static let usageURL = URL(string: "https://crof.ai/usage_api/")! private static let timeoutSeconds: TimeInterval = 15 public static func fetchUsage( apiKey: String, session transport: any ProviderHTTPTransport = ProviderHTTPClient.shared) async throws -> CrofUsageSnapshot { let trimmed = apiKey.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { throw CrofUsageError.missingCredentials } var request = URLRequest(url: self.usageURL) request.httpMethod = "GET" request.timeoutInterval = Self.timeoutSeconds request.setValue("Bearer \(trimmed)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Accept") let response: ProviderHTTPResponse do { response = try await transport.response(for: request) } catch { throw CrofUsageError.networkError(error.localizedDescription) } guard response.statusCode == 200 else { throw CrofUsageError.apiError(response.statusCode) } return try self.parseSnapshot(response.data) } static func _parseSnapshotForTesting(_ data: Data) throws -> CrofUsageSnapshot { try self.parseSnapshot(data) } private static func parseSnapshot(_ data: Data) throws -> CrofUsageSnapshot { let decoded: CrofUsageResponse do { decoded = try JSONDecoder().decode(CrofUsageResponse.self, from: data) } catch { throw CrofUsageError.parseFailed(error.localizedDescription) } return CrofUsageSnapshot( credits: decoded.credits, requestsPlan: decoded.requestsPlan, usableRequests: decoded.usableRequests, updatedAt: Date()) } }