fix(codex-auth): add reset card actions and tests

This commit is contained in:
leokun
2026-09-05 00:12:26 +08:00
parent c379c315e6
commit 20fdea8fb3
4 changed files with 118 additions and 8 deletions
@@ -12,8 +12,8 @@ import { buildResponsesBody } from "cursor-byok:protocol/openai-responses";
import { codexProvider, isQuotaError } from "./provider.ts";
import {
accountIdentity,
credentialDraft,
consumeResetCardAction,
credentialDraft,
listResetCardsAction,
parseCodexUsage,
parseCredentialFiles,
@@ -142,9 +142,113 @@ Deno.test("usage maps secondary to weekly and primary to five-hour quota", () =>
assertEquals(quota.weekly?.remainingPercent, 75);
assertEquals(quota.fiveHour?.remainingPercent, 20);
assertEquals(quota.weekly?.resetAtMs, 1_900_000_000_000);
assertEquals(quota.resetCreditsAvailable, 2);
assertEquals(quotaState(quota, 1_700_000_000_000), { status: "ready" });
});
Deno.test("reset card action lists safe card metadata and optional expiry", async () => {
const result = await listResetCardsAction.run(
snapshot({
accessToken: "access-secret",
accountId: "acct-1",
refreshToken: null,
displayName: "person@example.com",
quota: null,
}),
null,
context({
fetch: (url, init) => {
assertEquals(url, "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits");
assertEquals(init?.headers?.["ChatGPT-Account-Id"], "acct-1");
return {
status: 200,
headers: {},
body: JSON.stringify({
available_count: 1,
credits: [{
id: "credit-1",
reset_type: "codex_rate_limits",
status: "available",
granted_at: "2026-06-12T01:33:14Z",
expires_at: "2026-07-12T01:33:14Z",
title: "One free rate limit reset",
}],
}),
};
},
}),
);
assertEquals(result.cards, [{
id: "credit-1",
title: "One free rate limit reset",
status: "available",
grantedAtMs: Date.parse("2026-06-12T01:33:14Z"),
expiresAtMs: Date.parse("2026-07-12T01:33:14Z"),
fields: [{
id: "reset-type",
label: { "en-US": "Reset type", "zh-CN": "重置类型" },
value: "codex_rate_limits",
}],
}]);
});
Deno.test("reset card action consumes a selected card and refreshes quota state", async () => {
let requestNumber = 0;
const result = await consumeResetCardAction.run(
snapshot({
accessToken: "access-secret",
accountId: "acct-1",
refreshToken: null,
displayName: "person@example.com",
quota: null,
}),
{ cardId: "credit-1" },
context({
fetch: (url, init) => {
requestNumber += 1;
if (requestNumber === 1 || requestNumber === 4) {
assertEquals(url, "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits");
return {
status: 200,
headers: {},
body: JSON.stringify(
requestNumber === 1
? { available_count: 1, credits: [{ id: "credit-1", status: "available" }] }
: { available_count: 0, credits: [] },
),
};
}
if (requestNumber === 2) {
assertEquals(
url,
"https://chatgpt.com/backend-api/wham/rate-limit-reset-credits/consume",
);
assertEquals(JSON.parse(init?.body ?? "{}"), {
credit_id: "credit-1",
redeem_request_id: JSON.parse(init?.body ?? "{}").redeem_request_id,
});
return { status: 200, headers: {}, body: JSON.stringify({ code: "reset" }) };
}
assertEquals(url, "https://chatgpt.com/backend-api/wham/usage");
return {
status: 200,
headers: {},
body: JSON.stringify({ rate_limit_reset_credits: { available_count: 0 } }),
};
},
}),
);
assertEquals(requestNumber, 4);
assertEquals(result.cards, []);
const quota = (result.patch?.privateData as Record<string, unknown>).quota as Record<
string,
unknown
>;
assertEquals(quota.resetCreditsAvailable, 0);
assertEquals(quota.weekly, null);
assertEquals(quota.fiveHour, null);
});
Deno.test("exhausted quota projects a cooling state until the latest reset", () => {
const quota = parseCodexUsage({
rate_limit: {
+1 -1
View File
@@ -2,8 +2,8 @@ import { defineProviderPlugin } from "cursor-byok:plugin";
import { codexDeviceOAuth } from "./oauth.ts";
import { codexProvider } from "./provider.ts";
import {
credentialImport,
consumeResetCardAction,
credentialImport,
listResetCardsAction,
presentAccount,
refreshAccount,
@@ -350,7 +350,9 @@ async function fetchResetCredits(
const availableCount = number(root.available_count ?? root.availableCount);
return {
cards,
availableCount: availableCount === null ? cards.filter((card) => card.status === "available").length : Math.max(0, Math.floor(availableCount)),
availableCount: availableCount === null
? cards.filter((card) => card.status === "available").length
: Math.max(0, Math.floor(availableCount)),
};
}
@@ -426,7 +428,10 @@ async function consumeResetCard(
export const listResetCardsAction: ResourceAction = {
id: LIST_RESET_CARDS_ACTION_ID,
displayName: { "en-US": "View reset cards", "zh-CN": "查看重置卡" },
description: { "en-US": "List available Codex reset cards.", "zh-CN": "查看当前账号的 Codex 重置卡。" },
description: {
"en-US": "List available Codex reset cards.",
"zh-CN": "查看当前账号的 Codex 重置卡。",
},
target: "resource",
run: listResetCards,
};
@@ -434,7 +439,10 @@ export const listResetCardsAction: ResourceAction = {
export const consumeResetCardAction: ResourceAction = {
id: CONSUME_RESET_CARD_ACTION_ID,
displayName: { "en-US": "Use reset card", "zh-CN": "使用重置卡" },
description: { "en-US": "Redeem one available Codex reset card.", "zh-CN": "消耗一张可用的 Codex 重置卡。" },
description: {
"en-US": "Redeem one available Codex reset card.",
"zh-CN": "消耗一张可用的 Codex 重置卡。",
},
target: "card",
destructive: true,
run: consumeResetCard,
+1 -3
View File
@@ -142,9 +142,7 @@ async function dispatch(message: { id: string; method: string; params?: JsonValu
case "oauth.begin": {
const support = resourceSupport(params.resourceType);
const method = addMethod(support, params.methodId);
result = method.type === "oauth2.authorization-code"
? await method.begin(params.authorization as never, context)
: await method.begin(context);
result = method.type === "oauth2.authorization-code" ? await method.begin(params.authorization as never, context) : await method.begin(context);
break;
}
case "oauth.poll": {