feat(codex): show reset credit expiry details (#2290)

Add read-only GET to inspect per-credit reset inventory (status, granted,
expiry, remaining) with a Quota Tracker modal. DRY the route via shared
connection/refresh helpers; keep existing consume POST unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Rafli Ahmad Zulfikar
2026-07-03 15:07:45 +07:00
committed by decolua
co-authored by Cursor
parent cd557a2552
commit 5cc4f222f8
6 changed files with 495 additions and 57 deletions
+56
View File
@@ -8,9 +8,23 @@ import { U, parseResetTime, toFiniteNumber } from "./shared.js";
// Codex (OpenAI) API config
const CODEX_CONFIG = {
usageUrl: U("codex").url,
resetCreditsUrl: U("codex").resetCreditsUrl,
resetCreditsConsumeUrl: U("codex").resetCreditsConsumeUrl,
};
function toIsoDate(value) {
if (!value) return null;
const date = value instanceof Date
? value
: new Date(typeof value === "number" && value < 1e12 ? value * 1000 : value);
const time = date.getTime();
return Number.isFinite(time) ? date.toISOString() : null;
}
function getCodexAccountId(providerSpecificData) {
return providerSpecificData?.workspaceId || providerSpecificData?.accountId || providerSpecificData?.chatgptAccountId || null;
}
function getCodexRateLimitBody(snapshot) {
if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot)) return null;
return snapshot.rate_limit && typeof snapshot.rate_limit === "object"
@@ -101,6 +115,48 @@ export async function getCodexUsage(accessToken, proxyOptions = null) {
}
}
export async function getCodexRateLimitResetCredits(accessToken, proxyOptions = null, providerSpecificData = null) {
if (!accessToken) {
throw new Error("No Codex access token available. Please re-authorize the connection.");
}
const accountId = getCodexAccountId(providerSpecificData);
const headers = {
"Authorization": `Bearer ${accessToken}`,
"Accept": "application/json",
"OpenAI-Beta": "codex-1",
"originator": "codex_cli_rs",
};
if (accountId) headers["ChatGPT-Account-ID"] = accountId;
const response = await proxyAwareFetch(CODEX_CONFIG.resetCreditsUrl, {
method: "GET",
headers,
}, proxyOptions);
let data = null;
try {
data = await response.json();
} catch {
data = null;
}
if (!response.ok) {
const message = data?.message || data?.error || data?.detail || `Codex reset credits API unavailable (${response.status}).`;
throw new Error(message);
}
const credits = Array.isArray(data?.credits) ? data.credits : [];
return {
availableCount: Math.max(0, toFiniteNumber(data?.available_count ?? data?.availableCount, 0)),
credits: credits.map((credit) => ({
status: String(credit?.status || "unknown"),
grantedAt: toIsoDate(credit?.granted_at ?? credit?.grantedAt),
expiresAt: toIsoDate(credit?.expires_at ?? credit?.expiresAt),
})),
};
}
// Consume one Codex rate-limit reset credit (irreversible, spends 1 credit)
export async function consumeCodexRateLimitResetCredit(accessToken, redeemRequestId, proxyOptions = null) {
if (!accessToken) {