mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 06:42:12 +03:00
feat(codex): clamp reasoning effort per model (feature-07) - Add MAX_EFFORT_BY_MODEL table in CodexExecutor - Add clampEffort() applied after effort derivation - Logs debug when clamp is applied feat(catalog): OpenRouter catalog with persistent cache (feature-09) - New src/lib/catalog/openrouterCatalog.ts with TTL 24h + stale-if-error - New GET /api/models/openrouter-catalog endpoint (authenticated) - Reduces redundant OpenRouter API calls from dashboard feat(quota): quota preflight with per-provider toggle (feature-04) - New open-sse/services/quotaPreflight.ts - Toggle: providerSpecificData.quotaPreflightEnabled (default: false) - Extensible via registerQuotaFetcher() pattern - Graceful degradation when no fetcher registered feat(quota): quota session monitor with per-provider toggle (feature-06) - New open-sse/services/quotaMonitor.ts - Toggle: providerSpecificData.quotaMonitorEnabled (default: false) - Adaptive polling: 60s normal / 15s critical - Alert deduplication (5min suppression window) - timer.unref() ensures clean process exit feat(providers): support providerSpecificData patch in PUT /api/providers/[id] - Partial merge of providerSpecificData (preserves existing keys) - Schema updated to accept providerSpecificData in updateProviderConnectionSchema
80 lines
2.1 KiB
TypeScript
80 lines
2.1 KiB
TypeScript
/**
|
|
* quotaPreflight.ts — Feature 04
|
|
* Quota Preflight & Troca Proativa de Conta
|
|
*
|
|
* Toggle: providerSpecificData.quotaPreflightEnabled (default: false)
|
|
* Providers register quota fetchers via registerQuotaFetcher().
|
|
* Graceful degradation when no fetcher registered.
|
|
*/
|
|
|
|
export interface PreflightQuotaResult {
|
|
proceed: boolean;
|
|
reason?: string;
|
|
quotaPercent?: number;
|
|
}
|
|
|
|
export interface QuotaInfo {
|
|
used: number;
|
|
total: number;
|
|
percentUsed: number;
|
|
}
|
|
|
|
export type QuotaFetcher = (connectionId: string) => Promise<QuotaInfo | null>;
|
|
|
|
const EXHAUSTION_THRESHOLD = 0.95;
|
|
const WARN_THRESHOLD = 0.8;
|
|
|
|
const quotaFetcherRegistry = new Map<string, QuotaFetcher>();
|
|
|
|
export function registerQuotaFetcher(provider: string, fetcher: QuotaFetcher): void {
|
|
quotaFetcherRegistry.set(provider, fetcher);
|
|
}
|
|
|
|
export function isQuotaPreflightEnabled(connection: Record<string, unknown>): boolean {
|
|
const psd = connection?.providerSpecificData as Record<string, unknown> | undefined;
|
|
return psd?.quotaPreflightEnabled === true;
|
|
}
|
|
|
|
export async function preflightQuota(
|
|
provider: string,
|
|
connectionId: string,
|
|
connection: Record<string, unknown>
|
|
): Promise<PreflightQuotaResult> {
|
|
if (!isQuotaPreflightEnabled(connection)) {
|
|
return { proceed: true };
|
|
}
|
|
|
|
const fetcher = quotaFetcherRegistry.get(provider);
|
|
if (!fetcher) {
|
|
return { proceed: true };
|
|
}
|
|
|
|
let quota: QuotaInfo | null = null;
|
|
try {
|
|
quota = await fetcher(connectionId);
|
|
} catch {
|
|
return { proceed: true };
|
|
}
|
|
|
|
if (!quota) {
|
|
return { proceed: true };
|
|
}
|
|
|
|
const { percentUsed } = quota;
|
|
|
|
if (percentUsed >= EXHAUSTION_THRESHOLD) {
|
|
console.info(
|
|
`[QuotaPreflight] ${provider}/${connectionId}: ${(percentUsed * 100).toFixed(1)}% used — switching`
|
|
);
|
|
return { proceed: false, reason: "quota_exhausted", quotaPercent: percentUsed };
|
|
}
|
|
|
|
if (percentUsed >= WARN_THRESHOLD) {
|
|
console.warn(
|
|
`[QuotaPreflight] ${provider}/${connectionId}: ${(percentUsed * 100).toFixed(1)}% used — approaching limit`
|
|
);
|
|
}
|
|
|
|
return { proceed: true, quotaPercent: percentUsed };
|
|
}
|