Files
2026-07-13 12:22:33 +08:00

188 lines
5.7 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}