786 lines · 30.4 KB · Swift
RawDownload
import Foundation 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 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],        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.updatedAt = updatedAt    }} 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            }        }         func build(modelName: String) -> ModelBreakdown {            ModelBreakdown(                modelName: modelName,                costUSD: self.sawCost ? self.costUSD : nil,                totalTokens: self.sawTotalTokens ? self.totalTokens : nil,                standardCostUSD: self.sawStandardCost ? self.standardCostUSD : nil,                priorityCostUSD: self.sawPriorityCost ? self.priorityCostUSD : nil,                standardTokens: self.sawStandardTokens ? self.standardTokens : nil,                priorityTokens: self.sawPriorityTokens ? self.priorityTokens : nil)        }    }     private struct EntryAccumulator {        var inputTokens: Int = 0        var sawInputTokens = false        var cacheReadTokens: Int = 0        var sawCacheReadTokens = false        var cacheCreationTokens: Int = 0        var sawCacheCreationTokens = false        var outputTokens: Int = 0        var sawOutputTokens = false        var totalTokens: Int = 0        var sawTotalTokens = false        var derivedTotalTokensWithoutExplicitTotal: Int = 0        var costUSD: Double = 0        var sawCost = false        var modelsUsed: Set<String> = []        var breakdowns: [String: BreakdownAccumulator] = [:]         mutating func add(_ entry: Entry) {            let entryDerivedTotalTokens = (entry.inputTokens ?? 0)                + (entry.cacheReadTokens ?? 0)                + (entry.cacheCreationTokens ?? 0)                + (entry.outputTokens ?? 0)            if let inputTokens = entry.inputTokens {                self.inputTokens += inputTokens                self.sawInputTokens = true            }            if let cacheReadTokens = entry.cacheReadTokens {                self.cacheReadTokens += cacheReadTokens                self.sawCacheReadTokens = true            }            if let cacheCreationTokens = entry.cacheCreationTokens {                self.cacheCreationTokens += cacheCreationTokens                self.sawCacheCreationTokens = true            }            if let outputTokens = entry.outputTokens {                self.outputTokens += outputTokens                self.sawOutputTokens = true            }            if let totalTokens = entry.totalTokens {                self.totalTokens += totalTokens                self.sawTotalTokens = true            } else if entryDerivedTotalTokens > 0 {                self.derivedTotalTokensWithoutExplicitTotal += entryDerivedTotalTokens            }            if let costUSD = entry.costUSD {                self.costUSD += costUSD                self.sawCost = true            }            if let modelsUsed = entry.modelsUsed {                self.modelsUsed.formUnion(modelsUsed)            }            if let modelBreakdowns = entry.modelBreakdowns {                for breakdown in modelBreakdowns {                    var accumulator = self.breakdowns[breakdown.modelName] ?? BreakdownAccumulator()                    accumulator.add(breakdown)                    self.breakdowns[breakdown.modelName] = accumulator                    self.modelsUsed.insert(breakdown.modelName)                }            }        }         func build(date: String) -> Entry {            let derivedTotalTokens = self.inputTokens                + self.cacheReadTokens                + self.cacheCreationTokens                + self.outputTokens            let totalTokens: Int? = if self.sawTotalTokens {                self.totalTokens + self.derivedTotalTokensWithoutExplicitTotal            } else if derivedTotalTokens > 0 {                derivedTotalTokens            } else {                nil            }            let modelBreakdowns: [ModelBreakdown]? = {                guard !self.breakdowns.isEmpty else { return nil }                return CostUsageDailyReport.sortedModelBreakdowns(                    self.breakdowns                        .map { modelName, accumulator in                            accumulator.build(modelName: modelName)                        })            }()            let modelsUsed = self.modelsUsed.isEmpty ? nil : self.modelsUsed.sorted()            return Entry(                date: date,                inputTokens: self.sawInputTokens ? self.inputTokens : nil,                outputTokens: self.sawOutputTokens ? self.outputTokens : nil,                cacheReadTokens: self.sawCacheReadTokens ? self.cacheReadTokens : nil,                cacheCreationTokens: self.sawCacheCreationTokens ? self.cacheCreationTokens : nil,                totalTokens: totalTokens,                costUSD: self.sawCost ? self.costUSD : nil,                modelsUsed: modelsUsed,                modelBreakdowns: modelBreakdowns)        }    }     public func merged(with other: CostUsageDailyReport) -> CostUsageDailyReport {        Self.merged([self, other])    }     public static func merged(_ reports: [CostUsageDailyReport]) -> CostUsageDailyReport {        let entries = self.mergedEntries(from: reports)        guard !entries.isEmpty else { return CostUsageDailyReport(data: [], summary: nil) }        return CostUsageDailyReport(data: entries, summary: self.mergedSummary(from: entries))    }     private static func mergedEntries(from reports: [CostUsageDailyReport]) -> [Entry] {        var dayAccumulators: [String: EntryAccumulator] = [:]        for report in reports {            for entry in report.data {                var accumulator = dayAccumulators[entry.date] ?? EntryAccumulator()                accumulator.add(entry)                dayAccumulators[entry.date] = accumulator            }        }         return dayAccumulators            .keys            .sorted()            .map { date in                dayAccumulators[date, default: EntryAccumulator()].build(date: date)            }    }     private static func mergedSummary(from entries: [Entry]) -> Summary {        var totalInputTokens = 0        var sawTotalInputTokens = false        var totalOutputTokens = 0        var sawTotalOutputTokens = false        var totalCacheReadTokens = 0        var sawTotalCacheReadTokens = false        var totalCacheCreationTokens = 0        var sawTotalCacheCreationTokens = false        var totalTokens = 0        var sawTotalTokens = false        var totalCostUSD = 0.0        var sawTotalCostUSD = false         for entry in entries {            if let inputTokens = entry.inputTokens {                totalInputTokens += inputTokens                sawTotalInputTokens = true            }            if let outputTokens = entry.outputTokens {                totalOutputTokens += outputTokens                sawTotalOutputTokens = true            }            if let cacheReadTokens = entry.cacheReadTokens {                totalCacheReadTokens += cacheReadTokens                sawTotalCacheReadTokens = true            }            if let cacheCreationTokens = entry.cacheCreationTokens {                totalCacheCreationTokens += cacheCreationTokens

Lines 1–500 of 786.