mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-02 13:22:11 +03:00
161 lines
3.9 KiB
TypeScript
161 lines
3.9 KiB
TypeScript
/**
|
|
* Fallback Policy — Domain Layer (T-19)
|
|
*
|
|
* Declarative fallback chain for model routing.
|
|
* When a primary provider is unavailable, the policy engine
|
|
* resolves to alternative providers in priority order.
|
|
*
|
|
* State is persisted in SQLite via domainState.js.
|
|
*
|
|
* @module domain/fallbackPolicy
|
|
*/
|
|
|
|
import {
|
|
saveFallbackChain,
|
|
loadFallbackChain,
|
|
loadAllFallbackChains,
|
|
deleteFallbackChain,
|
|
deleteAllFallbackChains,
|
|
} from "../lib/db/domainState";
|
|
|
|
/**
|
|
* @typedef {Object} FallbackEntry
|
|
* @property {string} provider - Provider ID
|
|
* @property {number} [priority=0] - Lower = higher priority
|
|
* @property {boolean} [enabled=true] - Whether this fallback is active
|
|
*/
|
|
|
|
/** @type {Map<string, FallbackEntry[]>} In-memory cache backed by SQLite */
|
|
const fallbackChains = new Map();
|
|
|
|
/** @type {boolean} Whether we've loaded from DB yet */
|
|
let _loaded = false;
|
|
|
|
/**
|
|
* Ensure in-memory cache is hydrated from SQLite.
|
|
*/
|
|
function ensureLoaded() {
|
|
if (_loaded) return;
|
|
try {
|
|
const all = loadAllFallbackChains();
|
|
for (const [model, chain] of Object.entries(all)) {
|
|
fallbackChains.set(model, chain);
|
|
}
|
|
} catch {
|
|
// DB may not be ready yet (build phase), that's ok
|
|
}
|
|
_loaded = true;
|
|
}
|
|
|
|
/**
|
|
* Register a fallback chain for a model.
|
|
*
|
|
* @param {string} model - Model identifier (e.g. "gpt-4o")
|
|
* @param {FallbackEntry[]} chain - Ordered list of fallback providers
|
|
*/
|
|
export function registerFallback(model, chain) {
|
|
ensureLoaded();
|
|
const sorted = [...chain]
|
|
.map((e) => ({
|
|
provider: e.provider,
|
|
priority: e.priority ?? 0,
|
|
enabled: e.enabled ?? true,
|
|
}))
|
|
.sort((a, b) => a.priority - b.priority);
|
|
|
|
fallbackChains.set(model, sorted);
|
|
try {
|
|
saveFallbackChain(model, sorted);
|
|
} catch {
|
|
// Non-critical: in-memory still works
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve the fallback chain for a model.
|
|
* Returns only enabled providers, sorted by priority.
|
|
*
|
|
* @param {string} model
|
|
* @param {string[]} [excludeProviders=[]] - Providers to skip (e.g. already tried)
|
|
* @returns {FallbackEntry[]} Ordered list of fallback providers
|
|
*/
|
|
export function resolveFallbackChain(model, excludeProviders = []) {
|
|
ensureLoaded();
|
|
const chain = fallbackChains.get(model);
|
|
if (!chain) return [];
|
|
|
|
const excludeSet = new Set(excludeProviders);
|
|
return chain.filter((e) => e.enabled && !excludeSet.has(e.provider));
|
|
}
|
|
|
|
/**
|
|
* Get the next provider in the fallback chain.
|
|
*
|
|
* @param {string} model
|
|
* @param {string[]} [excludeProviders=[]]
|
|
* @returns {string | null} Next provider ID or null if chain exhausted
|
|
*/
|
|
export function getNextFallback(model, excludeProviders = []) {
|
|
const chain = resolveFallbackChain(model, excludeProviders);
|
|
return chain.length > 0 ? chain[0].provider : null;
|
|
}
|
|
|
|
/**
|
|
* Check if a model has any fallback providers configured.
|
|
*
|
|
* @param {string} model
|
|
* @returns {boolean}
|
|
*/
|
|
export function hasFallback(model) {
|
|
ensureLoaded();
|
|
const chain = fallbackChains.get(model);
|
|
return !!chain && chain.some((e) => e.enabled);
|
|
}
|
|
|
|
/**
|
|
* Remove a fallback chain for a model.
|
|
*
|
|
* @param {string} model
|
|
* @returns {boolean} true if removed
|
|
*/
|
|
export function removeFallback(model) {
|
|
ensureLoaded();
|
|
const removed = fallbackChains.delete(model);
|
|
if (removed) {
|
|
try {
|
|
deleteFallbackChain(model);
|
|
} catch {
|
|
// Non-critical
|
|
}
|
|
}
|
|
return removed;
|
|
}
|
|
|
|
/**
|
|
* Get all registered fallback chains (for dashboard).
|
|
*
|
|
* @returns {Record<string, FallbackEntry[]>}
|
|
*/
|
|
export function getAllFallbackChains() {
|
|
ensureLoaded();
|
|
/** @type {Record<string, FallbackEntry[]>} */
|
|
const result = {};
|
|
for (const [model, chain] of fallbackChains.entries()) {
|
|
result[model] = chain;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Reset all fallback chains (for testing).
|
|
*/
|
|
export function resetAllFallbacks() {
|
|
fallbackChains.clear();
|
|
_loaded = false;
|
|
try {
|
|
deleteAllFallbackChains();
|
|
} catch {
|
|
// Non-critical
|
|
}
|
|
}
|