mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 02:17:31 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user