feat(quota): Phase 2 adapters, reset timers, analytics, and dashboard API

This commit is contained in:
benzntech
2026-08-12 00:00:10 +05:30
committed by adevwithpurpose
parent 6143da70d1
commit 9f7bcf4391
5 changed files with 452 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
/**
* /api/settings/quota/state — Dashboard visibility endpoint for provider quota states.
*
* GET: Returns live quota states, reset timers, and aggregated usage analytics.
* POST: Resets expired quota windows or purges a specific connection quota record.
*
* Part of: Quota-aware provider scheduling (Phase 2).
*/
import { NextResponse } from "next/server";
import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors";
import { getQuotaAnalyticsSummary } from "@/lib/quota/quotaAnalytics";
import { getActiveQuotaResetItems, resetExpiredQuotaWindows } from "@/lib/quota/quotaResetTimers";
import { getProviderQuota, clearProviderQuotaState } from "@/lib/quota/providerQuotaState";
export async function OPTIONS() {
return handleCorsOptions();
}
export async function GET() {
try {
const analytics = getQuotaAnalyticsSummary();
const resetTimers = getActiveQuotaResetItems();
return NextResponse.json(
{
success: true,
analytics,
resetTimers,
timestamp: new Date().toISOString(),
},
{ headers: CORS_HEADERS }
);
} catch (error) {
return NextResponse.json(
{ success: false, error: (error as Error).message },
{ status: 500, headers: CORS_HEADERS }
);
}
}
export async function POST(request: Request) {
try {
const body = await request.json().catch(() => ({}));
const { action, connectionId, model } = body as {
action?: string;
connectionId?: string;
model?: string;
};
if (action === "reset_expired") {
const resetCount = resetExpiredQuotaWindows();
return NextResponse.json(
{ success: true, resetCount, message: `Reset ${resetCount} expired quota windows.` },
{ headers: CORS_HEADERS }
);
}
if (action === "clear_connection" && connectionId && model) {
clearProviderQuotaState(connectionId, model);
return NextResponse.json(
{ success: true, message: `Cleared quota state for connection ${connectionId} (${model}).` },
{ headers: CORS_HEADERS }
);
}
return NextResponse.json(
{ success: false, error: "Invalid action or missing parameters" },
{ status: 400, headers: CORS_HEADERS }
);
} catch (error) {
return NextResponse.json(
{ success: false, error: (error as Error).message },
{ status: 500, headers: CORS_HEADERS }
);
}
}

View File

