@99percentpeople/pi-codex-api 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -183
- package/dist/{index.js → index.ts} +244 -24
- package/dist/{index.js.map → index.ts.map} +4 -4
- package/package.json +3 -3
|
@@ -1202,6 +1202,11 @@ import {
|
|
|
1202
1202
|
getAgentDir
|
|
1203
1203
|
} from "@earendil-works/pi-coding-agent";
|
|
1204
1204
|
var USAGE_PATH = "../wham/usage";
|
|
1205
|
+
var REDEEM_CREDITS_PATH = "../wham/rate-limit-reset-credits";
|
|
1206
|
+
var REDEEM_PATH = "../wham/rate-limit-reset-credits/consume";
|
|
1207
|
+
var REDEEM_CONFIRM_WINDOW_MS = 1e4;
|
|
1208
|
+
var REDEEM_DIALOG_TIMEOUT_MS = 30000;
|
|
1209
|
+
var REDEEM_RETRY_WINDOW_MS = 5 * 60000;
|
|
1205
1210
|
var USAGE_REFRESH_INTERVAL_MS = 60000;
|
|
1206
1211
|
var AUTH_WATCH_DEBOUNCE_MS = 100;
|
|
1207
1212
|
var STATUS_KEY = "codex-api-usage";
|
|
@@ -1215,6 +1220,18 @@ function payloadNumber(value) {
|
|
|
1215
1220
|
const number = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
1216
1221
|
return Number.isFinite(number) ? number : undefined;
|
|
1217
1222
|
}
|
|
1223
|
+
function payloadDate(value) {
|
|
1224
|
+
if (typeof value === "number")
|
|
1225
|
+
return Number.isFinite(value) ? value : undefined;
|
|
1226
|
+
if (typeof value === "string") {
|
|
1227
|
+
const ms = Date.parse(value);
|
|
1228
|
+
return Number.isFinite(ms) ? ms : undefined;
|
|
1229
|
+
}
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
function payloadString(value) {
|
|
1233
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
1234
|
+
}
|
|
1218
1235
|
function payloadBool(value) {
|
|
1219
1236
|
if (typeof value === "boolean")
|
|
1220
1237
|
return value;
|
|
@@ -1253,14 +1270,65 @@ function payloadCredits(value) {
|
|
|
1253
1270
|
balance: typeof balance === "string" && balance ? balance : undefined
|
|
1254
1271
|
};
|
|
1255
1272
|
}
|
|
1256
|
-
function payloadSnapshot(limitId, limitName, rateLimitValue, creditsValue) {
|
|
1273
|
+
function payloadSnapshot(limitId, limitName, rateLimitValue, creditsValue, limitReached) {
|
|
1257
1274
|
const rateLimit = object(rateLimitValue);
|
|
1258
1275
|
return {
|
|
1259
1276
|
limitId,
|
|
1260
1277
|
limitName,
|
|
1261
1278
|
primary: payloadWindow(rateLimit && property(rateLimit, "primary_window", "primaryWindow")),
|
|
1262
1279
|
secondary: payloadWindow(rateLimit && property(rateLimit, "secondary_window", "secondaryWindow")),
|
|
1263
|
-
credits: payloadCredits(creditsValue)
|
|
1280
|
+
credits: payloadCredits(creditsValue),
|
|
1281
|
+
limitReached
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
function parseCodexAccountInfo(value) {
|
|
1285
|
+
const input = object(value);
|
|
1286
|
+
if (!input)
|
|
1287
|
+
return;
|
|
1288
|
+
const planType = payloadString(property(input, "plan_type", "planType"));
|
|
1289
|
+
const email = payloadString(property(input, "email", "email"));
|
|
1290
|
+
if (planType === undefined && email === undefined)
|
|
1291
|
+
return;
|
|
1292
|
+
return { planType, email };
|
|
1293
|
+
}
|
|
1294
|
+
function maskCodexEmail(email) {
|
|
1295
|
+
const [local, domain] = email.split("@");
|
|
1296
|
+
if (!domain)
|
|
1297
|
+
return "***";
|
|
1298
|
+
const head = local.length > 3 ? local.slice(0, 3) : local.slice(0, 1);
|
|
1299
|
+
return `${head}***@${domain}`;
|
|
1300
|
+
}
|
|
1301
|
+
function parseCodexRedeemCredits(value) {
|
|
1302
|
+
const input = object(value);
|
|
1303
|
+
if (!input)
|
|
1304
|
+
return;
|
|
1305
|
+
const availableCount = payloadNumber(property(input, "available_count", "availableCount"));
|
|
1306
|
+
if (availableCount === undefined)
|
|
1307
|
+
return;
|
|
1308
|
+
const credits = [];
|
|
1309
|
+
const list = property(input, "credits", "credits");
|
|
1310
|
+
if (Array.isArray(list)) {
|
|
1311
|
+
for (const item of list) {
|
|
1312
|
+
const credit = object(item);
|
|
1313
|
+
if (!credit)
|
|
1314
|
+
continue;
|
|
1315
|
+
const id = payloadString(property(credit, "id", "id"));
|
|
1316
|
+
if (!id)
|
|
1317
|
+
continue;
|
|
1318
|
+
credits.push({
|
|
1319
|
+
id,
|
|
1320
|
+
title: payloadString(property(credit, "title", "title")),
|
|
1321
|
+
description: payloadString(property(credit, "description", "description")),
|
|
1322
|
+
status: payloadString(property(credit, "status", "status")),
|
|
1323
|
+
grantedAt: payloadDate(property(credit, "granted_at", "grantedAt")),
|
|
1324
|
+
expiresAt: payloadDate(property(credit, "expires_at", "expiresAt"))
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
return {
|
|
1329
|
+
availableCount,
|
|
1330
|
+
totalEarnedCount: payloadNumber(property(input, "total_earned_count", "totalEarnedCount")),
|
|
1331
|
+
credits
|
|
1264
1332
|
};
|
|
1265
1333
|
}
|
|
1266
1334
|
function parseCodexUsagePayload(value) {
|
|
@@ -1268,7 +1336,9 @@ function parseCodexUsagePayload(value) {
|
|
|
1268
1336
|
if (!input)
|
|
1269
1337
|
return [];
|
|
1270
1338
|
const rateLimit = property(input, "rate_limit", "rateLimit");
|
|
1271
|
-
const
|
|
1339
|
+
const rateLimitObject = object(rateLimit);
|
|
1340
|
+
const limitReached = payloadBool(rateLimitObject && property(rateLimitObject, "limit_reached", "limitReached"));
|
|
1341
|
+
const snapshots = rateLimit !== undefined || input.credits !== undefined ? [payloadSnapshot("codex", undefined, rateLimit, input.credits, limitReached)] : [];
|
|
1272
1342
|
const additional = property(input, "additional_rate_limits", "additionalRateLimits");
|
|
1273
1343
|
if (Array.isArray(additional)) {
|
|
1274
1344
|
for (const value2 of additional) {
|
|
@@ -1330,14 +1400,16 @@ function parseCodexRateLimits(input) {
|
|
|
1330
1400
|
unlimited,
|
|
1331
1401
|
balance: headers["x-codex-credits-balance"]
|
|
1332
1402
|
} : undefined;
|
|
1333
|
-
|
|
1403
|
+
const limitReached = prefix === "x-codex" ? headers["x-codex-rate-limit-reached-type"] !== undefined : undefined;
|
|
1404
|
+
if (!primary && !secondary && !credits && !limitReached)
|
|
1334
1405
|
return [];
|
|
1335
1406
|
return [{
|
|
1336
1407
|
limitId: prefix.slice(2).replace(/-/g, "_"),
|
|
1337
1408
|
limitName: headers[`${prefix}-limit-name`],
|
|
1338
1409
|
primary,
|
|
1339
1410
|
secondary,
|
|
1340
|
-
credits
|
|
1411
|
+
credits,
|
|
1412
|
+
limitReached
|
|
1341
1413
|
}];
|
|
1342
1414
|
});
|
|
1343
1415
|
}
|
|
@@ -1350,13 +1422,17 @@ function resetText(epochSeconds, now = Date.now()) {
|
|
|
1350
1422
|
const remainingMs = epochSeconds * 1000 - now;
|
|
1351
1423
|
if (remainingMs <= 0)
|
|
1352
1424
|
return;
|
|
1353
|
-
const
|
|
1354
|
-
if (
|
|
1355
|
-
return `${
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1425
|
+
const totalMinutes = Math.ceil(remainingMs / 60000);
|
|
1426
|
+
if (totalMinutes < 60)
|
|
1427
|
+
return `${totalMinutes}m`;
|
|
1428
|
+
if (totalMinutes < 24 * 60) {
|
|
1429
|
+
const hours2 = Math.floor(totalMinutes / 60);
|
|
1430
|
+
const minutes = totalMinutes % 60;
|
|
1431
|
+
return minutes > 0 ? `${hours2}h ${minutes}m` : `${hours2}h`;
|
|
1432
|
+
}
|
|
1433
|
+
const days = Math.floor(totalMinutes / (24 * 60));
|
|
1434
|
+
const hours = Math.floor(totalMinutes % (24 * 60) / 60);
|
|
1435
|
+
return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
|
|
1360
1436
|
}
|
|
1361
1437
|
var KNOWN_WINDOWS = [
|
|
1362
1438
|
{ minutes: 5 * 60, label: "5h" },
|
|
@@ -1393,10 +1469,11 @@ function usageBar(remaining) {
|
|
|
1393
1469
|
const filled = Math.round(remaining / 100 * USAGE_BAR_WIDTH);
|
|
1394
1470
|
return `[${"█".repeat(filled)}${"░".repeat(USAGE_BAR_WIDTH - filled)}]`;
|
|
1395
1471
|
}
|
|
1396
|
-
function windowText(item, labelWidth, now) {
|
|
1472
|
+
function windowText(item, labelWidth, now, limitReached) {
|
|
1397
1473
|
const reset = resetText(item.window.resetsAt, now);
|
|
1398
1474
|
const remaining = remainingPercent(item.window);
|
|
1399
|
-
|
|
1475
|
+
const state = limitReached ? "limit reached" : `${percent(remaining)}% left`;
|
|
1476
|
+
return `${item.label.padEnd(labelWidth)} ${usageBar(limitReached ? 0 : remaining)} ${state}${reset ? ` resets in ${reset}` : ""}`;
|
|
1400
1477
|
}
|
|
1401
1478
|
function creditsText(credits) {
|
|
1402
1479
|
if (credits.unlimited)
|
|
@@ -1406,11 +1483,41 @@ function creditsText(credits) {
|
|
|
1406
1483
|
}
|
|
1407
1484
|
return "no additional credits";
|
|
1408
1485
|
}
|
|
1409
|
-
function
|
|
1486
|
+
function planLabel(planType) {
|
|
1487
|
+
return planType.charAt(0).toUpperCase() + planType.slice(1);
|
|
1488
|
+
}
|
|
1489
|
+
function formatDateTime(epochMs) {
|
|
1490
|
+
const date = new Date(epochMs);
|
|
1491
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
1492
|
+
const offsetMinutes = -new Date(epochMs).getTimezoneOffset();
|
|
1493
|
+
const sign = offsetMinutes >= 0 ? "+" : "-";
|
|
1494
|
+
const abs = Math.abs(offsetMinutes);
|
|
1495
|
+
const offset = abs % 60 === 0 ? `UTC${sign}${abs / 60}` : `UTC${sign}${Math.floor(abs / 60)}:${pad(abs % 60)}`;
|
|
1496
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())} ${offset}`;
|
|
1497
|
+
}
|
|
1498
|
+
function formatCodexRedeemCredits(redeemCredits, now = Date.now()) {
|
|
1499
|
+
if (!redeemCredits || redeemCredits.availableCount <= 0)
|
|
1500
|
+
return [];
|
|
1501
|
+
const lines = [`rate limit redeem${redeemCredits.availableCount === 1 ? "" : ` ×${redeemCredits.availableCount}`}`];
|
|
1502
|
+
const available = redeemCredits.credits.filter((credit) => credit.status === undefined || credit.status === "available").sort((left, right) => (left.expiresAt ?? Number.POSITIVE_INFINITY) - (right.expiresAt ?? Number.POSITIVE_INFINITY));
|
|
1503
|
+
for (const credit of available) {
|
|
1504
|
+
const details = ["available"];
|
|
1505
|
+
if (credit.expiresAt !== undefined) {
|
|
1506
|
+
details.push(credit.expiresAt > now ? `expires ${formatDateTime(credit.expiresAt)}` : "expired");
|
|
1507
|
+
}
|
|
1508
|
+
lines.push(` ${credit.title ?? "reset credit"} (${details.join(", ")})`);
|
|
1509
|
+
}
|
|
1510
|
+
return lines.length > 1 ? lines : [];
|
|
1511
|
+
}
|
|
1512
|
+
function formatCodexUsage(snapshots, now = Date.now(), extras = {}) {
|
|
1410
1513
|
if (snapshots.length === 0) {
|
|
1411
1514
|
return "No Codex usage data is available. Run /codex-usage with an active Codex subscription model to refresh it.";
|
|
1412
1515
|
}
|
|
1413
1516
|
const lines = ["Codex usage"];
|
|
1517
|
+
if (extras.account) {
|
|
1518
|
+
const plan = extras.account.planType ? planLabel(extras.account.planType) : "unknown plan";
|
|
1519
|
+
lines.push("", `account · ${plan}${extras.account.email ? ` (${maskCodexEmail(extras.account.email)})` : ""}`);
|
|
1520
|
+
}
|
|
1414
1521
|
for (const snapshot of snapshots) {
|
|
1415
1522
|
const name = snapshot.limitName ?? snapshot.limitId;
|
|
1416
1523
|
const windows = activeWindows(snapshot, now);
|
|
@@ -1419,10 +1526,13 @@ function formatCodexUsage(snapshots, now = Date.now()) {
|
|
|
1419
1526
|
if (windows.length === 0)
|
|
1420
1527
|
lines.push(" no active usage windows");
|
|
1421
1528
|
else
|
|
1422
|
-
lines.push(...windows.map((window) => ` ${windowText(window, labelWidth, now)}`));
|
|
1529
|
+
lines.push(...windows.map((window) => ` ${windowText(window, labelWidth, now, snapshot.limitReached === true)}`));
|
|
1423
1530
|
if (snapshot.credits)
|
|
1424
1531
|
lines.push(` ${creditsText(snapshot.credits)}`);
|
|
1425
1532
|
}
|
|
1533
|
+
const redeemLines = formatCodexRedeemCredits(extras.redeemCredits, now);
|
|
1534
|
+
if (redeemLines.length > 0)
|
|
1535
|
+
lines.push("", ...redeemLines);
|
|
1426
1536
|
return lines.join(`
|
|
1427
1537
|
`);
|
|
1428
1538
|
}
|
|
@@ -1441,7 +1551,8 @@ function formatCodexStatus(snapshots, fastMode, now = Date.now()) {
|
|
|
1441
1551
|
return;
|
|
1442
1552
|
const remaining = remainingPercent(shortest.window);
|
|
1443
1553
|
const reset = resetText(shortest.window.resetsAt, now);
|
|
1444
|
-
|
|
1554
|
+
const usage = snapshot.limitReached === true ? "limit reached" : `${percent(remaining)}%`;
|
|
1555
|
+
return `Codex ${shortest.label} ${usage}${reset ? ` ${reset}` : ""}${fastMode ? " Fast" : ""}`;
|
|
1445
1556
|
}
|
|
1446
1557
|
function applyFastModePayload(payload, enabled) {
|
|
1447
1558
|
if (!enabled || !payload || typeof payload !== "object" || Array.isArray(payload))
|
|
@@ -1450,6 +1561,7 @@ function applyFastModePayload(payload, enabled) {
|
|
|
1450
1561
|
}
|
|
1451
1562
|
function registerCodexUsageAndFast(pi, controller, options = {}) {
|
|
1452
1563
|
const usageByAccount = new Map;
|
|
1564
|
+
const pendingRedeemByAccount = new Map;
|
|
1453
1565
|
let activeAccountId;
|
|
1454
1566
|
let credentialRevision = 0;
|
|
1455
1567
|
let latestContext;
|
|
@@ -1481,6 +1593,7 @@ function registerCodexUsageAndFast(pi, controller, options = {}) {
|
|
|
1481
1593
|
credentialRevision += 1;
|
|
1482
1594
|
activeAccountId = undefined;
|
|
1483
1595
|
usageByAccount.clear();
|
|
1596
|
+
pendingRedeemByAccount.clear();
|
|
1484
1597
|
if (action === "set")
|
|
1485
1598
|
showSyncingStatus(ctx);
|
|
1486
1599
|
else
|
|
@@ -1492,6 +1605,7 @@ function registerCodexUsageAndFast(pi, controller, options = {}) {
|
|
|
1492
1605
|
credentialRevision += 1;
|
|
1493
1606
|
activeAccountId = accountId;
|
|
1494
1607
|
usageByAccount.clear();
|
|
1608
|
+
pendingRedeemByAccount.clear();
|
|
1495
1609
|
showSyncingStatus(ctx);
|
|
1496
1610
|
return true;
|
|
1497
1611
|
};
|
|
@@ -1542,6 +1656,7 @@ function registerCodexUsageAndFast(pi, controller, options = {}) {
|
|
|
1542
1656
|
if (parsed.length === 0)
|
|
1543
1657
|
throw new Error("Codex usage API returned no usage data");
|
|
1544
1658
|
state.snapshots = parsed;
|
|
1659
|
+
state.account = parseCodexAccountInfo(payload);
|
|
1545
1660
|
state.lastFetchAt = Date.now();
|
|
1546
1661
|
})();
|
|
1547
1662
|
let nextFetch;
|
|
@@ -1646,20 +1761,120 @@ function registerCodexUsageAndFast(pi, controller, options = {}) {
|
|
|
1646
1761
|
}
|
|
1647
1762
|
};
|
|
1648
1763
|
pi.registerCommand("codex-usage", {
|
|
1649
|
-
description: "Refresh and show Codex subscription usage
|
|
1764
|
+
description: "Refresh and show Codex subscription usage, plan, and rate limit redeems",
|
|
1650
1765
|
handler: async (_args, ctx) => {
|
|
1651
1766
|
try {
|
|
1652
1767
|
await refreshUsage(ctx, true);
|
|
1653
1768
|
} catch (error) {
|
|
1654
|
-
const
|
|
1769
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
1655
1770
|
const snapshots = currentState()?.snapshots ?? [];
|
|
1656
1771
|
if (snapshots.length === 0) {
|
|
1657
|
-
ctx.ui.notify(`Failed to refresh Codex usage: ${
|
|
1772
|
+
ctx.ui.notify(`Failed to refresh Codex usage: ${message2}`, "error");
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
ctx.ui.notify(`Failed to refresh Codex usage; showing the latest snapshot: ${message2}`, "warning");
|
|
1776
|
+
}
|
|
1777
|
+
try {
|
|
1778
|
+
const resolved = await resolveActiveClient(ctx, controller.getConfig());
|
|
1779
|
+
const state2 = accountState(resolved.accountId);
|
|
1780
|
+
const payload = await resolved.client.get(REDEEM_CREDITS_PATH);
|
|
1781
|
+
state2.redeemCredits = parseCodexRedeemCredits(payload);
|
|
1782
|
+
} catch {}
|
|
1783
|
+
const state = currentState();
|
|
1784
|
+
const message = formatCodexUsage(state?.snapshots ?? [], Date.now(), {
|
|
1785
|
+
account: state?.account,
|
|
1786
|
+
redeemCredits: state?.redeemCredits
|
|
1787
|
+
});
|
|
1788
|
+
ctx.ui.notify(ctx.ui.theme ? ctx.ui.theme.fg("muted", message) : message, "info");
|
|
1789
|
+
}
|
|
1790
|
+
});
|
|
1791
|
+
pi.registerCommand("codex-redeem", {
|
|
1792
|
+
description: "Preview and redeem an earned Codex rate limit reset credit (confirmation required)",
|
|
1793
|
+
handler: async (_args, ctx) => {
|
|
1794
|
+
const config = controller.getConfig();
|
|
1795
|
+
if (ctx.model?.provider !== "openai-codex" && !config.allowOtherProviders) {
|
|
1796
|
+
ctx.ui.notify("An active openai-codex model is required to redeem a rate limit reset. " + "Enable Other providers in /99settings to use the logged-in Codex subscription from another model.", "error");
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
let resolved;
|
|
1800
|
+
try {
|
|
1801
|
+
resolved = await resolveActiveClient(ctx, config);
|
|
1802
|
+
} catch (error) {
|
|
1803
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1804
|
+
ctx.ui.notify(`Failed to resolve the Codex subscription: ${message}`, "error");
|
|
1805
|
+
return;
|
|
1806
|
+
}
|
|
1807
|
+
const state = accountState(resolved.accountId);
|
|
1808
|
+
try {
|
|
1809
|
+
const payload = await resolved.client.get(REDEEM_CREDITS_PATH);
|
|
1810
|
+
const redeemCredits = parseCodexRedeemCredits(payload);
|
|
1811
|
+
state.redeemCredits = redeemCredits;
|
|
1812
|
+
const now = Date.now();
|
|
1813
|
+
if (!redeemCredits || redeemCredits.availableCount <= 0) {
|
|
1814
|
+
pendingRedeemByAccount.delete(resolved.accountId);
|
|
1815
|
+
ctx.ui.notify("No Codex rate limit reset credits are available to redeem.", "info");
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
1818
|
+
const availableCredits = [...redeemCredits.credits].filter((item) => item.status === undefined || item.status === "available").sort((left, right) => (left.expiresAt ?? Number.POSITIVE_INFINITY) - (right.expiresAt ?? Number.POSITIVE_INFINITY));
|
|
1819
|
+
const credit = availableCredits[0] ?? redeemCredits.credits[0];
|
|
1820
|
+
const expiryOf = (item) => item?.expiresAt !== undefined && item.expiresAt > now ? ` (expires ${formatDateTime(item.expiresAt)})` : "";
|
|
1821
|
+
let selected = credit;
|
|
1822
|
+
if (ctx.hasUI) {
|
|
1823
|
+
if (availableCredits.length > 1) {
|
|
1824
|
+
const options2 = availableCredits.map((item) => `${item.title ?? "reset credit"}${expiryOf(item)}`);
|
|
1825
|
+
const choice2 = await ctx.ui.select("Select a reset credit to redeem", options2, {
|
|
1826
|
+
timeout: REDEEM_DIALOG_TIMEOUT_MS
|
|
1827
|
+
});
|
|
1828
|
+
if (choice2 === undefined) {
|
|
1829
|
+
pendingRedeemByAccount.delete(resolved.accountId);
|
|
1830
|
+
ctx.ui.notify("Redeem cancelled — no reset credit was consumed.", "info");
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
const index = options2.indexOf(choice2);
|
|
1834
|
+
selected = availableCredits[index] ?? credit;
|
|
1835
|
+
}
|
|
1836
|
+
const confirmOptions = ["No", "Yes"];
|
|
1837
|
+
const choice = await ctx.ui.select(`Redeem ${selected?.title ?? "Full reset"}${expiryOf(selected)}?`, confirmOptions, { timeout: REDEEM_DIALOG_TIMEOUT_MS });
|
|
1838
|
+
if (choice !== confirmOptions[1]) {
|
|
1839
|
+
pendingRedeemByAccount.delete(resolved.accountId);
|
|
1840
|
+
ctx.ui.notify("Redeem cancelled — no reset credit was consumed.", "info");
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
const targetId = selected?.id;
|
|
1845
|
+
const existing = pendingRedeemByAccount.get(resolved.accountId);
|
|
1846
|
+
const pending = existing && existing.expiresAt > now && existing.creditId === targetId ? existing : {
|
|
1847
|
+
redeemRequestId: crypto.randomUUID(),
|
|
1848
|
+
creditId: targetId,
|
|
1849
|
+
expiresAt: now + REDEEM_CONFIRM_WINDOW_MS
|
|
1850
|
+
};
|
|
1851
|
+
pendingRedeemByAccount.set(resolved.accountId, pending);
|
|
1852
|
+
if (!ctx.hasUI && (!existing || existing.expiresAt <= now || existing.creditId !== targetId)) {
|
|
1853
|
+
ctx.ui.notify(`${redeemCredits.availableCount} rate limit reset redeem available: ${credit?.title ?? "Full reset"}${expiryOf(credit)}. ` + `Run /codex-redeem again within ${REDEEM_CONFIRM_WINDOW_MS / 1000}s to confirm.`, "warning");
|
|
1658
1854
|
return;
|
|
1659
1855
|
}
|
|
1660
|
-
|
|
1856
|
+
try {
|
|
1857
|
+
await resolved.client.post(REDEEM_PATH, {
|
|
1858
|
+
redeem_request_id: pending.redeemRequestId,
|
|
1859
|
+
credit_id: pending.creditId
|
|
1860
|
+
});
|
|
1861
|
+
pendingRedeemByAccount.delete(resolved.accountId);
|
|
1862
|
+
try {
|
|
1863
|
+
await refreshUsage(ctx, true);
|
|
1864
|
+
} catch {}
|
|
1865
|
+
const status = formatCodexStatus(currentState()?.snapshots ?? [], config.fastMode);
|
|
1866
|
+
ctx.ui.notify(`✓ Rate limit reset redeemed — usage reset.${status ? `
|
|
1867
|
+
${status}` : ""}`, "info");
|
|
1868
|
+
} catch (error) {
|
|
1869
|
+
pending.expiresAt = Date.now() + REDEEM_RETRY_WINDOW_MS;
|
|
1870
|
+
pendingRedeemByAccount.set(resolved.accountId, pending);
|
|
1871
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1872
|
+
ctx.ui.notify(`Failed to redeem a rate limit reset: ${message}. ` + "Run /codex-redeem again to retry with the same request ID.", "error");
|
|
1873
|
+
}
|
|
1874
|
+
} catch (error) {
|
|
1875
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1876
|
+
ctx.ui.notify(`Failed to redeem a rate limit reset: ${message}`, "error");
|
|
1661
1877
|
}
|
|
1662
|
-
ctx.ui.notify(formatCodexUsage(currentState()?.snapshots ?? []), "info");
|
|
1663
1878
|
}
|
|
1664
1879
|
});
|
|
1665
1880
|
pi.on("before_provider_request", (event, ctx) => {
|
|
@@ -1691,6 +1906,7 @@ function registerCodexUsageAndFast(pi, controller, options = {}) {
|
|
|
1691
1906
|
credentialRevision += 1;
|
|
1692
1907
|
activeAccountId = undefined;
|
|
1693
1908
|
usageByAccount.clear();
|
|
1909
|
+
pendingRedeemByAccount.clear();
|
|
1694
1910
|
latestContext = undefined;
|
|
1695
1911
|
accountObserverActive = false;
|
|
1696
1912
|
if (authWatchDebounce)
|
|
@@ -1741,14 +1957,18 @@ export {
|
|
|
1741
1957
|
registerCodexImageTool,
|
|
1742
1958
|
registerCodexApiSettings,
|
|
1743
1959
|
parseCodexUsagePayload,
|
|
1960
|
+
parseCodexRedeemCredits,
|
|
1744
1961
|
parseCodexRateLimits,
|
|
1962
|
+
parseCodexAccountInfo,
|
|
1745
1963
|
normalizeCodexImageSize,
|
|
1746
1964
|
normalizeCodexApiConfig,
|
|
1965
|
+
maskCodexEmail,
|
|
1747
1966
|
loadCodexApiConfig,
|
|
1748
1967
|
getCodexApiConfigPath,
|
|
1749
1968
|
formatCodexUsage,
|
|
1750
1969
|
formatCodexStatus,
|
|
1751
1970
|
formatCodexSearchDisplay,
|
|
1971
|
+
formatCodexRedeemCredits,
|
|
1752
1972
|
extractCodexAccountId,
|
|
1753
1973
|
codex_api_default as default,
|
|
1754
1974
|
createCodexSearchDisplay,
|
|
@@ -1765,5 +1985,5 @@ export {
|
|
|
1765
1985
|
CODEX_API_SETTINGS_NAMESPACE
|
|
1766
1986
|
};
|
|
1767
1987
|
|
|
1768
|
-
//# debugId=
|
|
1769
|
-
//# sourceMappingURL=index.
|
|
1988
|
+
//# debugId=356A61CA111E3F1D64756E2164756E21
|
|
1989
|
+
//# sourceMappingURL=index.ts.map
|