143 lines
5.9 KiB
Swift
143 lines
5.9 KiB
Swift
import CodexBarCore
|
|
import Foundation
|
|
|
|
@MainActor
|
|
extension UsageStore {
|
|
/// Returns the enabled candidate provider with the highest usage percentage (closest to rate limit).
|
|
/// Excludes providers that are fully rate-limited.
|
|
func providerWithHighestUsage(candidateProviders: [UsageProvider]? = nil, now: Date = Date())
|
|
-> (provider: UsageProvider, usedPercent: Double)?
|
|
{
|
|
let candidateSet = candidateProviders.map(Set.init)
|
|
var highest: (provider: UsageProvider, usedPercent: Double)?
|
|
for provider in self.enabledProviders()
|
|
where candidateSet?.contains(provider) ?? true
|
|
{
|
|
guard let snapshot = self.snapshots[provider] else { continue }
|
|
guard let window = self.menuBarMetricWindowForHighestUsage(
|
|
provider: provider,
|
|
snapshot: snapshot,
|
|
now: now)
|
|
else {
|
|
continue
|
|
}
|
|
let percent = window.usedPercent
|
|
guard !self.shouldExcludeFromHighestUsage(
|
|
provider: provider,
|
|
snapshot: snapshot,
|
|
metricPercent: percent,
|
|
now: now)
|
|
else {
|
|
continue
|
|
}
|
|
if highest == nil || percent > highest!.usedPercent {
|
|
highest = (provider, percent)
|
|
}
|
|
}
|
|
return highest
|
|
}
|
|
|
|
private func menuBarMetricWindowForHighestUsage(
|
|
provider: UsageProvider,
|
|
snapshot: UsageSnapshot,
|
|
now: Date) -> RateWindow?
|
|
{
|
|
let effectivePreference = self.settings.menuBarMetricPreference(for: provider, snapshot: snapshot)
|
|
if provider == .antigravity, effectivePreference == .automatic {
|
|
return Self.mostConstrainedAntigravityQuotaSummaryWindow(snapshot: snapshot)
|
|
}
|
|
if provider == .codex {
|
|
return self.codexMenuBarMetricWindow(snapshot: snapshot, now: now)
|
|
}
|
|
return MenuBarMetricWindowResolver.rateWindow(
|
|
preference: effectivePreference,
|
|
provider: provider,
|
|
snapshot: snapshot,
|
|
supportsAverage: self.settings.menuBarMetricSupportsAverage(for: provider))
|
|
}
|
|
|
|
private func shouldExcludeFromHighestUsage(
|
|
provider: UsageProvider,
|
|
snapshot: UsageSnapshot,
|
|
metricPercent: Double,
|
|
now: Date)
|
|
-> Bool
|
|
{
|
|
let effectivePreference = self.settings.menuBarMetricPreference(for: provider, snapshot: snapshot)
|
|
guard metricPercent >= 100 else { return false }
|
|
if provider == .codex || provider == .claude, effectivePreference == .primaryAndSecondary {
|
|
if provider == .codex,
|
|
self.codexConsumerProjection(
|
|
surface: .menuBar,
|
|
snapshotOverride: snapshot,
|
|
now: now).hasBindingWeeklyCap
|
|
{
|
|
return true
|
|
}
|
|
// A Claude spend-limit-only snapshot has no real session/weekly lanes; the metric resolves to
|
|
// the spend-limit window, so reaching here (metricPercent >= 100) means the spend limit itself
|
|
// is exhausted. Mirror that resolver fallback and exclude, instead of inspecting the raw 0%
|
|
// placeholder primary that would otherwise keep it eligible.
|
|
if provider == .claude, MenuBarMetricWindowResolver.claudeSpendLimitWindow(snapshot: snapshot) != nil {
|
|
return true
|
|
}
|
|
// Ignore synthesized placeholder lanes (e.g. Claude web's null `five_hour` 0% session) so a
|
|
// fully exhausted weekly-only account is excluded rather than kept eligible by a phantom 0%.
|
|
let percents = [snapshot.primary, snapshot.secondary]
|
|
.compactMap(\.self)
|
|
.filter { !$0.isSyntheticPlaceholder }
|
|
.map(\.usedPercent)
|
|
guard !percents.isEmpty else { return true }
|
|
return percents.allSatisfy { $0 >= 100 }
|
|
}
|
|
if provider == .antigravity, effectivePreference == .automatic {
|
|
let windows = Self.antigravityRenderedQuotaSummaryWindows(snapshot: snapshot)
|
|
guard !windows.isEmpty else { return true }
|
|
return windows.allSatisfy { $0.usedPercent >= 100 }
|
|
}
|
|
if provider == .copilot,
|
|
effectivePreference == .automatic,
|
|
let primary = snapshot.primary,
|
|
let secondary = snapshot.secondary
|
|
{
|
|
// In automatic mode Copilot can have one depleted lane while another still has quota.
|
|
return primary.usedPercent >= 100 && secondary.usedPercent >= 100
|
|
}
|
|
if provider == .cursor,
|
|
effectivePreference == .automatic
|
|
{
|
|
let percents = [
|
|
snapshot.primary?.usedPercent,
|
|
snapshot.secondary?.usedPercent,
|
|
snapshot.tertiary?.usedPercent,
|
|
].compactMap(\.self)
|
|
guard !percents.isEmpty else { return true }
|
|
return percents.allSatisfy { $0 >= 100 }
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
private nonisolated static func mostConstrainedAntigravityQuotaSummaryWindow(
|
|
snapshot: UsageSnapshot)
|
|
-> RateWindow?
|
|
{
|
|
let windows = self.antigravityRenderedQuotaSummaryWindows(snapshot: snapshot)
|
|
guard !windows.isEmpty else { return nil }
|
|
|
|
let usableWindows = windows.filter { $0.usedPercent < 100 }
|
|
if let maxUsable = usableWindows.max(by: { $0.usedPercent < $1.usedPercent }) {
|
|
return maxUsable
|
|
}
|
|
return windows.max(by: { $0.usedPercent < $1.usedPercent })
|
|
}
|
|
|
|
private nonisolated static func antigravityRenderedQuotaSummaryWindows(
|
|
snapshot: UsageSnapshot)
|
|
-> [RateWindow]
|
|
{
|
|
let windows = IconRemainingResolver.resolvedWindows(snapshot: snapshot, style: .antigravity)
|
|
return [windows.primary, windows.secondary].compactMap(\.self)
|
|
}
|
|
}
|