diff --git a/packages/web/server/lib/quota/providers/codex.js b/packages/web/server/lib/quota/providers/codex.js index 20a5ffa2..629e5eec 100644 --- a/packages/web/server/lib/quota/providers/codex.js +++ b/packages/web/server/lib/quota/providers/codex.js @@ -97,6 +97,24 @@ export const fetchQuota = async () => { }); } + // Business/enterprise accounts expose a dollar spend cap under + // `spend_control.individual_limit`. Surface it as an additive `credits` + // window so existing consumers keep working. + if (payload?.spend_control?.individual_limit) { + const spendLimit = payload.spend_control.individual_limit; + const used = toNumber(spendLimit.used); + const limit = toNumber(spendLimit.limit); + const valueLabel = used !== null && limit !== null + ? `${used.toFixed(0)} / ${limit.toFixed(0)} used` + : null; + windows.credits = toUsageWindow({ + usedPercent: toNumber(spendLimit.used_percent), + windowSeconds: null, + resetAt: null, + valueLabel + }); + } + return buildResult({ providerId, providerName, diff --git a/packages/web/server/lib/quota/providers/codex.test.js b/packages/web/server/lib/quota/providers/codex.test.js index a7253578..18160678 100644 --- a/packages/web/server/lib/quota/providers/codex.test.js +++ b/packages/web/server/lib/quota/providers/codex.test.js @@ -45,4 +45,32 @@ describe('Codex quota windows', () => { expect(result.usage.windows['5h'].usedPercent).toBe(10); expect(result.usage.windows.weekly.usedPercent).toBe(20); }); + + it('surfaces spend_control individual limit for business accounts', async () => { + const fetchMock = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ + plan_type: 'business', + rate_limit: null, + credits: { has_credits: true, unlimited: false, balance: null }, + spend_control: { + individual_limit: { + limit: '7500', + used: '2674.8724080324173', + remaining: '4825.127591967583', + used_percent: 36, + remaining_percent: 64 + } + } + }) + }); + vi.stubGlobal('fetch', fetchMock); + + const result = await fetchQuota(); + + expect(result.ok).toBe(true); + expect(result.usage.windows.credits.usedPercent).toBe(36); + expect(result.usage.windows.credits.valueLabel).toBe('2675 / 7500 used'); + expect(fetchMock).toHaveBeenCalled(); + }); });