1000 lines · 38.8 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    }} 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 historyLabel: String?    public let daily: [CostUsageDailyReport.Entry]    public let projects: [CostUsageProjectBreakdown]    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,        historyLabel: String? = nil,        daily: [CostUsageDailyReport.Entry],        projects: [CostUsageProjectBreakdown] = [],        updatedAt: Date)    {        self.sessionTokens = sessionTokens        self.sessionCostUSD = sessionCostUSD        self.sessionRequests = sessionRequests        self.last30DaysTokens = last30DaysTokens        self.last30DaysCostUSD = last30DaysCostUSD        self.last30DaysRequests = last30DaysRequests        self.currencyCode = currencyCode.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty            ? "USD"            : currencyCode.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()        self.historyDays = historyDays        self.historyLabel = historyLabel        self.daily = daily        self.projects = projects        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)         if container.contains(.type) {            _ = try container.decode(String.self, forKey: .type)            self.data = try container.decode([Entry].self, forKey: .data)            self.summary = try container.decodeIfPresent(Summary.self, forKey: .summary)            return        }         self.data = try container.decode([Entry].self, forKey: .daily)        if container.contains(.totals) {            let totals = try container.decode(CostUsageLegacyTotals.self, forKey: .totals)            self.summary = Summary(                totalInputTokens: totals.totalInputTokens,                totalOutputTokens: totals.totalOutputTokens,                cacheReadTokens: totals.cacheReadTokens,                cacheCreationTokens: totals.cacheCreationTokens,                totalTokens: totals.totalTokens,                totalCostUSD: totals.totalCost)        } else {            self.summary = nil        }    }     public init(data: [Entry], summary: Summary?) {        self.data = data        self.summary = summary    }} extension CostUsageDailyReport {    private struct BreakdownAccumulator {        var totalTokens: Int = 0        var sawTotalTokens = false        var costUSD: Double = 0        var sawCost = false        var standardCostUSD: Double = 0        var sawStandardCost = false        var priorityCostUSD: Double = 0        var sawPriorityCost = false        var standardTokens: Int = 0        var sawStandardTokens = false        var priorityTokens: Int = 0        var sawPriorityTokens = false         mutating func add(_ breakdown: ModelBreakdown) {            if let totalTokens = breakdown.totalTokens {                self.totalTokens += totalTokens                self.sawTotalTokens = true            }            if let costUSD = breakdown.costUSD {                self.costUSD += costUSD                self.sawCost = true            }            if let standardCostUSD = breakdown.standardCostUSD {                self.standardCostUSD += standardCostUSD                self.sawStandardCost = true            }            if let priorityCostUSD = breakdown.priorityCostUSD {                self.priorityCostUSD += priorityCostUSD                self.sawPriorityCost = true            }            if let standardTokens = breakdown.standardTokens {                self.standardTokens += standardTokens                self.sawStandardTokens = true            }            if let priorityTokens = breakdown.priorityTokens {                self.priorityTokens += priorityTokens                self.sawPriorityTokens = true            }

Lines 1–500 of 1000.