752 lines · 31.2 KB · Swift
RawDownload
import CodexBarCoreimport Foundation @MainActorstruct MenuDescriptor {    struct SubmenuItem: Equatable {        let title: String        let action: MenuAction?        let isEnabled: Bool        let isChecked: Bool         init(title: String, action: MenuAction?, isEnabled: Bool = true, isChecked: Bool = false) {            self.title = title            self.action = action            self.isEnabled = isEnabled            self.isChecked = isChecked        }    }     struct Section {        var entries: [Entry]    }     enum Entry {        case text(String, TextStyle)        case action(String, MenuAction)        case unavailable(String, String?)        case submenu(String, String?, [SubmenuItem])        case divider         var isActionable: Bool {            switch self {            case .action, .submenu, .unavailable: true            case .text, .divider: false            }        }    }     enum MenuActionSystemImage: String {        case installUpdate = "arrow.down.circle"        case refresh = "arrow.clockwise"        case dashboard = "chart.xyaxis.line"        case statusPage = "waveform.path.ecg"        case changelog = "list.bullet.rectangle"        case addAccount = "plus"        case systemAccount = "person.crop.circle"        case switchAccount = "key"        case openTerminal = "terminal"        case loginToProvider = "arrow.right.square"        case settings = "gearshape"        case about = "info.circle"        case quit = "xmark.rectangle"        case copyError = "doc.on.doc"    }     enum TextStyle {        case headline        case primary        case secondary    }     enum MenuAction: Equatable {        case installUpdate        case refresh        case refreshAugmentSession        case dashboard        case statusPage        case changelog        case addCodexAccount        case requestCodexSystemPromotion(UUID)        case addProviderAccount(UsageProvider)        case switchAccount(UsageProvider)        case openTerminal(command: String)        case loginToProvider(url: String)        case settings        case about        case quit        case copyError(String)        case focusAgentSession(AgentSession, remoteHost: String?)    }     var sections: [Section]     static func build(        provider: UsageProvider?,        store: UsageStore,        settings: SettingsStore,        account: AccountInfo,        managedCodexAccountCoordinator: ManagedCodexAccountCoordinator? = nil,        codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator? = nil,        updateReady: Bool,        includeContextualActions: Bool = true,        agentSessionsEnabled: Bool = false,        localAgentSessions: [AgentSession] = [],        remoteAgentHosts: [RemoteSessionHostResult] = [],        now: Date = Date()) -> MenuDescriptor    {        var sections: [Section] = []         if let provider {            let fallbackAccount = store.accountInfo(for: provider)            sections.append(Self.usageSection(for: provider, store: store, settings: settings))            if let accountSection = Self.accountSection(                for: provider,                store: store,                settings: settings,                account: fallbackAccount)            {                sections.append(accountSection)            }        } else {            var addedUsage = false             for enabledProvider in store.enabledProviders() {                sections.append(Self.usageSection(for: enabledProvider, store: store, settings: settings))                addedUsage = true            }            if addedUsage {                if let accountProvider = Self.accountProviderForCombined(store: store),                   let fallbackAccount = Optional(store.accountInfo(for: accountProvider)),                   let accountSection = Self.accountSection(                       for: accountProvider,                       store: store,                       settings: settings,                       account: fallbackAccount)                {                    sections.append(accountSection)                }            } else {                sections.append(Section(entries: [.text(L("No usage configured."), .secondary)]))            }        }         if includeContextualActions {            let actions = Self.actionsSection(                for: provider,                store: store,                account: account,                managedCodexAccountCoordinator: managedCodexAccountCoordinator,                codexAccountPromotionCoordinator: codexAccountPromotionCoordinator)            if !actions.entries.isEmpty {                sections.append(actions)            }        }        if agentSessionsEnabled {            sections.append(Self.agentSessionsSection(                localSessions: localAgentSessions,                remoteHosts: remoteAgentHosts,                now: now))        }        sections.append(Self.metaSection(updateReady: updateReady))         return MenuDescriptor(sections: sections)    }     static func agentSessionsSection(        localSessions: [AgentSession],        remoteHosts: [RemoteSessionHostResult],        now: Date = Date()) -> Section    {        let totalCount = localSessions.count + remoteHosts.reduce(0) { $0 + $1.sessions.count }        var entries: [Entry] = [.text("Agent Sessions (\(totalCount))", .headline)]         for session in localSessions {            entries.append(.action(                self.agentSessionRowTitle(session, now: now),                .focusAgentSession(session, remoteHost: nil)))        }        for remoteHost in remoteHosts {            if let error = remoteHost.error {                entries.append(.unavailable("\(remoteHost.host) — unreachable", error))                continue            }            entries.append(.text("\(remoteHost.host) — \(remoteHost.sessions.count)", .secondary))            for session in remoteHost.sessions {                entries.append(.action(                    self.agentSessionRowTitle(session, now: now),                    .focusAgentSession(session, remoteHost: remoteHost.host)))            }        }        if totalCount == 0 {            entries.append(.unavailable("No agent sessions found", nil))        }        return Section(entries: entries)    }     private static func agentSessionRowTitle(_ session: AgentSession, now: Date) -> String {        let state = session.state == .active ? "●" : "○"        let providerGlyph = session.provider == .codex ? "⌘" : "✦"        let project = session.projectName ?? "Unknown project"        return "\(state) \(providerGlyph) \(project) — \(session.provider.rawValue) · " +            "\(session.source.rawValue) · \(self.agentSessionAge(session, now: now))"    }     private static func agentSessionAge(_ session: AgentSession, now: Date) -> String {        guard let activity = session.lastActivityAt ?? session.startedAt else { return "now" }        let seconds = max(0, Int(now.timeIntervalSince(activity)))        if seconds < 60 {            return "\(seconds)s"        }        if seconds < 3600 {            return "\(seconds / 60)m"        }        if seconds < 86400 {            return "\(seconds / 3600)h"        }        return "\(seconds / 86400)d"    }     private static func usageSection(        for provider: UsageProvider,        store: UsageStore,        settings: SettingsStore) -> Section    {        let meta = store.metadata(for: provider)        var entries: [Entry] = []        let headlineText: String = {            if let ver = Self.versionNumber(for: provider, store: store) {                return "\(meta.displayName) \(ver)"            }            return meta.displayName        }()        entries.append(.text(headlineText, .headline))         if let snap = store.snapshot(for: provider) {            let resetStyle = settings.resetTimeDisplayStyle            let labels = Self.rateWindowLabels(provider: provider, metadata: meta, snapshot: snap)            if let primary = snap.primary {                let primaryDetail = primary.resetDescription?.trimmingCharacters(in: .whitespacesAndNewlines)                let primaryDescriptionIsDetail = provider == .warp || provider == .kilo || provider == .abacus ||                    provider == .deepseek || provider == .azureopenai || provider == .mimo || provider == .qoder ||                    provider == .sub2api                let primaryWindow = if primaryDescriptionIsDetail {                    // Some providers use resetDescription for non-reset detail                    // (e.g., "Unlimited", "X/Y credits"). Avoid rendering it as a "Resets ..." line.                    RateWindow(                        usedPercent: primary.usedPercent,                        windowMinutes: primary.windowMinutes,                        resetsAt: primary.resetsAt,                        resetDescription: nil)                } else {                    primary                }                Self.appendRateWindow(                    entries: &entries,                    title: labels.primary,                    window: primaryWindow,                    resetStyle: resetStyle,                    showUsed: settings.usageBarsShowUsed)                if primaryDescriptionIsDetail,                   let primaryDetail,                   !primaryDetail.isEmpty                {                    entries.append(.text(primaryDetail, .secondary))                }                if provider == .crof,                   primary.resetsAt != nil,                   let detail = primary.resetDescription?.trimmingCharacters(in: .whitespacesAndNewlines),                   !detail.isEmpty                {                    entries.append(.text(detail, .secondary))                }                if provider == .abacus,                   let pace = store.weeklyPace(provider: provider, window: primary)                {                    let paceSummary = UsagePaceText.weeklySummary(provider: provider, pace: pace)                    entries.append(.text(paceSummary, .secondary))                }                if let paceSummary = UsagePaceText.sessionSummary(provider: provider, window: primary) {                    entries.append(.text(paceSummary, .secondary))                }            }            if let weekly = snap.secondary {                let weeklyResetOverride: String? = {                    guard provider == .warp || provider == .kilo || provider == .perplexity || provider == .crof ||                        provider == .sub2api                    else { return nil }                    let detail = weekly.resetDescription?.trimmingCharacters(in: .whitespacesAndNewlines)                    guard let detail, !detail.isEmpty else { return nil }                    if provider == .kilo, weekly.resetsAt != nil {                        return nil                    }                    return detail                }()                Self.appendRateWindow(                    entries: &entries,                    title: labels.secondary,                    window: weekly,                    resetStyle: resetStyle,                    showUsed: settings.usageBarsShowUsed,                    resetOverride: weeklyResetOverride)                if provider == .kilo,                   weekly.resetsAt != nil,                   let detail = weekly.resetDescription?.trimmingCharacters(in: .whitespacesAndNewlines),                   !detail.isEmpty                {                    entries.append(.text(detail, .secondary))                }                if let pace = store.weeklyPace(provider: provider, window: weekly) {                    let paceSummary = UsagePaceText.weeklySummary(provider: provider, pace: pace)                    entries.append(.text(paceSummary, .secondary))                }            }            if labels.showsTertiary, let opus = snap.tertiary {                // Perplexity purchased credits don't reset; show the balance as plain text.                let opusResetOverride: String? = provider == .perplexity || provider == .sub2api                    ? opus.resetDescription?.trimmingCharacters(in: .whitespacesAndNewlines)                    : nil                Self.appendRateWindow(                    entries: &entries,                    title: labels.tertiary,                    window: opus,                    resetStyle: resetStyle,                    showUsed: settings.usageBarsShowUsed,                    resetOverride: opusResetOverride)            }             Self.appendProviderUsageSummaries(                entries: &entries,                snapshot: snap,                showOptionalUsage: settings.showOptionalCreditsAndExtraUsage)            if snap.rateLimitsUnavailable(for: provider) {                entries.append(.text(L("Limits not available"), .secondary))            }        } else if !store.isStale(provider: provider),                  store.knownLimitsAvailability(for: provider)?.isUnavailable == true        {            entries.append(.text(L("Limits not available"), .secondary))        } else {            entries.append(.text(L("No usage yet"), .secondary))        }         let usageContext = ProviderMenuUsageContext(            provider: provider,            store: store,            settings: settings,            metadata: meta,            snapshot: store.snapshot(for: provider))        ProviderCatalog.implementation(for: provider)?            .appendUsageMenuEntries(context: usageContext, entries: &entries)         return Section(entries: entries)    }     private static func appendProviderUsageSummaries(        entries: inout [Entry],        snapshot: UsageSnapshot,        showOptionalUsage: Bool)    {        if let cost = snapshot.providerCost {            if cost.currencyCode == "Quota" {                let used = String(format: "%.0f", cost.used)                let limit = String(format: "%.0f", cost.limit)                entries.append(.text("\(L("Quota")): \(used) / \(limit)", .primary))            }        }        if let openAIAPIUsage = snapshot.openAIAPIUsage {            Self.appendOpenAIAPIUsageSummary(entries: &entries, usage: openAIAPIUsage)        }        if let claudeAdminAPIUsage = snapshot.claudeAdminAPIUsage {            Self.appendClaudeAdminAPIUsageSummary(entries: &entries, usage: claudeAdminAPIUsage)        }        if let openRouterUsage = snapshot.openRouterUsage {            Self.appendOpenRouterUsageSummary(entries: &entries, usage: openRouterUsage)        }        if let clawRouterUsage = snapshot.clawRouterUsage {            entries.append(.text(                "\(UsageFormatter.tokenCountString(clawRouterUsage.requestCount)) \(L("requests")) · " +                    "\(UsageFormatter.tokenCountString(clawRouterUsage.totalTokens)) \(L("tokens"))",                .secondary))            if !clawRouterUsage.providers.isEmpty {                let mix = clawRouterUsage.providers.prefix(5)                    .map { "\($0.provider): \(UsageFormatter.tokenCountString($0.requestCount))" }                    .joined(separator: " · ")                entries.append(.text("Routed providers: \(mix)", .secondary))            }        }        if let wayfinderUsage = snapshot.wayfinderUsage {            Self.appendWayfinderUsageSummary(entries: &entries, usage: wayfinderUsage)        }        if let poeUsage = snapshot.poeUsage, !poeUsage.daily.isEmpty {            Self.appendPoeUsageSummary(entries: &entries, usage: poeUsage)        }        if let mistralUsage = snapshot.mistralUsage, !mistralUsage.daily.isEmpty {            Self.appendMistralUsageSummary(entries: &entries, usage: mistralUsage)        }        if let mimoUsage = snapshot.mimoUsage {            entries.append(.text("\(L("Balance")): \(mimoUsage.balanceDetail)", .primary))        }        // Sakana pay-as-you-go is optional data gated by "Show optional credits and extra usage".        // Gate the render on the setting too, not just the fetch: toggling the setting off only        // rebuilds the menu, it does not immediately refetch, so a previously-populated        // sakanaPayAsYouGo would otherwise linger in the cached snapshot until the next refresh.        if showOptionalUsage, let sakanaPayAsYouGo = snapshot.sakanaPayAsYouGo {            entries.append(.text("\(L("Balance")): \(sakanaPayAsYouGo.balanceDetail)", .primary))            if let periodUsageTotal = sakanaPayAsYouGo.periodUsageTotal {                entries.append(.text(                    "\(L("Usage")): \(UsageFormatter.usdString(periodUsageTotal))",                    .secondary))            }        }    }     private static func accountSection(        for provider: UsageProvider,        store: UsageStore,        settings: SettingsStore,        account: AccountInfo) -> Section?    {        let snapshot = store.snapshot(for: provider)        let metadata = store.metadata(for: provider)        let entries = Self.accountEntries(            provider: provider,            snapshot: snapshot,            metadata: metadata,            fallback: account,            hidePersonalInfo: settings.hidePersonalInfo)        guard !entries.isEmpty else { return nil }        return Section(entries: entries)    }     private static func accountEntries(        provider: UsageProvider,        snapshot: UsageSnapshot?,        metadata: ProviderMetadata,        fallback: AccountInfo,        hidePersonalInfo: Bool) -> [Entry]    {        var entries: [Entry] = []        let emailText = snapshot?.accountEmail(for: provider)?            .trimmingCharacters(in: .whitespacesAndNewlines)        let loginMethodText = snapshot?.loginMethod(for: provider)?            .trimmingCharacters(in: .whitespacesAndNewlines)        let redactedEmail = PersonalInfoRedactor.redactEmail(emailText, isEnabled: hidePersonalInfo)         if let emailText, !emailText.isEmpty, !redactedEmail.isEmpty {            entries.append(.text("\(L("Account")): \(redactedEmail)", .secondary))        }        if provider == .kiro {            if let plan = snapshot?.kiroUsage?.displayPlanName,               !plan.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty            {                entries.append(.text("\(L("Plan")): \(plan)", .secondary))            }            if let loginMethodText, !loginMethodText.isEmpty {                entries.append(.text("\(L("Auth")): \(loginMethodText)", .secondary))            }            if let overages = snapshot?.kiroUsage?.overagesStatus,               !overages.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty            {                entries.append(.text("\(L("Overages")): \(overages)", .secondary))            }        } else if provider == .kilo {            let kiloLogin = self.kiloLoginParts(loginMethod: loginMethodText)            if let pass = kiloLogin.pass {                entries.append(.text("\(L("Plan")): \(AccountFormatter.plan(pass, provider: provider))", .secondary))            }            for detail in kiloLogin.details {                entries.append(.text("\(L("Activity")): \(detail)", .secondary))            }        } else if provider == .crossmodel {            if let loginMethodText, !loginMethodText.isEmpty {                entries.append(.text("\(L("Auth")): \(loginMethodText)", .secondary))            }        } else if let loginMethodText, !loginMethodText.isEmpty {            if provider == .openrouter || provider == .mimo || provider == .poe,               loginMethodText.localizedCaseInsensitiveContains("balance:")            {                let balanceValue = loginMethodText                    .replacingOccurrences(                        of: #"(?i)^\s*balance:\s*"#,                        with: "",                        options: [.regularExpression])                    .trimmingCharacters(in: .whitespacesAndNewlines)                let value = balanceValue.isEmpty ? loginMethodText : balanceValue                entries.append(                    .text("\(L("Balance")): \(AccountFormatter.plan(value, provider: provider))", .secondary))            } else {                entries.append(                    .text(                        "\(L("Plan")): \(AccountFormatter.plan(loginMethodText, provider: provider))",                        .secondary))            }        }         if metadata.usesAccountFallback {            if emailText?.isEmpty ?? true, let fallbackEmail = fallback.email, !fallbackEmail.isEmpty {                let redacted = PersonalInfoRedactor.redactEmail(fallbackEmail, isEnabled: hidePersonalInfo)                if !redacted.isEmpty {                    entries.append(.text("\(L("Account")): \(redacted)", .secondary))                }            }            if loginMethodText?.isEmpty ?? true, let fallbackPlan = fallback.plan, !fallbackPlan.isEmpty {                entries.append(                    .text(                        "\(L("Plan")): \(AccountFormatter.plan(fallbackPlan, provider: provider))",                        .secondary))            }        } 

Lines 1–500 of 752.