(async function () {
const USD_PER_CREDIT = 0.04;
const RATE = {
label: "GPT-5.5",
input: 125,
cached: 12.5,
output: 750,
};
const num = (v) => {
if (v == null || v === "") return 0;
if (typeof v === "number") return Number.isFinite(v) ? v : 0;
const matched = String(v).replace(/[$,\s%]/g, "").match(/-?\d+(\.\d+)?/);
return matched ? Number(matched[0]) : 0;
};
const first = (...values) => values.map(num).find((v) => v > 0) || 0;
const fmtCredits = (v) => num(v).toFixed(3);
const fmtUsd = (v) => "$" + num(v).toFixed(2);
const fmtPercent = (ratio) => (num(ratio) * 100).toFixed(2) + "%";
const localDate = (time = Date.now()) => {
const d = new Date(time);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${y}-${m}-${day}`;
};
function deepSum(obj, tester, seen = new WeakSet()) {
if (!obj || typeof obj !== "object" || seen.has(obj)) return 0;
seen.add(obj);
let sum = 0;
for (const [key, value] of Object.entries(obj)) {
const lowerKey = key.toLowerCase();
if (tester(lowerKey)) {
sum += num(value);
}
sum += deepSum(value, tester, seen);
}
return sum;
}
function getDate(row = {}) {
return (
row.date ||
row.day ||
row.start_date ||
row.created_date ||
row.bucket ||
row.period_start ||
"-"
);
}
function getTokenParts(row = {}) {
const t = row.totals || {};
const cached =
first(
row.cached_text_input_tokens,
t.cached_text_input_tokens,
row.cached_input_tokens,
t.cached_input_tokens,
) ||
deepSum(
row,
(k) =>
k.includes("cached") &&
k.includes("token") &&
!k.includes("uncached") &&
!k.includes("total"),
);
const explicitUncached =
first(
row.uncached_text_input_tokens,
t.uncached_text_input_tokens,
row.uncached_input_tokens,
t.uncached_input_tokens,
) ||
deepSum(row, (k) => k.includes("uncached") && k.includes("token"));
const totalInput = first(
row.text_input_tokens,
t.text_input_tokens,
row.input_tokens,
t.input_tokens,
row.prompt_tokens,
t.prompt_tokens,
);
const uncached =
explicitUncached || (totalInput ? Math.max(totalInput - cached, 0) : 0);
const output =
first(
row.text_output_tokens,
t.text_output_tokens,
row.output_tokens,
t.output_tokens,
row.completion_tokens,
t.completion_tokens,
) ||
deepSum(
row,
(k) =>
k.includes("token") &&
(k.includes("output") || k.includes("completion")),
);
const total =
first(
row.text_total_tokens,
t.text_total_tokens,
row.total_tokens,
t.total_tokens,
row.tokens,
t.tokens,
) ||
cached + uncached + output;
return { cached, uncached, output, total };
}
function estimateCredits(row) {
const p = getTokenParts(row);
return (
(p.uncached / 1_000_000) * RATE.input +
(p.cached / 1_000_000) * RATE.cached +
(p.output / 1_000_000) * RATE.output
);
}
function getTurns(row = {}) {
const t = row.totals || {};
return first(
row.turns,
t.turns,
row.total_turns,
t.total_turns,
row.requests,
t.requests,
row.request_count,
t.request_count,
row.count,
t.count,
);
}
function calcStats(list) {
return list.reduce(
(acc, row) => {
const p = getTokenParts(row);
const credits = estimateCredits(row);
acc.input_tokens += p.uncached;
acc.cached_tokens += p.cached;
acc.output_tokens += p.output;
acc.total_tokens += p.total;
acc.turns += getTurns(row);
acc.credits += credits;
acc.usd += credits * USD_PER_CREDIT;
return acc;
},
{
input_tokens: 0,
cached_tokens: 0,
output_tokens: 0,
total_tokens: 0,
turns: 0,
credits: 0,
usd: 0,
},
);
}
function toRow(metric, stats, extra = {}) {
return {
metric,
credits: Number(stats.credits.toFixed(6)),
usd: fmtUsd(stats.usd),
input_tokens: stats.input_tokens,
cached_tokens: stats.cached_tokens,
output_tokens: stats.output_tokens,
total_tokens: stats.total_tokens,
turns: stats.turns,
...extra,
};
}
async function apiGet(path, token) {
const res = await fetch(path, {
credentials: "include",
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
},
});
const text = await res.text();
const json = text ? JSON.parse(text) : {};
if (!res.ok) {
throw new Error(json?.detail || json?.message || `HTTP ${res.status}`);
}
return json;
}
const bootstrap =
document.getElementById("client-bootstrap")?.textContent || "";
const token = bootstrap.match(/[\w-]{30,}\.[\w-]{30,}\.[\w-]{30,}/)?.[0];
if (!token) {
throw new Error("获取 token 失败:请在 ChatGPT Codex Analytics 页面执行。");
}
const usage = await apiGet("/backend-api/wham/usage", token);
const windowInfo =
usage?.rate_limit?.secondary_window ||
usage?.rate_limit?.primary_window ||
usage?.secondary_window ||
usage?.primary_window ||
{};
const usedPercentRaw = first(
windowInfo.used_percent,
windowInfo.usage_percent,
windowInfo.percent_used,
windowInfo.usedPercentage,
windowInfo.usagePercentage,
);
const usedRatio = usedPercentRaw > 1 ? usedPercentRaw / 100 : usedPercentRaw;
const resetAt = num(windowInfo.reset_at);
const windowSeconds = num(windowInfo.limit_window_seconds);
const todayDate = localDate();
const startDate = localDate(Date.now() - 30 * 86400000);
const endDate = localDate(Date.now() + 86400000);
const cycleStartDate =
resetAt && windowSeconds
? localDate((resetAt - windowSeconds) * 1000)
: startDate;
const resetDate = resetAt ? new Date(resetAt * 1000).toLocaleString() : "-";
const dailyData = await apiGet(
`/backend-api/wham/analytics/daily-workspace-usage-counts?start_date=${startDate}&end_date=${endDate}&group_by=day`,
token,
);
const dailyList =
Array.isArray(dailyData.data)
? dailyData.data
: Array.isArray(dailyData.items)
? dailyData.items
: Array.isArray(dailyData.results)
? dailyData.results
: [];
const todayList = dailyList.filter((row) => getDate(row) === todayDate);
const cycleKeepStartDayList = dailyList.filter((row) => {
const date = getDate(row);
return date === "-" || new Date(date) >= new Date(cycleStartDate);
});
const cycleDropStartDayList = dailyList.filter((row) => {
const date = getDate(row);
return date === "-" || new Date(date) > new Date(cycleStartDate);
});
const today = calcStats(todayList);
const currentKeepStartDay = calcStats(cycleKeepStartDayList);
const currentDropStartDay = calcStats(cycleDropStartDayList);
const last30Days = calcStats(dailyList);
const limitKeepStartDay =
usedRatio > 0 ? currentKeepStartDay.credits / usedRatio : 0;
const limitDropStartDay =
usedRatio > 0 ? currentDropStartDay.credits / usedRatio : 0;
const limitLow = Math.min(limitDropStartDay, limitKeepStartDay);
const limitHigh = Math.max(limitDropStartDay, limitKeepStartDay);
const remainingLow = Math.max(limitLow - currentKeepStartDay.credits, 0);
const remainingHigh = Math.max(limitHigh - currentDropStartDay.credits, 0);
const summary = [
toRow("今天消耗", today),
toRow("本周期已用:保留开始日", currentKeepStartDay, {
percent: fmtPercent(usedRatio),
note: "可能偏高",
}),
toRow("本周期已用:踢掉开始日", currentDropStartDay, {
percent: fmtPercent(usedRatio),
note: "可能偏低",
}),
{
metric: "推算周期额度:下限",
credits: Number(limitLow.toFixed(6)),
usd: fmtUsd(limitLow * USD_PER_CREDIT),
note: "按踢掉开始日估算",
},
{
metric: "推算周期额度:上限",
credits: Number(limitHigh.toFixed(6)),
usd: fmtUsd(limitHigh * USD_PER_CREDIT),
note: "按保留开始日估算",
},
{
metric: "推算剩余额度:范围",
credits: `${fmtCredits(remainingLow)} ~ ${fmtCredits(remainingHigh)}`,
usd: `${fmtUsd(remainingLow * USD_PER_CREDIT)} ~ ${fmtUsd(
remainingHigh * USD_PER_CREDIT,
)}`,
},
toRow("近30天已用", last30Days),
];
const dailyTable = dailyList.map((row) => {
const p = getTokenParts(row);
const credits = estimateCredits(row);
return {
date: getDate(row),
input_tokens: p.uncached,
cached_tokens: p.cached,
output_tokens: p.output,
total_tokens: p.total,
credits: Number(credits.toFixed(6)),
usd: fmtUsd(credits * USD_PER_CREDIT),
turns: getTurns(row),
};
});
console.group("[Codex Quota Compass] GPT-5.5 周限范围分析");
console.table(summary);
console.table(dailyTable);
console.log("todayDate:", todayDate);
console.log("cycleStartDate:", cycleStartDate);
console.log("resetDate:", resetDate);
console.log("usedPercent:", fmtPercent(usedRatio));
console.log("今天消耗 Credits:", fmtCredits(today.credits));
console.log("本周期已用 Credits,保留开始日:", fmtCredits(currentKeepStartDay.credits));
console.log("本周期已用 Credits,踢掉开始日:", fmtCredits(currentDropStartDay.credits));
console.log(
"推算周期额度 Credits 范围:",
`${fmtCredits(limitLow)} ~ ${fmtCredits(limitHigh)}`,
);
console.log(
"推算周期额度金额范围:",
`${fmtUsd(limitLow * USD_PER_CREDIT)} ~ ${fmtUsd(limitHigh * USD_PER_CREDIT)}`,
);
console.log(
"推算剩余额度 Credits 范围:",
`${fmtCredits(remainingLow)} ~ ${fmtCredits(remainingHigh)}`,
);
console.groupEnd();
return {
model: RATE.label,
todayDate,
cycleStartDate,
resetDate,
usedRatio,
usedPercent: fmtPercent(usedRatio),
today,
currentKeepStartDay,
currentDropStartDay,
inferredCycleLimit: {
lowCredits: limitLow,
highCredits: limitHigh,
lowUsd: limitLow * USD_PER_CREDIT,
highUsd: limitHigh * USD_PER_CREDIT,
creditsRange: `${fmtCredits(limitLow)} ~ ${fmtCredits(limitHigh)}`,
usdRange: `${fmtUsd(limitLow * USD_PER_CREDIT)} ~ ${fmtUsd(
limitHigh * USD_PER_CREDIT,
)}`,
},
inferredRemaining: {
lowCredits: remainingLow,
highCredits: remainingHigh,
lowUsd: remainingLow * USD_PER_CREDIT,
highUsd: remainingHigh * USD_PER_CREDIT,
creditsRange: `${fmtCredits(remainingLow)} ~ ${fmtCredits(remainingHigh)}`,
usdRange: `${fmtUsd(remainingLow * USD_PER_CREDIT)} ~ ${fmtUsd(
remainingHigh * USD_PER_CREDIT,
)}`,
},
last30Days,
summary,
dailyTable,
raw: {
usage,
windowInfo,
dailyData,
},
};
})();
查询你的 codex 周限额度
查询你的 codex 周限额度