fix(usage): render OpenRouter PAYG credit pool with real denominator (#12468)

* fix(usage) handle OpenRouter PAYG credit percentage

OpenRouter PAYG accounts without a per-key limit previously rendered
the credits row as 'total: 0, remainingPercentage: 100, unlimited: true',
treating /credits balance as unlimited even when a real credit pool was
present. Route the credit pool through the credits renderer with the
real denominator: total = totalCredits when positive, used = total -
creditBalance, remaining = creditBalance, remainingPercentage =
round(balance / total * 100), isCredits: true, unlimited: false. Per-key
limit still wins. A non-positive pool surfaces the row but never invents
a 100% bar.

Tests cover: explicit key limit, PAYG account credits without key limit,
key limit taking priority over account credits, and a balance without a
positive denominator.

* fix(usage) render OpenRouter PAYG quota as a metered percentage bar

The frontend parser was routing every OpenRouter 'credits' quota through
buildCreditsQuota(), which sets isCredits: true. QuotaCardExpanded
short-circuits on that flag and shows only the USD balance as a bare
number, so a real PAYG payload (used: 7.33, total: 10, remaining: 2.67,
remainingPercentage: 27) was rendered as '$2.67' instead of the '27% left
/ 7.33 / 10' bar the backend already computed.

Drop isCredits: true for any payload whose total is a positive finite
number - the row then goes through the normal normalizeQuotaEntry() path
with currency preserved as an extra. The balance-only fallback (total 0
or non-finite denominator, used by legacy /credits responses) still uses
buildCreditsQuota() so the row stays renderable, and never invents a
100% percentage.

The frontend test now asserts:
- PAYG positive denominator -> total: 10, remainingPercentage: 27,
  currency: 'USD', isCredits !== true.
- Balance-only payload -> isCredits === true, creditCount === 2.67,
  total: 0, no fabricated 100%.
- NaN denominator -> balance-only fallback.
- Non-credits keys -> unchanged normalizeQuotaEntry() path.
- Mixed payload -> normal quota row + PAYG row, both kept.

* docs(changelog): add OpenRouter PAYG fix fragment

* docs(changelog): remove self credit
This commit is contained in:
killer30001000
2026-09-18 17:21:17 +02:00
committed by GitHub
parent ad633c8440
commit d71d0f76e5
5 changed files with 308 additions and 10 deletions

View File

@@ -331,13 +331,26 @@ function parseAgentrouter(data: any) {
// USD. Free-tier request windows keep the generic percentage treatment.
function parseOpenrouterQuota(quotaKey: string, quota: any) {
if (quotaKey !== "credits") return normalizeQuotaEntry(quotaKey, quota);
// OpenRouter backend (PRs #12256 + #12468) reports a positive-denominator
// PAYG payload (used, total, remaining, remainingPercentage) and a
// balance-only payload under legacy keys. The credits renderer in
// QuotaCardExpanded short-circuits when `isCredits: true` and only shows
// the remaining balance as USD - so a positive-denominator PAYG row
// must NOT take that branch. Positive denominators go through the regular
// normalizeQuotaEntry() path (which keeps currency as an extra); only a
// missing/non-positive denominator falls back to buildCreditsQuota() so
// the balance row stays renderable without inventing a 100% percentage.
const total = Number(quota?.total ?? 0);
if (Number.isFinite(total) && total > 0) {
return normalizeQuotaEntry(quotaKey, quota, {
currency: quota?.currency ?? "USD",
});
}
const remaining = Math.max(0, Number(quota?.remaining ?? 0));
const currency = quota?.currency || "USD";
const remainingPercentage =
safePercentage(quota?.remainingPercentage) ?? (remaining > 0 ? 100 : 0);
const currency = quota?.currency ?? "USD";
const remainingPercentage = safePercentage(quota?.remainingPercentage) ?? 0;
return buildCreditsQuota("credits", remaining, remainingPercentage, { currency });
}
function parseOpenrouter(data: any) {
return quotaEntries(data).map(([quotaKey, quota]) => parseOpenrouterQuota(quotaKey, quota));
}