1074 lines · 41.3 KB · Swift
RawDownload
import Foundation public struct CostUsageWindowSummary: Sendable, Equatable {    public let days: Int    public let totalTokens: Int?    public let totalCostUSD: Double?    public let totalRequests: Int?    public let entryCount: Int     public init(        days: Int,        totalTokens: Int?,        totalCostUSD: Double?,        totalRequests: Int?,        entryCount: Int)    {        self.days = days        self.totalTokens = totalTokens        self.totalCostUSD = totalCostUSD        self.totalRequests = totalRequests        self.entryCount = entryCount    }} /// An estimated local Codex conversation total derived from one session log./// This is intentionally distinct from account-level billing or quota data.public struct CostUsageSessionBreakdown: Sendable, Equatable, Identifiable {    public let sessionID: String    public let lastActivity: Date    public let inputTokens: Int?    public let cachedInputTokens: Int?    public let outputTokens: Int?    public let totalTokens: Int?    public let requestCount: Int?    public let costUSD: Double?    public let modelBreakdowns: [CostUsageDailyReport.ModelBreakdown]     public var id: String {        self.sessionID    }     public init(        sessionID: String,        lastActivity: Date,        inputTokens: Int?,        cachedInputTokens: Int?,        outputTokens: Int?,        totalTokens: Int?,        requestCount: Int?,        costUSD: Double?,        modelBreakdowns: [CostUsageDailyReport.ModelBreakdown])    {        self.sessionID = sessionID        self.lastActivity = lastActivity        self.inputTokens = inputTokens        self.cachedInputTokens = cachedInputTokens        self.outputTokens = outputTokens        self.totalTokens = totalTokens        self.requestCount = requestCount        self.costUSD = costUSD        self.modelBreakdowns = modelBreakdowns    }} public struct CostUsageTokenSnapshot: Sendable, Equatable {    public let sessionTokens: Int?    public let sessionCostUSD: Double?    public let sessionRequests: Int?    public let last30DaysTokens: Int?    public let last30DaysCostUSD: Double?    public let last30DaysRequests: Int?    public let currencyCode: String    public let historyDays: Int    public let historyCoverageIsEstablished: Bool    public let historyLabel: String?    /// Provider-metered spend over the same window as `last30DaysCostUSD` — what the plan    /// actually deducts, as opposed to the API-rate estimate. Only some providers (e.g. Cursor)    /// report this; `nil` when unknown.    public let meteredCostUSD: Double?    /// Internal credential scope used to prevent cross-account cache publication. This is a    /// non-reversible fingerprint, not account identity, and is not emitted by CLI payloads.    public let credentialScopeFingerprint: String?    public let daily: [CostUsageDailyReport.Entry]    public let projects: [CostUsageProjectBreakdown]    public let sessions: [CostUsageSessionBreakdown]    public let updatedAt: Date     public init(        sessionTokens: Int?,        sessionCostUSD: Double?,        sessionRequests: Int? = nil,        last30DaysTokens: Int?,        last30DaysCostUSD: Double?,        last30DaysRequests: Int? = nil,        currencyCode: String = "USD",        historyDays: Int = 30,        historyCoverageIsEstablished: Bool = true,        historyLabel: String? = nil,        meteredCostUSD: Double? = nil,        credentialScopeFingerprint: String? = nil,        daily: [CostUsageDailyReport.Entry],        projects: [CostUsageProjectBreakdown] = [],        sessions: [CostUsageSessionBreakdown] = [],        updatedAt: Date)    {        self.sessionTokens = sessionTokens        self.sessionCostUSD = sessionCostUSD        self.sessionRequests = sessionRequests        self.last30DaysTokens = last30DaysTokens        self.last30DaysCostUSD = last30DaysCostUSD        self.last30DaysRequests = last30DaysRequests        let normalizedCurrencyCode = currencyCode.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()        self.currencyCode = normalizedCurrencyCode.isEmpty ? "XXX" : normalizedCurrencyCode        self.historyDays = historyDays        self.historyCoverageIsEstablished = historyCoverageIsEstablished        self.historyLabel = historyLabel        self.meteredCostUSD = meteredCostUSD        self.credentialScopeFingerprint = credentialScopeFingerprint        self.daily = daily        self.projects = projects        self.sessions = sessions        self.updatedAt = updatedAt    }     public func currentDayEntry(calendar: Calendar = .current) -> CostUsageDailyReport.Entry? {        Self.entry(in: self.daily, forLocalDayContaining: self.updatedAt, calendar: calendar)    }     public func summary(forLastDays requestedDays: Int, calendar: Calendar = .current) -> CostUsageWindowSummary {        let days = max(1, requestedDays)        let today = calendar.startOfDay(for: self.updatedAt)        let start = calendar.date(byAdding: .day, value: -(days - 1), to: today) ?? today        let startKey = CostUsageLocalDay.key(from: start, calendar: calendar)        let endKey = CostUsageLocalDay.key(from: today, calendar: calendar)        let entries = self.daily.filter { entry in            guard let dayKey = Self.localDayKey(for: entry.date, calendar: calendar) else { return false }            return dayKey >= startKey && dayKey <= endKey        }        let costs = entries.compactMap(\.costUSD)        let tokens = entries.compactMap(\.totalTokens)        let requests = entries.compactMap(\.requestCount)        return CostUsageWindowSummary(            days: days,            totalTokens: tokens.isEmpty ? nil : tokens.reduce(0, +),            totalCostUSD: costs.isEmpty ? nil : costs.reduce(0, +),            totalRequests: requests.isEmpty ? nil : requests.reduce(0, +),            entryCount: entries.count)    }     public func comparisonSummaries(        periods: [Int] = [7, 30, 90],        calendar: Calendar = .current) -> [CostUsageWindowSummary]    {        Array(Set(periods.map { max(1, $0) }))            .filter { $0 < self.historyDays }            .sorted()            .map { self.summary(forLastDays: $0, calendar: calendar) }    }     public static func latestEntry(in entries: [CostUsageDailyReport.Entry]) -> CostUsageDailyReport.Entry? {        entries.compactMap { entry -> (entry: CostUsageDailyReport.Entry, date: Date)? in            guard let date = CostUsageDateParser.parse(entry.date) else { return nil }            return (entry, date)        }        .max { lhs, rhs in            if lhs.date != rhs.date {                return lhs.date < rhs.date            }            let lCost = lhs.entry.costUSD ?? -1            let rCost = rhs.entry.costUSD ?? -1            if lCost != rCost {                return lCost < rCost            }            let lTokens = lhs.entry.totalTokens ?? -1            let rTokens = rhs.entry.totalTokens ?? -1            if lTokens != rTokens {                return lTokens < rTokens            }            return lhs.entry.date < rhs.entry.date        }?.entry    }     public static func entry(        in entries: [CostUsageDailyReport.Entry],        forLocalDayContaining date: Date,        calendar: Calendar = .current) -> CostUsageDailyReport.Entry?    {        let dayKey = CostUsageLocalDay.key(from: date, calendar: calendar)        return entries.first { entry in            let rawDate = entry.date.trimmingCharacters(in: .whitespacesAndNewlines)            if rawDate == dayKey {                return true            }            guard let parsed = CostUsageDateParser.parse(rawDate) else { return false }            return CostUsageLocalDay.key(from: parsed, calendar: calendar) == dayKey        }    }     private static func localDayKey(for rawDate: String, calendar: Calendar) -> String? {        let trimmed = rawDate.trimmingCharacters(in: .whitespacesAndNewlines)        if trimmed.count >= 10 {            let prefix = String(trimmed.prefix(10))            if prefix.count == 10, prefix[prefix.index(prefix.startIndex, offsetBy: 4)] == "-",               prefix[prefix.index(prefix.startIndex, offsetBy: 7)] == "-"            {                return prefix            }        }        guard let parsed = CostUsageDateParser.parse(trimmed) else { return nil }        return CostUsageLocalDay.key(from: parsed, calendar: calendar)    }} public struct CostUsageProjectBreakdown: Sendable, Equatable {    public static let unknownProjectName = "Unknown project"     public let name: String    public let path: String?    public let totalTokens: Int?    public let totalCostUSD: Double?    public let daily: [CostUsageDailyReport.Entry]    public let modelBreakdowns: [CostUsageDailyReport.ModelBreakdown]?    public let sources: [CostUsageProjectSourceBreakdown]     public init(        name: String,        path: String?,        totalTokens: Int?,        totalCostUSD: Double?,        daily: [CostUsageDailyReport.Entry],        modelBreakdowns: [CostUsageDailyReport.ModelBreakdown]?,        sources: [CostUsageProjectSourceBreakdown] = [])    {        self.name = name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty            ? Self.unknownProjectName            : name        let cleanPath = path?.trimmingCharacters(in: .whitespacesAndNewlines)        self.path = cleanPath?.isEmpty == true ? nil : cleanPath        self.totalTokens = totalTokens        self.totalCostUSD = totalCostUSD        self.daily = daily        self.modelBreakdowns = modelBreakdowns        self.sources = sources    }} public struct CostUsageProjectSourceBreakdown: Sendable, Equatable {    public let name: String    public let path: String?    public let totalTokens: Int?    public let totalCostUSD: Double?    public let daily: [CostUsageDailyReport.Entry]    public let modelBreakdowns: [CostUsageDailyReport.ModelBreakdown]?     public init(        name: String,        path: String?,        totalTokens: Int?,        totalCostUSD: Double?,        daily: [CostUsageDailyReport.Entry],        modelBreakdowns: [CostUsageDailyReport.ModelBreakdown]?)    {        self.name = name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty            ? CostUsageProjectBreakdown.unknownProjectName            : name        let cleanPath = path?.trimmingCharacters(in: .whitespacesAndNewlines)        self.path = cleanPath?.isEmpty == true ? nil : cleanPath        self.totalTokens = totalTokens        self.totalCostUSD = totalCostUSD        self.daily = daily        self.modelBreakdowns = modelBreakdowns    }} public struct CostUsageDailyReport: Sendable, Decodable {    public struct ModelBreakdown: Sendable, Decodable, Equatable {        public let modelName: String        public let costUSD: Double?        public let totalTokens: Int?        public let requestCount: Int?        public let standardCostUSD: Double?        public let priorityCostUSD: Double?        public let standardTokens: Int?        public let priorityTokens: Int?         private enum CodingKeys: String, CodingKey {            case modelName            case costUSD            case cost            case totalTokens            case requestCount            case requests            case standardCostUSD            case priorityCostUSD            case standardTokens            case priorityTokens        }         public init(from decoder: Decoder) throws {            let container = try decoder.container(keyedBy: CodingKeys.self)            self.modelName = try container.decode(String.self, forKey: .modelName)            self.costUSD =                try container.decodeIfPresent(Double.self, forKey: .costUSD)                ?? container.decodeIfPresent(Double.self, forKey: .cost)            self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens)            self.requestCount =                try container.decodeIfPresent(Int.self, forKey: .requestCount)                ?? container.decodeIfPresent(Int.self, forKey: .requests)            self.standardCostUSD = try container.decodeIfPresent(Double.self, forKey: .standardCostUSD)            self.priorityCostUSD = try container.decodeIfPresent(Double.self, forKey: .priorityCostUSD)            self.standardTokens = try container.decodeIfPresent(Int.self, forKey: .standardTokens)            self.priorityTokens = try container.decodeIfPresent(Int.self, forKey: .priorityTokens)        }         public init(            modelName: String,            costUSD: Double?,            totalTokens: Int? = nil,            requestCount: Int? = nil,            standardCostUSD: Double? = nil,            priorityCostUSD: Double? = nil,            standardTokens: Int? = nil,            priorityTokens: Int? = nil)        {            self.modelName = modelName            self.costUSD = costUSD            self.totalTokens = totalTokens            self.requestCount = requestCount            self.standardCostUSD = standardCostUSD            self.priorityCostUSD = priorityCostUSD            self.standardTokens = standardTokens            self.priorityTokens = priorityTokens        }    }     public struct Entry: Sendable, Decodable, Equatable {        public let date: String        public let inputTokens: Int?        public let cacheReadTokens: Int?        public let cacheCreationTokens: Int?        public let outputTokens: Int?        public let totalTokens: Int?        public let requestCount: Int?        public let costUSD: Double?        public let modelsUsed: [String]?        public let modelBreakdowns: [ModelBreakdown]?         private enum CodingKeys: String, CodingKey {            case date            case inputTokens            case cacheReadTokens            case cacheCreationTokens            case cacheReadInputTokens            case cacheCreationInputTokens            case outputTokens            case totalTokens            case requestCount            case requests            case costUSD            case totalCost            case modelsUsed            case models            case modelBreakdowns        }         public init(from decoder: Decoder) throws {            let container = try decoder.container(keyedBy: CodingKeys.self)            self.date = try container.decode(String.self, forKey: .date)            self.inputTokens = try container.decodeIfPresent(Int.self, forKey: .inputTokens)            self.cacheReadTokens =                try container.decodeIfPresent(Int.self, forKey: .cacheReadTokens)                ?? container.decodeIfPresent(Int.self, forKey: .cacheReadInputTokens)            self.cacheCreationTokens =                try container.decodeIfPresent(Int.self, forKey: .cacheCreationTokens)                ?? container.decodeIfPresent(Int.self, forKey: .cacheCreationInputTokens)            self.outputTokens = try container.decodeIfPresent(Int.self, forKey: .outputTokens)            self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens)            self.requestCount =                try container.decodeIfPresent(Int.self, forKey: .requestCount)                ?? container.decodeIfPresent(Int.self, forKey: .requests)            self.costUSD =                try container.decodeIfPresent(Double.self, forKey: .costUSD)                ?? container.decodeIfPresent(Double.self, forKey: .totalCost)            self.modelsUsed = Self.decodeModelsUsed(from: container)            self.modelBreakdowns = try container.decodeIfPresent([ModelBreakdown].self, forKey: .modelBreakdowns)        }         public init(            date: String,            inputTokens: Int?,            outputTokens: Int?,            cacheReadTokens: Int? = nil,            cacheCreationTokens: Int? = nil,            totalTokens: Int?,            requestCount: Int? = nil,            costUSD: Double?,            modelsUsed: [String]?,            modelBreakdowns: [ModelBreakdown]?)        {            self.date = date            self.inputTokens = inputTokens            self.outputTokens = outputTokens            self.cacheReadTokens = cacheReadTokens            self.cacheCreationTokens = cacheCreationTokens            self.totalTokens = totalTokens            self.requestCount = requestCount            self.costUSD = costUSD            self.modelsUsed = modelsUsed            self.modelBreakdowns = modelBreakdowns        }         private static func decodeModelsUsed(from container: KeyedDecodingContainer<CodingKeys>) -> [String]? {            func decodeStringList(_ key: CodingKeys) -> [String]? {                (try? container.decodeIfPresent([String].self, forKey: key)).flatMap(\.self)            }             if let modelsUsed = decodeStringList(.modelsUsed) {                return modelsUsed            }            if let models = decodeStringList(.models) {                return models            }             guard container.contains(.models) else { return nil }             guard let modelMap = try? container.nestedContainer(keyedBy: CostUsageAnyCodingKey.self, forKey: .models)            else { return nil }             let modelNames = modelMap.allKeys.map(\.stringValue).sorted()            return modelNames.isEmpty ? nil : modelNames        }    }     public struct Summary: Sendable, Decodable, Equatable {        public let totalInputTokens: Int?        public let totalOutputTokens: Int?        public let cacheReadTokens: Int?        public let cacheCreationTokens: Int?        public let totalTokens: Int?        public let totalCostUSD: Double?         private enum CodingKeys: String, CodingKey {            case totalInputTokens            case totalOutputTokens            case cacheReadTokens            case cacheCreationTokens            case totalCacheReadTokens            case totalCacheCreationTokens            case totalTokens            case totalCostUSD            case totalCost        }         public init(            totalInputTokens: Int?,            totalOutputTokens: Int?,            cacheReadTokens: Int? = nil,            cacheCreationTokens: Int? = nil,            totalTokens: Int?,            totalCostUSD: Double?)        {            self.totalInputTokens = totalInputTokens            self.totalOutputTokens = totalOutputTokens            self.cacheReadTokens = cacheReadTokens            self.cacheCreationTokens = cacheCreationTokens            self.totalTokens = totalTokens            self.totalCostUSD = totalCostUSD        }         public init(from decoder: Decoder) throws {            let container = try decoder.container(keyedBy: CodingKeys.self)            self.totalInputTokens = try container.decodeIfPresent(Int.self, forKey: .totalInputTokens)            self.totalOutputTokens = try container.decodeIfPresent(Int.self, forKey: .totalOutputTokens)            self.cacheReadTokens =                try container.decodeIfPresent(Int.self, forKey: .cacheReadTokens)                ?? container.decodeIfPresent(Int.self, forKey: .totalCacheReadTokens)            self.cacheCreationTokens =                try container.decodeIfPresent(Int.self, forKey: .cacheCreationTokens)                ?? container.decodeIfPresent(Int.self, forKey: .totalCacheCreationTokens)            self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens)            self.totalCostUSD =                try container.decodeIfPresent(Double.self, forKey: .totalCostUSD)                ?? container.decodeIfPresent(Double.self, forKey: .totalCost)        }    }     public let data: [Entry]    public let summary: Summary?     private enum CodingKeys: String, CodingKey {        case type        case data        case summary        case daily        case totals    }     public init(from decoder: Decoder) throws {        let container = try decoder.container(keyedBy: CodingKeys.self) 

Lines 1–500 of 1074.