feat: model-centric usage & spend view across providers (Scope B from #2257)#2322
feat: model-centric usage & spend view across providers (Scope B from #2257)#2322Yuxin-Qiao wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0bb85adb5a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .frame(maxWidth: .infinity, minHeight: 220) | ||
| } | ||
| } else { | ||
| let modelHostGroupID = self.controller.model.groups.first?.id |
There was a problem hiding this comment.
Render token-only model history without a currency group
When local Kimi history is the only available source, KimiCodeSessionScanner emits currencyCode: "XXX" and SpendDashboardModel.currencyCode filters that out of groups, so this host-group path is never reached and the groups.isEmpty branch above shows “No local cost history yet” despite model.modelAnalysis containing Kimi rows. Kimi-only users therefore cannot see the new model history unless there is also a priced currency group; render the global model section outside currency groups or synthesize a host for token-only sources.
Useful? React with 👍 / 👎.
|
Codex review: needs maintainer review before merge. Reviewed July 19, 2026, 6:00 AM ET / 10:00 UTC. Summary Reproducibility: not applicable. this is a new dashboard capability rather than a report of broken existing behavior. The contributor supplied direct screenshots of the changed 7-day, 30-day, and All states plus a manual macOS verification claim. Review metrics: 2 noteworthy metrics.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review detailsBest possible solution: Decide and document the supported aggregation contract first: either land this as clearly source-additive exact-name reporting with prominent limits, or narrow it until canonical identity and double-counting handling have a maintainer-approved design. Do we have a high-confidence way to reproduce the issue? Not applicable: this is a new dashboard capability rather than a report of broken existing behavior. The contributor supplied direct screenshots of the changed 7-day, 30-day, and All states plus a manual macOS verification claim. Is this the best way to solve the issue? Unclear. The UI and data path are coherent, but the linked discussion shows that exact-name aggregation without canonical aliases or source deduplication is a product-policy choice that needs explicit approval. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 1d307430a13d. Label changesLabel changes:
Label justifications:
Evidence reviewedWhat I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (6 earlier review cycles)
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 18a58d70fb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| inputs.append(SpendDashboardModel.ProviderInput( | ||
| id: "kimi:local", | ||
| provider: .kimi, | ||
| displayName: "Kimi Code CLI", | ||
| modelProviderName: ProviderDescriptorRegistry.descriptor(for: .kimi).metadata.displayName, | ||
| snapshot: snapshot)) |
There was a problem hiding this comment.
Preserve Kimi results through forced refresh reconciliation
When the user clicks Refresh, the controller runs a .forceRefresh load and then a .captureOnly reconciliation; merge(outcome:capture:) only carries forced loader outputs whose ids are in forcedCodexIDs or barrierFailed, so this new kimi:local input is dropped because Kimi has no captured publication and is not a Codex account. In a Kimi-enabled dashboard, a successful manual refresh therefore removes the freshly scanned Kimi model history from loadedInputs instead of displaying it.
Useful? React with 👍 / 👎.
811bc6f to
92714de
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 92714def17
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| @State private var selectedDay: Date? | ||
|
|
||
| private var presentation: SpendModelsPresentation { | ||
| SpendModelsPresentation(analysis: self.analysis, metric: .tokens) |
There was a problem hiding this comment.
Render spend-only model history
When the model data comes from a cost-only source such as Bedrock, the breakdowns carry costUSD but no totalTokens, so hard-wiring the section to .tokens makes every row value nil and presentation.points empty; the card then renders “No model-level history” even though analysis.dailyValues contains spend values. Choose .estimatedSpend when token coverage is unavailable, or expose the intended metric switch, so spend-only providers remain visible.
Useful? React with 👍 / 👎.
37f7807 to
fe82c42
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 37f78075d2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for line in data.split(separator: 0x0A, omittingEmptySubsequences: true) { | ||
| guard let event = try? decoder.decode(WireEvent.self, from: Data(line)), | ||
| event.type == "usage.record", | ||
| event.usageScope == "turn", |
There was a problem hiding this comment.
Accept unscoped Kimi usage records
Kimi Code 0.14 wire.jsonl rows in the same ~/.kimi-code/sessions/.../agents/... layout can omit usageScope (see ccusage#1261), while scoped session rows are the cumulative ones to skip. With the strict event.usageScope == "turn" guard, those installs have every valid usage row ignored and scan returns nil/no local model history; please treat a missing scope as a turn record and continue excluding explicit session rows.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fe82c42ca0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| fileManager: FileManager = .default, | ||
| historyDays: Int = defaultHistoryDays, | ||
| now: Date = Date(), | ||
| calendar: Calendar = .current) -> CostUsageTokenSnapshot? |
There was a problem hiding this comment.
Use Gregorian dates for Kimi history keys
When the user’s macOS calendar is non-Gregorian (for example Buddhist), this default calendar makes CostUsageLocalDay.key write Kimi daily entries with years such as 2569. The spend dashboard later normalizes and parses provider dates with a Gregorian calendar, so those Kimi entries fall outside the current 2026 window and the local model history disappears. Please generate Kimi snapshot day keys with a Gregorian calendar that preserves the current time zone, as the dashboard model does.
Useful? React with 👍 / 👎.
fe82c42 to
ff057d5
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ff057d5869
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| private static func normalizedDays(_ value: Int) -> Int { | ||
| value == 7 ? 7 : 30 | ||
| [7, 30, 365].contains(value) ? value : 30 |
There was a problem hiding this comment.
Keep the overview range in sync with the picker
When settingsSpendDashboardDays is already 365 (or any caller invokes selectDays(365), which this now persists), the controller builds 365-day currency/provider totals, but the header picker in PreferencesSpendDashboardPane still only offers 7 and 30 tags. The pane can therefore open with no matching segmented selection while silently showing an unsupported 365-day overview; either keep this normalization to the picker’s 7/30 options or add the All option to that picker.
Useful? React with 👍 / 👎.
Implements Scope B from steipete#2257. - Replace per-currency "model breakdown unavailable" panel with an embedded Models card showing daily stacked usage bars and a full model ranking. - Merge models across providers by trimmed, case-insensitive exact name matching; keep token aggregation separate from cost to avoid cross-currency sums. - Add independent 7d / 30d / All range control for the model card without affecting the existing Overview 30d spend semantics. - Add Kimi Code session scanner so Kimi token-only history can contribute to the cross-provider model analysis. - Localize new UI strings across all supported languages. - Adopt upstream completeModelSummaries filtering for models list while keeping modelAnalysis built from all summaries for partial coverage. - Regenerate CodexParserHash for merged Vendored/CostUsage sources. Tests: SpendModelsPresentationTests, SpendDashboardModelTests, SpendDashboardKimiModelTests, KimiCodeSessionScannerTests, CostUsageDailyReportMergeTests, ShareStatsTests, UserFacingLocalizationCoverageTests (63 tests). Co-authored-by: Cursor <cursoragent@cursor.com>
ff057d5 to
7321949
Compare
Summary
This PR implements Scope B from #2257: a model-centric usage & spend view embedded in the existing Usage & Spend dashboard.
Instead of the previous per-currency "Model breakdown unavailable" placeholder, users now see a Models card with:
The card sits between "By subscription" and "Daily estimated spend" and does not add a new tab, route, or Overview navigation.
Design notes
Test plan
swift test --filter "SpendModelsPresentationTests|SpendDashboardModelTests|SpendDashboardKimiModelTests|KimiCodeSessionScannerTests|CostUsageDailyReportMergeTests|ShareStatsTests|UserFacingLocalizationCoverageTests|LocalizationLanguageCatalogTests|CostUsageFetcherTests"— 106 tests pass.swift buildsucceeds.Screenshots
7d
30d
All
Fixes #2257