import Foundation public enum CostUsageError: LocalizedError, Sendable { case unsupportedProvider(UsageProvider) case timedOut(seconds: Int) case cursorPaginationIncomplete(expected: Int?, received: Int) case cursorPaginationInconsistent(expected: Int, received: Int) public var errorDescription: String? { switch self { case let .unsupportedProvider(provider): return "Cost summary is not supported for \(provider.rawValue)." case let .timedOut(seconds): if seconds >= 60, seconds % 60 == 0 { return "Cost refresh timed out after \(seconds / 60)m." } return "Cost refresh timed out after \(seconds)s." case let .cursorPaginationIncomplete(expected, received): if let expected { return "Cursor cost refresh was incomplete (received \(received) of \(expected) events)." } return "Cursor cost refresh reached its pagination safety limit after \(received) events." case let .cursorPaginationInconsistent(expected, received): return "Cursor cost pagination was inconsistent (expected \(expected), received \(received) events)." } }} public struct CostUsageFetcher: Sendable { package struct CachedCodexTokenSnapshotResult: Sendable { package let snapshot: CostUsageTokenSnapshot package let lastRefreshAt: Date? } private let scannerOptions: CostUsageScanner.Options? public init(cacheRoot: URL? = nil) { self.scannerOptions = cacheRoot.map { CostUsageScanner.Options(cacheRoot: $0) } } init(scannerOptions: CostUsageScanner.Options) { self.scannerOptions = scannerOptions } public func loadCachedCodexTokenSnapshot( now: Date = Date(), codexHomePath: String? = nil, historyDays: Int = 30) async -> CostUsageTokenSnapshot? { await Self.loadCachedCodexTokenSnapshot( now: now, codexHomePath: codexHomePath, historyDays: historyDays, scannerOptions: self.scannerOptionsOverride()) } package func loadCachedCodexTokenSnapshotResult( now: Date = Date(), codexHomePath: String? = nil, historyDays: Int = 30) async -> CachedCodexTokenSnapshotResult? { await Self.loadCachedCodexTokenSnapshotResult( now: now, codexHomePath: codexHomePath, historyDays: historyDays, scannerOptions: self.scannerOptionsOverride()) } public func loadTokenSnapshot( provider: UsageProvider, environment: [String: String] = ProcessInfo.processInfo.environment, now: Date = Date(), forceRefresh: Bool = false, allowVertexClaudeFallback: Bool = false, codexHomePath: String? = nil, historyDays: Int = 30, cursorCookieHeaderOverride: String? = nil, allowPricingRefresh: Bool = true, refreshPricingInBackground: Bool = true, includePiSessions: Bool = true) async throws -> CostUsageTokenSnapshot { try await Self.loadTokenSnapshot( provider: provider, environment: environment, now: now, forceRefresh: forceRefresh, allowVertexClaudeFallback: allowVertexClaudeFallback, codexHomePath: codexHomePath, historyDays: historyDays, cursorCookieHeaderOverride: cursorCookieHeaderOverride, allowPricingRefresh: allowPricingRefresh, refreshPricingInBackground: refreshPricingInBackground, includePiSessions: includePiSessions, bypassScannerDebounce: false, scannerOptions: self.scannerOptionsOverride()) } package func loadTokenSnapshot( provider: UsageProvider, environment: [String: String] = ProcessInfo.processInfo.environment, now: Date = Date(), forceRefresh: Bool = false, allowVertexClaudeFallback: Bool = false, codexHomePath: String? = nil, historyDays: Int = 30, cursorCookieHeaderOverride: String? = nil, allowPricingRefresh: Bool = true, refreshPricingInBackground: Bool = true, includePiSessions: Bool = true, bypassScannerDebounce: Bool) async throws -> CostUsageTokenSnapshot { try await Self.loadTokenSnapshot( provider: provider, environment: environment, now: now, forceRefresh: forceRefresh, allowVertexClaudeFallback: allowVertexClaudeFallback, codexHomePath: codexHomePath, historyDays: historyDays, cursorCookieHeaderOverride: cursorCookieHeaderOverride, allowPricingRefresh: allowPricingRefresh, refreshPricingInBackground: refreshPricingInBackground, includePiSessions: includePiSessions, bypassScannerDebounce: bypassScannerDebounce, scannerOptions: self.scannerOptionsOverride()) } @available(*, deprecated, message: "Codex token-cost scans are uncapped; this limit is ignored.") public func loadTokenSnapshot( provider: UsageProvider, environment: [String: String] = ProcessInfo.processInfo.environment, now: Date = Date(), forceRefresh: Bool = false, allowVertexClaudeFallback: Bool = false, codexHomePath: String? = nil, historyDays: Int = 30, allowPricingRefresh: Bool = true, refreshPricingInBackground: Bool = true, automaticCodexScanByteLimit _: Int64?) async throws -> CostUsageTokenSnapshot { try await self.loadTokenSnapshot( provider: provider, environment: environment, now: now, forceRefresh: forceRefresh, allowVertexClaudeFallback: allowVertexClaudeFallback, codexHomePath: codexHomePath, historyDays: historyDays, allowPricingRefresh: allowPricingRefresh, refreshPricingInBackground: refreshPricingInBackground) } private func scannerOptionsOverride() -> CostUsageScanner.Options? { self.scannerOptions } private static func resolvedScannerOptions( _ override: CostUsageScanner.Options?, provider: UsageProvider, codexHomePath: String?) -> CostUsageScanner.Options { var options = override ?? CostUsageScanner.Options() if provider == .codex, let codexHomePath = codexHomePath?.trimmingCharacters(in: .whitespacesAndNewlines), !codexHomePath.isEmpty { options.codexSessionsRoot = URL(fileURLWithPath: codexHomePath, isDirectory: true) .appendingPathComponent("sessions", isDirectory: true) } return options } static func loadTokenSnapshot( provider: UsageProvider, environment: [String: String] = ProcessInfo.processInfo.environment, now: Date = Date(), forceRefresh: Bool = false, allowVertexClaudeFallback: Bool = false, codexHomePath: String? = nil, historyDays: Int = 30, cursorCookieHeaderOverride: String? = nil, allowPricingRefresh: Bool = true, refreshPricingInBackground: Bool = true, includePiSessions: Bool = true, bypassScannerDebounce: Bool = false, scannerOptions overrideScannerOptions: CostUsageScanner.Options? = nil, piScannerOptions overridePiScannerOptions: PiSessionCostScanner .Options? = nil, modelsDevClient: ModelsDevClient = ModelsDevClient(), retryUnknownPricing: Bool = true) async throws -> CostUsageTokenSnapshot { guard self.supportsTokenSnapshot(provider) else { throw CostUsageError.unsupportedProvider(provider) } let until = now let clampedHistoryDays = max(1, min(365, historyDays)) // Rolling window is inclusive, so a 30-day display starts 29 days before `now`. let since = Calendar.current.date(byAdding: .day, value: -(clampedHistoryDays - 1), to: now) ?? now if let remoteSnapshot = try await self.loadRemoteTokenSnapshot( provider: provider, environment: environment, now: now, historyDays: clampedHistoryDays, cursorCookieHeaderOverride: cursorCookieHeaderOverride) { return remoteSnapshot } var options = Self.resolvedScannerOptions( overrideScannerOptions, provider: provider, codexHomePath: codexHomePath) let scopedCodexHomePath = codexHomePath?.trimmingCharacters(in: .whitespacesAndNewlines) let shouldMergePiUsage = provider != .codex || scopedCodexHomePath?.isEmpty != false await Self.refreshPricingIfAllowed( options: PricingRefreshOptions( provider: provider, isAllowed: allowPricingRefresh, retryUnknown: retryUnknownPricing, inBackground: refreshPricingInBackground), now: now, cacheRoot: options.cacheRoot, client: modelsDevClient) if provider == .vertexai { options.claudeLogProviderFilter = allowVertexClaudeFallback ? .all : .vertexAIOnly } else if provider == .claude { options.claudeLogProviderFilter = .excludeVertexAI } if forceRefresh || bypassScannerDebounce { options.refreshMinIntervalSeconds = 0 } var resolvedPiOptions = overridePiScannerOptions ?? PiSessionCostScanner.Options() if resolvedPiOptions.cacheRoot == nil { resolvedPiOptions.cacheRoot = options.cacheRoot } if forceRefresh || bypassScannerDebounce { resolvedPiOptions.refreshMinIntervalSeconds = 0 } let piOptions = resolvedPiOptions try Task.checkCancellation() // The corpus scans below are synchronous and can run for minutes on large session // archives. They execute on the dedicated scan queue so they never occupy a cooperative // pool thread; CostUsageScanExecutor bridges this task's cancellation into the // scanner-level checks. let scanOptions = options let scanResult = try await CostUsageScanExecutor.run { checkCancellation in var daily = try CostUsageScanner.loadDailyReportCancellable( provider: provider, since: since, until: until, now: now, options: scanOptions, checkCancellation: checkCancellation) try checkCancellation() if provider == .vertexai, !allowVertexClaudeFallback, scanOptions.claudeLogProviderFilter == .vertexAIOnly, daily.data.isEmpty { var fallback = scanOptions fallback.claudeLogProviderFilter = .all daily = try CostUsageScanner.loadDailyReportCancellable( provider: provider, since: since, until: until, now: now, options: fallback, checkCancellation: checkCancellation) try checkCancellation() } var projects: [CostUsageProjectBreakdown] = [] var sessions: [CostUsageSessionBreakdown] = [] var piDaily: CostUsageDailyReport? if provider == .codex { let roots = CostUsageScanner.codexSessionsRoots(options: scanOptions) let cache = CostUsageScanner.codexCache( CostUsageCacheIO.load(provider: .codex, cacheRoot: scanOptions.cacheRoot), scopedTo: roots) let range = CostUsageScanner.CostUsageDayRange(since: since, until: until) projects = CostUsageScanner.buildCodexProjectBreakdownsFromCache( cache: cache, range: range, modelsDevCacheRoot: scanOptions.cacheRoot) sessions = CostUsageScanner.buildCodexSessionBreakdownsFromCache( cache: cache, range: range, modelsDevCacheRoot: scanOptions.cacheRoot, sessionRoots: roots) } if includePiSessions, provider == .claude || (provider == .codex && shouldMergePiUsage) { let piReport = try PiSessionCostScanner.loadDailyReportCancellable( provider: provider, since: since, until: until, now: now, options: piOptions, checkCancellation: checkCancellation) try checkCancellation() if provider == .codex { piDaily = piReport } daily = CostUsageDailyReport.merged([daily, piReport]) } if provider == .codex { projects = Self.mergedProjectBreakdowns( projects + [piDaily.flatMap(Self.unknownProjectBreakdown(from:))].compactMap(\.self)) if piDaily?.data.isEmpty == false { sessions = [] } } return (daily: daily, projects: projects, sessions: sessions) } if allowPricingRefresh, retryUnknownPricing, let request = Self.unknownPricingRefreshRequest( provider: provider, daily: scanResult.daily, now: now, cacheRoot: options.cacheRoot, client: modelsDevClient), await Self.refreshUnknownPricingIfNeeded(request, inBackground: refreshPricingInBackground) { return try await self.loadTokenSnapshot( provider: provider, environment: environment, now: now, forceRefresh: forceRefresh, allowVertexClaudeFallback: allowVertexClaudeFallback, codexHomePath: codexHomePath, historyDays: historyDays, cursorCookieHeaderOverride: cursorCookieHeaderOverride, allowPricingRefresh: allowPricingRefresh, refreshPricingInBackground: false, includePiSessions: includePiSessions, scannerOptions: options, piScannerOptions: piOptions, modelsDevClient: modelsDevClient, retryUnknownPricing: false) } return Self.tokenSnapshot( from: scanResult.daily, now: now, historyDays: clampedHistoryDays, projects: scanResult.projects, sessions: scanResult.sessions) } private struct PricingRefreshOptions: Sendable { let provider: UsageProvider let isAllowed: Bool let retryUnknown: Bool let inBackground: Bool } private static func refreshPricingIfAllowed( options: PricingRefreshOptions, now: Date, cacheRoot: URL?, client: ModelsDevClient) async { guard options.isAllowed, options.retryUnknown, options.provider == .codex || options.provider == .claude else { return } if options.inBackground { Task.detached(priority: .utility) { await ModelsDevPricingPipeline.refreshIfNeeded(now: now, cacheRoot: cacheRoot, client: client) } } else { await ModelsDevPricingPipeline.refreshIfNeeded(now: now, cacheRoot: cacheRoot, client: client) } } private struct UnknownPricingRefreshRequest: Sendable { let providerID: String let modelIDs: Set<String> let now: Date let cacheRoot: URL? let client: ModelsDevClient } private static func unknownPricingRefreshRequest( provider: UsageProvider, daily: CostUsageDailyReport, now: Date, cacheRoot: URL?, client: ModelsDevClient) -> UnknownPricingRefreshRequest? { guard provider == .codex || provider == .claude else { return nil } let unknownModelIDs = Set(daily.data.flatMap { entry in entry.modelBreakdowns?.compactMap { breakdown -> String? in guard breakdown.costUSD == nil else { return nil } if provider == .codex, CostUsagePricing.isCodexUnattributedModel(breakdown.modelName) { return nil } return breakdown.modelName } ?? [] }) guard !unknownModelIDs.isEmpty else { return nil } return UnknownPricingRefreshRequest( providerID: provider == .codex ? "openai" : "anthropic", modelIDs: unknownModelIDs, now: now, cacheRoot: cacheRoot, client: client) } private static func refreshUnknownPricingIfNeeded( _ request: UnknownPricingRefreshRequest, inBackground: Bool) async -> Bool { if inBackground { Task.detached(priority: .utility) { _ = await ModelsDevPricingPipeline.refreshForUnknownModelsIfNeeded( providerID: request.providerID, modelIDs: request.modelIDs, now: request.now, cacheRoot: request.cacheRoot, client: request.client) } return false } return await ModelsDevPricingPipeline.refreshForUnknownModelsIfNeeded( providerID: request.providerID, modelIDs: request.modelIDs, now: request.now, cacheRoot: request.cacheRoot, client: request.client) == .pricingAvailable } static func loadCachedCodexTokenSnapshot( now: Date = Date(), codexHomePath: String? = nil, historyDays: Int = 30, scannerOptions overrideScannerOptions: CostUsageScanner.Options? = nil) async -> CostUsageTokenSnapshot? { await self.loadCachedCodexTokenSnapshotResult( now: now, codexHomePath: codexHomePath, historyDays: historyDays, scannerOptions: overrideScannerOptions)?.snapshot } static func loadCachedCodexTokenSnapshotResult( now: Date = Date(), codexHomePath: String? = nil, historyDays: Int = 30, scannerOptions overrideScannerOptions: CostUsageScanner.Options? = nil) async -> CachedCodexTokenSnapshotResult? { if let codexHomePath = codexHomePath?.trimmingCharacters(in: .whitespacesAndNewlines), !codexHomePath.isEmpty { return nil } // Decoding the persisted scan cache parses multi-megabyte JSON; keep it off the // cooperative pool alongside the scans themselves. let cachedSnapshot: CachedCodexTokenSnapshotResult?? = try? await CostUsageScanExecutor.run { _ in let clampedHistoryDays = max(1, min(365, historyDays)) let until = now let since = Calendar.current.date(byAdding: .day, value: -(clampedHistoryDays - 1), to: now) ?? now let range = CostUsageScanner.CostUsageDayRange(since: since, until: until) let options = overrideScannerOptions ?? CostUsageScanner.Options() let roots = CostUsageScanner.codexSessionsRoots(options: options) let cache = CostUsageScanner.codexCache( CostUsageCacheIO.load(provider: .codex, cacheRoot: options.cacheRoot), scopedTo: roots) var reports: [CostUsageDailyReport] = [] var projects: [CostUsageProjectBreakdown] = [] var sessions: [CostUsageSessionBreakdown] = [] // Raw inputs for the derived result fields below: the native cache's own scan // time, every constituent scan time, and whether a second source joined the merge. var nativeScanAt: Date? var scanTimes: [Date] = [] var piMerged = false if !cache.days.isEmpty, cache.roots == CostUsageScanner.codexRootsFingerprint(options: options), !CostUsageScanner.requestedWindowExpandsCache(range: range, cache: cache) { let daily = CostUsageScanner.buildCodexReportFromCache( cache: cache, range: range, modelsDevCacheRoot: options.cacheRoot) if !daily.data.isEmpty { reports.append(daily) if cache.lastScanUnixMs > 0 {Lines 1–500 of 924. Load more
import Foundation public enum CostUsageError: LocalizedError, Sendable { case unsupportedProvider(UsageProvider) case timedOut(seconds: Int) case cursorPaginationIncomplete(expected: Int?, received: Int) case cursorPaginationInconsistent(expected: Int, received: Int) public var errorDescription: String? { switch self { case let .unsupportedProvider(provider): return "Cost summary is not supported for \(provider.rawValue)." case let .timedOut(seconds): if seconds >= 60, seconds % 60 == 0 { return "Cost refresh timed out after \(seconds / 60)m." } return "Cost refresh timed out after \(seconds)s." case let .cursorPaginationIncomplete(expected, received): if let expected { return "Cursor cost refresh was incomplete (received \(received) of \(expected) events)." } return "Cursor cost refresh reached its pagination safety limit after \(received) events." case let .cursorPaginationInconsistent(expected, received): return "Cursor cost pagination was inconsistent (expected \(expected), received \(received) events)." } } } public struct CostUsageFetcher: Sendable { package struct CachedCodexTokenSnapshotResult: Sendable { package let snapshot: CostUsageTokenSnapshot package let lastRefreshAt: Date? } private let scannerOptions: CostUsageScanner.Options? public init(cacheRoot: URL? = nil) { self.scannerOptions = cacheRoot.map { CostUsageScanner.Options(cacheRoot: $0) } } init(scannerOptions: CostUsageScanner.Options) { self.scannerOptions = scannerOptions } public func loadCachedCodexTokenSnapshot( now: Date = Date(), codexHomePath: String? = nil, historyDays: Int = 30) async -> CostUsageTokenSnapshot? { await Self.loadCachedCodexTokenSnapshot( now: now, codexHomePath: codexHomePath, historyDays: historyDays, scannerOptions: self.scannerOptionsOverride()) } package func loadCachedCodexTokenSnapshotResult( now: Date = Date(), codexHomePath: String? = nil, historyDays: Int = 30) async -> CachedCodexTokenSnapshotResult? { await Self.loadCachedCodexTokenSnapshotResult( now: now, codexHomePath: codexHomePath, historyDays: historyDays, scannerOptions: self.scannerOptionsOverride()) } public func loadTokenSnapshot( provider: UsageProvider, environment: [String: String] = ProcessInfo.processInfo.environment, now: Date = Date(), forceRefresh: Bool = false, allowVertexClaudeFallback: Bool = false, codexHomePath: String? = nil, historyDays: Int = 30, cursorCookieHeaderOverride: String? = nil, allowPricingRefresh: Bool = true, refreshPricingInBackground: Bool = true, includePiSessions: Bool = true) async throws -> CostUsageTokenSnapshot { try await Self.loadTokenSnapshot( provider: provider, environment: environment, now: now, forceRefresh: forceRefresh, allowVertexClaudeFallback: allowVertexClaudeFallback, codexHomePath: codexHomePath, historyDays: historyDays, cursorCookieHeaderOverride: cursorCookieHeaderOverride, allowPricingRefresh: allowPricingRefresh, refreshPricingInBackground: refreshPricingInBackground, includePiSessions: includePiSessions, bypassScannerDebounce: false, scannerOptions: self.scannerOptionsOverride()) } package func loadTokenSnapshot( provider: UsageProvider, environment: [String: String] = ProcessInfo.processInfo.environment, now: Date = Date(), forceRefresh: Bool = false, allowVertexClaudeFallback: Bool = false, codexHomePath: String? = nil, historyDays: Int = 30, cursorCookieHeaderOverride: String? = nil, allowPricingRefresh: Bool = true, refreshPricingInBackground: Bool = true, includePiSessions: Bool = true, bypassScannerDebounce: Bool) async throws -> CostUsageTokenSnapshot { try await Self.loadTokenSnapshot( provider: provider, environment: environment, now: now, forceRefresh: forceRefresh, allowVertexClaudeFallback: allowVertexClaudeFallback, codexHomePath: codexHomePath, historyDays: historyDays, cursorCookieHeaderOverride: cursorCookieHeaderOverride, allowPricingRefresh: allowPricingRefresh, refreshPricingInBackground: refreshPricingInBackground, includePiSessions: includePiSessions, bypassScannerDebounce: bypassScannerDebounce, scannerOptions: self.scannerOptionsOverride()) } @available(*, deprecated, message: "Codex token-cost scans are uncapped; this limit is ignored.") public func loadTokenSnapshot( provider: UsageProvider, environment: [String: String] = ProcessInfo.processInfo.environment, now: Date = Date(), forceRefresh: Bool = false, allowVertexClaudeFallback: Bool = false, codexHomePath: String? = nil, historyDays: Int = 30, allowPricingRefresh: Bool = true, refreshPricingInBackground: Bool = true, automaticCodexScanByteLimit _: Int64?) async throws -> CostUsageTokenSnapshot { try await self.loadTokenSnapshot( provider: provider, environment: environment, now: now, forceRefresh: forceRefresh, allowVertexClaudeFallback: allowVertexClaudeFallback, codexHomePath: codexHomePath, historyDays: historyDays, allowPricingRefresh: allowPricingRefresh, refreshPricingInBackground: refreshPricingInBackground) } private func scannerOptionsOverride() -> CostUsageScanner.Options? { self.scannerOptions } private static func resolvedScannerOptions( _ override: CostUsageScanner.Options?, provider: UsageProvider, codexHomePath: String?) -> CostUsageScanner.Options { var options = override ?? CostUsageScanner.Options() if provider == .codex, let codexHomePath = codexHomePath?.trimmingCharacters(in: .whitespacesAndNewlines), !codexHomePath.isEmpty { options.codexSessionsRoot = URL(fileURLWithPath: codexHomePath, isDirectory: true) .appendingPathComponent("sessions", isDirectory: true) } return options } static func loadTokenSnapshot( provider: UsageProvider, environment: [String: String] = ProcessInfo.processInfo.environment, now: Date = Date(), forceRefresh: Bool = false, allowVertexClaudeFallback: Bool = false, codexHomePath: String? = nil, historyDays: Int = 30, cursorCookieHeaderOverride: String? = nil, allowPricingRefresh: Bool = true, refreshPricingInBackground: Bool = true, includePiSessions: Bool = true, bypassScannerDebounce: Bool = false, scannerOptions overrideScannerOptions: CostUsageScanner.Options? = nil, piScannerOptions overridePiScannerOptions: PiSessionCostScanner .Options? = nil, modelsDevClient: ModelsDevClient = ModelsDevClient(), retryUnknownPricing: Bool = true) async throws -> CostUsageTokenSnapshot { guard self.supportsTokenSnapshot(provider) else { throw CostUsageError.unsupportedProvider(provider) } let until = now let clampedHistoryDays = max(1, min(365, historyDays)) // Rolling window is inclusive, so a 30-day display starts 29 days before `now`. let since = Calendar.current.date(byAdding: .day, value: -(clampedHistoryDays - 1), to: now) ?? now if let remoteSnapshot = try await self.loadRemoteTokenSnapshot( provider: provider, environment: environment, now: now, historyDays: clampedHistoryDays, cursorCookieHeaderOverride: cursorCookieHeaderOverride) { return remoteSnapshot } var options = Self.resolvedScannerOptions( overrideScannerOptions, provider: provider, codexHomePath: codexHomePath) let scopedCodexHomePath = codexHomePath?.trimmingCharacters(in: .whitespacesAndNewlines) let shouldMergePiUsage = provider != .codex || scopedCodexHomePath?.isEmpty != false await Self.refreshPricingIfAllowed( options: PricingRefreshOptions( provider: provider, isAllowed: allowPricingRefresh, retryUnknown: retryUnknownPricing, inBackground: refreshPricingInBackground), now: now, cacheRoot: options.cacheRoot, client: modelsDevClient) if provider == .vertexai { options.claudeLogProviderFilter = allowVertexClaudeFallback ? .all : .vertexAIOnly } else if provider == .claude { options.claudeLogProviderFilter = .excludeVertexAI } if forceRefresh || bypassScannerDebounce { options.refreshMinIntervalSeconds = 0 } var resolvedPiOptions = overridePiScannerOptions ?? PiSessionCostScanner.Options() if resolvedPiOptions.cacheRoot == nil { resolvedPiOptions.cacheRoot = options.cacheRoot } if forceRefresh || bypassScannerDebounce { resolvedPiOptions.refreshMinIntervalSeconds = 0 } let piOptions = resolvedPiOptions try Task.checkCancellation() // The corpus scans below are synchronous and can run for minutes on large session // archives. They execute on the dedicated scan queue so they never occupy a cooperative // pool thread; CostUsageScanExecutor bridges this task's cancellation into the // scanner-level checks. let scanOptions = options let scanResult = try await CostUsageScanExecutor.run { checkCancellation in var daily = try CostUsageScanner.loadDailyReportCancellable( provider: provider, since: since, until: until, now: now, options: scanOptions, checkCancellation: checkCancellation) try checkCancellation() if provider == .vertexai, !allowVertexClaudeFallback, scanOptions.claudeLogProviderFilter == .vertexAIOnly, daily.data.isEmpty { var fallback = scanOptions fallback.claudeLogProviderFilter = .all daily = try CostUsageScanner.loadDailyReportCancellable( provider: provider, since: since, until: until, now: now, options: fallback, checkCancellation: checkCancellation) try checkCancellation() } var projects: [CostUsageProjectBreakdown] = [] var sessions: [CostUsageSessionBreakdown] = [] var piDaily: CostUsageDailyReport? if provider == .codex { let roots = CostUsageScanner.codexSessionsRoots(options: scanOptions) let cache = CostUsageScanner.codexCache( CostUsageCacheIO.load(provider: .codex, cacheRoot: scanOptions.cacheRoot), scopedTo: roots) let range = CostUsageScanner.CostUsageDayRange(since: since, until: until) projects = CostUsageScanner.buildCodexProjectBreakdownsFromCache( cache: cache, range: range, modelsDevCacheRoot: scanOptions.cacheRoot) sessions = CostUsageScanner.buildCodexSessionBreakdownsFromCache( cache: cache, range: range, modelsDevCacheRoot: scanOptions.cacheRoot, sessionRoots: roots) } if includePiSessions, provider == .claude || (provider == .codex && shouldMergePiUsage) { let piReport = try PiSessionCostScanner.loadDailyReportCancellable( provider: provider, since: since, until: until, now: now, options: piOptions, checkCancellation: checkCancellation) try checkCancellation() if provider == .codex { piDaily = piReport } daily = CostUsageDailyReport.merged([daily, piReport]) } if provider == .codex { projects = Self.mergedProjectBreakdowns( projects + [piDaily.flatMap(Self.unknownProjectBreakdown(from:))].compactMap(\.self)) if piDaily?.data.isEmpty == false { sessions = [] } } return (daily: daily, projects: projects, sessions: sessions) } if allowPricingRefresh, retryUnknownPricing, let request = Self.unknownPricingRefreshRequest( provider: provider, daily: scanResult.daily, now: now, cacheRoot: options.cacheRoot, client: modelsDevClient), await Self.refreshUnknownPricingIfNeeded(request, inBackground: refreshPricingInBackground) { return try await self.loadTokenSnapshot( provider: provider, environment: environment, now: now, forceRefresh: forceRefresh, allowVertexClaudeFallback: allowVertexClaudeFallback, codexHomePath: codexHomePath, historyDays: historyDays, cursorCookieHeaderOverride: cursorCookieHeaderOverride, allowPricingRefresh: allowPricingRefresh, refreshPricingInBackground: false, includePiSessions: includePiSessions, scannerOptions: options, piScannerOptions: piOptions, modelsDevClient: modelsDevClient, retryUnknownPricing: false) } return Self.tokenSnapshot( from: scanResult.daily, now: now, historyDays: clampedHistoryDays, projects: scanResult.projects, sessions: scanResult.sessions) } private struct PricingRefreshOptions: Sendable { let provider: UsageProvider let isAllowed: Bool let retryUnknown: Bool let inBackground: Bool } private static func refreshPricingIfAllowed( options: PricingRefreshOptions, now: Date, cacheRoot: URL?, client: ModelsDevClient) async { guard options.isAllowed, options.retryUnknown, options.provider == .codex || options.provider == .claude else { return } if options.inBackground { Task.detached(priority: .utility) { await ModelsDevPricingPipeline.refreshIfNeeded(now: now, cacheRoot: cacheRoot, client: client) } } else { await ModelsDevPricingPipeline.refreshIfNeeded(now: now, cacheRoot: cacheRoot, client: client) } } private struct UnknownPricingRefreshRequest: Sendable { let providerID: String let modelIDs: Set<String> let now: Date let cacheRoot: URL? let client: ModelsDevClient } private static func unknownPricingRefreshRequest( provider: UsageProvider, daily: CostUsageDailyReport, now: Date, cacheRoot: URL?, client: ModelsDevClient) -> UnknownPricingRefreshRequest? { guard provider == .codex || provider == .claude else { return nil } let unknownModelIDs = Set(daily.data.flatMap { entry in entry.modelBreakdowns?.compactMap { breakdown -> String? in guard breakdown.costUSD == nil else { return nil } if provider == .codex, CostUsagePricing.isCodexUnattributedModel(breakdown.modelName) { return nil } return breakdown.modelName } ?? [] }) guard !unknownModelIDs.isEmpty else { return nil } return UnknownPricingRefreshRequest( providerID: provider == .codex ? "openai" : "anthropic", modelIDs: unknownModelIDs, now: now, cacheRoot: cacheRoot, client: client) } private static func refreshUnknownPricingIfNeeded( _ request: UnknownPricingRefreshRequest, inBackground: Bool) async -> Bool { if inBackground { Task.detached(priority: .utility) { _ = await ModelsDevPricingPipeline.refreshForUnknownModelsIfNeeded( providerID: request.providerID, modelIDs: request.modelIDs, now: request.now, cacheRoot: request.cacheRoot, client: request.client) } return false } return await ModelsDevPricingPipeline.refreshForUnknownModelsIfNeeded( providerID: request.providerID, modelIDs: request.modelIDs, now: request.now, cacheRoot: request.cacheRoot, client: request.client) == .pricingAvailable } static func loadCachedCodexTokenSnapshot( now: Date = Date(), codexHomePath: String? = nil, historyDays: Int = 30, scannerOptions overrideScannerOptions: CostUsageScanner.Options? = nil) async -> CostUsageTokenSnapshot? { await self.loadCachedCodexTokenSnapshotResult( now: now, codexHomePath: codexHomePath, historyDays: historyDays, scannerOptions: overrideScannerOptions)?.snapshot } static func loadCachedCodexTokenSnapshotResult( now: Date = Date(), codexHomePath: String? = nil, historyDays: Int = 30, scannerOptions overrideScannerOptions: CostUsageScanner.Options? = nil) async -> CachedCodexTokenSnapshotResult? { if let codexHomePath = codexHomePath?.trimmingCharacters(in: .whitespacesAndNewlines), !codexHomePath.isEmpty { return nil } // Decoding the persisted scan cache parses multi-megabyte JSON; keep it off the // cooperative pool alongside the scans themselves. let cachedSnapshot: CachedCodexTokenSnapshotResult?? = try? await CostUsageScanExecutor.run { _ in let clampedHistoryDays = max(1, min(365, historyDays)) let until = now let since = Calendar.current.date(byAdding: .day, value: -(clampedHistoryDays - 1), to: now) ?? now let range = CostUsageScanner.CostUsageDayRange(since: since, until: until) let options = overrideScannerOptions ?? CostUsageScanner.Options() let roots = CostUsageScanner.codexSessionsRoots(options: options) let cache = CostUsageScanner.codexCache( CostUsageCacheIO.load(provider: .codex, cacheRoot: options.cacheRoot), scopedTo: roots) var reports: [CostUsageDailyReport] = [] var projects: [CostUsageProjectBreakdown] = [] var sessions: [CostUsageSessionBreakdown] = [] // Raw inputs for the derived result fields below: the native cache's own scan // time, every constituent scan time, and whether a second source joined the merge. var nativeScanAt: Date? var scanTimes: [Date] = [] var piMerged = false if !cache.days.isEmpty, cache.roots == CostUsageScanner.codexRootsFingerprint(options: options), !CostUsageScanner.requestedWindowExpandsCache(range: range, cache: cache) { let daily = CostUsageScanner.buildCodexReportFromCache( cache: cache, range: range, modelsDevCacheRoot: options.cacheRoot) if !daily.data.isEmpty { reports.append(daily) if cache.lastScanUnixMs > 0 {