mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
104 lines
3.0 KiB
TypeScript
104 lines
3.0 KiB
TypeScript
/**
|
|
* Emergency Fallback — Budget Exhaustion Redirect
|
|
*
|
|
* When a request fails due to budget exhaustion (HTTP 402 or budget keywords
|
|
* in the error body), optionally redirect to a free-tier model
|
|
* (default provider/model: nvidia + openai/gpt-oss-120b at $0.00/M tokens).
|
|
*
|
|
* Inspired by ClawRouter: "gpt-oss-120b costs nothing and serves as
|
|
* automatic fallback when wallet is empty."
|
|
*/
|
|
|
|
export interface EmergencyFallbackConfig {
|
|
enabled: boolean;
|
|
provider: string;
|
|
model: string;
|
|
triggerOn402: boolean;
|
|
triggerOnBudgetKeywords: boolean;
|
|
budgetKeywords: string[];
|
|
/** Skip fallback for tool requests (gpt-oss-120b may not support structured tool calling) */
|
|
skipForToolRequests: boolean;
|
|
maxOutputTokens: number;
|
|
}
|
|
|
|
export const EMERGENCY_FALLBACK_CONFIG: EmergencyFallbackConfig = {
|
|
enabled: true,
|
|
provider: "nvidia",
|
|
model: "openai/gpt-oss-120b",
|
|
triggerOn402: true,
|
|
triggerOnBudgetKeywords: true,
|
|
budgetKeywords: [
|
|
"insufficient funds",
|
|
"insufficient_funds",
|
|
"budget exceeded",
|
|
"budget_exceeded",
|
|
"quota exceeded",
|
|
"quota_exceeded",
|
|
"billing",
|
|
"payment required",
|
|
"out of credits",
|
|
"no credits",
|
|
"credit limit",
|
|
"spending limit",
|
|
"saldo insuficiente",
|
|
"limite de gastos",
|
|
"cota excedida",
|
|
],
|
|
skipForToolRequests: true,
|
|
maxOutputTokens: 4096,
|
|
};
|
|
|
|
export interface FallbackDecision {
|
|
shouldFallback: true;
|
|
reason: string;
|
|
provider: string;
|
|
model: string;
|
|
maxOutputTokens: number;
|
|
}
|
|
|
|
export interface NoFallbackDecision {
|
|
shouldFallback: false;
|
|
reason: string;
|
|
}
|
|
|
|
export type FallbackResult = FallbackDecision | NoFallbackDecision;
|
|
|
|
export function shouldUseFallback(
|
|
status: number,
|
|
errorBody: string,
|
|
requestHasTools: boolean,
|
|
config: EmergencyFallbackConfig = EMERGENCY_FALLBACK_CONFIG
|
|
): FallbackResult {
|
|
if (!config.enabled) return { shouldFallback: false, reason: "emergency fallback disabled" };
|
|
if (config.skipForToolRequests && requestHasTools) {
|
|
return { shouldFallback: false, reason: "skipped: request has tools" };
|
|
}
|
|
if (config.triggerOn402 && status === 402) {
|
|
return {
|
|
shouldFallback: true,
|
|
reason: `HTTP 402 → emergency fallback to ${config.provider}/${config.model}`,
|
|
provider: config.provider,
|
|
model: config.model,
|
|
maxOutputTokens: config.maxOutputTokens,
|
|
};
|
|
}
|
|
if (config.triggerOnBudgetKeywords && errorBody) {
|
|
const lowerBody = errorBody.toLowerCase();
|
|
const matched = config.budgetKeywords.find((kw) => lowerBody.includes(kw.toLowerCase()));
|
|
if (matched) {
|
|
return {
|
|
shouldFallback: true,
|
|
reason: `Budget error detected ('${matched}') → emergency fallback to ${config.provider}/${config.model}`,
|
|
provider: config.provider,
|
|
model: config.model,
|
|
maxOutputTokens: config.maxOutputTokens,
|
|
};
|
|
}
|
|
}
|
|
return { shouldFallback: false, reason: "no budget error detected" };
|
|
}
|
|
|
|
export function isFallbackDecision(result: FallbackResult): result is FallbackDecision {
|
|
return result.shouldFallback === true;
|
|
}
|