Skip to content
Merged
2 changes: 1 addition & 1 deletion Sources/CodexBar/InlineUsageDashboardContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ extension UsageMenuCardView.Model {
{
return Self.poeInlineDashboard(usage, now: input.now)
}
if [.codex, .claude, .vertexai, .bedrock, .cursor].contains(input.provider),
if [.codex, .claude, .vertexai, .bedrock, .cursor, .opencodego].contains(input.provider),
input.tokenCostInlineDashboardEnabled,
let tokenSnapshot = input.tokenSnapshot,
!tokenSnapshot.daily.isEmpty || tokenSnapshot.meteredCostUSD != nil
Expand Down
8 changes: 8 additions & 0 deletions Sources/CodexBar/PlanUtilizationHistoryChartMenuView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,10 @@ struct PlanUtilizationHistoryChartMenuView: View {
{
names.insert(.opus)
}
case .opencodego:
if snapshot.primary != nil { names.insert(.session) }
if snapshot.secondary != nil { names.insert(.weekly) }
if snapshot.tertiary != nil { names.insert(.monthly) }
default:
let windows = [snapshot.primary, snapshot.secondary, snapshot.tertiary].compactMap(\.self)
+ (snapshot.extraRateWindows?.filter(\.usageKnown).map(\.window) ?? [])
Expand Down Expand Up @@ -621,6 +625,8 @@ struct PlanUtilizationHistoryChartMenuView: View {
L(metadata?.sessionLabel ?? "Session")
case .weekly:
L(metadata?.weeklyLabel ?? "Weekly")
case .monthly:
metadata?.opusLabel ?? "Monthly"
case .opus:
metadata?.opusLabel ?? "Opus"
default:
Expand All @@ -641,6 +647,8 @@ struct PlanUtilizationHistoryChartMenuView: View {
0
case .weekly:
1
case .monthly:
2
case .opus:
2
default:
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/PlanUtilizationHistoryStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct PlanUtilizationSeriesName: RawRepresentable, Hashable, Codable, Expressib

static let session: Self = "session"
static let weekly: Self = "weekly"
static let monthly: Self = "monthly"
static let opus: Self = "opus"

func canonicalWindowMinutes(_ windowMinutes: Int) -> Int {
Expand Down
17 changes: 14 additions & 3 deletions Sources/CodexBar/StatusItemController+Menu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ extension StatusItemController {
guard let model = self.menuCardModel(for: context.selectedProvider) else { return false }
let renderedModel = self.menuCardRefreshMonitor.model(for: model.provider, fallback: model)
if context.openAIContext.hasOpenAIWebMenuItems ||
self.hasProviderNativeCostHistorySubmenu(provider: context.currentProvider)
self.requiresSectionedMenuForProviderDerivedCost(provider: context.currentProvider)
{
let webItems = OpenAIWebMenuItems(
hasUsageBreakdown: context.openAIContext.hasUsageBreakdown,
Expand Down Expand Up @@ -1476,7 +1476,11 @@ extension StatusItemController {
if provider == .openai {
return self.makeOpenAIAPIUsageSubmenu(provider: provider, width: width)
}
if UsageStore.tokenCostRequiresProviderSnapshot(provider) {
// Mistral's top usage pane has no rate-limit bars of its own, so its cost history hangs
// off this row instead. Other `tokenCostRequiresProviderSnapshot` providers (e.g.
// opencodego) show real rate-limit bars here and get their own "Cost" row instead
// (see `makeCostMenuCardItem`), matching Codex/Claude's structure.
if provider == .mistral {
return self.makeCostHistorySubmenu(provider: provider, width: width)
}
if provider == .zai {
Expand Down Expand Up @@ -1573,7 +1577,14 @@ extension StatusItemController {
provider == .openai && self.tokenSnapshotForCostHistorySubmenu(provider: provider)?.daily.isEmpty == false
}

private func hasProviderNativeCostHistorySubmenu(provider: UsageProvider) -> Bool {
/// Unlike `makeUsageSubmenu`'s and `tokenCostMenuSectionEnabled`'s provider checks, this one
/// intentionally reuses `tokenCostRequiresProviderSnapshot`: any provider whose cost is
/// sourced by projecting a snapshot field (rather than the CostUsageFetcher pipeline) can only
/// render that cost through `addMenuCardSections`'s sectioned layout, so the two concepts are
/// genuinely coupled here, not coincidentally aliased. The name is deliberately broader than
/// "top-pane submenu" — opencodego satisfies this via its collapsible "Cost" row, not a
/// provider-native top-pane submenu like openai/mistral.
private func requiresSectionedMenuForProviderDerivedCost(provider: UsageProvider) -> Bool {
UsageStore.tokenCostRequiresProviderSnapshot(provider) &&
self.tokenSnapshotForCostHistorySubmenu(provider: provider)?.daily.isEmpty == false
}
Expand Down
10 changes: 9 additions & 1 deletion Sources/CodexBar/StatusItemController+MenuCardModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,15 @@ extension StatusItemController {
tokenCostUsageEnabled: self.settings.isCostUsageEffectivelyEnabled(for: target),
codexLocalSessionCostLedgerEnabled: self.settings.codexLocalSessionCostLedgerEnabled,
tokenCostInlineDashboardEnabled: self.settings.costSummaryShowsInlineDashboard(for: target),
tokenCostMenuSectionEnabled: !UsageStore.tokenCostRequiresProviderSnapshot(target) &&
// openai/mistral's cost history always surfaces via the inline dashboard or a
// dedicated top-pane submenu (see `makeUsageSubmenu`), so they skip the generic
// "Cost" row. This must stay an explicit provider check rather than reusing
// `usesProviderCostHistoryAsPrimaryDashboard` (or `tokenCostRequiresProviderSnapshot`):
// both of those sets are shared with unrelated concerns (inline-dashboard eligibility,
// provider-derived snapshot sourcing) and gain members for reasons that have nothing to
// do with whether this row should show, silently disabling the Cost row for those
// providers too (e.g. groq's addition to the inline-dashboard set previously did this).
tokenCostMenuSectionEnabled: target != .mistral && target != .openai &&
self.settings.costSummaryShowsSubmenu(for: target),
costComparisonPeriodsEnabled: self.settings.costComparisonPeriodsEnabled,
showOptionalCreditsAndExtraUsage: self.settings.showOptionalCreditsAndExtraUsage,
Expand Down
6 changes: 5 additions & 1 deletion Sources/CodexBar/StatusItemController+OverviewSubmenus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ extension StatusItemController {
{
return submenu
}
if UsageStore.tokenCostRequiresProviderSnapshot(provider),
// Mistral's top usage pane has no rate-limit bars of its own, so its Overview row always
// prioritizes cost history too. Other `tokenCostRequiresProviderSnapshot` providers (e.g.
// opencodego) show real rate-limit bars and should fall through to the settings-gated
// check below, same as Codex/Claude (see StatusItemController+Menu.swift's makeUsageSubmenu).
if provider == .mistral,
let submenu = self.makeCostHistorySubmenu(provider: provider, width: width)
{
return submenu
Expand Down
8 changes: 6 additions & 2 deletions Sources/CodexBar/UsageStore+PlanUtilization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension UsageStore {

func supportsPlanUtilizationHistory(for provider: UsageProvider) -> Bool {
switch provider {
case .codex, .claude, .antigravity:
case .codex, .claude, .antigravity, .opencodego:
true
default:
if self.planUtilizationHistory[provider]?.isEmpty == false {
Expand Down Expand Up @@ -338,7 +338,7 @@ extension UsageStore {

private func shouldRecordPlanUtilizationHistory(for provider: UsageProvider) -> Bool {
switch provider {
case .codex, .claude, .antigravity:
case .codex, .claude, .antigravity, .opencodego:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope OpenCode Go history by source and workspace

When Auto mode falls back from workspace-scoped web usage to the device-wide local estimate, or the configured workspace changes, these samples are all persisted because both OpenCode Go paths produce UsageSnapshots with identity: nil; resolvePlanUtilizationAccountKey therefore puts them in the same unscoped bucket. The Plan Usage chart consequently connects percentages belonging to different scopes/accounts. Attach a stable workspace/source owner to the snapshot, or avoid recording identityless OpenCode Go samples.

Useful? React with 👍 / 👎.

true
default:
self.settings.historicalTrackingEnabled
Expand Down Expand Up @@ -578,6 +578,10 @@ extension UsageStore {
appendWindow(snapshot.primary, name: .session)
appendWindow(snapshot.secondary, name: .weekly)
appendWindow(snapshot.tertiary, name: .opus)
case .opencodego:
appendWindow(snapshot.primary, name: .session)
appendWindow(snapshot.secondary, name: .weekly)
appendWindow(snapshot.tertiary, name: .monthly)
case .antigravity:
if forSessionEquivalents {
guard let windows = self.sessionEquivalentWindows(provider: provider, snapshot: snapshot) else {
Expand Down
10 changes: 9 additions & 1 deletion Sources/CodexBar/UsageStore+TokenCost.swift
Original file line number Diff line number Diff line change
Expand Up @@ -403,14 +403,22 @@ extension UsageStore {
snapshot?.openAIAPIUsage?.toCostUsageTokenSnapshot()
case .mistral:
snapshot?.mistralUsage?.toCostUsageTokenSnapshot(historyDays: self.settings.costUsageHistoryDays)
case .opencodego:
// Web-only source mode and machines with no readable local database leave
// `opencodegoUsage.daily` empty; a non-nil-but-dataless projection would still
// surface a Cost row whose history submenu has nothing to render.
snapshot?.opencodegoUsage.flatMap { usage in
usage.daily.isEmpty ? nil : usage
.toCostUsageTokenSnapshot(historyDays: self.settings.costUsageHistoryDays)
}
default:
nil
}
}

nonisolated static func tokenCostRequiresProviderSnapshot(_ provider: UsageProvider) -> Bool {
switch provider {
case .mistral, .openai:
case .mistral, .openai, .opencodego:
true
default:
false
Expand Down
Loading