import CodexBarCore import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif struct ProviderPayload: Encodable { let provider: String let account: String? let cacheAccountKey: String? let version: String? let source: String let status: ProviderStatusPayload? let usage: UsageSnapshot? let credits: CreditsSnapshot? let antigravityPlanInfo: AntigravityPlanInfoSummary? let openaiDashboard: OpenAIDashboardSnapshot? let error: ProviderErrorPayload? let pace: ProviderPacePayload? private enum CodingKeys: String, CodingKey { case provider case account case version case source case status case usage case credits case antigravityPlanInfo case openaiDashboard case error case pace } init( provider: UsageProvider, account: String?, cacheAccountKey: String? = nil, version: String?, source: String, status: ProviderStatusPayload?, usage: UsageSnapshot?, credits: CreditsSnapshot?, antigravityPlanInfo: AntigravityPlanInfoSummary?, openaiDashboard: OpenAIDashboardSnapshot?, error: ProviderErrorPayload?, pace: ProviderPacePayload? = nil) { self.provider = provider.rawValue self.account = account self.cacheAccountKey = cacheAccountKey self.version = version self.source = source self.status = status self.usage = usage self.credits = credits self.antigravityPlanInfo = antigravityPlanInfo self.openaiDashboard = openaiDashboard self.error = error self.pace = pace } init( providerID: String, account: String?, cacheAccountKey: String? = nil, version: String?, source: String, status: ProviderStatusPayload?, usage: UsageSnapshot?, credits: CreditsSnapshot?, antigravityPlanInfo: AntigravityPlanInfoSummary?, openaiDashboard: OpenAIDashboardSnapshot?, error: ProviderErrorPayload?, pace: ProviderPacePayload? = nil) { self.provider = providerID self.account = account self.cacheAccountKey = cacheAccountKey self.version = version self.source = source self.status = status self.usage = usage self.credits = credits self.antigravityPlanInfo = antigravityPlanInfo self.openaiDashboard = openaiDashboard self.error = error self.pace = pace } } struct ProviderPacePayload: Encodable { let primary: PacePayload? let secondary: PacePayload? } struct PacePayload: Encodable { let stage: String /// Rounded (used − expected); positive = deficit, negative = reserve. let deltaPercent: Double let expectedUsedPercent: Double let willLastToReset: Bool let etaSeconds: TimeInterval? /// Always absent in CLI output; kept for schema parity. let runOutProbability: Double? let summary: String } struct ProviderStatusPayload: Encodable { let indicator: ProviderStatusIndicator let description: String? let updatedAt: Date? let url: String enum ProviderStatusIndicator: String, Encodable { case none case minor case major case critical case maintenance case unknown var label: String { switch self { case .none: "Operational" case .minor: "Partial outage" case .major: "Major outage" case .critical: "Critical issue" case .maintenance: "Maintenance" case .unknown: "Status unknown" } } } var descriptionSuffix: String { guard let description, !description.isEmpty else { return "" } return " – \(description)" } } enum StatusFetcher { static func fetch(from baseURL: URL) async throws -> ProviderStatusPayload { let apiURL = baseURL.appendingPathComponent("api/v2/status.json") var request = URLRequest(url: apiURL) request.timeoutInterval = 10 let (data, _) = try await URLSession.shared.data(for: request) struct Response: Decodable { struct Status: Decodable { let indicator: String let description: String? } struct Page: Decodable { let updatedAt: Date? private enum CodingKeys: String, CodingKey { case updatedAt = "updated_at" } } let page: Page? let status: Status } let decoder = JSONDecoder() decoder.dateDecodingStrategy = .custom { decoder in let container = try decoder.singleValueContainer() let raw = try container.decode(String.self) let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] if let date = formatter.date(from: raw) { return date } formatter.formatOptions = [.withInternetDateTime] if let date = formatter.date(from: raw) { return date } throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid ISO8601 date") } let response = try decoder.decode(Response.self, from: data) let indicator = ProviderStatusPayload.ProviderStatusIndicator(rawValue: response.status.indicator) ?? .unknown return ProviderStatusPayload( indicator: indicator, description: response.status.description, updatedAt: response.page?.updatedAt, url: baseURL.absoluteString) } }