mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 20:32:20 +03:00
Auto-combo transparency + budget controls: the engine's budgetCap enforcement always degraded to the globally cheapest candidate when every candidate exceeded the cap - silently overspending instead of respecting the cap. - engine.ts: budgetFallback "cheapest" (default, legacy) | "strict" (BudgetExceededError when no candidate fits budgetCap) - requestControls.ts: X-OmniRoute-Budget-Fallback header + resolveRequestAutoControls() consolidating mode/budget/fallback parsing - resolveAutoStrategy.ts / autoConfig.ts: thread combo-level config.budgetFallback and catch BudgetExceededError into an HTTP 402 - chat.ts: switch to the consolidated resolveRequestAutoControls() helper (net line reduction, stays under the frozen file-size baseline) Regression guard: tests/unit/auto-combo-budget-fallback-3470.test.ts
130 lines
5.3 KiB
TypeScript
130 lines
5.3 KiB
TypeScript
/**
|
|
* Per-request Auto-Combo routing controls (#6023 / #6024 / #6025 / #3470).
|
|
*
|
|
* These let a caller steer an `auto` combo on a single request via response-safe
|
|
* request headers, without changing the combo's stored config:
|
|
*
|
|
* X-OmniRoute-Mode: fast | balanced | quality | <raw mode-pack name> (#6024/#6025)
|
|
* X-OmniRoute-Budget: <max USD per request> (#6023)
|
|
* X-OmniRoute-Budget-Fallback: cheapest | strict (#3470)
|
|
*
|
|
* All resolvers are pure so they can be unit-tested and reused by the entry
|
|
* handler (src/sse/handlers/chat.ts) and the combo router (open-sse/services/combo.ts).
|
|
* The resolved values feed the auto-combo engine's existing `config.modePack` /
|
|
* `config.budgetCap` / `config.budgetFallback` inputs.
|
|
*/
|
|
|
|
import { MODE_PACKS } from "./modePacks";
|
|
|
|
/**
|
|
* Friendly latency-vs-quality preset aliases (#6024). These map human-facing
|
|
* preset names to the concrete scoring mode packs the engine already ships.
|
|
* `balanced`/`default` are handled specially (they mean "no pack" = default weights).
|
|
*/
|
|
const MODE_PACK_ALIASES: Record<string, string> = {
|
|
fast: "ship-fast",
|
|
fastest: "ship-fast",
|
|
speed: "ship-fast",
|
|
quality: "quality-first",
|
|
best: "quality-first",
|
|
cheap: "cost-saver",
|
|
cost: "cost-saver",
|
|
saver: "cost-saver",
|
|
reliable: "reliability-first",
|
|
offline: "offline-friendly",
|
|
};
|
|
|
|
export interface RequestModePack {
|
|
/** True when the request explicitly selected a mode (overrides combo config). */
|
|
override: boolean;
|
|
/** Resolved mode-pack name, or undefined for the balanced/default profile. */
|
|
modePack: string | undefined;
|
|
}
|
|
|
|
/**
|
|
* Resolve the `X-OmniRoute-Mode` header value into a mode-pack override.
|
|
*
|
|
* - A friendly alias (`fast`, `quality`, `cheap`, …) or a raw mode-pack name
|
|
* (`ship-fast`, `quality-first`, …) → `{ override: true, modePack: <name> }`.
|
|
* - `balanced` / `default` → `{ override: true, modePack: undefined }` (default weights).
|
|
* - Unknown / empty / non-string → `{ override: false }` so the combo's own
|
|
* stored `modePack` config is preserved.
|
|
*/
|
|
export function resolveRequestModePack(input: unknown): RequestModePack {
|
|
const noOverride: RequestModePack = { override: false, modePack: undefined };
|
|
if (typeof input !== "string") return noOverride;
|
|
const key = input.trim().toLowerCase();
|
|
if (!key) return noOverride;
|
|
if (key === "balanced" || key === "default") return { override: true, modePack: undefined };
|
|
if (Object.prototype.hasOwnProperty.call(MODE_PACKS, key)) {
|
|
return { override: true, modePack: key };
|
|
}
|
|
const alias = MODE_PACK_ALIASES[key];
|
|
if (alias) return { override: true, modePack: alias };
|
|
return noOverride;
|
|
}
|
|
|
|
/**
|
|
* Parse the `X-OmniRoute-Budget` header into a hard per-request cost ceiling (USD).
|
|
* Only a finite, strictly-positive amount is accepted; anything else returns
|
|
* `undefined` so the combo's own stored `budgetCap` (if any) stays in effect.
|
|
*/
|
|
export function parseRequestBudgetCap(input: unknown): number | undefined {
|
|
const n =
|
|
typeof input === "number"
|
|
? input
|
|
: typeof input === "string"
|
|
? Number(input.trim())
|
|
: NaN;
|
|
if (!Number.isFinite(n) || n <= 0) return undefined;
|
|
return n;
|
|
}
|
|
|
|
/** Policy applied when every candidate exceeds `budgetCap` — see `AutoComboConfig.budgetFallback`. */
|
|
export type RequestBudgetFallback = "cheapest" | "strict";
|
|
|
|
/**
|
|
* Parse the `X-OmniRoute-Budget-Fallback` header into a budget-fallback policy override.
|
|
* Unknown/empty/non-string values return `undefined` so the combo's own stored
|
|
* `config.budgetFallback` (or the engine default of `"cheapest"`) stays in effect.
|
|
*/
|
|
export function parseRequestBudgetFallback(input: unknown): RequestBudgetFallback | undefined {
|
|
if (typeof input !== "string") return undefined;
|
|
const key = input.trim().toLowerCase();
|
|
if (key === "strict" || key === "block" || key === "hard") return "strict";
|
|
if (key === "cheapest" || key === "cheapest-viable" || key === "soft") return "cheapest";
|
|
return undefined;
|
|
}
|
|
|
|
/** Aggregated per-request auto-combo overrides resolved from request headers (#3470). */
|
|
export interface PerRequestAutoControls {
|
|
mode?: string;
|
|
budgetCap?: number;
|
|
budgetFallback?: RequestBudgetFallback;
|
|
}
|
|
|
|
/**
|
|
* Resolve all per-request Auto-Combo headers in one pass, returning only the keys
|
|
* that were actually overridden. Consolidates `resolveRequestModePack()` /
|
|
* `parseRequestBudgetCap()` / `parseRequestBudgetFallback()` so entry handlers (e.g.
|
|
* `src/sse/handlers/chat.ts`) can thread a single object into `relayOptions` instead
|
|
* of repeating the per-header boilerplate for each new control.
|
|
*/
|
|
export function resolveRequestAutoControls(headers: {
|
|
get(name: string): string | null;
|
|
}): PerRequestAutoControls {
|
|
const modeHeader = headers.get("x-omniroute-mode")?.trim() || null;
|
|
const budgetHeader = headers.get("x-omniroute-budget")?.trim() || null;
|
|
const budgetFallbackHeader = headers.get("x-omniroute-budget-fallback")?.trim() || null;
|
|
|
|
const mode = resolveRequestModePack(modeHeader);
|
|
const budgetCap = parseRequestBudgetCap(budgetHeader);
|
|
const budgetFallback = parseRequestBudgetFallback(budgetFallbackHeader);
|
|
|
|
return {
|
|
...(mode.override && modeHeader ? { mode: modeHeader } : {}),
|
|
...(budgetCap !== undefined ? { budgetCap } : {}),
|
|
...(budgetFallback !== undefined ? { budgetFallback } : {}),
|
|
};
|
|
}
|