mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
Enterprise-tier Claude accounts (default_raven_enterprise) don't get
five_hour/seven_day utilization windows from Anthropic's OAuth usage
endpoint — only an extra_usage credit-billing block. parseClaude()
only read data.quotas, so quotas stayed {} and the dashboard showed
"No quota data" even when extraUsage showed the account 100%
exhausted. parseClaude() now folds an enabled extraUsage block into a
credits-style quota row (mirroring parseCodex's bankedResetCredits
pattern), both when quotas is empty and when it's already populated.
This commit is contained in:
committed by
GitHub
parent
69e47cdec5
commit
ac81235609
1
changelog.d/fixes/6806-6806-claude-quota-data.md
Normal file
1
changelog.d/fixes/6806-6806-claude-quota-data.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(dashboard): surface Claude extraUsage credits in quota card when quotas is empty (#6806)
|
||||
@@ -158,12 +158,39 @@ function parseCodex(data: any) {
|
||||
return quotas;
|
||||
}
|
||||
|
||||
function buildClaudeExtraUsageQuota(extraUsage: any) {
|
||||
const monthlyLimit = Number(extraUsage?.monthly_limit ?? 0);
|
||||
const usedCredits = Number(extraUsage?.used_credits ?? 0);
|
||||
const utilization = Number(extraUsage?.utilization ?? 0);
|
||||
const remainingPercentage = Number.isFinite(utilization)
|
||||
? Math.max(0, 100 - utilization)
|
||||
: undefined;
|
||||
const remaining = Number.isFinite(monthlyLimit) ? Math.max(0, monthlyLimit - usedCredits) : 0;
|
||||
|
||||
return buildCreditsQuota("extra_usage", remaining, remainingPercentage ?? 100, {
|
||||
used: Number.isFinite(usedCredits) ? usedCredits : 0,
|
||||
total: Number.isFinite(monthlyLimit) ? monthlyLimit : 0,
|
||||
currency: extraUsage?.currency,
|
||||
});
|
||||
}
|
||||
|
||||
// #6806: some Claude plans (e.g. "default_raven_enterprise") return no
|
||||
// five_hour/seven_day utilization windows at all — only a credit-billing
|
||||
// extraUsage block — so quotas can be {} while extraUsage still holds real,
|
||||
// actionable usage data. Fold it in instead of falling back to "No quota data".
|
||||
function parseClaude(data: any) {
|
||||
if (data?.message)
|
||||
return [{ name: "error", used: 0, total: 0, resetAt: null, message: data.message }];
|
||||
return quotaEntries(data).map(([name, quota]) =>
|
||||
|
||||
const quotas = quotaEntries(data).map(([name, quota]) =>
|
||||
normalizeQuotaEntry(name, quota, { isPercentageOnly: true })
|
||||
);
|
||||
|
||||
if (data?.extraUsage?.is_enabled) {
|
||||
quotas.push(buildClaudeExtraUsageQuota(data.extraUsage));
|
||||
}
|
||||
|
||||
return quotas;
|
||||
}
|
||||
|
||||
function parseDeepseekQuota(quotaKey: string, quota: any) {
|
||||
|
||||
98
tests/unit/quota-parsing-claude-extra-usage.test.ts
Normal file
98
tests/unit/quota-parsing-claude-extra-usage.test.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { parseQuotaData } from "../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx";
|
||||
|
||||
interface QuotaRow {
|
||||
name: string;
|
||||
isCredits?: boolean;
|
||||
remainingPercentage?: number;
|
||||
}
|
||||
|
||||
// Reproduces issue #6806: Claude Code "raven_enterprise" plan usage response has an
|
||||
// empty `quotas: {}` (no five_hour/seven_day utilization fields returned by Anthropic
|
||||
// for this plan) but a fully populated, 100%-exhausted `extraUsage` block. The UI must
|
||||
// not fall back to "No quota data" when extraUsage has real, usable data.
|
||||
test("#6806: parseQuotaData surfaces Claude extraUsage credits when quotas object is empty", () => {
|
||||
const data = {
|
||||
plan: "default_raven_enterprise",
|
||||
quotas: {},
|
||||
extraUsage: {
|
||||
is_enabled: true,
|
||||
monthly_limit: 120000,
|
||||
used_credits: 120015,
|
||||
utilization: 100,
|
||||
currency: "USD",
|
||||
decimal_places: 2,
|
||||
disabled_reason: null,
|
||||
daily: null,
|
||||
weekly: null,
|
||||
},
|
||||
bootstrap: {
|
||||
account_uuid: "redacted",
|
||||
account_email: "redacted",
|
||||
organization_uuid: "redacted",
|
||||
organization_name: "redacted",
|
||||
organization_type: "claude_enterprise",
|
||||
organization_rate_limit_tier: "default_raven_enterprise",
|
||||
},
|
||||
};
|
||||
|
||||
const parsed = parseQuotaData("claude", data);
|
||||
|
||||
assert.ok(
|
||||
parsed.length > 0,
|
||||
`expected at least one quota row derived from extraUsage, got empty array: ${JSON.stringify(parsed)}`
|
||||
);
|
||||
|
||||
const creditRow = parsed.find((row: QuotaRow) => row.isCredits);
|
||||
assert.ok(creditRow, "expected a credits-style quota row derived from extraUsage");
|
||||
assert.equal(creditRow.remainingPercentage, 0, "utilization 100% means 0% remaining");
|
||||
});
|
||||
|
||||
test("#6806: parseQuotaData still surfaces extraUsage credits when quotas is also populated", () => {
|
||||
const data = {
|
||||
plan: "pro",
|
||||
quotas: {
|
||||
"session (5h)": { used: 10, total: 100, remainingPercentage: 90, resetAt: null },
|
||||
},
|
||||
extraUsage: {
|
||||
is_enabled: true,
|
||||
monthly_limit: 5000,
|
||||
used_credits: 1000,
|
||||
utilization: 20,
|
||||
currency: "USD",
|
||||
decimal_places: 2,
|
||||
disabled_reason: null,
|
||||
daily: null,
|
||||
weekly: null,
|
||||
},
|
||||
};
|
||||
|
||||
const parsed = parseQuotaData("claude", data);
|
||||
|
||||
assert.equal(parsed.length, 2, `expected session quota + credits row, got: ${JSON.stringify(parsed)}`);
|
||||
const creditRow = parsed.find((row: QuotaRow) => row.isCredits);
|
||||
assert.ok(creditRow, "expected a credits-style quota row derived from extraUsage");
|
||||
assert.equal(creditRow.remainingPercentage, 80, "utilization 20% means 80% remaining");
|
||||
});
|
||||
|
||||
test("#6806: parseQuotaData does not add a credits row when extraUsage is disabled", () => {
|
||||
const data = {
|
||||
plan: "pro",
|
||||
quotas: {
|
||||
"session (5h)": { used: 10, total: 100, remainingPercentage: 90, resetAt: null },
|
||||
},
|
||||
extraUsage: {
|
||||
is_enabled: false,
|
||||
monthly_limit: 5000,
|
||||
used_credits: 0,
|
||||
utilization: 0,
|
||||
},
|
||||
};
|
||||
|
||||
const parsed = parseQuotaData("claude", data);
|
||||
|
||||
assert.equal(parsed.length, 1, `expected only the session quota, got: ${JSON.stringify(parsed)}`);
|
||||
assert.equal(parsed.some((row: QuotaRow) => row.isCredits), false);
|
||||
});
|
||||
Reference in New Issue
Block a user