Files
Joshua 7bfe902089 feat(quota): support OpenAI business-account spend_control in codex quota display (#2403)
* fix(quota): support OpenAI business-account spend_control in codex

Business/enterprise OpenAI accounts return a usage payload with
spend_control.individual_limit instead of rate-limit windows. Keep the
existing primary/secondary/credits blocks unchanged and add a new
spend_limit window that surfaces used_percent and a "used / limit credits"
value label. additional_rate_limits is intentionally ignored.
2026-07-29 18:00:39 +03:00

135 lines
3.8 KiB
JavaScript

import { readAuthFile } from '../../opencode/auth.js';
import {
getAuthEntry,
normalizeAuthEntry,
buildResult,
toUsageWindow,
toNumber,
toTimestamp,
resolveWindowLabel,
formatMoney
} from '../utils/index.js';
export const providerId = 'codex';
export const providerName = 'Codex';
const aliases = ['openai', 'codex', 'chatgpt'];
export const isConfigured = () => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
return Boolean(entry?.access || entry?.token);
};
export const fetchQuota = async () => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
const accessToken = entry?.access ?? entry?.token;
const accountId = entry?.accountId;
if (!accessToken) {
return buildResult({
providerId,
providerName,
ok: false,
configured: false,
error: 'Not configured'
});
}
try {
const headers = {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
...(accountId ? { 'ChatGPT-Account-Id': accountId } : {})
};
const response = await fetch('https://chatgpt.com/backend-api/wham/usage', {
method: 'GET',
headers
});
if (!response.ok) {
return buildResult({
providerId,
providerName,
ok: false,
configured: true,
error: response.status === 401
? 'Session expired \u2014 please re-authenticate with OpenAI'
: `API error: ${response.status}`
});
}
const payload = await response.json();
const primary = payload?.rate_limit?.primary_window ?? null;
const secondary = payload?.rate_limit?.secondary_window ?? null;
const credits = payload?.credits ?? null;
const windows = {};
if (primary) {
const windowSeconds = toNumber(primary.limit_window_seconds);
windows[resolveWindowLabel(windowSeconds)] = toUsageWindow({
usedPercent: toNumber(primary.used_percent),
windowSeconds,
resetAt: toTimestamp(primary.reset_at)
});
}
if (secondary) {
const windowSeconds = toNumber(secondary.limit_window_seconds);
windows[resolveWindowLabel(windowSeconds)] = toUsageWindow({
usedPercent: toNumber(secondary.used_percent),
windowSeconds,
resetAt: toTimestamp(secondary.reset_at)
});
}
if (credits) {
const balance = toNumber(credits.balance);
const unlimited = Boolean(credits.unlimited);
const label = unlimited
? 'Unlimited'
: balance !== null
? `$${formatMoney(balance)}`
: null;
windows.credits_balance = toUsageWindow({
usedPercent: null,
windowSeconds: null,
resetAt: null,
valueLabel: label
});
}
// 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,
ok: true,
configured: true,
usage: { windows }
});
} catch (error) {
return buildResult({
providerId,
providerName,
ok: false,
configured: true,
error: error instanceof Error ? error.message : 'Request failed'
});
}
};