Skip to content
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

### Fixed
- Codex menu: hide error-only optional Credits and OpenAI web setup diagnostics while keeping them visible in provider Settings.
- Codex quotas: show the session quota as unavailable while an exhausted weekly limit is still binding, including menu-bar icons and widgets. Thanks @Yuxin-Qiao!
- Codex cost history: reuse cached aggregate pricing and one pricing catalog across daily and project reports, carry fresh cache state across launches, and treat unpriced models as migrated, avoiding repeated row scans, filesystem work, and duplicate background scans on large local histories.
- Kimi: show the five-hour rate limit before the weekly quota while preserving existing menu-bar metric preferences. Thanks @Zihao-Qi!

Expand Down
28 changes: 17 additions & 11 deletions Sources/CodexBar/IconRemainingResolver.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import CodexBarCore
import Foundation

enum IconRemainingResolver {
private static let visibleZeroPercent = 0.0001
Expand All @@ -7,7 +8,7 @@ enum IconRemainingResolver {
private static let sessionWindowMinutes = 5 * 60
private static let weeklyWindowMinutes = 7 * 24 * 60

private static func codexProjection(snapshot: UsageSnapshot) -> CodexConsumerProjection {
private static func codexProjection(snapshot: UsageSnapshot, now: Date) -> CodexConsumerProjection {
CodexConsumerProjection.make(
surface: .menuBar,
context: CodexConsumerProjection.Context(
Expand All @@ -19,12 +20,12 @@ enum IconRemainingResolver {
rawDashboardError: nil,
dashboardAttachmentAuthorized: false,
dashboardRequiresLogin: false,
now: snapshot.updatedAt))
now: now))
}

private static func codexVisibleWindows(snapshot: UsageSnapshot) -> [RateWindow] {
let projection = self.codexProjection(snapshot: snapshot)
return projection.visibleRateLanes.compactMap { projection.rateWindow(for: $0) }
private static func codexVisibleWindows(snapshot: UsageSnapshot, now: Date) -> [RateWindow] {
let projection = self.codexProjection(snapshot: snapshot, now: now)
return projection.visibleRateLanes.compactMap { projection.menuBarSelectableRateWindow(for: $0) }
}

private static func antigravityQuotaSummaryWindows(
Expand Down Expand Up @@ -67,7 +68,8 @@ enum IconRemainingResolver {
static func resolvedWindows(
snapshot: UsageSnapshot,
style: IconStyle,
secondaryOverrideWindowID: String? = nil)
secondaryOverrideWindowID: String? = nil,
now: Date = Date())
-> (primary: RateWindow?, secondary: RateWindow?)
{
if style == .perplexity {
Expand All @@ -82,7 +84,7 @@ enum IconRemainingResolver {
?? (primary: nil, secondary: nil)
}
if style == .codex {
let windows = self.codexVisibleWindows(snapshot: snapshot)
let windows = self.codexVisibleWindows(snapshot: snapshot, now: now)
return (
primary: windows.first,
secondary: windows.dropFirst().first)
Expand All @@ -103,13 +105,15 @@ enum IconRemainingResolver {
static func resolvedRemaining(
snapshot: UsageSnapshot,
style: IconStyle,
secondaryOverrideWindowID: String? = nil)
secondaryOverrideWindowID: String? = nil,
now: Date = Date())
-> (primary: Double?, secondary: Double?)
{
let windows = self.resolvedWindows(
snapshot: snapshot,
style: style,
secondaryOverrideWindowID: secondaryOverrideWindowID)
secondaryOverrideWindowID: secondaryOverrideWindowID,
now: now)
return (
primary: windows.primary?.remainingPercent,
secondary: windows.secondary?.remainingPercent)
Expand All @@ -120,13 +124,15 @@ enum IconRemainingResolver {
style: IconStyle,
showUsed: Bool,
renderingStyle: IconStyle? = nil,
secondaryOverrideWindowID: String? = nil)
secondaryOverrideWindowID: String? = nil,
now: Date = Date())
-> (primary: Double?, secondary: Double?)
{
let windows = Self.resolvedWindows(
snapshot: snapshot,
style: style,
secondaryOverrideWindowID: secondaryOverrideWindowID)
secondaryOverrideWindowID: secondaryOverrideWindowID,
now: now)
var percents = (
primary: showUsed ? windows.primary?.usedPercent : windows.primary?.remainingPercent,
secondary: showUsed ? windows.secondary?.usedPercent : windows.secondary?.remainingPercent)
Expand Down
10 changes: 7 additions & 3 deletions Sources/CodexBar/MenuContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -231,19 +231,23 @@ struct StatusIconView: View {
}

private var icon: NSImage {
let now = Date()
let snapshot = self.store.snapshot(for: self.provider)
let remaining = snapshot.map {
IconRemainingResolver.resolvedRemaining(snapshot: $0, style: self.store.style(for: self.provider))
IconRemainingResolver.resolvedRemaining(
snapshot: $0,
style: self.store.style(for: self.provider),
now: now)
}
let creditsProjection = self.store.codexConsumerProjectionIfNeeded(
for: self.provider,
surface: .menuBar,
snapshotOverride: snapshot,
now: snapshot?.updatedAt ?? Date())
now: now)
let creditsRemaining = creditsProjection?.menuBarFallback == .creditsBalance
? self.store.codexMenuBarCreditsRemaining(
snapshotOverride: snapshot,
now: snapshot?.updatedAt ?? Date())
now: now)
: nil
return IconRenderer.makeIcon(
primaryRemaining: remaining?.primary,
Expand Down
142 changes: 137 additions & 5 deletions Sources/CodexBar/Providers/Codex/CodexConsumerProjection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ struct CodexConsumerProjection {
private let rateWindowsByLane: [RateLane: RateWindow]
private let codeReviewRemainingPercent: Double?
private let codeReviewLimit: RateWindow?
private let evaluationTime: Date

static func make(surface: Surface, context: Context) -> CodexConsumerProjection {
let allowsLiveAdjuncts = surface != .overrideCard
Expand Down Expand Up @@ -290,20 +291,64 @@ struct CodexConsumerProjection {
credits: creditsProjection,
menuBarFallback: self.menuBarFallback(
creditsRemaining: creditsProjection?.remaining,
rateWindowsByLane: rateWindowsByLane),
rateWindowsByLane: rateWindowsByLane,
evaluationTime: context.now),
userFacingErrors: userFacingErrors,
canShowBuyCredits: canShowBuyCredits,
hasUsageBreakdown: hasUsageBreakdown,
hasCreditsHistory: hasCreditsHistory,
rateWindowsByLane: rateWindowsByLane,
codeReviewRemainingPercent: dashboardVisibility == .attached ? dashboard?.codeReviewRemainingPercent : nil,
codeReviewLimit: dashboardVisibility == .attached ? dashboard?.codeReviewLimit : nil)
codeReviewLimit: dashboardVisibility == .attached ? dashboard?.codeReviewLimit : nil,
evaluationTime: context.now)
}

func rateWindow(for lane: RateLane) -> RateWindow? {
guard let window = self.rateWindowsByLane[lane] else { return nil }
switch lane {
case .session:
return Self.sessionDisplayWindow(
session: window,
weekly: self.rateWindowsByLane[.weekly],
evaluationTime: self.evaluationTime)
case .weekly:
return window
}
}

func sourceRateWindow(for lane: RateLane) -> RateWindow? {
self.rateWindowsByLane[lane]
}

func menuBarSelectableRateWindow(for lane: RateLane) -> RateWindow? {
guard let window = self.rateWindow(for: lane) else { return nil }
guard window.remainingPercent <= 0,
let resetAt = window.resetsAt,
resetAt <= self.evaluationTime
else {
return window
}
return nil
}

var nextMenuBarStateChangeAt: Date? {
self.rateWindowsByLane.values.compactMap { window in
guard window.remainingPercent <= 0,
let resetAt = window.resetsAt,
resetAt > self.evaluationTime
else {
return nil
}
return resetAt
}.min()
}

var hasBindingWeeklyCap: Bool {
Self.weeklyCapsSession(
weekly: self.rateWindowsByLane[.weekly],
evaluationTime: self.evaluationTime)
}

func remainingPercent(for metric: SupplementalMetric) -> Double? {
switch metric {
case .codeReview:
Expand Down Expand Up @@ -399,18 +444,72 @@ struct CodexConsumerProjection {
return (lane, window)
}

/// When Codex's weekly lane is exhausted, it is the binding cap: session quota cannot be used until
/// the weekly window resets, even if the API still reports room in the 5-hour bucket.
private static func weeklyCapsSession(weekly: RateWindow?, evaluationTime: Date) -> Bool {
guard let weekly else { return false }
guard weekly.remainingPercent <= 0 else { return false }
return weekly.resetsAt.map { $0 > evaluationTime } ?? true
}

private static func sessionDisplayWindow(
session: RateWindow,
weekly: RateWindow?,
evaluationTime: Date) -> RateWindow
{
guard self.weeklyCapsSession(weekly: weekly, evaluationTime: evaluationTime) else {
return session
}
let reset = self.bindingReset(
session: session,
weekly: weekly,
evaluationTime: evaluationTime)
return RateWindow(
usedPercent: max(session.usedPercent, 100),
windowMinutes: session.windowMinutes,
resetsAt: reset.date,
resetDescription: reset.description,
nextRegenPercent: session.nextRegenPercent,
isSyntheticPlaceholder: session.isSyntheticPlaceholder)
}

private static func bindingReset(
session: RateWindow,
weekly: RateWindow?,
evaluationTime: Date) -> (date: Date?, description: String?)
{
guard let weekly else { return (nil, nil) }
let sessionIsExhausted = session.remainingPercent <= 0 &&
(session.resetsAt.map { $0 > evaluationTime } ?? true)
guard sessionIsExhausted else {
return (weekly.resetsAt, weekly.resetDescription)
}
guard let sessionReset = session.resetsAt, let weeklyReset = weekly.resetsAt else {
return (nil, nil)
}
if sessionReset > weeklyReset {
return (sessionReset, session.resetDescription)
}
return (weeklyReset, weekly.resetDescription)
}

private static func menuBarFallback(
creditsRemaining: Double?,
rateWindowsByLane: [RateLane: RateWindow]) -> MenuBarFallback
rateWindowsByLane: [RateLane: RateWindow],
evaluationTime: Date) -> MenuBarFallback
{
guard let creditsRemaining, creditsRemaining > 0 else { return .none }
let hasExhaustedLane = rateWindowsByLane.values.contains { $0.remainingPercent <= 0 }
let hasExhaustedLane = rateWindowsByLane.values.contains {
$0.remainingPercent <= 0 && ($0.resetsAt.map { $0 > evaluationTime } ?? true)
}
let hasNoRateWindows = rateWindowsByLane.isEmpty
return (hasExhaustedLane || hasNoRateWindows) ? .creditsBalance : .none
}

var hasExhaustedRateLane: Bool {
self.rateWindowsByLane.values.contains { $0.remainingPercent <= 0 }
self.rateWindowsByLane.values.contains {
$0.remainingPercent <= 0 && ($0.resetsAt.map { $0 > self.evaluationTime } ?? true)
}
}
}

Expand Down Expand Up @@ -459,4 +558,37 @@ extension UsageStore {
guard projection.menuBarFallback == .creditsBalance else { return nil }
return projection.credits?.remaining
}

func codexMenuBarMetricWindow(snapshot: UsageSnapshot, now: Date = Date()) -> RateWindow? {
let projection = self.codexConsumerProjection(
surface: .menuBar,
snapshotOverride: snapshot,
now: now)
let windows = projection.visibleRateLanes.compactMap {
projection.menuBarSelectableRateWindow(for: $0)
}
let first = windows.first
let second = windows.dropFirst().first

switch self.settings.menuBarMetricPreference(for: .codex, snapshot: snapshot) {
case .secondary, .tertiary:
return second ?? first
case .extraUsage:
return first
case .average:
guard self.settings.menuBarMetricSupportsAverage(for: .codex),
let primary = first,
let secondary = second
else {
return first
}
let usedPercent = (primary.usedPercent + secondary.usedPercent) / 2
return RateWindow(
usedPercent: usedPercent, windowMinutes: nil, resetsAt: nil, resetDescription: nil)
case .primaryAndSecondary:
return windows.prefix(2).max(by: { $0.usedPercent < $1.usedPercent })
case .automatic, .primary, .monthlyPlan:
return first
}
}
}
Loading