diff --git a/packages/vscode/src/quotaProviders.test.ts b/packages/vscode/src/quotaProviders.test.ts index 8245c713..00af15d3 100644 --- a/packages/vscode/src/quotaProviders.test.ts +++ b/packages/vscode/src/quotaProviders.test.ts @@ -7,6 +7,7 @@ import fs from 'node:fs'; // configured and proceed straight to fetch. const ORIGINAL_FS = { ...fs }; const AUTH = JSON.stringify({ + openai: { access: 'test-token' }, crof: { key: 'test-token' }, neuralwatt: { key: 'test-token' }, }); @@ -112,6 +113,31 @@ describe('Crof quota provider (VS Code parity)', () => { }); }); +describe('Codex quota provider (VS Code parity)', () => { + test('surfaces spend_control individual limit for business accounts', async () => { + stubFetchReturning(() => Promise.resolve(mockResponse({ + 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, + }, + }, + }))); + + const result = await fetchQuotaForProvider('codex'); + + assert.equal(result.ok, true); + assert.equal(result.usage!.windows.credits!.usedPercent, 36); + assert.equal(result.usage!.windows.credits!.valueLabel, '2675 / 7500 used'); + }); +}); + describe('NeuralWatt quota provider (VS Code parity)', () => { test('builds subscription window keyed by plan name (windowSeconds null)', async () => { stubFetchReturning(() => Promise.resolve(mockResponse(DOCUMENTED_SUBSCRIPTION_PAYLOAD))); diff --git a/packages/vscode/src/quotaProviders.ts b/packages/vscode/src/quotaProviders.ts index 5f0662b4..759cb03e 100644 --- a/packages/vscode/src/quotaProviders.ts +++ b/packages/vscode/src/quotaProviders.ts @@ -40,6 +40,13 @@ type OpenAiUsagePayload = { balance?: number | string; unlimited?: boolean; }; + spend_control?: { + individual_limit?: { + limit?: number | string; + used?: number | string; + used_percent?: number | string; + }; + }; }; type GoogleModelsPayload = { @@ -558,6 +565,20 @@ const fetchCodexQuota = async (): Promise => { valueLabel, }); } + 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: 'codex',