Skip to content

Commit 31638de

Browse files
authored
Merge pull request #3 from pj-workspace/fix/auto-pool-spend-display
fix: Auto pool spend shows $0 when bonusSpend is absent
2 parents 1913c12 + c415245 commit 31638de

4 files changed

Lines changed: 98 additions & 22 deletions

File tree

‎Sources/CursorUsageMenuBar/Services/CursorAPIClient.swift‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ struct CursorAPIClient: Sendable {
6060
period: periodUsage,
6161
aggregated: aggregatedUsage
6262
)
63-
let usagePools = UsageAnalytics.resolveUsagePools(summary: summary, period: periodUsage)
63+
let usagePools = UsageAnalytics.resolveUsagePools(
64+
summary: summary,
65+
period: periodUsage,
66+
aggregated: aggregatedUsage
67+
)
6468
let modelBreakdown = UsageAnalytics.modelBreakdown(from: aggregatedUsage, events: [])
6569

6670
return DashboardSnapshot(
@@ -79,7 +83,11 @@ struct CursorAPIClient: Sendable {
7983
dailyModelShare: [],
8084
cumulativeModelSpend: [],
8185
spendSummary: UsageAnalytics.spendSummary(from: periodUsage, aggregated: aggregatedUsage),
82-
spendingBreakdown: UsageAnalytics.spendingBreakdown(from: periodUsage, summary: summary),
86+
spendingBreakdown: UsageAnalytics.spendingBreakdown(
87+
from: periodUsage,
88+
summary: summary,
89+
aggregated: aggregatedUsage
90+
),
8391
todayStats: .empty,
8492
partialErrors: partialErrors,
8593
billingCycleStartMs: startMsString,

‎Sources/CursorUsageMenuBar/Services/UsageAnalytics.swift‎

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import Foundation
22

33
enum UsageAnalytics {
4-
/// 双用量池:API 按 apiPercentUsed × limit 计算;Auto+Composer 按 bonusSpend
4+
/// 双用量池:API 按 apiPercentUsed × limit 计算;Auto+Composer 按第一方模型花费汇总
55
static func resolveUsagePools(
66
summary: UsageSummary?,
7-
period: PeriodUsageResponse?
7+
period: PeriodUsageResponse?,
8+
aggregated: AggregatedUsageResponse? = nil
89
) -> [UsagePoolMetrics] {
910
let plan = summary?.billingPlan
1011
let periodPlan = period?.planUsage
12+
let autoBucketModels = period?.autoBucketModels
1113

1214
let apiPercent = plan?.apiPercentUsed ?? periodPlan?.apiPercentUsed
1315
let autoPercent = plan?.autoPercentUsed ?? periodPlan?.autoPercentUsed
@@ -17,14 +19,13 @@ enum UsageAnalytics {
1719
limitCents: apiLimit,
1820
explicitSpend: periodPlan?.apiSpend
1921
)
20-
let autoSpend = periodPlan?.bonusSpend
21-
?? periodPlan?.autoSpend
22-
?? {
23-
guard let total = periodPlan?.totalSpend, let included = periodPlan?.includedSpend else {
24-
return nil
25-
}
26-
return max(0, total - included)
27-
}()
22+
let autoSpend = resolveAutoSpendCents(
23+
summary: summary,
24+
period: period,
25+
aggregated: aggregated,
26+
autoBucketModels: autoBucketModels,
27+
apiUsedCents: apiUsed
28+
)
2829

2930
let apiMessage = summary?.namedModelSelectedDisplayMessage
3031
?? period?.namedModelSelectedDisplayMessage
@@ -76,6 +77,58 @@ enum UsageAnalytics {
7677
return explicitSpend
7778
}
7879

80+
/// Auto 池花费:接口常不再返回 bonusSpend;优先 aggregated 第一方汇总,其次 total − API 已用
81+
static func resolveAutoSpendCents(
82+
summary: UsageSummary?,
83+
period: PeriodUsageResponse?,
84+
aggregated: AggregatedUsageResponse?,
85+
autoBucketModels: [String]?,
86+
apiUsedCents: Double?
87+
) -> Double? {
88+
let periodPlan = period?.planUsage
89+
90+
if let bonusSpend = periodPlan?.bonusSpend, bonusSpend > 0 {
91+
return bonusSpend
92+
}
93+
if let autoSpend = periodPlan?.autoSpend, autoSpend > 0 {
94+
return autoSpend
95+
}
96+
97+
if let fromAggregated = poolSpendCents(
98+
from: aggregated,
99+
autoBucketModels: autoBucketModels,
100+
pool: .firstParty
101+
) {
102+
return fromAggregated
103+
}
104+
105+
let totalSpend = periodPlan?.totalSpend
106+
?? aggregated?.totalCostCents
107+
?? summary?.billingPlan?.used
108+
if let totalSpend, let apiUsedCents, totalSpend >= apiUsedCents {
109+
let derived = totalSpend - apiUsedCents
110+
if derived > 0 { return derived }
111+
}
112+
113+
return nil
114+
}
115+
116+
static func poolSpendCents(
117+
from aggregated: AggregatedUsageResponse?,
118+
autoBucketModels: [String]?,
119+
pool: ModelPricingCatalog.Pool
120+
) -> Double? {
121+
guard let aggregations = aggregated?.aggregations, !aggregations.isEmpty else { return nil }
122+
123+
let total = aggregations.reduce(0.0) { sum, item in
124+
guard let model = item.modelIntent else { return sum }
125+
let itemPool = resolveUsagePool(for: model, autoBucketModels: autoBucketModels)
126+
guard itemPool == pool else { return sum }
127+
return sum + (item.totalCents ?? 0)
128+
}
129+
return total > 0 ? total : nil
130+
}
131+
79132
/// Billing 总口径:totalPercentUsed + 全周期花费
80133
static func resolveBillingBaseline(
81134
summary: UsageSummary?,
@@ -88,7 +141,7 @@ enum UsageAnalytics {
88141
?? periodPlan?.totalPercentUsed
89142
?? 0
90143
let canonicalCostCents = aggregated?.totalCostCents ?? periodPlan?.totalSpend
91-
let pools = resolveUsagePools(summary: summary, period: period)
144+
let pools = resolveUsagePools(summary: summary, period: period, aggregated: aggregated)
92145

93146
guard percent > 0 || canonicalCostCents != nil || pools.contains(where: { $0.percent != nil }) else {
94147
return nil
@@ -685,9 +738,11 @@ enum UsageAnalytics {
685738

686739
static func spendingBreakdown(
687740
from period: PeriodUsageResponse?,
688-
summary: UsageSummary?
741+
summary: UsageSummary?,
742+
aggregated: AggregatedUsageResponse? = nil
689743
) -> [SpendingBreakdownItem] {
690744
var items: [SpendingBreakdownItem] = []
745+
let periodPlan = period?.planUsage
691746

692747
if let plan = summary?.billingPlan {
693748
if let total = plan.totalPercentUsed {
@@ -696,18 +751,29 @@ enum UsageAnalytics {
696751
id: "total",
697752
label: "Billing 总计",
698753
percent: total,
699-
spendCents: period?.planUsage?.totalSpend,
754+
spendCents: periodPlan?.totalSpend ?? aggregated?.totalCostCents,
700755
colorName: "blue"
701756
)
702757
)
703758
}
704759
if let auto = plan.autoPercentUsed {
760+
let apiUsed = resolveAPIUsedCents(
761+
percent: plan.apiPercentUsed ?? periodPlan?.apiPercentUsed,
762+
limitCents: plan.limit ?? periodPlan?.limit,
763+
explicitSpend: periodPlan?.apiSpend
764+
)
705765
items.append(
706766
SpendingBreakdownItem(
707767
id: "auto",
708768
label: "Auto + Composer",
709769
percent: auto,
710-
spendCents: period?.planUsage?.bonusSpend,
770+
spendCents: resolveAutoSpendCents(
771+
summary: summary,
772+
period: period,
773+
aggregated: aggregated,
774+
autoBucketModels: period?.autoBucketModels,
775+
apiUsedCents: apiUsed
776+
),
711777
colorName: "purple"
712778
)
713779
)
@@ -716,7 +782,7 @@ enum UsageAnalytics {
716782
let apiUsed = resolveAPIUsedCents(
717783
percent: api,
718784
limitCents: plan.limit,
719-
explicitSpend: period?.planUsage?.apiSpend
785+
explicitSpend: periodPlan?.apiSpend
720786
)
721787
items.append(
722788
SpendingBreakdownItem(

‎Sources/CursorUsageMenuBar/ViewModels/UsageViewModel.swift‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ final class UsageViewModel: ObservableObject {
5252
dashboard.usagePools.first(where: { $0.id == "api" })?.usedCents
5353
}
5454

55+
var autoPoolSpendCents: Double? {
56+
dashboard.usagePools.first(where: { $0.id == "auto" })?.spendCents
57+
}
58+
5559
var apiPoolLimitCents: Double? {
5660
dashboard.usagePools.first(where: { $0.id == "api" })?.limitCents
5761
?? summary?.planLimit

‎Sources/CursorUsageMenuBar/Views/DashboardView.swift‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,7 @@ struct DashboardView: View {
198198
)
199199
PanelRow(
200200
label: l10n.t(.autoBonusSpend),
201-
value: viewModel.formattedCurrencyFromCents(
202-
viewModel.dashboard.periodUsage?.planUsage?.bonusSpend
203-
)
201+
value: viewModel.formattedCurrencyFromCents(viewModel.autoPoolSpendCents)
204202
)
205203
PanelRow(
206204
label: l10n.t(.apiIncludedLimit),
@@ -323,7 +321,7 @@ struct DashboardView: View {
323321
)
324322
PanelRow(
325323
label: l10n.t(.autoBonusSpend),
326-
value: viewModel.formattedCurrencyFromCents(plan.bonusSpend)
324+
value: viewModel.formattedCurrencyFromCents(viewModel.autoPoolSpendCents)
327325
)
328326
}
329327
}
@@ -442,7 +440,7 @@ struct DashboardView: View {
442440
)
443441
PanelRow(
444442
label: l10n.t(.autoBonusSpend),
445-
value: viewModel.formattedCurrencyFromCents(plan.bonusSpend)
443+
value: viewModel.formattedCurrencyFromCents(viewModel.autoPoolSpendCents)
446444
)
447445
}
448446
}

0 commit comments

Comments
 (0)