mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-03 22:02:08 +03:00
feat(fallback): add OMNIROUTE_EMERGENCY_FALLBACK env switch (#3741)
Adds an OMNIROUTE_EMERGENCY_FALLBACK env switch to disable the emergency budget-exhaustion fallback (reroute to free nvidia/gpt-oss-120b). Default unchanged (enabled). Closes #3739, related #2879. Integrated into release/v3.8.23.
This commit is contained in:
@@ -874,6 +874,7 @@ Provider quota endpoints, network tunnels (Tailscale, Ngrok, MITM debug proxy),
|
||||
| `ALIBABA_CODING_PLAN_QUOTA_URL` | derived from host | `open-sse/services/bailianQuotaFetcher.ts` | Full quota URL override for Alibaba Bailian. |
|
||||
| `CONTEXT_RESERVE_TOKENS` | `1024` | `open-sse/services/contextManager.ts` | Tokens reserved for completion output when computing prompt budgets. |
|
||||
| `MODEL_ALIAS_COMPAT_ENABLED` | enabled | `open-sse/services/model.ts` | Toggle the legacy model-alias compatibility layer used by older clients. |
|
||||
| `OMNIROUTE_EMERGENCY_FALLBACK` | enabled | `open-sse/services/emergencyFallback.ts` | Set `false` (or `0`) to disable the emergency budget-exhaustion fallback that reroutes failed requests to the free `nvidia`/`openai/gpt-oss-120b` model. |
|
||||
| `COMMAND_CODE_CALLBACK_PORT` | _(unset)_ | `src/app/api/providers/command-code/auth/shared.ts` | Local port used for OAuth-style callbacks from the Command Code CLI helper. |
|
||||
| `COMMAND_CODE_VERSION` | `0.33.2` | `open-sse/executors/commandCode.ts` | Value sent as the `x-command-code-version` header to the Command Code upstream. Override to bump the CLI version. |
|
||||
| `MITM_LOCAL_PORT` | `443` | `src/mitm/server.cjs` | Local bind port for the MITM debug proxy. |
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
*
|
||||
* Inspired by ClawRouter: "gpt-oss-120b costs nothing and serves as
|
||||
* automatic fallback when wallet is empty."
|
||||
*
|
||||
* Operators can disable the redirect entirely with
|
||||
* `OMNIROUTE_EMERGENCY_FALLBACK=false` (or `0`). Default remains enabled.
|
||||
*/
|
||||
|
||||
export interface EmergencyFallbackConfig {
|
||||
@@ -63,6 +66,11 @@ export interface NoFallbackDecision {
|
||||
|
||||
export type FallbackResult = FallbackDecision | NoFallbackDecision;
|
||||
|
||||
export function isEmergencyFallbackEnvEnabled(): boolean {
|
||||
const raw = process.env.OMNIROUTE_EMERGENCY_FALLBACK;
|
||||
return raw !== "false" && raw !== "0";
|
||||
}
|
||||
|
||||
export function shouldUseFallback(
|
||||
status: number,
|
||||
errorBody: string,
|
||||
@@ -70,6 +78,12 @@ export function shouldUseFallback(
|
||||
config: EmergencyFallbackConfig = EMERGENCY_FALLBACK_CONFIG
|
||||
): FallbackResult {
|
||||
if (!config.enabled) return { shouldFallback: false, reason: "emergency fallback disabled" };
|
||||
if (!isEmergencyFallbackEnvEnabled()) {
|
||||
return {
|
||||
shouldFallback: false,
|
||||
reason: "emergency fallback disabled via OMNIROUTE_EMERGENCY_FALLBACK",
|
||||
};
|
||||
}
|
||||
if (config.skipForToolRequests && requestHasTools) {
|
||||
return { shouldFallback: false, reason: "skipped: request has tools" };
|
||||
}
|
||||
|
||||
86
tests/unit/services/emergency-fallback.test.ts
Normal file
86
tests/unit/services/emergency-fallback.test.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
shouldUseFallback,
|
||||
isEmergencyFallbackEnvEnabled,
|
||||
EMERGENCY_FALLBACK_CONFIG,
|
||||
} from "../../../open-sse/services/emergencyFallback.ts";
|
||||
|
||||
function withEnv(value: string | undefined, fn: () => void) {
|
||||
const previous = process.env.OMNIROUTE_EMERGENCY_FALLBACK;
|
||||
if (value === undefined) {
|
||||
delete process.env.OMNIROUTE_EMERGENCY_FALLBACK;
|
||||
} else {
|
||||
process.env.OMNIROUTE_EMERGENCY_FALLBACK = value;
|
||||
}
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
if (previous === undefined) {
|
||||
delete process.env.OMNIROUTE_EMERGENCY_FALLBACK;
|
||||
} else {
|
||||
process.env.OMNIROUTE_EMERGENCY_FALLBACK = previous;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("emergency fallback stays enabled when the env switch is unset (default behavior)", () => {
|
||||
withEnv(undefined, () => {
|
||||
assert.equal(isEmergencyFallbackEnvEnabled(), true);
|
||||
const decision = shouldUseFallback(402, "", false);
|
||||
assert.equal(decision.shouldFallback, true);
|
||||
if (decision.shouldFallback) {
|
||||
assert.equal(decision.provider, EMERGENCY_FALLBACK_CONFIG.provider);
|
||||
assert.equal(decision.model, EMERGENCY_FALLBACK_CONFIG.model);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("budget keywords trigger fallback when the env switch is unset", () => {
|
||||
withEnv(undefined, () => {
|
||||
const decision = shouldUseFallback(429, "All accounts quota exceeded", false);
|
||||
assert.equal(decision.shouldFallback, true);
|
||||
});
|
||||
});
|
||||
|
||||
test("OMNIROUTE_EMERGENCY_FALLBACK=false disables the 402 redirect", () => {
|
||||
withEnv("false", () => {
|
||||
assert.equal(isEmergencyFallbackEnvEnabled(), false);
|
||||
const decision = shouldUseFallback(402, "", false);
|
||||
assert.equal(decision.shouldFallback, false);
|
||||
assert.match(decision.reason, /OMNIROUTE_EMERGENCY_FALLBACK/);
|
||||
});
|
||||
});
|
||||
|
||||
test("OMNIROUTE_EMERGENCY_FALLBACK=0 disables the budget-keyword redirect", () => {
|
||||
withEnv("0", () => {
|
||||
const decision = shouldUseFallback(429, "quota exceeded for account", false);
|
||||
assert.equal(decision.shouldFallback, false);
|
||||
assert.match(decision.reason, /OMNIROUTE_EMERGENCY_FALLBACK/);
|
||||
});
|
||||
});
|
||||
|
||||
test("explicit truthy values keep the fallback enabled", () => {
|
||||
withEnv("true", () => {
|
||||
assert.equal(isEmergencyFallbackEnvEnabled(), true);
|
||||
assert.equal(shouldUseFallback(402, "", false).shouldFallback, true);
|
||||
});
|
||||
});
|
||||
|
||||
test("env switch does not override config.enabled=false", () => {
|
||||
withEnv("true", () => {
|
||||
const decision = shouldUseFallback(402, "", false, {
|
||||
...EMERGENCY_FALLBACK_CONFIG,
|
||||
enabled: false,
|
||||
});
|
||||
assert.equal(decision.shouldFallback, false);
|
||||
});
|
||||
});
|
||||
|
||||
test("tool-bearing requests are still skipped regardless of env switch", () => {
|
||||
withEnv(undefined, () => {
|
||||
const decision = shouldUseFallback(402, "", true);
|
||||
assert.equal(decision.shouldFallback, false);
|
||||
assert.match(decision.reason, /tools/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user