@@ -0,0 +1,125 @@
/**
* quotaAdapters.ts — Provider-specific quota header adapters.
*
* Extracts and normalizes rate limit & token budget headers from provider HTTP responses
* (OpenAI, Anthropic, Gemini, OpenRouter, ModelScope, Generic) into standardized
* provider quota states.
*
* Part of: Quota-aware provider scheduling (Phase 2).
*/
import { recordProviderQuotaUsage, getProviderQuota } from "./providerQuotaState.ts";
export interface ParsedQuotaHeaderResult {
tokensUsed?: number;
tokenLimit?: number;
tokensRemaining?: number;
windowResetMs?: number;
}
/**
* Parse rate-limit headers from an HTTP Response or Headers object into a normalized quota result.
*/
export function parseProviderQuotaHeaders(
headers: Headers | Record<string, string | string[] | undefined>,
provider?: string
): ParsedQuotaHeaderResult | null {
if (!headers) return null;
const getHeader = (name: string): string | null => {
if (typeof (headers as Headers).get === "function") {
return (headers as Headers).get(name);
}
const record = headers as Record<string, string | string[] | undefined>;
const val = record[name] ?? record[name.toLowerCase()] ?? record[name.toUpperCase()];
if (Array.isArray(val)) return val[0] ?? null;
return val ?? null;
};
const parseNum = (val: string | null): number | undefined => {
if (!val) return undefined;
const cleaned = val.replace(/[^0-9.]/g, "");
const num = parseFloat(cleaned);
return isNaN(num) ? undefined : num;
};
const parseResetMs = (val: string | null): number | undefined => {
if (!val) return undefined;
const now = Date.now();
// Check if ISO date string
if (val.includes("T") || val.includes("Z")) {
const parsed = Date.parse(val);
if (!isNaN(parsed)) return Math.max(0, parsed - now);
}
// Check if seconds / ms string (e.g. "60s", "100ms", "0.5s", or raw number)
if (val.endsWith("ms")) return parseNum(val);
if (val.endsWith("s")) return (parseNum(val) ?? 0) * 1000;
if (val.endsWith("m")) return (parseNum(val) ?? 0) * 60 * 1000;
if (val.endsWith("h")) return (parseNum(val) ?? 0) * 3600 * 1000;
const rawNum = parseNum(val);
if (rawNum !== undefined) {
// If epoch timestamp (> 1e9), convert to remaining ms
if (rawNum > 1_000_000_000) {
return Math.max(0, rawNum * 1000 - now);
}
return rawNum * 1000; // assume relative seconds
}
return undefined;
};
const prov = (provider || "").toLowerCase();
// 1. Anthropic Headers
if (prov === "anthropic" || getHeader("anthropic-ratelimit-input-tokens-limit")) {
const limit = parseNum(getHeader("anthropic-ratelimit-input-tokens-limit"));
const remaining = parseNum(getHeader("anthropic-ratelimit-input-tokens-remaining"));
const resetStr = getHeader("anthropic-ratelimit-input-tokens-reset");
const windowResetMs = parseResetMs(resetStr);
if (limit !== undefined || remaining !== undefined) {
const tokensUsed = limit !== undefined && remaining !== undefined ? Math.max(0, limit - remaining) : undefined;
return { tokenLimit: limit, tokensRemaining: remaining, tokensUsed, windowResetMs };
}
}
// 2. OpenAI / Standard x-ratelimit-*
const limitTokens = parseNum(getHeader("x-ratelimit-limit-tokens"));
const remainingTokens = parseNum(getHeader("x-ratelimit-remaining-tokens"));
const resetTokens = getHeader("x-ratelimit-reset-tokens");
if (limitTokens !== undefined || remainingTokens !== undefined) {
const tokensUsed = limitTokens !== undefined && remainingTokens !== undefined ? Math.max(0, limitTokens - remainingTokens) : undefined;
return { tokenLimit: limitTokens, tokensRemaining: remainingTokens, tokensUsed, windowResetMs: parseResetMs(resetTokens) };
}
// 3. OpenRouter / Generic Request level headers
const genericLimit = parseNum(getHeader("x-ratelimit-limit"));
const genericRemaining = parseNum(getHeader("x-ratelimit-remaining"));
const genericReset = getHeader("x-ratelimit-reset");
if (genericLimit !== undefined || genericRemaining !== undefined) {
const tokensUsed = genericLimit !== undefined && genericRemaining !== undefined ? Math.max(0, genericLimit - genericRemaining) : undefined;
return { tokenLimit: genericLimit, tokensRemaining: genericRemaining, tokensUsed, windowResetMs: parseResetMs(genericReset) };
}
return null;
}
/**
* Apply parsed quota headers directly to the provider quota state ledger.
*/
export function applyQuotaHeadersToState(
connectionId: string,
model: string,
headers: Headers | Record<string, string | string[] | undefined>,
provider?: string
): void {
const parsed = parseProviderQuotaHeaders(headers, provider);
if (!parsed || (!parsed.tokensUsed && !parsed.tokenLimit)) return;
const now = Date.now();
const windowReset = parsed.windowResetMs ? now + parsed.windowResetMs : now + 60_000;
const tokenLimit = parsed.tokenLimit ?? 0;
const tokensUsed = parsed.tokensUsed ?? (parsed.tokenLimit && parsed.tokensRemaining ? parsed.tokenLimit - parsed.tokensRemaining : 0);
recordProviderQuotaUsage(connectionId, model, tokensUsed, tokenLimit, now, windowReset);
}

View File

