mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 10:52:17 +03:00
Rebased onto the current release/v3.8.51 tip as part of a combined provider-retirement/provenance merge batch (Designer Web, Felo Web, Runtime, GPL-derived removal all landed together already). Conflicts resolved: - `src/shared/constants/providerRetirement.ts`: add/add conflict — combined `felo-web`/`felo` (already-merged) with `qwen-web`/`qw` into one `RUNTIME_RETIRED_PROVIDER_IDS` set, kept both `assertRuntimeProviderAvailable`/`assertRuntimeModelProviderAvailable` helpers. - `open-sse/config/providers/registry/minimax/web/index.ts`: modify/delete — kept deleted (file is hailuo-web's registry entry, already retired by #11691; this PR's own change to it was just a comment reword on a since-removed target). - `open-sse/executors/index.ts`, `executorProxy.ts`, `virtualFactory.ts`, `autoStrategy.ts`, `model.ts`, `chat.ts`, `chatHelpers.ts`, `auth.ts`, `src/lib/db/providers.ts`, `reservedProviderPrefixes.ts`: combined the Designer + Runtime (Felo + Qwen) retirement guard calls at each shared chokepoint — compute-once-then-OR pattern, consistent with the prior Designer+Felo combination. - `src/shared/constants/providers/web-cookie.ts`, `tests/snapshots/provider/translate-path.json`, `tests/snapshots/executors/executor-map.json`: both sides had inserted a different retired provider (qwen-web vs. already-retired raycast/hailuo-web) at the same dict position — resolved by dropping both. `executor-map.json`'s `keyCount` recomputed to 135 (matches actual merged `entries`). - `tests/unit/chatcore-executor-proxy.test.ts`, `tests/unit/provider-node-reserved-prefix.test.ts`: split into independent Felo/Qwen test blocks (established pattern for coexisting retirement-mechanism tests); recomputed `RESERVED_PREFIX_COUNT` to 398 (Designer+Felo+Qwen tombstones on top of the post-#11691 REGISTRY, verified via direct module evaluation, not hand-derived). - `config/quality/test-masking-allowlist.json`: additive merge of Qwen's `_deletedWithReplacement` entries alongside Designer's. - `README.md` + all `docs/i18n/*/README.md` mirrors, `docs/getting-started/FREE-TIERS-GUIDE.md`, `docs/reference/FREE_TIERS.md`, `docs/diagrams/free-tier-budget.svg`, `docs/screenshots/free-tier-budget-card.svg`: recomputed the free-tier catalog counts (447 entries / 440 active / 7 discontinued) from the actual merged `freeModelCatalog.data.ts`, regenerated the budget-card SVG via its real generator (`scripts/research/gen-budget-card-svg.mjs`), and dropped the retired Qwen quick-start row / QWEN MODELS section from every i18n README (identical unlocalized block across all 34 locales). - Also fixed a duplicate-import merge artifact in `src/lib/db/providers.ts` (`isRuntimeRetiredProviderId` imported twice) caught by `typecheck:core`, and rebaselined `file-size-baseline.json` for the combined retirement-guard growth (`virtualFactory.ts` +3, with justification). Focused suite green (345/345 across executor-proxy, reserved-prefix, migration-167, qwen-web-retirement, virtual-auto-combo, web-cookie/session, executor-map-golden and siblings), plus `typecheck:core` and `check-file-size`/`check-changelog-integrity` clean. Thanks for the provenance-hold retirement work — appreciated.
192 lines
7.6 KiB
TypeScript
192 lines
7.6 KiB
TypeScript
/**
|
|
* Model Deprecation Auto-Forward — Feature 2
|
|
*
|
|
* Maps deprecated model IDs to their replacements so user configs
|
|
* don't break when providers rename or retire models.
|
|
*
|
|
* Supports both built-in aliases (static) and custom aliases (persisted via Settings API).
|
|
*/
|
|
|
|
import { hasKnownProviderModel } from "./model.ts";
|
|
|
|
// ── Built-in Deprecation Aliases ────────────────────────────────────────────
|
|
// These are known renames/retirements across providers.
|
|
// Format: deprecated ID → current ID
|
|
const BUILT_IN_ALIASES: Record<string, string> = {
|
|
// Gemini legacy → current
|
|
"gemini-pro": "gemini-2.5-pro",
|
|
"gemini-pro-vision": "gemini-2.5-pro",
|
|
"gemini-1.5-pro": "gemini-2.5-pro",
|
|
"gemini-1.5-flash": "gemini-2.5-flash",
|
|
"gemini-1.0-pro": "gemini-2.5-pro",
|
|
"gemini-2.0-flash": "gemini-2.5-flash",
|
|
"gemini-2.0-flash-lite": "gemini-3.1-flash-lite",
|
|
"gemini-3.1-flash-lite-preview": "gemini-3.1-flash-lite",
|
|
// #11503: the catalog spells this one with hyphens ("gemini-3-1-pro-high"); the
|
|
// dotted form is not a routable id, so the rewrite used to guarantee a 404.
|
|
"gemini-3-pro-high": "gemini-3-1-pro-high",
|
|
"gemini-3-pro-low": "gemini-3.1-pro-low",
|
|
// Retired free Gemma (was in the gemini-free pool) → current gemini-free model
|
|
"gemma-4": "gemini-3.1-flash-lite",
|
|
|
|
// Claude legacy → current.
|
|
// #11503: these five forwarded one retired id to another (claude-opus-4-20250514 and
|
|
// claude-sonnet-4-20250514 retired 2026-06-15, claude-3-5-sonnet-20241022 retired
|
|
// 2025-10-28), so every rewrite landed on a model the vendor no longer serves. The
|
|
// targets below are the replacements Anthropic publishes on its deprecations page.
|
|
"claude-3-opus-20240229": "claude-opus-4-8",
|
|
"claude-3-sonnet-20240229": "claude-sonnet-4-6",
|
|
"claude-3-haiku-20240307": "claude-haiku-4-5-20251001",
|
|
"claude-3-5-sonnet-latest": "claude-sonnet-4-6",
|
|
"claude-3-5-haiku-latest": "claude-haiku-4-5-20251001",
|
|
|
|
// Kimi/Moonshot — Fireworks long-path aliases (#265)
|
|
"accounts/fireworks/models/kimi-k2p5": "moonshotai/Kimi-K2.5",
|
|
"fireworks/accounts/fireworks/models/kimi-k2p5": "moonshotai/Kimi-K2.5",
|
|
"kimi-k2p5": "moonshotai/Kimi-K2.5",
|
|
"accounts/fireworks/models/kimi-k2": "moonshotai/Kimi-K2",
|
|
"fireworks/accounts/fireworks/models/kimi-k2": "moonshotai/Kimi-K2",
|
|
"kimi-k2": "moonshotai/Kimi-K2",
|
|
|
|
// Qwen — the model ships only under the `-preview` id (bailian-coding-plan, qoder,
|
|
// qwen-cloud-token-plan). Without this, the bare id missed MODEL_SPECS and
|
|
// the context preflight fell back to contextManager's `default: 128000`, rejecting
|
|
// prompts the model's real 1M window accepts. Drop this line if Alibaba ever ships a
|
|
// distinct GA `qwen3.8-max` — it would no longer be the same model.
|
|
"qwen3.8-max": "qwen3.8-max-preview",
|
|
|
|
// Mistral short aliases
|
|
"mistral-large": "mistral-large-latest",
|
|
"mistral-small": "mistral-small-latest",
|
|
codestral: "codestral-latest",
|
|
// Sweep 2026-06-19: codestral-2405 retired 2025-06-16 — forward to the current stable.
|
|
"codestral-2405": "codestral-2508",
|
|
|
|
// Llama short aliases
|
|
"llama-3.3": "llama-3.3-70b-versatile",
|
|
"llama-3-70b": "llama-3.3-70b-versatile",
|
|
// #11503: llama3-8b-8192 was deprecated by Groq on 2025-08-30 and is not in the
|
|
// catalog; llama-3.1-8b-instant is the replacement Groq names.
|
|
"llama-3-8b": "llama-3.1-8b-instant",
|
|
};
|
|
|
|
// ── Custom Aliases (persisted via Settings API) ─────────────────────────────
|
|
//
|
|
// Backed by globalThis so the singleton store is shared across the SEPARATE webpack
|
|
// module graphs Next.js builds for `instrumentation.ts` (boot-time hydration via
|
|
// applyRuntimeSettings → setCustomAliases) and the app-route `GET /api/settings/model-aliases`.
|
|
// A plain module-level `let` is DUPLICATED per graph, so startup hydration lands on the
|
|
// instrumentation graph's copy while the API route reads an empty copy — the exact
|
|
// symptom #5777 patched at the route layer. Migrating the store to globalThis fixes the
|
|
// root cause (both instances read/write one store), mirroring the #5312 pattern already
|
|
// applied to thinkingBudget.ts and backgroundTaskDetector.ts (and systemPrompt.ts #2470).
|
|
const CUSTOM_ALIASES_GLOBAL_KEY = "__omniroute_customAliases__";
|
|
const _aliasStore = globalThis as unknown as Record<string, Record<string, string> | undefined>;
|
|
|
|
function customAliases(): Record<string, string> {
|
|
if (!_aliasStore[CUSTOM_ALIASES_GLOBAL_KEY]) {
|
|
_aliasStore[CUSTOM_ALIASES_GLOBAL_KEY] = {};
|
|
}
|
|
return _aliasStore[CUSTOM_ALIASES_GLOBAL_KEY]!;
|
|
}
|
|
|
|
/**
|
|
* Set custom aliases (called from settings API or startup).
|
|
*/
|
|
export function setCustomAliases(aliases: Record<string, string>): void {
|
|
_aliasStore[CUSTOM_ALIASES_GLOBAL_KEY] = { ...aliases };
|
|
}
|
|
|
|
/**
|
|
* Get current custom aliases.
|
|
*/
|
|
export function getCustomAliases(): Record<string, string> {
|
|
return { ...customAliases() };
|
|
}
|
|
|
|
/**
|
|
* Get the full alias map (built-in + custom).
|
|
* Custom aliases take precedence over built-in.
|
|
*/
|
|
export function getAllAliases(): Record<string, string> {
|
|
return { ...BUILT_IN_ALIASES, ...customAliases() };
|
|
}
|
|
|
|
/**
|
|
* Resolve a model alias to its current ID.
|
|
* Custom aliases override built-in ones.
|
|
*
|
|
* The table is GLOBAL but the catalog is not: `kimi-k2`, `gemini-2.0-flash` and friends
|
|
* are still served under their original id by some aggregators, and rewriting them there
|
|
* turns a working request into a 404. So when the caller knows which provider will serve
|
|
* the request (#11503), a provider that lists the id as-is wins over the alias table.
|
|
* Callers with no provider in hand keep the previous unconditional behaviour.
|
|
*
|
|
* @param {string} modelId - The model ID to resolve
|
|
* @param {string | null} [provider] - Provider (id or alias) that will serve the request
|
|
* @returns {string} The resolved model ID, or the original if not deprecated
|
|
*/
|
|
export function resolveModelAlias(modelId: string, provider?: string | null): string {
|
|
if (!modelId) return modelId;
|
|
|
|
// Check custom aliases first (higher priority). An operator-authored alias is an
|
|
// explicit instruction, so it applies even when the provider serves the source id.
|
|
const custom = customAliases();
|
|
if (custom[modelId]) return custom[modelId];
|
|
|
|
if (!BUILT_IN_ALIASES[modelId]) return modelId;
|
|
|
|
// The provider serves this id itself — nothing is deprecated from its point of view.
|
|
if (provider && hasKnownProviderModel(provider, modelId)) return modelId;
|
|
|
|
return BUILT_IN_ALIASES[modelId];
|
|
}
|
|
|
|
/**
|
|
* Get a deprecation notice if the model is deprecated.
|
|
*
|
|
* @param {string} modelId - The model ID to check
|
|
* @returns {string | null} Deprecation message or null if not deprecated
|
|
*/
|
|
export function getDeprecationNotice(modelId: string): string | null {
|
|
if (!modelId) return null;
|
|
|
|
const resolved = resolveModelAlias(modelId);
|
|
if (resolved === modelId) return null;
|
|
|
|
return `Model "${modelId}" is deprecated. Forwarding to "${resolved}".`;
|
|
}
|
|
|
|
/**
|
|
* Check if a model is deprecated.
|
|
*/
|
|
export function isDeprecated(modelId: string): boolean {
|
|
return getDeprecationNotice(modelId) !== null;
|
|
}
|
|
|
|
/**
|
|
* Add a custom alias.
|
|
*/
|
|
export function addCustomAlias(from: string, to: string): void {
|
|
customAliases()[from] = to;
|
|
}
|
|
|
|
/**
|
|
* Remove a custom alias.
|
|
*/
|
|
export function removeCustomAlias(from: string): boolean {
|
|
const custom = customAliases();
|
|
if (custom[from]) {
|
|
delete custom[from];
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get the built-in aliases (read-only reference).
|
|
*/
|
|
export function getBuiltInAliases(): Record<string, string> {
|
|
return { ...BUILT_IN_ALIASES };
|
|
}
|