feat(usage): add Claude Fable quota tracker support
- Recognize Fable weekly windows and normalize to weekly fable (7d) - Fall back to 100% available weekly Fable window when Anthropic payload omits it - Forward remaining percentages and enforce canonical Claude quota order in Quota Tracker Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -102,14 +102,34 @@ async function fetchClaudeUsageRaw(accessToken, proxyOptions = null) {
|
|||||||
quotas["weekly (7d)"] = createQuotaObject(data.seven_day);
|
quotas["weekly (7d)"] = createQuotaObject(data.seven_day);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse model-specific weekly windows (e.g. seven_day_sonnet, seven_day_opus)
|
// Parse model-specific weekly windows (e.g. seven_day_sonnet, seven_day_opus, seven_day_fable)
|
||||||
|
const MODEL_DISPLAY_NAMES = {
|
||||||
|
fable_5_1: "fable",
|
||||||
|
fable_5: "fable",
|
||||||
|
};
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(data)) {
|
for (const [key, value] of Object.entries(data)) {
|
||||||
if (key.startsWith("seven_day_") && key !== "seven_day" && hasUtilization(value)) {
|
if (key.startsWith("seven_day_") && key !== "seven_day" && hasUtilization(value)) {
|
||||||
const modelName = key.replace("seven_day_", "");
|
const rawName = key.replace("seven_day_", "");
|
||||||
|
const modelName = MODEL_DISPLAY_NAMES[rawName] || rawName;
|
||||||
quotas[`weekly ${modelName} (7d)`] = createQuotaObject(value);
|
quotas[`weekly ${modelName} (7d)`] = createQuotaObject(value);
|
||||||
|
} else if ((key === "fable" || key === "fable_5" || key === "fable_5_1") && hasUtilization(value)) {
|
||||||
|
quotas["weekly fable (7d)"] = createQuotaObject(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback: surface Fable quota row if weekly window exists but Fable was not returned yet
|
||||||
|
if (!quotas["weekly fable (7d)"] && hasUtilization(data.seven_day)) {
|
||||||
|
quotas["weekly fable (7d)"] = {
|
||||||
|
used: 0,
|
||||||
|
total: 100,
|
||||||
|
remaining: 100,
|
||||||
|
remainingPercentage: 100,
|
||||||
|
resetAt: parseResetTime(data.seven_day.resets_at),
|
||||||
|
unlimited: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plan: "Claude Code",
|
plan: "Claude Code",
|
||||||
extraUsage: data.extra_usage ?? null,
|
extraUsage: data.extra_usage ?? null,
|
||||||
|
|||||||
@@ -510,6 +510,8 @@ export function parseQuotaData(provider, data) {
|
|||||||
name,
|
name,
|
||||||
used: quota.used || 0,
|
used: quota.used || 0,
|
||||||
total: quota.total || 0,
|
total: quota.total || 0,
|
||||||
|
remaining: quota.remaining !== undefined ? quota.remaining : Math.max(0, (quota.total || 100) - (quota.used || 0)),
|
||||||
|
remainingPercentage: quota.remainingPercentage !== undefined ? quota.remainingPercentage : calculatePercentage(quota.used, quota.total),
|
||||||
resetAt: quota.resetAt || null,
|
resetAt: quota.resetAt || null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -664,6 +666,18 @@ export function parseQuotaData(provider, data) {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (provider?.toLowerCase() === "claude") {
|
||||||
|
const CLAUDE_QUOTA_ORDER = {
|
||||||
|
"session (5h)": 0,
|
||||||
|
"weekly (7d)": 1,
|
||||||
|
"weekly fable (7d)": 2,
|
||||||
|
"weekly opus (7d)": 3,
|
||||||
|
"weekly sonnet (7d)": 4,
|
||||||
|
};
|
||||||
|
normalizedQuotas.sort((a, b) => (CLAUDE_QUOTA_ORDER[a.name] ?? 99) - (CLAUDE_QUOTA_ORDER[b.name] ?? 99));
|
||||||
|
return normalizedQuotas;
|
||||||
|
}
|
||||||
|
|
||||||
// Sort quotas according to PROVIDER_MODELS order
|
// Sort quotas according to PROVIDER_MODELS order
|
||||||
const modelOrder = getModelsByProviderId(provider);
|
const modelOrder = getModelsByProviderId(provider);
|
||||||
if (modelOrder.length > 0) {
|
if (modelOrder.length > 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user