fix(rate-limits): map Cursor pools for team-limited accounts (#36394)

Team-limited (enterprise) Cursor accounts report the member's included
allowance as `individualUsage.overall` instead of an enabled `plan` pool,
so the monthly meter showed nothing. Fall back to `overall` when `plan` is
absent or disabled. Team-wide `teamUsage.onDemand` stays unmapped because
it is not this member's allowance.

Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
David Cabrera
2026-10-30 16:16:23 -07:00
committed by GitHub
co-authored by Neil
parent 12df63b4ee
commit e78cbfba9b
2 changed files with 52 additions and 2 deletions
@@ -114,6 +114,42 @@ describe('mapCursorUsageSummary', () => {
expect(mapped.monthly).toBeNull()
})
it('maps a team-limited member allowance without publishing team-wide on-demand', () => {
const mapped = mapCursorUsageSummary(
parseCursorUsageSummary({
...CYCLE,
membershipType: 'enterprise',
limitType: 'team',
isUnlimited: false,
individualUsage: {
overall: { enabled: true, used: 75_000, limit: 500_000, remaining: 425_000 }
},
teamUsage: {
onDemand: { enabled: true, used: 500_000, limit: 2_500_000, remaining: 2_000_000 }
}
})
)
expect(mapped.planType).toBe('enterprise')
expect(mapped.monthly?.usedPercent).toBe(15)
expect(mapped.buckets).toEqual([])
})
it('uses the overall allowance only when the plan pool is absent or disabled', () => {
const overall = { enabled: true, used: 90, limit: 100 }
expect(
mapCursorUsageSummary({
...CYCLE,
individualUsage: { plan: { enabled: true, totalPercentUsed: 30 }, overall }
}).monthly?.usedPercent
).toBe(30)
expect(
mapCursorUsageSummary({
...CYCLE,
individualUsage: { plan: { enabled: false, totalPercentUsed: 0 }, overall }
}).monthly?.usedPercent
).toBe(90)
})
it('reports an unlimited plan without inventing a percentage', () => {
const mapped = mapCursorUsageSummary({ ...CYCLE, isUnlimited: true, membershipType: 'ultra' })
expect(mapped.isUnlimited).toBe(true)
+16 -2
View File
@@ -27,7 +27,12 @@ const cursorUsageSummarySchema = z
isUnlimited: z.unknown(),
// The dashboard sends null for absent pools; optional-only schemas would reject the entire valid response.
individualUsage: z
.object({ plan: cursorPoolSchema.nullish(), onDemand: cursorPoolSchema.nullish() })
.object({
plan: cursorPoolSchema.nullish(),
// Team-limited accounts report the member's included allowance as `overall`.
overall: cursorPoolSchema.nullish(),
onDemand: cursorPoolSchema.nullish()
})
.partial()
.nullish()
})
@@ -104,6 +109,14 @@ function windowMinutesFor(startMs: number | null, endMs: number | null): number
return Math.max(1, Math.round((endMs - startMs) / 60_000))
}
// A disabled primary pool must not hide the fallback pool Cursor actually meters.
function preferEnabledPool(
primary: CursorPool | null | undefined,
fallback: CursorPool | null | undefined
): CursorPool | null | undefined {
return primary?.enabled === false && fallback?.enabled === true ? fallback : (primary ?? fallback)
}
// On-demand's raw allowance avoids rounding in its reported percentage.
function poolPercent(
pool: CursorPool | null | undefined,
@@ -132,7 +145,7 @@ export function mapCursorUsageSummary(summary: CursorUsageSummary): CursorUsageM
resetDescription: description
})
const plan = summary.individualUsage?.plan
const plan = preferEnabledPool(summary.individualUsage?.plan, summary.individualUsage?.overall)
// Team-owned zero pools must not imply healthy quota or suppress legacy fallback.
const planEnabled = plan?.enabled !== false
const buckets: RateLimitBucket[] = []
@@ -145,6 +158,7 @@ export function mapCursorUsageSummary(summary: CursorUsageSummary): CursorUsageM
buckets.push({ name: CURSOR_OTHER_MODELS_BUCKET_NAME, ...toWindow(otherModels) })
}
// teamUsage.onDemand is team-wide spend, not this member's allowance, so it stays unmapped.
const onDemand = summary.individualUsage?.onDemand
const onDemandPercent =
onDemand?.enabled === true ? poolPercent(onDemand, 'totalPercentUsed') : null