fix(server): tone usage bars by how full they are (#2322)
A usage bar in Settings -> Usage stayed green all the way to 100%, so the one moment the meter matters was the moment it said nothing. The client already computes this. `window-bar.tsx` reads `window.tone ?? deriveTone(usedPct)`, and `deriveTone` escalates past 70% and 90%. But `usage.ts` only assigns `window.tone` when a provider sends one, so any provider that sends a tone opts out of the thresholds entirely. Claude and Kimi hardcoded `tone: "ok"`, and Codex escalated to `warning` past 70% but could never reach `danger`. Providers now derive tone from the percentage they already have, through one shared `toneFromUsedPct` whose thresholds match the client's. Claude, Codex and Kimi windows all escalate correctly. Balances had the same gap by a different route: `balance-bar.tsx` has no `deriveTone` fallback, and `balanceToneFromRemaining` only escalates once a balance is completely spent, so a credits bar at 99% was green. Cursor and Grok know their limit, so they now tone by used percentage. Codex credits report only a remaining balance with no limit, so they keep the remaining-based tone, documented as the no-limit case. MiniMax is unchanged: it maps a status its own API supplies rather than hardcoding a tone. Verified against the live Anthropic API with a session window at 83%, which now reports `warning` where it previously reported `ok`. Closes #2320 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
10da5ca169
commit
de69b2a2af
@@ -947,3 +947,81 @@ describe("real provider usage fetchers", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Regression for #2320: providers hardcoded `tone: "ok"`, which suppressed the client's
|
||||
// own thresholds (window-bar.tsx reads `window.tone ?? deriveTone(usedPct)`), so a bar
|
||||
// stayed green at 99%. Codex escalated to "warning" but could never reach "danger".
|
||||
describe("usage bars escalate as they fill", () => {
|
||||
let claudeHome: string;
|
||||
let codexHome: string;
|
||||
|
||||
beforeEach(() => {
|
||||
claudeHome = mkdtempSync(join(tmpdir(), "paseo-tone-claude-"));
|
||||
codexHome = mkdtempSync(join(tmpdir(), "paseo-tone-codex-"));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(claudeHome, { recursive: true, force: true });
|
||||
rmSync(codexHome, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
function claudeAt(utilization: number) {
|
||||
writeClaudeCredentials(claudeHome, "at_valid");
|
||||
return new ClaudeQuotaProvider({
|
||||
logger: createLogger(),
|
||||
claudeHome,
|
||||
claudeKeychainReader: async () => null,
|
||||
fetch: mockFetch(
|
||||
new Map([
|
||||
[
|
||||
"https://api.anthropic.com/api/oauth/usage",
|
||||
() =>
|
||||
jsonResponse({
|
||||
seven_day: { utilization, resets_at: "2026-06-04T00:00:00Z" },
|
||||
}),
|
||||
],
|
||||
]),
|
||||
),
|
||||
}).fetchUsage();
|
||||
}
|
||||
|
||||
it.each([
|
||||
[10, "ok"],
|
||||
[75, "warning"],
|
||||
[99, "danger"],
|
||||
])("a Claude window at %s%% is %s", async (utilization, tone) => {
|
||||
const usage = await claudeAt(utilization);
|
||||
expect(usage.windows).toEqual([expect.objectContaining({ id: "weekly", tone })]);
|
||||
});
|
||||
|
||||
it("a Codex window can reach danger, not just warning", async () => {
|
||||
writeCodexAuth(codexHome, "at_codex");
|
||||
const usage = await new CodexQuotaProvider({
|
||||
logger: createLogger(),
|
||||
codexHome,
|
||||
fetch: mockFetch(
|
||||
new Map([
|
||||
[
|
||||
"https://chatgpt.com/backend-api/wham/usage",
|
||||
() =>
|
||||
jsonResponse(
|
||||
makeCodexResponse({
|
||||
rate_limit: {
|
||||
primary_window: { used_percent: 12, reset_at: 1_748_812_800 },
|
||||
secondary_window: { used_percent: 96, reset_at: 1_749_072_000 },
|
||||
},
|
||||
}),
|
||||
),
|
||||
],
|
||||
]),
|
||||
),
|
||||
}).fetchUsage();
|
||||
|
||||
expect(usage.windows).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: "session", tone: "ok" }),
|
||||
expect.objectContaining({ id: "weekly", tone: "danger" }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user