feat(core): local real-usage data layer — unified event engine + registerable scanners + pricing foundation - #2527
Conversation
Split out the data layer from the Usage & Spend work so UI modules can land as small follow-up PRs on top of it. No UI / App-target changes. - Per-tool local session scanners: Antigravity, Gemini CLI, Kimi Code, MiniMax, OpenCode, Qwen Code (WAL-aware SQLite, incremental source fingerprints, Gregorian-day bucketing). - Pricing: models.dev catalog + Google/ThirdParty tiers, routing-prefix stripping, overflow-safe thresholds, provider-reported vs estimated cost. - Core plumbing: cost-usage scanner/cache helpers, subagent rollout shape, parser hash, branding/descriptor updates. Foundation for the dashboard UI; behavior additive and backward compatible. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Codex review: needs changes before merge. Reviewed August 2, 2026, 12:22 PM ET / 16:22 UTC. ClawSweeper reviewWhat this changesAdds a shared local usage-event aggregation and pricing layer, registerable scanners for several developer-tool histories, and visible progress during full Codex history refreshes. Merge readinessThis PR is still necessary as the proposed local Usage & Spend data-layer split, but two previously identified current-head defects remain: ZCode is not declared through the provider capability path, and cache-creation tokens do not affect long-context rate selection. The supplied proof directly shows the manual scan-progress UI and a populated dashboard, while the checkout’s missing PR-head object prevents an independent line-by-line branch read. Priority: P2 Review scores
Verification
How this fits togetherCodexBar’s Usage & Spend feature combines provider data and local tool histories into token/cost snapshots for the dashboard and refresh pipeline. Provider descriptors decide which sources are eligible; scanners parse local data, the shared engine aggregates it, and the dashboard presents the resulting history. flowchart LR
A[Local tool histories] --> B[Registered scanners]
B --> C[Unified usage events]
C --> D[Aggregation and pricing]
D --> E[Provider snapshots]
E --> F[Dashboard and token refresh]
G[Manual refresh] --> B
Decision needed
Why: The mechanical fix is small, but whether ZCode belongs under the Z.ai provider is a user-visible provider-product contract and should be confirmed by the owner of that descriptor. Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Copy recommended automerge instructionTechnical reviewBest possible solution: Declare ZCode on the intended Z.ai local-history capability, include cache-creation tokens in overflow-safe long-context threshold selection, and add focused regressions proving both dashboard eligibility and the pricing boundary before landing this data-layer split. Do we have a high-confidence way to reproduce the issue? Yes for the two blockers from source and supplied current-head review evidence: the descriptor path excludes ZCode when undeclared, and the pricing threshold omits cache creation. A live branch run was not possible because the PR-head object is unavailable in this checkout. Is this the best way to solve the issue? No: the current branch needs the two narrow corrections before it is the best landing path. The shared scanner/aggregation design remains a plausible maintainable direction once the provider contract is explicitly approved and the pricing boundary is fixed. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 1fd7ec83b5d9. LabelsLabel justifications:
EvidenceAcceptance criteria:
What I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (17 earlier review cycles; latest 8 shown)
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 854090a0b0
ℹ️ 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".
| supportsTokenCost: false, | ||
| localHistorySources: [.geminiCLI], |
There was a problem hiding this comment.
Wire local history sources into token-cost loading
For every newly opted-in provider, including Gemini, supportsTokenCost remains false and no production code consumes localHistorySources: SpendDashboardController.costCapableProviders filters these providers out, UsageStore.refreshTokenUsage resets their token state, and CostUsageFetcher.supportsTokenSnapshot rejects them if called directly. Consequently the six new scanners are reachable only from tests and users never see their local history; the dashboard and loading pipeline need to use supportsDashboardHistory and dispatch the declared adapters.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 3a22838. The token refresh pipeline now uses supportsDashboardHistory: UsageStore.refreshTokenUsage no longer resets local-history providers, SettingsStore.isCostUsageEffectivelyEnabled admits them, and CostUsageFetcher.supportsTokenSnapshot plus SpendDashboardController.costCapableProviders already dispatch the registered local scanners. Covered by UsageStoreManualTokenRefreshTests.
| if let cost = Self.estimatedCost( | ||
| model: model, | ||
| usage: usage, | ||
| pricingDate: pricingDate, | ||
| modelsDevCatalog: modelsDevCatalog, | ||
| modelsDevCacheRoot: modelsDevCacheRoot), | ||
| cost.isFinite | ||
| { | ||
| let nextCost = self.cost + cost | ||
| if nextCost.isFinite { | ||
| self.cost = nextCost | ||
| self.sawCost = true |
There was a problem hiding this comment.
Withhold Kimi totals when any turn is unpriced
When a day contains both a priceable Kimi model and a model absent from the pricing catalog, the latter turn still contributes tokens but this branch only records successful prices and leaves sawCost true. The later day and 30-day aggregation therefore publishes the priced subtotal as if it covered all usage, understating spend without an incompleteness signal; track unpriced usage and emit nil for the affected aggregate, as the Qwen and OpenCode scanners do.
Useful? React with 👍 / 👎.
Enabling `supportsTokenCost` for Groq changes which providers surface in the descriptor-driven generic Cost row — that is dashboard behavior whose expectations live in the App-layer tests (SpendDashboardModelTests, GroqMenuCardModelTests). Those App tests are not part of this data-layer split, so flipping the flag here breaks CI. Defer the flag to the UI follow-up that carries the matching test updates; the `costSource: .providerReported` tagging stays. Co-authored-by: Cursor <cursoragent@cursor.com>
…cursor adapters Generalize the Usage & Spend data layer into a registerable scanner framework modeled on tokscale's define_clients!, so adding support for a mainstream tool means registering one adapter instead of editing central switches. - LocalHistoryScanning: per-tool protocol (source id, display name, home resolution, scan) with a bundled LocalHistoryScanContext. - LocalHistoryScannerRegistry: source-keyed, order-preserving registry with a process-wide `shared` instance pre-populated from LocalHistoryBuiltInScanners. - Wrap the six existing scanners (Kimi/Gemini/OpenCode/MiniMax/Antigravity/ Qwen) as built-in registrations; no App-layer behavior change. - ZcodeSessionScanner: reads ~/.zcode/cli/rollout/model-io-sess_*.jsonl and normalizes ZCode's cache-inclusive input (cross-checked against totalTokens) so cached prefixes are never billed twice; priced at the official Z.ai rate. - Cursor/Trae degraded scanners: read the local state DBs to surface real per-day model activity, with all token/cost fields left nil because neither tool mirrors token usage locally (billing is server-side). Tests cover the registry, zcode cache normalization, and the degraded sources. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c565404c83
ℹ️ 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".
| modelName: modelName, | ||
| costUSD: self.sawCost ? self.costUSD : nil, | ||
| totalTokens: self.sawTotalTokens ? self.totalTokens : nil, | ||
| inputTokens: self.sawInputTokens && !self.missingInputTokens ? self.inputTokens : nil, |
There was a problem hiding this comment.
Preserve billing ownership while merging breakdowns
When any report containing an explicit billingProviderID passes through CostUsageDailyReport.merged, this accumulator rebuilds the breakdown without retaining that field, so even a single unambiguous provider becomes nil. This loses the structured ownership needed to attribute namespaced third-party usage correctly; accumulate the IDs and preserve the value when they agree.
AGENTS.md reference: AGENTS.md:L46-L46
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 204f1eb. BreakdownAccumulator now retains billingProviderID and keeps it when merged sources agree, drops it when they conflict, and never lets a source without evidence erase another source's evidence. Covered by CostUsageDailyReportMergeTests.
| // Cost only exists for `opencode.db` rows (JSON files carry none). When nothing was priced, | ||
| // stay token-only ("XXX"/nil) so the dashboard does not show a phantom zero spend. | ||
| let totalCost = self.sum(daily.compactMap(\.costUSD)) |
There was a problem hiding this comment.
Withhold OpenCode's total after any unpriced day
When the scan contains at least one provider-priced database day and another day containing legacy JSON or otherwise unpriced records, compactMap drops the nil day before summing. The snapshot therefore publishes a partial subtotal as the complete history cost even though the daily data marks coverage incomplete; return nil for the aggregate whenever any contributing day is unpriced.
Useful? React with 👍 / 👎.
| historyDays: days, | ||
| historyCoverageIsEstablished: true, | ||
| historyLabel: "OpenCode", | ||
| daily: daily, |
There was a problem hiding this comment.
Mark OpenCode database costs as provider-reported
When opencode.db supplies a nonnil cost, the scanner explicitly treats it as the provider-reported amount, but this initializer omits costSource and therefore defaults the snapshot to .estimated. Consumers that distinguish actual billed spend from rate-card estimates will mislabel every priced OpenCode snapshot; pass .providerReported when publishing these database-derived costs.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 65af90d. The OpenCode snapshot now withholds the headline cost whenever any token-bearing day is unpriced, instead of compactMap-ing the nil day and publishing a priced subtotal as the complete total. Covered by OpenCodeSessionScannerTests.
| totalTokens: totalTokens, | ||
| requestCount: total.requests, | ||
| costUSD: daySawCost ? dayCost : nil, |
There was a problem hiding this comment.
Withhold MiniMax cost when any turn is unpriced
When a MiniMax day mixes a priceable model with a turn whose model has no catalog rate and whose stored cost is nil or zero, daySawCost remains true from the priced turn and this publishes only that subtotal. The model/day and later history totals then understate spend without an incompleteness signal; track unpriced billable usage and emit nil for affected aggregates.
Useful? React with 👍 / 👎.
| totalTokens: totalTokens, | ||
| requestCount: total.requests, | ||
| costUSD: daySawCost ? dayCost : nil, |
There was a problem hiding this comment.
Withhold Antigravity cost when any generation is unpriced
When an Antigravity day contains both a recognized priced model and an unknown or newly introduced model, the unknown generation contributes tokens while daySawCost remains true from the recognized model. This line consequently exposes the priced subtotal as a complete day cost, and the history total inherits the understatement; propagate an unpriced-usage flag and withhold affected totals.
Useful? React with 👍 / 👎.
| // Anchor the (single) record on the last active day when known, otherwise today. Trae | ||
| // exposes no per-day token history, so there is exactly one degraded entry. | ||
| let anchor = lastActive ?? now | ||
| let anchorDay = calendar.startOfDay(for: anchor) | ||
| let dayKey = CostUsageLocalDay.key(from: anchorDay, calendar: calendar) |
There was a problem hiding this comment.
Exclude Trae activity outside the requested history window
When telemetry.lastSessionDate is older than historyDays, the scanner still anchors and returns an entry on that stale date because no start/end range check is performed. A user who has not opened Trae for months therefore receives an out-of-window activity record in a snapshot claiming established recent-history coverage; return nil or omit the entry when the anchor lies outside the requested interval.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 10fb613. TraeLocalActivityScanner now returns nil when the telemetry last-session date falls outside the requested history window, so a stale anchor no longer surfaces as recent activity. Covered by TraeLocalActivityScannerTests.
Refactor the local-history framework from "each scanner builds its own snapshot" to a two-layer design modeled on tokscale's UnifiedMessage: - UnifiedUsageEvent: a single normalized record (full token detail, or degraded model-only) that every thin tool parser emits. Per-event billingProviderID carries source evidence for harness tools. - UsageEventAggregator: the one place that does day/model bucketing, cached-prefix normalization (total cross-check, fallback subtract), models.dev pricing, provider-reported-cost passthrough, partial-pricing nil handling, and snapshot construction. Previously this logic was re-implemented (and occasionally dropped) in every scanner. Migrate Zcode, Qwen, Cursor, and Trae to thin parsers over the engine (Zcode 381->200 lines; Qwen -237). Engine output is byte-identical to the prior hand-rolled accumulators, verified by the existing suites. Add CopilotSessionScanner: reads GitHub Copilot CLI's session-state/*/events.jsonl session.shutdown modelMetrics rollup, normalizes model ids to pricing keys, and traces each model to the real billing vendor (claude-* -> anthropic, gpt-* -> openai, gemini-* -> google) so a harness is priced at the model source, not the tool. New mainstream tools now only need a thin parse->events adapter plus one registration line; aggregation/normalization/pricing are reused and cannot be forgotten. Engine covered by 7 focused tests; Copilot by 3. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1bba0c5744
ℹ️ 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".
| let costs = daily.compactMap(\.costUSD) | ||
| let totalCost = costs.isEmpty ? nil : costs.reduce(0, +) |
There was a problem hiding this comment.
Withhold aggregate cost after an unpriced day
When a Qwen, ZCode, or Copilot history contains at least one fully priced day and another day whose model is absent from the catalog, makeDaily correctly leaves the latter day's cost nil, but compactMap drops that incompleteness and publishes the priced subtotal as last30DaysCostUSD. Require every token-bearing day to have a cost before emitting the aggregate so the headline does not understate the history.
Useful? React with 👍 / 👎.
| let inputTokens: Int | ||
| let cacheReadInputTokens: Int | ||
| let outputTokens: Int |
There was a problem hiding this comment.
Apply cache-write rates in shared pricing
For Copilot and ZCode records with nonzero cacheWriteTokens, the aggregator retains cacheCreationTokens for display, but this request cannot pass that bucket to pricing, so cacheCreationInputCostPerToken is never applied. Depending on whether the source includes writes in its raw input, those tokens are either omitted from cost or charged at the ordinary input rate, producing an incorrect estimate whenever the catalog has a distinct cache-write rate.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Already addressed by 5c2ae86 at the current head: UsageEventAggregator passes cacheCreationInputTokens into ModelsDevCostRequest, so catalog cache-write rates are applied for Copilot/ZCode records. No further change needed.
| if let pricing = self.google[canonicalModel] { | ||
| return self.googleCostUSD( |
There was a problem hiding this comment.
Prefer refreshed Gemini rates over offline fallbacks
When the models.dev cache contains updated pricing for one of the four built-in Gemini models, this early return always chooses the embedded rate and never consults the refreshed catalog. The table is documented as an offline fallback for models that precede the catalog, so catalog corrections or later price changes remain ignored and Antigravity spend stays stale; consult the catalog first and use this table only when lookup fails.
Useful? React with 👍 / 👎.
Fixes CI swift-test-macos shard failures: blankLinesBetweenImports, redundantSwiftTestingSuite, indent, redundantThrows. No logic change; both suites still pass. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3eb62c2605
ℹ️ 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".
| AND json_extract(data, '$.time.created') >= ? | ||
| AND json_extract(data, '$.time.created') < ? |
There was a problem hiding this comment.
Fall back to OpenCode's timestamp column
For OpenCode databases where the timestamp is stored only in the message.time_created column, these predicates evaluate against NULL and filter out every otherwise valid assistant row. The repository's existing OpenCodeGoLocalUsageReader.messageUsageSQL explicitly uses COALESCE(json_extract(data, '$.time.created'), time_created) for this schema, so apply the same fallback here or these databases silently produce no local history.
Useful? React with 👍 / 👎.
| modelBreakdowns.append(CostUsageDailyReport.ModelBreakdown( | ||
| modelName: key.model, | ||
| costUSD: modelCost, |
There was a problem hiding this comment.
Preserve OpenCode provider ownership
When OpenCode routes requests through the providerID retained in its JSON/DB records, this breakdown always omits billingProviderID because the wire model, SQL query, and UsageRecord discard that field. Consequently downstream rendering cannot attribute models to the configured billing provider and may mix provider-specific usage; carry the recorded provider ID through to this initializer.
AGENTS.md reference: AGENTS.md:L46-L46
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 65af90d. The OpenCode snapshot now passes costSource: .providerReported whenever any database-derived cost is published, and .estimated when the total is withheld. Covered by OpenCodeSessionScannerTests.
| events.append(UnifiedUsageEvent( | ||
| day: dayKey, | ||
| model: model, | ||
| billingProviderID: Self.billingProvider(for: model), |
There was a problem hiding this comment.
Keep Copilot billing ownership with Copilot
For Copilot sessions using Claude, GPT, or Gemini models, this derives billingProviderID from the model family and labels GitHub-billed Copilot activity as another provider's usage. Keep the underlying vendor IDs only for rate-card lookup via pricingProviderIDs, but set breakdown ownership to Copilot unless the source supplies explicit routing evidence.
AGENTS.md reference: AGENTS.md:L46-L46
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Already addressed by 5c2ae86 at the current head: Copilot breakdowns always set billingProviderID to copilot and use the vendor (anthropic/openai/google) only in pricingProviderIDs for rate lookup. Covered by CopilotSessionScannerTests.
Manual refresh reconciles the entire local history in one pass instead of creeping across refreshes: - CostUsageFetcher: forceRefresh forces a rescan and lifts the 512MB per-refresh byte budget (configureFullRescan), so users with gigabytes of session history see the true total on a single manual refresh rather than watching token counts climb over several clicks. - CostUsageScanner: per-file progress callback (progressHandler) threaded through the scan loop. - SpendDashboardController: CodexScanProgressStore bounces scan-queue progress onto the main actor for live UI updates. - PreferencesSpendDashboardPane: show "Scanning history X/Y" beside the refresh spinner during a full rescan (21 locales). Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b366ef851d
ℹ️ 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".
| MiniMaxLocalHistoryScanner(), | ||
| AntigravityLocalHistoryScanner(), | ||
| QwenCodeLocalHistoryScanner(), | ||
| ZcodeLocalHistoryScanner(), |
There was a problem hiding this comment.
Declare ZCode history on the z.ai provider
Fresh evidence after the earlier pipeline issue is that declared local adapters are now dispatched, but the newly registered .zcode adapter is still not declared by any provider descriptor: a repo-wide search of localHistorySources finds no .zcode, and ZaiProviderDescriptor remains unsupported. Consequently z.ai is filtered out by supportsDashboardHistory, so this production scanner remains reachable only from tests; add .zcode to the appropriate provider's token-cost configuration.
Useful? React with 👍 / 👎.
| totalTokens: input + cacheRead + output, | ||
| cacheReadTokens: cacheRead, | ||
| cacheCreationTokens: cacheCreation, |
There was a problem hiding this comment.
Include Kimi cache-creation tokens in usage totals
When a Kimi turn has nonzero inputCacheCreation, these fields are disjoint from inputOther, but totalTokens excludes the cache-creation bucket. The shared aggregator likewise derives its daily and headline totals without that bucket, so last30DaysTokens is understated and no longer equals the displayed input/cache-read/cache-creation/output components; include cache creation in the accumulated consumption total while preserving the uncached-input normalization signal separately.
Useful? React with 👍 / 👎.
| let context = input.addingReportingOverflow(cacheRead) | ||
| let usesLongContextRates = pricing.thresholdTokens.map { | ||
| context.overflow || context.partialValue > $0 | ||
| } ?? false |
There was a problem hiding this comment.
Count cache creation when selecting long-context rates
When cache-creation tokens push a request over a model's thresholdTokens while input + cacheRead remains below it, this context check selects the short-context rates even though cache creation is subsequently priced as a separate input bucket. This disagrees with the Claude pricing path, which includes cache creation in the threshold calculation, and underprices affected Copilot/ZCode requests; include cacheCreation using overflow-safe arithmetic.
Useful? React with 👍 / 👎.
| continue | ||
| } | ||
| let size = max(0, resourceValues?.fileSize ?? 0) | ||
| guard size <= self.maximumBytes - visitedBytes else { break } |
There was a problem hiding this comment.
Mark byte-limited Kimi scans as incomplete
When eligible Kimi history exceeds the 512 MiB budget, this exits at the first file that does not fit and permanently omits that file plus every later enumerated file. The resulting snapshot nevertheless sets historyCoverageIsEstablished to true and has no truncation signal or incremental cache, so the dashboard presents an arbitrary partial subtotal as complete on every refresh; retain/report incomplete coverage or scan a deterministic complete window.
Useful? React with 👍 / 👎.
Session directories enumerate in arbitrary order, so an unsorted first-N pass can fill the 20000-file cap with old messages and omit recent usage. Sort candidates by modification date before enforcing the cap; add a fileLimit override for tests and a recency regression test.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 20c872500f
ℹ️ 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".
| "qwen": UsageProvider.qwencloud.rawValue, | ||
| "z.ai": UsageProvider.zai.rawValue, | ||
| ] | ||
| for namespace in components.reversed() { |
There was a problem hiding this comment.
Preserve the outer billing provider for nested routes
When a model ID contains a routing provider plus a vendor namespace, such as openrouter/anthropic/claude-*, iterating the namespaces in reverse selects Anthropic and attributes the breakdown to Claude even though OpenRouter owns the billed request. Prefer the outer explicit route, or withhold ownership when the namespace chain is ambiguous, so routed usage is not displayed under a different provider.
AGENTS.md reference: AGENTS.md:L46-L46
Useful? React with 👍 / 👎.
| let hasUnpricedTokenDay = daily.contains { $0.totalTokens != nil && $0.costUSD == nil } | ||
| let costs = daily.compactMap(\.costUSD) | ||
| let totalCost = (costs.isEmpty || hasUnpricedTokenDay) ? nil : costs.reduce(0, +) |
There was a problem hiding this comment.
Withhold partial costs from comparison windows
When a requested comparison period contains both a priced day and one of the unpriced token days detected here, the headline correctly becomes nil, but CostUsageTokenSnapshot.summary(forLastDays:) still compactMaps the same daily costs and exposes the priced subtotal in the 7/30/90-day UI. Propagate the incomplete-day check into window summaries so a narrower comparison does not present partial spend as complete.
Useful? React with 👍 / 👎.
| SELECT (timestamp / 1000) AS seconds, model | ||
| FROM ai_code_hashes | ||
| WHERE timestamp >= ? AND timestamp < ? AND model IS NOT NULL AND model != '' | ||
| LIMIT \(self.maximumRows); |
There was a problem hiding this comment.
Deduplicate Cursor rows before applying the cap
When the requested Cursor window contains more than 500,000 ai_code_hashes rows, this unordered limit is applied to raw activity rather than distinct (day, model) pairs. The selected rows can therefore be dominated by one busy day/model, and the later in-memory dedup cannot recover models or days beyond the limit even though the snapshot reports established coverage; select distinct buckets before limiting, or mark the result incomplete.
Useful? React with 👍 / 👎.
- Walk model namespaces from the outside in so nested routes such as openrouter/anthropic/claude-* keep the routing provider as the billing owner instead of resolving to the inner vendor. - summary(forLastDays:) now returns nil cost whenever any token-bearing entry in the window is unpriced, so 7/30/90-day comparisons never present a priced subtotal as complete.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 40949c04df
ℹ️ 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".
| billingProviderID: UsageProvider.kimi.rawValue, | ||
| inputTokens: input, | ||
| outputTokens: output, | ||
| totalTokens: input + cacheRead + output, |
There was a problem hiding this comment.
Check Kimi token-total additions for overflow
When a malformed Kimi history record contains nonnegative token counts whose sum exceeds Int.max, validOrNil accepts each field but this expression traps before the shared aggregator's checked arithmetic can reject the record, crashing the usage refresh instead of skipping corrupt input. Build the reported total with overflow-reporting additions, or omit it when the sum is not representable.
Useful? React with 👍 / 👎.
|
Maintainer decision on the Usage & Spend program (applies to #2322, #2527, #2548, #2569, #2575, #2577 — closing together): We want the ideas — the token-activity view and the cross-provider cost overview in particular are genuinely good, and we're adopting them. What we can't absorb is the delivery shape: 8k–20k-line PRs bundling scanners, pricing, identity, and UI can't be reviewed honestly, and live testing of the current heads surfaced the kind of defects that big surfaces hide (duplicate token-activity cards in the Models view; the unified Overview rendering no chip for an enabled provider; the account-attribution issue flagged earlier). So we're taking this work as a base in-house and rebuilding it in small, individually landable slices — first the token-activity view rebuilt on the shared scan cache (no separate annual scan), then the cost overview aligned with the #2578 design discussion. Your commits will be cherry-picked where they fit, and every slice that draws on this work will credit you in the changelog, as today's merges already do. @Yuxin-Qiao — you're this repo's most prolific contributor and much of today's release is your work. Focused PRs the size of #2574 land same-day; that's the shape that serves both of us. |
No description provided.