mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-08 15:43:10 +08:00
fix(codex-auth): add reset card actions and tests
This commit is contained in:
@@ -12,8 +12,8 @@ import { buildResponsesBody } from "cursor-byok:protocol/openai-responses";
|
|||||||
import { codexProvider, isQuotaError } from "./provider.ts";
|
import { codexProvider, isQuotaError } from "./provider.ts";
|
||||||
import {
|
import {
|
||||||
accountIdentity,
|
accountIdentity,
|
||||||
credentialDraft,
|
|
||||||
consumeResetCardAction,
|
consumeResetCardAction,
|
||||||
|
credentialDraft,
|
||||||
listResetCardsAction,
|
listResetCardsAction,
|
||||||
parseCodexUsage,
|
parseCodexUsage,
|
||||||
parseCredentialFiles,
|
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.weekly?.remainingPercent, 75);
|
||||||
assertEquals(quota.fiveHour?.remainingPercent, 20);
|
assertEquals(quota.fiveHour?.remainingPercent, 20);
|
||||||
assertEquals(quota.weekly?.resetAtMs, 1_900_000_000_000);
|
assertEquals(quota.weekly?.resetAtMs, 1_900_000_000_000);
|
||||||
|
assertEquals(quota.resetCreditsAvailable, 2);
|
||||||
assertEquals(quotaState(quota, 1_700_000_000_000), { status: "ready" });
|
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", () => {
|
Deno.test("exhausted quota projects a cooling state until the latest reset", () => {
|
||||||
const quota = parseCodexUsage({
|
const quota = parseCodexUsage({
|
||||||
rate_limit: {
|
rate_limit: {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { defineProviderPlugin } from "cursor-byok:plugin";
|
|||||||
import { codexDeviceOAuth } from "./oauth.ts";
|
import { codexDeviceOAuth } from "./oauth.ts";
|
||||||
import { codexProvider } from "./provider.ts";
|
import { codexProvider } from "./provider.ts";
|
||||||
import {
|
import {
|
||||||
credentialImport,
|
|
||||||
consumeResetCardAction,
|
consumeResetCardAction,
|
||||||
|
credentialImport,
|
||||||
listResetCardsAction,
|
listResetCardsAction,
|
||||||
presentAccount,
|
presentAccount,
|
||||||
refreshAccount,
|
refreshAccount,
|
||||||
|
|||||||
@@ -350,7 +350,9 @@ async function fetchResetCredits(
|
|||||||
const availableCount = number(root.available_count ?? root.availableCount);
|
const availableCount = number(root.available_count ?? root.availableCount);
|
||||||
return {
|
return {
|
||||||
cards,
|
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 = {
|
export const listResetCardsAction: ResourceAction = {
|
||||||
id: LIST_RESET_CARDS_ACTION_ID,
|
id: LIST_RESET_CARDS_ACTION_ID,
|
||||||
displayName: { "en-US": "View reset cards", "zh-CN": "查看重置卡" },
|
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",
|
target: "resource",
|
||||||
run: listResetCards,
|
run: listResetCards,
|
||||||
};
|
};
|
||||||
@@ -434,7 +439,10 @@ export const listResetCardsAction: ResourceAction = {
|
|||||||
export const consumeResetCardAction: ResourceAction = {
|
export const consumeResetCardAction: ResourceAction = {
|
||||||
id: CONSUME_RESET_CARD_ACTION_ID,
|
id: CONSUME_RESET_CARD_ACTION_ID,
|
||||||
displayName: { "en-US": "Use reset card", "zh-CN": "使用重置卡" },
|
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",
|
target: "card",
|
||||||
destructive: true,
|
destructive: true,
|
||||||
run: consumeResetCard,
|
run: consumeResetCard,
|
||||||
|
|||||||
@@ -142,9 +142,7 @@ async function dispatch(message: { id: string; method: string; params?: JsonValu
|
|||||||
case "oauth.begin": {
|
case "oauth.begin": {
|
||||||
const support = resourceSupport(params.resourceType);
|
const support = resourceSupport(params.resourceType);
|
||||||
const method = addMethod(support, params.methodId);
|
const method = addMethod(support, params.methodId);
|
||||||
result = method.type === "oauth2.authorization-code"
|
result = method.type === "oauth2.authorization-code" ? await method.begin(params.authorization as never, context) : await method.begin(context);
|
||||||
? await method.begin(params.authorization as never, context)
|
|
||||||
: await method.begin(context);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "oauth.poll": {
|
case "oauth.poll": {
|
||||||
|
|||||||
Reference in New Issue
Block a user