@@ -0,0 +1,114 @@
/**
* quotaAnalytics.ts — Usage analytics for provider quota state & remaining capacity.
*
* Computes aggregated capacity metrics, average remaining ratios, exhausted connection counts,
* and per-connection quota usage summaries for dashboard visibility.
*
* Part of: Quota-aware provider scheduling (Phase 2).
*/
import { getDbInstance } from "@/lib/db/core";
import { createLogger } from "@/shared/utils/logger";
const log = createLogger("quota:analytics");
export interface QuotaAnalyticsSummary {
totalConnectionsTracked: number;
exhaustedConnections: number;
healthyConnections: number;
averageRemainingRatio: number;
totalTokensUsed: number;
totalTokenLimit: number;
connections: Array<{
connectionId: string;
model: string;
tokensUsed: number;
tokenLimit: number;
tokensRemaining: number;
remainingRatio: number;
windowReset: number;
isExhausted: boolean;
}>;
}
/**
* Compute real-time quota analytics across all provider connections.
*/
export function getQuotaAnalyticsSummary(): QuotaAnalyticsSummary {
try {
const db = getDbInstance();
const rows = db.prepare("SELECT * FROM provider_quota_state").all() as Array<{
connection_id: string;
model: string;
tokens_used: number;
token_limit: number;
window_start: number;
window_reset: number;
}>;
const now = Date.now();
let totalTokensUsed = 0;
let totalTokenLimit = 0;
let exhaustedConnections = 0;
let healthyConnections = 0;
let ratioSum = 0;
const connections = rows.map((r) => {
const tokensUsed = Number(r.tokens_used ?? 0);
const tokenLimit = Number(r.token_limit ?? 0);
const windowReset = Number(r.window_reset ?? 0);
const isExpired = windowReset > 0 && now > windowReset;
const effectiveUsed = isExpired ? 0 : tokensUsed;
const effectiveLimit = isExpired ? 0 : tokenLimit;
const tokensRemaining = Math.max(0, effectiveLimit - effectiveUsed);
const remainingRatio = effectiveLimit > 0 ? tokensRemaining / effectiveLimit : 1.0;
const isExhausted = effectiveLimit > 0 && remainingRatio <= 0.05;
totalTokensUsed += effectiveUsed;
totalTokenLimit += effectiveLimit;
ratioSum += remainingRatio;
if (isExhausted) {
exhaustedConnections++;
} else {
healthyConnections++;
}
return {
connectionId: String(r.connection_id),
model: String(r.model),
tokensUsed: effectiveUsed,
tokenLimit: effectiveLimit,
tokensRemaining,
remainingRatio,
windowReset,
isExhausted,
};
});
const count = connections.length;
const averageRemainingRatio = count > 0 ? ratioSum / count : 1.0;
return {
totalConnectionsTracked: count,
exhaustedConnections,
healthyConnections,
averageRemainingRatio,
totalTokensUsed,
totalTokenLimit,
connections,
};
} catch (error) {
log.error("Failed to compute quota analytics summary", error);
return {
totalConnectionsTracked: 0,
exhaustedConnections: 0,
healthyConnections: 0,
averageRemainingRatio: 1.0,
totalTokensUsed: 0,
totalTokenLimit: 0,
connections: [],
};
}
}

View File

