feat(usage): API-key USD quota percent + reset hints, weekly window cutoff (#4398)

Adds USD usage percentages and reset hints to @@om-usage and 400 quota rejections, and cuts the weekly USD window at the real observed reset via quota_snapshots instead of resetAt-7d. Rebuilt onto release/v3.8.33 (squash-base-stale; delta = 5 files/+353).

Integrated into release/v3.8.33.
This commit is contained in:
Witroch4
2026-06-21 12:02:34 -03:00
committed by GitHub
parent 69f63898de
commit d3f1b23a93
5 changed files with 353 additions and 30 deletions

View File

@@ -18,6 +18,7 @@ export interface ApiKeyUsageLimitPayload {
dailySpentUsd: number;
weeklySpentUsd: number;
dailyWindowStartIso: string;
dailyResetAtIso: string;
weeklyWindowStartIso: string;
weeklyResetAtIso: string | null;
dailyExceeded: boolean;

View File

@@ -4,6 +4,7 @@ import { calculateCost } from "./costCalculator";
import { buildErrorBody, sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
const FORTALEZA_UTC_OFFSET_MS = 3 * 60 * 60 * 1000;
const DAY_MS = 24 * 60 * 60 * 1000;
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
export interface ApiKeyUsageLimitMetadata {
@@ -21,6 +22,7 @@ export interface ApiKeyUsageLimitStatus {
dailySpentUsd: number;
weeklySpentUsd: number;
dailyWindowStartIso: string;
dailyResetAtIso: string;
weeklyWindowStartIso: string;
weeklyResetAtIso: string | null;
dailyExceeded: boolean;
@@ -46,6 +48,18 @@ interface UsageCostRow {
reasoningTokens: number | null;
}
interface WeeklyResetCandidate {
connectionId: string;
resetAtIso: string;
observedWindowStartIso: string | null;
}
interface QuotaSnapshotRow {
remainingPercentage: number | null;
nextResetAt: string | null;
createdAt: string | null;
}
function toNumber(value: unknown): number {
if (typeof value === "number" && Number.isFinite(value)) return value;
if (typeof value === "string" && value.trim()) {
@@ -70,11 +84,49 @@ function roundUsd(value: number): number {
return Math.round(value * 1_000_000) / 1_000_000;
}
function clampPercent(value: number): number {
return Math.max(0, Math.min(100, value));
}
function formatUsd(value: number | null): string {
if (value === null || !Number.isFinite(value)) return "Not configured";
return `$${value.toFixed(2)}`;
}
function getUsagePercent(spentUsd: number, limitUsd: number | null): number | null {
if (limitUsd === null || !Number.isFinite(limitUsd) || limitUsd <= 0) return null;
return (spentUsd / limitUsd) * 100;
}
function formatUsagePercent(percent: number | null): string {
if (percent === null || !Number.isFinite(percent)) return "Unavailable";
return `${Math.round(percent)}%`;
}
function formatResetIn(resetAt: string | null, now = Date.now()): string {
if (!resetAt) return "unknown";
const resetMs = Date.parse(resetAt);
if (!Number.isFinite(resetMs)) return "unknown";
const deltaMs = resetMs - now;
if (deltaMs <= 0) return "now";
const minuteMs = 60_000;
const hourMs = 60 * minuteMs;
const dayMs = 24 * hourMs;
if (deltaMs < hourMs) return `${Math.max(1, Math.ceil(deltaMs / minuteMs))}m`;
if (deltaMs < dayMs) return `${Math.max(1, Math.ceil(deltaMs / hourMs))}h`;
return `${Math.max(1, Math.ceil(deltaMs / dayMs))}d`;
}
function resetDay(value: string | null): string | null {
if (!value) return null;
const parsed = Date.parse(value);
if (!Number.isFinite(parsed)) return null;
return new Date(parsed).toISOString().slice(0, 10);
}
export function getFortalezaDayStartIso(nowMs = Date.now()): string {
const fortalezaLocal = new Date(nowMs - FORTALEZA_UTC_OFFSET_MS);
return new Date(
@@ -90,6 +142,10 @@ export function getFortalezaDayStartIso(nowMs = Date.now()): string {
).toISOString();
}
export function getFortalezaDayResetIso(nowMs = Date.now()): string {
return new Date(Date.parse(getFortalezaDayStartIso(nowMs)) + DAY_MS).toISOString();
}
export function getRollingWeekStartIso(nowMs = Date.now()): string {
return new Date(nowMs - WEEK_MS).toISOString();
}
@@ -144,6 +200,64 @@ function connectionFromValue(value: unknown): { id: string; provider: string } |
return { id, provider };
}
function isWeeklyQuotaResetSnapshot(row: QuotaSnapshotRow, targetResetAtIso: string): boolean {
const targetDay = resetDay(targetResetAtIso);
if (!targetDay) return false;
return resetDay(row.nextResetAt) === targetDay;
}
function getObservedWeeklyWindowStartIso(
connectionId: string,
targetResetAtIso: string,
nowMs: number
): string | null {
if (!connectionId || !targetResetAtIso) return null;
try {
const rows = getDbInstance()
.prepare(
`
SELECT
remaining_percentage as remainingPercentage,
next_reset_at as nextResetAt,
created_at as createdAt
FROM quota_snapshots
WHERE connection_id = @connectionId
AND LOWER(window_key) LIKE '%weekly%'
AND LOWER(window_key) NOT LIKE '%sonnet%'
AND created_at <= @nowIso
ORDER BY created_at ASC, id ASC
`
)
.all({ connectionId, nowIso: new Date(nowMs).toISOString() }) as QuotaSnapshotRow[];
let observedStartIso: string | null = null;
let previousUsedPercent: number | null = null;
for (const row of rows) {
if (!row.createdAt || !isWeeklyQuotaResetSnapshot(row, targetResetAtIso)) continue;
const remaining = toNumber(row.remainingPercentage);
const usedPercent = clampPercent(100 - remaining);
if (!observedStartIso) {
observedStartIso = row.createdAt;
} else if (previousUsedPercent !== null) {
const droppedToResetFloor = usedPercent <= 1 && previousUsedPercent > usedPercent;
const significantDrop = previousUsedPercent - usedPercent >= 5;
if (droppedToResetFloor || significantDrop) {
observedStartIso = row.createdAt;
}
}
previousUsedPercent = usedPercent;
}
return observedStartIso;
} catch {
return null;
}
}
async function resolveDeps(deps: ApiKeyUsageLimitDeps): Promise<Required<ApiKeyUsageLimitDeps>> {
const providers =
deps.getProviderConnectionById && deps.getProviderConnections
@@ -165,16 +279,16 @@ async function resolveDeps(deps: ApiKeyUsageLimitDeps): Promise<Required<ApiKeyU
};
}
async function getProviderWeeklyResetAt(
async function getProviderWeeklyWindow(
metadata: ApiKeyUsageLimitMetadata,
deps: Required<ApiKeyUsageLimitDeps>,
nowMs: number
): Promise<string | null> {
): Promise<{ resetAtIso: string | null; windowStartIso: string | null }> {
const allowedConnections = Array.isArray(metadata.allowedConnections)
? metadata.allowedConnections.filter((id) => typeof id === "string" && id.trim())
: [];
const resetCandidates: string[] = [];
const resetCandidates: WeeklyResetCandidate[] = [];
if (allowedConnections.length > 0) {
for (const connectionId of allowedConnections) {
const connection = connectionFromValue(await deps.getProviderConnectionById(connectionId));
@@ -183,7 +297,13 @@ async function getProviderWeeklyResetAt(
deps.getProviderLimitsCache(connection.id)?.quotas,
nowMs
);
if (resetAt) resetCandidates.push(resetAt);
if (resetAt) {
resetCandidates.push({
connectionId: connection.id,
resetAtIso: resetAt,
observedWindowStartIso: getObservedWeeklyWindowStartIso(connection.id, resetAt, nowMs),
});
}
}
} else {
const caches = deps.getAllProviderLimitsCache();
@@ -192,11 +312,24 @@ async function getProviderWeeklyResetAt(
const connection = connectionFromValue(rawConnection);
if (!connection || connection.provider.toLowerCase() !== "claude") continue;
const resetAt = findWeeklyQuotaResetAt(caches[connection.id]?.quotas, nowMs);
if (resetAt) resetCandidates.push(resetAt);
if (resetAt) {
resetCandidates.push({
connectionId: connection.id,
resetAtIso: resetAt,
observedWindowStartIso: getObservedWeeklyWindowStartIso(connection.id, resetAt, nowMs),
});
}
}
}
return resetCandidates.sort((left, right) => Date.parse(left) - Date.parse(right)).at(0) ?? null;
const selected =
resetCandidates
.sort((left, right) => Date.parse(left.resetAtIso) - Date.parse(right.resetAtIso))
.at(0) ?? null;
return {
resetAtIso: selected?.resetAtIso ?? null,
windowStartIso: selected?.observedWindowStartIso ?? null,
};
}
async function getApiKeyUsdSpendSince(apiKeyId: string, sinceIso: string): Promise<number> {
@@ -257,10 +390,14 @@ export async function getApiKeyUsageLimitStatus(
const resolvedDeps = await resolveDeps(deps);
const now = resolvedDeps.now();
const dailyWindowStartIso = getFortalezaDayStartIso(now);
const weeklyResetAtIso = await getProviderWeeklyResetAt(metadata, resolvedDeps, now);
const weeklyWindowStartIso = weeklyResetAtIso
? new Date(Date.parse(weeklyResetAtIso) - WEEK_MS).toISOString()
: getRollingWeekStartIso(now);
const dailyResetAtIso = getFortalezaDayResetIso(now);
const weeklyWindow = await getProviderWeeklyWindow(metadata, resolvedDeps, now);
const weeklyResetAtIso = weeklyWindow.resetAtIso;
const weeklyWindowStartIso = weeklyWindow.windowStartIso
? weeklyWindow.windowStartIso
: weeklyResetAtIso
? new Date(Date.parse(weeklyResetAtIso) - WEEK_MS).toISOString()
: getRollingWeekStartIso(now);
const dailyLimitUsd = normalizeLimitUsd(metadata.dailyUsageLimitUsd);
const weeklyLimitUsd = normalizeLimitUsd(metadata.weeklyUsageLimitUsd);
const enabled = metadata.usageLimitEnabled === true;
@@ -277,6 +414,7 @@ export async function getApiKeyUsageLimitStatus(
dailySpentUsd,
weeklySpentUsd,
dailyWindowStartIso,
dailyResetAtIso,
weeklyWindowStartIso,
weeklyResetAtIso,
dailyExceeded: enabled && dailyLimitUsd !== null && dailySpentUsd >= dailyLimitUsd,
@@ -284,26 +422,39 @@ export async function getApiKeyUsageLimitStatus(
};
}
export function buildApiKeyUsageLimitText(status: ApiKeyUsageLimitStatus): string {
export function buildApiKeyUsageLimitText(
status: ApiKeyUsageLimitStatus,
now = Date.now()
): string {
return [
"Cota diaria",
formatUsd(status.dailyLimitUsd),
"Gasto diario",
formatUsd(status.dailySpentUsd),
"Uso diario",
formatUsagePercent(getUsagePercent(status.dailySpentUsd, status.dailyLimitUsd)),
`Resets in ${formatResetIn(status.dailyResetAtIso, now)}`,
"",
"Cota semanal",
formatUsd(status.weeklyLimitUsd),
"Gasto semanal",
formatUsd(status.weeklySpentUsd),
"Uso semanal",
formatUsagePercent(getUsagePercent(status.weeklySpentUsd, status.weeklyLimitUsd)),
`Resets in ${formatResetIn(status.weeklyResetAtIso, now)}`,
].join("\n");
}
function buildUsageLimitExceededMessage(status: ApiKeyUsageLimitStatus): string {
function buildUsageLimitExceededMessage(status: ApiKeyUsageLimitStatus, now = Date.now()): string {
if (status.dailyExceeded && status.dailyLimitUsd !== null) {
return `This API key reached its daily USD usage quota (${formatUsd(status.dailySpentUsd)} of ${formatUsd(status.dailyLimitUsd)}). Choose another allowed model or wait for quota reset.`;
const percent = formatUsagePercent(getUsagePercent(status.dailySpentUsd, status.dailyLimitUsd));
return `This API key reached its daily USD usage quota (${formatUsd(status.dailySpentUsd)} of ${formatUsd(status.dailyLimitUsd)}, ${percent}). Resets in ${formatResetIn(status.dailyResetAtIso, now)}. Choose another allowed model after reset.`;
}
if (status.weeklyExceeded && status.weeklyLimitUsd !== null) {
return `This API key reached its weekly USD usage quota (${formatUsd(status.weeklySpentUsd)} of ${formatUsd(status.weeklyLimitUsd)}). Choose another allowed model or wait for quota reset.`;
const percent = formatUsagePercent(
getUsagePercent(status.weeklySpentUsd, status.weeklyLimitUsd)
);
return `This API key reached its weekly USD usage quota (${formatUsd(status.weeklySpentUsd)} of ${formatUsd(status.weeklyLimitUsd)}, ${percent}). Resets in ${formatResetIn(status.weeklyResetAtIso, now)}. Choose another allowed model after reset.`;
}
return "This API key reached its USD usage quota. Choose another allowed model or wait for quota reset.";
}
@@ -319,9 +470,10 @@ function isAnthropicMessagesRequest(request: Request): boolean {
export function buildApiKeyUsageLimitRejection(
request: Request,
status: ApiKeyUsageLimitStatus
status: ApiKeyUsageLimitStatus,
now = Date.now()
): Response {
const message = sanitizeErrorMessage(buildUsageLimitExceededMessage(status));
const message = sanitizeErrorMessage(buildUsageLimitExceededMessage(status, now));
if (isAnthropicMessagesRequest(request)) {
return new Response(
JSON.stringify({

View File

@@ -376,7 +376,8 @@ export async function buildUsageCommandText(
const resolvedDeps = await normalizeDeps(deps);
if (metadata.usageLimitEnabled === true) {
return buildApiKeyUsageLimitText(
await resolvedDeps.getApiKeyUsageLimitStatus(metadata, { now: resolvedDeps.now })
await resolvedDeps.getApiKeyUsageLimitStatus(metadata, { now: resolvedDeps.now }),
resolvedDeps.now()
);
}

View File

@@ -138,26 +138,152 @@ test("getApiKeyUsageLimitStatus aligns weekly USD spend with provider resetAt wh
assert.equal(status.weeklySpentUsd, 5);
assert.equal(status.dailyLimitUsd, 10);
assert.equal(status.weeklyLimitUsd, 20);
assert.equal(status.dailyResetAtIso, "2026-06-20T03:00:00.000Z");
assert.equal(status.weeklyWindowStartIso, "2026-06-18T20:00:00.000Z");
assert.equal(status.weeklyResetAtIso, weeklyResetAt);
assert.equal(status.dailyExceeded, false);
assert.equal(status.weeklyExceeded, false);
});
test("buildApiKeyUsageLimitText returns only the quota and spent USD lines", async () => {
const text = usageLimits.buildApiKeyUsageLimitText({
enabled: true,
dailyLimitUsd: 10,
weeklyLimitUsd: 50,
dailySpentUsd: 2,
weeklySpentUsd: 5.25,
dailyWindowStartIso: "2026-06-19T03:00:00.000Z",
weeklyWindowStartIso: "2026-06-12T20:00:00.000Z",
weeklyResetAtIso: "2026-06-19T20:00:00.000Z",
dailyExceeded: false,
weeklyExceeded: false,
test("getApiKeyUsageLimitStatus cuts weekly USD spend at observed provider quota reset", async () => {
await localDb.updatePricing({
claude: {
"claude-opus-4-8": {
input: 1,
cached: 1,
output: 1,
reasoning: 1,
cache_creation: 1,
},
},
});
const created = await apiKeysDb.createApiKey("Reset Cut Key", "machine-limit-reset");
await apiKeysDb.updateApiKeyPermissions(created.id, {
usageLimitEnabled: true,
dailyUsageLimitUsd: 10,
weeklyUsageLimitUsd: 20,
});
await usageHistory.saveRequestUsage({
provider: "claude",
model: "claude-opus-4-8",
apiKeyId: created.id,
apiKeyName: "Reset Cut Key",
tokens: { input: 7_000_000, output: 0 },
success: true,
timestamp: "2026-06-19T23:30:00.000Z",
});
await usageHistory.saveRequestUsage({
provider: "claude",
model: "claude-opus-4-8",
apiKeyId: created.id,
apiKeyName: "Reset Cut Key",
tokens: { input: 2_000_000, output: 0 },
success: true,
timestamp: "2026-06-20T02:00:00.000Z",
});
const db = core.getDbInstance();
const insertSnapshot = db.prepare(`
INSERT INTO quota_snapshots (
provider,
connection_id,
window_key,
remaining_percentage,
is_exhausted,
next_reset_at,
window_duration_ms,
raw_data,
created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
insertSnapshot.run(
"claude",
"conn-claude",
"weekly (7d)",
72,
0,
"2026-06-25T23:00:00.000Z",
null,
null,
"2026-06-19T23:55:00.000Z"
);
insertSnapshot.run(
"claude",
"conn-claude",
"weekly (7d)",
100,
0,
"2026-06-25T23:00:00.000Z",
null,
null,
"2026-06-20T01:42:52.590Z"
);
insertSnapshot.run(
"claude",
"conn-claude",
"weekly (7d)",
99,
0,
"2026-06-25T23:00:00.000Z",
null,
null,
"2026-06-20T02:10:00.000Z"
);
const metadata = await apiKeysDb.getApiKeyMetadata(created.key);
assert.ok(metadata);
const weeklyResetAt = "2026-06-25T23:00:00.000Z";
const status = await usageLimits.getApiKeyUsageLimitStatus(
{ ...metadata, allowedConnections: ["conn-claude"] },
{
now: () => Date.parse("2026-06-20T14:30:00.000Z"),
getProviderConnectionById: async () => ({
id: "conn-claude",
provider: "claude",
isActive: true,
}),
getProviderConnections: async () => [],
getProviderLimitsCache: () => ({
plan: "Claude Max",
quotas: {
"weekly (7d)": {
used: 5,
total: 100,
resetAt: weeklyResetAt,
},
},
message: null,
fetchedAt: "2026-06-20T14:30:00.000Z",
}),
getAllProviderLimitsCache: () => ({}),
}
);
assert.equal(status.weeklyWindowStartIso, "2026-06-20T01:42:52.590Z");
assert.equal(status.weeklySpentUsd, 2);
});
test("buildApiKeyUsageLimitText returns API-key quota spend percentage and reset lines", async () => {
const text = usageLimits.buildApiKeyUsageLimitText(
{
enabled: true,
dailyLimitUsd: 10,
weeklyLimitUsd: 50,
dailySpentUsd: 2,
weeklySpentUsd: 5.25,
dailyWindowStartIso: "2026-06-19T03:00:00.000Z",
dailyResetAtIso: "2026-06-20T03:00:00.000Z",
weeklyWindowStartIso: "2026-06-12T20:00:00.000Z",
weeklyResetAtIso: "2026-06-25T20:00:00.000Z",
dailyExceeded: false,
weeklyExceeded: false,
},
Date.parse("2026-06-19T20:00:00.000Z")
);
assert.equal(
text,
[
@@ -165,15 +291,50 @@ test("buildApiKeyUsageLimitText returns only the quota and spent USD lines", asy
"$10.00",
"Gasto diario",
"$2.00",
"Uso diario",
"20%",
"Resets in 7h",
"",
"Cota semanal",
"$50.00",
"Gasto semanal",
"$5.25",
"Uso semanal",
"11%",
"Resets in 6d",
].join("\n")
);
});
test("buildApiKeyUsageLimitRejection includes over-quota percentage and reset hint", async () => {
const response = usageLimits.buildApiKeyUsageLimitRejection(
new Request("http://localhost/v1/messages", {
headers: { "anthropic-version": "2023-06-01" },
}),
{
enabled: true,
dailyLimitUsd: 10,
weeklyLimitUsd: 1,
dailySpentUsd: 0.25,
weeklySpentUsd: 1.09,
dailyWindowStartIso: "2026-06-19T03:00:00.000Z",
dailyResetAtIso: "2026-06-20T03:00:00.000Z",
weeklyWindowStartIso: "2026-06-12T20:00:00.000Z",
weeklyResetAtIso: "2026-06-25T20:00:00.000Z",
dailyExceeded: false,
weeklyExceeded: true,
},
Date.parse("2026-06-19T20:00:00.000Z")
);
assert.equal(response.status, 400);
const body = (await response.json()) as { error: { message: string } };
assert.equal(
body.error.message,
"This API key reached its weekly USD usage quota ($1.09 of $1.00, 109%). Resets in 6d. Choose another allowed model after reset."
);
});
test("buildApiKeyUsageLimitRejection uses 400 so Claude Code does not trigger login", () => {
const response = usageLimits.buildApiKeyUsageLimitRejection(
new Request("http://localhost/v1/messages", {
@@ -186,6 +347,7 @@ test("buildApiKeyUsageLimitRejection uses 400 so Claude Code does not trigger lo
dailySpentUsd: 12,
weeklySpentUsd: 20,
dailyWindowStartIso: "2026-06-19T03:00:00.000Z",
dailyResetAtIso: "2026-06-20T03:00:00.000Z",
weeklyWindowStartIso: "2026-06-12T20:00:00.000Z",
weeklyResetAtIso: "2026-06-19T20:00:00.000Z",
dailyExceeded: true,

View File

@@ -132,8 +132,9 @@ test("buildUsageCommandText formats API key USD limits when fair usage is enable
dailySpentUsd: 2,
weeklySpentUsd: 5.25,
dailyWindowStartIso: "2026-06-16T03:00:00.000Z",
dailyResetAtIso: "2026-06-17T03:00:00.000Z",
weeklyWindowStartIso: "2026-06-09T12:00:00.000Z",
weeklyResetAtIso: "2026-06-16T12:00:00.000Z",
weeklyResetAtIso: "2026-06-23T12:00:00.000Z",
dailyExceeded: false,
weeklyExceeded: false,
}),
@@ -159,11 +160,17 @@ test("buildUsageCommandText formats API key USD limits when fair usage is enable
"$10.00",
"Gasto diario",
"$2.00",
"Uso diario",
"20%",
"Resets in 15h",
"",
"Cota semanal",
"$50.00",
"Gasto semanal",
"$5.25",
"Uso semanal",
"11%",
"Resets in 7d",
].join("\n")
);
});