433 lines · 16.1 KB · Swift
RawDownload
import CodexBarCoreimport Foundation struct CodexUIErrorMapper {    private static let codexCLINotSignedInMessage =        "Codex CLI is not signed in. Run `codex login --device-auth`, then refresh."     static func userFacingMessage(_ raw: String?) -> String? {        guard let raw, !raw.isEmpty else { return nil }        let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)        guard !trimmed.isEmpty else { return nil }         let lower = trimmed.lowercased()        if self.isAlreadyUserFacing(lower: lower) {            return trimmed        }         if let cachedMessage = self.cachedMessage(raw: trimmed, lower: lower) {            return cachedMessage        }         if self.looksCodexCLIMissing(lower: lower) {            return CodexStatusProbeError.codexNotInstalled.localizedDescription        }         if self.looksCodexCLILoginRequired(lower: lower) {            return self.codexCLINotSignedInMessage        }         if self.looksExpired(lower: lower) {            return "Codex session expired. Sign in again."        }         if lower.contains("frame load interrupted") {            return "OpenAI web refresh was interrupted. Refresh OpenAI cookies and try again."        }         if self.looksOpenAIWebTimeout(lower: lower) {            return "OpenAI web refresh timed out. Refresh OpenAI cookies and try again."        }         if self.looksOpenAIWebNetworkError(lower: lower) {            return "OpenAI web refresh hit a network error. "                + "Check your connection, then refresh OpenAI cookies and try again."        }         if self.looksInternalTransport(lower: lower) {            return "Codex usage is temporarily unavailable. Try refreshing."        }         return trimmed    }     private static func cachedMessage(raw: String, lower: String) -> String? {        let cachedMarker = " Cached values from "        guard let suffixRange = raw.range(of: cachedMarker) else { return nil }         let suffix = String(raw[suffixRange.lowerBound...]).trimmingCharacters(in: .whitespacesAndNewlines)        if lower.hasPrefix("last codex credits refresh failed:"),           let base = self.userFacingMessage(String(raw[..<suffixRange.lowerBound]))        {            return "\(base) \(suffix)"        }         if lower.hasPrefix("last openai dashboard refresh failed:"),           let base = self.userFacingMessage(String(raw[..<suffixRange.lowerBound]))        {            return "\(base) \(suffix)"        }         return nil    }     private static func isAlreadyUserFacing(lower: String) -> Bool {        lower.contains("openai cookies are for")            || lower.contains("sign in to chatgpt.com")            || lower.contains("requires a signed-in chatgpt.com session")            || lower.contains("managed codex account data is unavailable")            || lower.contains("selected managed codex account is unavailable")            || lower.contains("codex credits are still loading")            || lower.contains("codex account changed; importing browser cookies")            || lower.contains("codex cli is not signed in.")            || lower.contains("codex session expired. sign in again.")            || lower.contains("openai web refresh timed out. refresh openai cookies and try again.")            || lower.contains(                "openai web refresh hit a network error. "                    + "check your connection, then refresh openai cookies and try again.")            || lower.contains("codex usage is temporarily unavailable. try refreshing.")    }     private static func looksCodexCLIMissing(lower: String) -> Bool {        lower.contains("codex cli missing")            || lower.contains("codex cli not found")            || lower.contains("missing cli codex")            || lower.contains("missing cli 'codex'")            || lower.contains("missing cli \"codex\"")            || (lower.contains("binary not found") && lower.contains("codex"))    }     private static func looksCodexCLILoginRequired(lower: String) -> Bool {        lower.contains("codex account authentication required")            || lower.contains("account authentication required to read rate limits")            || lower.contains("requiresopenaiauth")    }     private static func looksExpired(lower: String) -> Bool {        lower.contains("token_expired")            || lower.contains("authentication token is expired")            || lower.contains("oauth token has expired")            || lower.contains("provided authentication token is expired")            || lower.contains("please try signing in again")            || lower.contains("please sign in again")            || (lower.contains("401") && lower.contains("unauthorized"))    }     private static func looksInternalTransport(lower: String) -> Bool {        lower.contains("codex connection failed")            || lower.contains("failed to fetch codex rate limits")            || lower.contains("/backend-api/")            || lower.contains("content-type=")            || lower.contains("body={")            || lower.contains("body=")            || lower.contains("get https://")            || lower.contains("get http://")            || lower.contains("returned invalid data")    }     private static func looksOpenAIWebTimeout(lower: String) -> Bool {        lower.contains("nsurlerrordomain")            && (lower.contains("timed out") || lower.contains("error -1001"))    }     private static func looksOpenAIWebNetworkError(lower: String) -> Bool {        lower.contains("nsurlerrordomain")    }} struct CodexConsumerProjection {    enum Surface {        case liveCard        case overrideCard        case widget        case menuBar    }     enum RateLane: String {        case session        case weekly    }     enum SupplementalMetric: String {        case codeReview    }     struct PlanUtilizationLane {        let role: PlanUtilizationSeriesName        let window: RateWindow    }     enum DashboardVisibility {        case hidden        case displayOnly        case attached    }     struct CreditsProjection {        let snapshot: CreditsSnapshot?        let userFacingError: String?         var remaining: Double? {            self.snapshot?.remaining        }    }     struct UserFacingErrors {        let usage: String?        let credits: String?        let dashboard: String?    }     struct Context {        let snapshot: UsageSnapshot?        let rawUsageError: String?        let liveCredits: CreditsSnapshot?        let rawCreditsError: String?        let liveDashboard: OpenAIDashboardSnapshot?        let rawDashboardError: String?        let dashboardAttachmentAuthorized: Bool        let dashboardRequiresLogin: Bool        let now: Date    }     enum MenuBarFallback {        case none        case creditsBalance    }     let visibleRateLanes: [RateLane]    let supplementalMetrics: [SupplementalMetric]    let planUtilizationLanes: [PlanUtilizationLane]    let dashboardVisibility: DashboardVisibility    let credits: CreditsProjection?    let menuBarFallback: MenuBarFallback    let userFacingErrors: UserFacingErrors    let canShowBuyCredits: Bool    let hasUsageBreakdown: Bool    let hasCreditsHistory: Bool     private let rateWindowsByLane: [RateLane: RateWindow]    private let codeReviewRemainingPercent: Double?    private let codeReviewLimit: RateWindow?     static func make(surface: Surface, context: Context) -> CodexConsumerProjection {        let allowsLiveAdjuncts = surface != .overrideCard        let dashboardVisibility = self.dashboardVisibility(surface: surface, context: context)        let dashboard = allowsLiveAdjuncts && dashboardVisibility != .hidden ? context.liveDashboard : nil         let rateWindowsByLane = self.rateWindowsByLane(snapshot: context.snapshot)        let visibleRateLanes = self.visibleRateLanes(from: rateWindowsByLane, snapshot: context.snapshot)        let planUtilizationLanes = self.planUtilizationLanes(from: rateWindowsByLane)         let creditsProjection: CreditsProjection? = if allowsLiveAdjuncts,                                                       context.liveCredits != nil || context.rawCreditsError != nil        {            CreditsProjection(                snapshot: context.liveCredits,                userFacingError: CodexUIErrorMapper.userFacingMessage(context.rawCreditsError))        } else {            nil        }         let userFacingErrors = UserFacingErrors(            usage: CodexUIErrorMapper.userFacingMessage(context.rawUsageError),            credits: allowsLiveAdjuncts ? CodexUIErrorMapper.userFacingMessage(context.rawCreditsError) : nil,            dashboard: allowsLiveAdjuncts ? CodexUIErrorMapper.userFacingMessage(context.rawDashboardError) : nil)         let supplementalMetrics: [SupplementalMetric] = if surface == .liveCard,                                                           dashboardVisibility == .attached,                                                           dashboard?.codeReviewRemainingPercent != nil        {            [.codeReview]        } else {            []        }         let displayableUsageBreakdown = OpenAIDashboardDailyBreakdown.removingSkillUsageServices(            from: dashboard?.usageBreakdown ?? [])        let canShowBuyCredits = surface == .liveCard        let hasUsageBreakdown = surface == .liveCard            && dashboardVisibility == .attached            && !displayableUsageBreakdown.isEmpty        let hasCreditsHistory = surface == .liveCard            && dashboardVisibility == .attached            && !(dashboard?.dailyBreakdown ?? []).isEmpty         return CodexConsumerProjection(            visibleRateLanes: visibleRateLanes,            supplementalMetrics: supplementalMetrics,            planUtilizationLanes: planUtilizationLanes,            dashboardVisibility: dashboardVisibility,            credits: creditsProjection,            menuBarFallback: self.menuBarFallback(                creditsRemaining: creditsProjection?.remaining,                rateWindowsByLane: rateWindowsByLane),            userFacingErrors: userFacingErrors,            canShowBuyCredits: canShowBuyCredits,            hasUsageBreakdown: hasUsageBreakdown,            hasCreditsHistory: hasCreditsHistory,            rateWindowsByLane: rateWindowsByLane,            codeReviewRemainingPercent: dashboardVisibility == .attached ? dashboard?.codeReviewRemainingPercent : nil,            codeReviewLimit: dashboardVisibility == .attached ? dashboard?.codeReviewLimit : nil)    }     func rateWindow(for lane: RateLane) -> RateWindow? {        self.rateWindowsByLane[lane]    }     func remainingPercent(for metric: SupplementalMetric) -> Double? {        switch metric {        case .codeReview:            self.codeReviewRemainingPercent        }    }     func limitWindow(for metric: SupplementalMetric) -> RateWindow? {        switch metric {        case .codeReview:            self.codeReviewLimit        }    }     private static func dashboardVisibility(surface: Surface, context: Context) -> DashboardVisibility {        guard surface != .overrideCard else { return .hidden }        guard context.dashboardRequiresLogin == false, context.liveDashboard != nil else { return .hidden }        return context.dashboardAttachmentAuthorized ? .attached : .displayOnly    }     private static func rateWindowsByLane(snapshot: UsageSnapshot?) -> [RateLane: RateWindow] {        guard let snapshot else { return [:] }         var windowsByLane: [RateLane: RateWindow] = [:]        let slottedWindows: [(RateLane, RateWindow)] = [            self.classifyRateWindow(snapshot.primary, slot: .primary),            self.classifyRateWindow(snapshot.secondary, slot: .secondary),        ].compactMap(\.self)         for (lane, window) in slottedWindows {            windowsByLane[lane] = window        }        return windowsByLane    }     private static func visibleRateLanes(        from rateWindowsByLane: [RateLane: RateWindow],        snapshot: UsageSnapshot?) -> [RateLane]    {        guard let snapshot else { return [] }         let slottedLanes = [            self.classifyRateWindow(snapshot.primary, slot: .primary)?.0,            self.classifyRateWindow(snapshot.secondary, slot: .secondary)?.0,        ].compactMap(\.self)         var visible: [RateLane] = []        for lane in slottedLanes where rateWindowsByLane[lane] != nil && !visible.contains(lane) {            visible.append(lane)        }        return visible    }     private static func planUtilizationLanes(from rateWindowsByLane: [RateLane: RateWindow]) -> [PlanUtilizationLane] {        let semanticOrder: [RateLane] = [.session, .weekly]        return semanticOrder.compactMap { lane in            guard let window = rateWindowsByLane[lane] else { return nil }            return PlanUtilizationLane(role: self.planUtilizationRole(for: lane), window: window)        }    }     private static func planUtilizationRole(for lane: RateLane) -> PlanUtilizationSeriesName {        switch lane {        case .session:            .session        case .weekly:            .weekly        }    }     private enum SnapshotSlot {        case primary        case secondary    }     private static func classifyRateWindow(_ window: RateWindow?, slot: SnapshotSlot) -> (RateLane, RateWindow)? {        guard let window else { return nil }         let lane: RateLane = switch window.windowMinutes {        case 300:            .session        case 10080:            .weekly        default:            switch slot {            case .primary:                .session            case .secondary:                .weekly            }        }         return (lane, window)    }     private static func menuBarFallback(        creditsRemaining: Double?,        rateWindowsByLane: [RateLane: RateWindow]) -> MenuBarFallback    {        guard let creditsRemaining, creditsRemaining > 0 else { return .none }        let hasExhaustedLane = rateWindowsByLane.values.contains { $0.remainingPercent <= 0 }        let hasNoRateWindows = rateWindowsByLane.isEmpty        return (hasExhaustedLane || hasNoRateWindows) ? .creditsBalance : .none    }     var hasExhaustedRateLane: Bool {        self.rateWindowsByLane.values.contains { $0.remainingPercent <= 0 }    }} extension UsageStore {    func codexConsumerProjectionIfNeeded(        for provider: UsageProvider,        surface: CodexConsumerProjection.Surface,        snapshotOverride: UsageSnapshot? = nil,        errorOverride: String? = nil,        now: Date = Date()) -> CodexConsumerProjection?    {        guard provider == .codex else { return nil }        return self.codexConsumerProjection(            surface: surface,            snapshotOverride: snapshotOverride,            errorOverride: errorOverride,            now: now)    }     func codexConsumerProjection(        surface: CodexConsumerProjection.Surface,        snapshotOverride: UsageSnapshot? = nil,        errorOverride: String? = nil,        now: Date = Date()) -> CodexConsumerProjection    {        let snapshot = surface == .overrideCard ? snapshotOverride : snapshotOverride ?? self.snapshots[.codex]        let rawUsageError = surface == .overrideCard ? errorOverride : errorOverride ?? self.errors[.codex]        let context = CodexConsumerProjection.Context(            snapshot: snapshot,            rawUsageError: rawUsageError,            liveCredits: self.credits,            rawCreditsError: self.lastCreditsError,            liveDashboard: self.openAIDashboard,            rawDashboardError: self.lastOpenAIDashboardError,            dashboardAttachmentAuthorized: self.openAIDashboardAttachmentAuthorized,            dashboardRequiresLogin: self.openAIDashboardRequiresLogin,            now: now)        return CodexConsumerProjection.make(surface: surface, context: context)    }     func codexMenuBarCreditsRemaining(snapshotOverride: UsageSnapshot? = nil, now: Date = Date()) -> Double? {        let projection = self.codexConsumerProjection(            surface: .menuBar,            snapshotOverride: snapshotOverride,            now: now)        guard projection.menuBarFallback == .creditsBalance else { return nil }        return projection.credits?.remaining    }}