fix(ai): keep header ingests from clearing usage-fetch cooldowns
This commit is contained in:
@@ -3172,7 +3172,8 @@ export class AuthStorage {
|
||||
}
|
||||
|
||||
if (this.#fetchUsageReportsOverride || this.#store.fetchUsageReports) return false;
|
||||
const prior = this.#usageCache.getStale<UsageReport | null>(cacheKey)?.value;
|
||||
const priorEntry = this.#usageCache.getStale<UsageReport | null>(cacheKey);
|
||||
const prior = priorEntry?.value;
|
||||
let merged = report;
|
||||
if (prior && Array.isArray(prior.limits)) {
|
||||
const headerLimitsById = new Map(report.limits.map(limit => [limit.id, limit]));
|
||||
@@ -3203,9 +3204,12 @@ export class AuthStorage {
|
||||
}
|
||||
|
||||
// Header-only reports are last-good hints, not completed usage fetches.
|
||||
// Keep them durable but stale so the next poll probes the provider; a
|
||||
// failed probe still applies the normal short backoff in #fetchUsageCached.
|
||||
const expiresAt = merged.metadata?.source === "ratelimit-headers" ? now - 1 : now + USAGE_REPORT_TTL_MS;
|
||||
// Keep them durable but stale so the next poll probes the provider, while
|
||||
// preserving any active failure backoff installed by #fetchUsageCached.
|
||||
const expiresAt =
|
||||
merged.metadata?.source === "ratelimit-headers"
|
||||
? Math.max(priorEntry?.expiresAt ?? now - 1, now - 1)
|
||||
: now + USAGE_REPORT_TTL_MS;
|
||||
this.#usageCache.set(cacheKey, { value: merged, expiresAt });
|
||||
this.#usageHeaderIngestAt.set(cacheKey, now);
|
||||
return true;
|
||||
|
||||
@@ -460,6 +460,38 @@ describe("AuthStorage usage cache: header ingestion", () => {
|
||||
expect(storage.ingestUsageHeaders("anthropic", usageHeaders("0.05", "0.6"), { sessionId: "s" })).toBe(false);
|
||||
});
|
||||
|
||||
it("preserves a failed fetch cooldown across exhausted header ingestion, then retries after expiry", async () => {
|
||||
const start = Date.now();
|
||||
const now = vi.spyOn(Date, "now").mockReturnValue(start);
|
||||
let calls = 0;
|
||||
vi.spyOn(claudeUsage.claudeUsageProvider, "fetchUsage").mockImplementation(async () => {
|
||||
calls += 1;
|
||||
return null;
|
||||
});
|
||||
|
||||
expect(await storage.getApiKey("anthropic", "cooldown-session")).toBe("oat-1");
|
||||
expect(
|
||||
storage.ingestUsageHeaders("anthropic", usageHeaders("0.02", "0.3"), {
|
||||
sessionId: "cooldown-session",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(await storage.fetchUsageReports()).toHaveLength(1);
|
||||
expect(calls).toBe(1);
|
||||
|
||||
now.mockReturnValue(start + 1_000);
|
||||
expect(
|
||||
storage.ingestUsageHeaders("anthropic", usageHeaders("1", "0.3"), {
|
||||
sessionId: "cooldown-session",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(await storage.fetchUsageReports()).toHaveLength(1);
|
||||
expect(calls).toBe(1);
|
||||
|
||||
now.mockReturnValue(start + 12_501);
|
||||
expect(await storage.fetchUsageReports()).toHaveLength(1);
|
||||
expect(calls).toBe(2);
|
||||
});
|
||||
|
||||
it("merges header umbrella windows onto the last real report and preserves tier limits", async () => {
|
||||
const realReport = makeTieredReport("a@example.com");
|
||||
let calls = 0;
|
||||
|
||||
Reference in New Issue
Block a user