@@ -0,0 +1,69 @@
/**
* quotaResetTimers.ts — Automated quota window reset timers and capacity recovery.
*
* Tracks window reset timestamps and automatically clears exhausted provider quota
* states when their reset windows elapse.
*
* Part of: Quota-aware provider scheduling (Phase 2).
*/
import { getDbInstance } from "@/lib/db/core";
import { createLogger } from "@/shared/utils/logger";
const log = createLogger("quota:reset-timers");
export interface QuotaResetItem {
connectionId: string;
model: string;
tokensUsed: number;
tokenLimit: number;
windowReset: number;
timeRemainingMs: number;
}
/**
* Get all active quota states and their remaining window reset times.
*/
export function getActiveQuotaResetItems(): QuotaResetItem[] {
try {
const db = getDbInstance();
const rows = db.prepare("SELECT * FROM provider_quota_state WHERE window_reset > 0").all() as Array<{
connection_id: string;
model: string;
tokens_used: number;
token_limit: number;
window_reset: number;
}>;
const now = Date.now();
return rows.map((r) => ({
connectionId: String(r.connection_id),
model: String(r.model),
tokensUsed: Number(r.tokens_used),
tokenLimit: Number(r.token_limit),
windowReset: Number(r.window_reset),
timeRemainingMs: Math.max(0, Number(r.window_reset) - now),
}));
} catch (error) {
log.error("Failed to query active quota reset items", error);
return [];
}
}
/**
* Purge or reset all expired quota windows in SQLite.
* Returns the count of reset connections.
*/
export function resetExpiredQuotaWindows(): number {
try {
const db = getDbInstance();
const now = Date.now();
const result = db
.prepare("DELETE FROM provider_quota_state WHERE window_reset > 0 AND window_reset <= ?")
.run(now);
return result.changes ?? 0;
} catch (error) {
log.error("Failed to reset expired quota windows", error);
return 0;
}
}

View File

@@ -0,0 +1,67 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { parseProviderQuotaHeaders, applyQuotaHeadersToState } from "../../src/lib/quota/quotaAdapters.ts";
import { getQuotaAnalyticsSummary } from "../../src/lib/quota/quotaAnalytics.ts";
import { getActiveQuotaResetItems, resetExpiredQuotaWindows } from "../../src/lib/quota/quotaResetTimers.ts";
import { recordProviderQuotaUsage, getProviderQuota } from "../../src/lib/quota/providerQuotaState.ts";
test("parseProviderQuotaHeaders: parses OpenAI rate limit headers", () => {
const headers = new Headers({
"x-ratelimit-limit-tokens": "100000",
"x-ratelimit-remaining-tokens": "80000",
"x-ratelimit-reset-tokens": "60s",
});
const parsed = parseProviderQuotaHeaders(headers, "openai");
assert.ok(parsed);
assert.equal(parsed?.tokenLimit, 100000);
assert.equal(parsed?.tokensRemaining, 80000);
assert.equal(parsed?.tokensUsed, 20000);
assert.equal(parsed?.windowResetMs, 60000);
});
test("parseProviderQuotaHeaders: parses Anthropic rate limit headers", () => {
const headers = new Headers({
"anthropic-ratelimit-input-tokens-limit": "50000",
"anthropic-ratelimit-input-tokens-remaining": "10000",
"anthropic-ratelimit-input-tokens-reset": "30s",
});
const parsed = parseProviderQuotaHeaders(headers, "anthropic");
assert.ok(parsed);
assert.equal(parsed?.tokenLimit, 50000);
assert.equal(parsed?.tokensRemaining, 10000);
assert.equal(parsed?.tokensUsed, 40000);
assert.equal(parsed?.windowResetMs, 30000);
});
test("applyQuotaHeadersToState & getQuotaAnalyticsSummary: records and aggregates quota analytics", () => {
const connId = "test-conn-p2-01";
const model = "gpt-4o";
const headers = {
"x-ratelimit-limit-tokens": "100000",
"x-ratelimit-remaining-tokens": "20000",
"x-ratelimit-reset-tokens": "120s",
};
applyQuotaHeadersToState(connId, model, headers, "openai");
const snapshot = getProviderQuota(connId, model);
assert.ok(snapshot);
assert.equal(snapshot?.tokensUsed, 80000);
assert.equal(snapshot?.tokenLimit, 100000);
const analytics = getQuotaAnalyticsSummary();
assert.ok(analytics.totalConnectionsTracked > 0);
assert.ok(analytics.connections.some((c) => c.connectionId === connId));
});
test("quotaResetTimers: tracks active reset items and purges expired windows", () => {
const connId = "test-conn-expired";
const model = "claude-sonnet-4-6";
const now = Date.now();
// Record already expired window
recordProviderQuotaUsage(connId, model, 5000, 5000, now - 10000, now - 1000);
const expiredCount = resetExpiredQuotaWindows();
assert.ok(expiredCount >= 1);
});