From 75310bed0af4af63a75eb28d73ea7ffc2c959963 Mon Sep 17 00:00:00 2001 From: Serhii Vecherenko Date: Mon, 13 Jul 2026 10:44:08 -0700 Subject: [PATCH] fix(cursor): keep API usage dollars tied to plan limit --- .../src/collectors/cursor.test.ts | 40 ++++++++++++----- .../agents-usage/src/collectors/cursor.ts | 43 ++++++++----------- 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/packages/agents-usage/src/collectors/cursor.test.ts b/packages/agents-usage/src/collectors/cursor.test.ts index af26f08d8..2f4790e70 100644 --- a/packages/agents-usage/src/collectors/cursor.test.ts +++ b/packages/agents-usage/src/collectors/cursor.test.ts @@ -74,11 +74,10 @@ describe("parseCursorUsage", () => { expect(api.resetsAt).toBe(1_719_600_000_000); }); - it("treats breakdown.total as real spend and derives the allowance from the percent", () => { - // Live payload shape: used/limit/remaining clamp at the included $20 while - // breakdown.total carries the real spend ($20 included + $8.75 bonus - // consumed = $28.75). The authoritative 55.07% then implies a ~$52.21 - // allowance (28.75 / 0.5507), keeping the dollars consistent with the bar. + it("treats breakdown.total as real spend against the vendor plan limit", () => { + // Live payload: used/limit clamp at included $20 while breakdown.total is + // real spend ($20 included + $8.75 bonus = $28.75). API % is a separate + // meter — do not invent limit = spend / percent ($28.75 / 55% ≈ $52). const body = { membershipType: "pro", individualUsage: { @@ -96,12 +95,11 @@ describe("parseCursorUsage", () => { const api = snap.windows.find((w) => w.id === "cursor-api")!; expect(api.usedPercent).toBeCloseTo(55.07); expect(api.used).toBeCloseTo(28.75); - expect(api.limit).toBeCloseTo(52.21, 1); + expect(api.limit).toBeCloseTo(20); }); - it("derives the allowance when the clamped dollars disagree with the percent (no breakdown)", () => { - // Older payloads without a breakdown: used/limit cap at the plan price - // ($20/$20) while the percent says 44% consumed → allowance ≈ $45.45. + it("keeps plan dollars when they disagree with API percent (no breakdown)", () => { + // Clamped plan price $20/$20; API bar can still be 44% of a different pool. const body = { membershipType: "pro", individualUsage: { @@ -112,7 +110,29 @@ describe("parseCursorUsage", () => { const api = snap.windows.find((w) => w.id === "cursor-api")!; expect(api.usedPercent).toBe(44); expect(api.used).toBeCloseTo(20); - expect(api.limit).toBeCloseTo(45.45, 1); + expect(api.limit).toBeCloseTo(20); + }); + + it("shows overspend past the plan limit without inventing a percent-derived ceiling", () => { + // User case: ~$35.61 total cost, $20 Pro included, API bar ~4.5%. + // Old math: $35.61 / 0.04555 ≈ $782 nonsense allowance. + const body = { + membershipType: "pro", + individualUsage: { + plan: { + used: 2000, + limit: 2000, + breakdown: { included: 2000, bonus: 1561, total: 3561 }, + autoPercentUsed: 22.37, + apiPercentUsed: 4.555, + }, + }, + }; + const snap = parseCursorUsage(body, {}, NOW); + const api = snap.windows.find((w) => w.id === "cursor-api")!; + expect(api.usedPercent).toBeCloseTo(4.555); + expect(api.used).toBeCloseTo(35.61); + expect(api.limit).toBeCloseTo(20); }); it("keeps the vendor limit when the dollars already agree with the percent", () => { diff --git a/packages/agents-usage/src/collectors/cursor.ts b/packages/agents-usage/src/collectors/cursor.ts index e7e73312e..e544a85cb 100644 --- a/packages/agents-usage/src/collectors/cursor.ts +++ b/packages/agents-usage/src/collectors/cursor.ts @@ -12,9 +12,8 @@ import type { UsageSnapshot, UsageWindow } from "../types"; * Schema (per codexbar) of GET /api/usage-summary → individualUsage.plan: * { used (cents), limit (cents), breakdown { included, bonus, total }, * totalPercentUsed, autoPercentUsed, apiPercentUsed } + billingCycleEnd + - * membershipType. Surfaced as Auto and API breakdown windows. The dollar - * allowance belongs to API usage; see `apiDollars` for how the clamped - * dollar fields are reconciled with the authoritative percents. + * membershipType. Surfaced as Auto and API windows. API dollars use real + * spend over the vendor plan limit; the bar uses apiPercentUsed separately. */ export const CURSOR_USAGE_ENDPOINT = "https://cursor.com/api/usage-summary"; @@ -63,21 +62,22 @@ function clampPercent(value: number | undefined): number | undefined { } /** - * Cursor's plan dollar fields need reconciliation (verified against the live - * payload and CodexBar's Cursor probe, which scrapes the same endpoint): + * Cursor plan dollars vs API percent are different meters: * - * - `used`/`limit`/`remaining` clamp at the nominal plan price (e.g. $20/$20 - * while the percent says 55%), so they understate real spend. - * - `breakdown.total` is the total credits *consumed* (included + bonus spend), - * not the allowance — treating it as the limit was CodexBar regression #240. - * Prefer it as the real spend when it exceeds the clamped `used`. - * - `apiPercentUsed` is the authoritative consumed fraction (it's what - * Cursor's own dashboard messages report). When the dollar pair disagrees - * with it, keep the spend and derive the allowance as `spend / percent` so - * the dollar text always matches the bar (e.g. $28.75 / $52.21 at 55%, - * instead of a clamped, full-looking $20 / $20). + * - `used`/`limit` clamp at the nominal plan price (e.g. $20/$20) and can + * understate real spend once bonus credit is consumed. + * - `breakdown.total` is credits *consumed* (included + bonus spend), not the + * allowance — treating it as the limit was CodexBar regression #240. + * - `apiPercentUsed` drives the bar / "% by reset" pace; it is not a fraction + * of the plan dollar cap. Deriving `limit = spend / apiPercent` invents a + * nonsense ceiling (e.g. $35.61 / $775 at 4.5%) that is neither spend nor + * the Pro included allowance. + * + * Surface honest money: real spend over the vendor plan limit. The bar may + * then disagree with the dollar ratio — that is correct; they measure different + * things. */ -function apiDollars(plan: CursorPlanUsage, percent: number): { used?: number; limit?: number } { +function apiDollars(plan: CursorPlanUsage): { used?: number; limit?: number } { const reportedUsed = centsToUsd(plan.used); const reportedLimit = centsToUsd(plan.limit); const breakdownTotal = centsToUsd(plan.breakdown?.total); @@ -89,15 +89,6 @@ function apiDollars(plan: CursorPlanUsage, percent: number): { used?: number; li if (spend === undefined) { return reportedLimit !== undefined && reportedLimit > 0 ? { limit: reportedLimit } : {}; } - if (spend > 0 && percent > 0) { - const impliedPercent = - reportedLimit !== undefined && reportedLimit > 0 ? (spend / reportedLimit) * 100 : undefined; - // 1-point tolerance so ordinary rounding drift in the reported percent - // doesn't override a limit the spend already agrees with. - if (impliedPercent === undefined || Math.abs(impliedPercent - percent) > 1) { - return { used: spend, limit: spend / (percent / 100) }; - } - } return { used: spend, ...(reportedLimit !== undefined && reportedLimit > 0 ? { limit: reportedLimit } : {}), @@ -146,7 +137,7 @@ export function parseCursorUsage( usedPercent: apiPercent, unit: "percent", currency: "USD", - ...apiDollars(plan, apiPercent), + ...apiDollars(plan), ...withReset, }); }