model-registry: split direct vs family_failover providers per model family
Prior PROXY_FAMILY_PRIORITY table conflated "direct provider" with
"failover provider that happens to serve this family". Observed case:
claude-* family listed anthropic, google-antigravity, and
github-copilot all as "providers" — but only anthropic is the direct
vendor. google-antigravity re-serves Claude via Google's sandbox
IDE product (same endpoint as gemini-cli, different auth contract);
github-copilot re-serves via GitHub's paid platform.
This matters for the 429 fallback chain: a broken anthropic key
should try genuinely-vendored endpoints first (none, for Claude),
then fall into family_failover (antigravity, copilot), and only then
reach the generic GLOBAL_PROVIDER_FALLBACK (opencode, opencode-go,
openrouter, ollama-cloud). The old all-flat list hid this distinction.
New shape:
{ providers: [...], family_failover?: [...] }
Corrections applied:
claude-*: providers=[anthropic], failover=[google-antigravity, github-copilot]
gemini-*: providers=[google-gemini-cli, google, google-vertex],
failover=[github-copilot]
gpt-* / o* / codex-*: providers=[openai],
failover=[azure-openai-responses, openai-codex, github-copilot]
mimo-*: providers=[xiaomi] (new: was [] — Xiaomi MiMo Open Platform
is direct API at api.xiaomimimo.com / token-plan-sgp.xiaomimimo.com)
buildCandidateOrder stitches [direct, family_failover, global_fallback]
with deduplication. User overrides via settings.proxy.providerPriority
continue to replace only the direct-provider list, keeping family
failover and global fallback intact.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0f0dcbf8c7
commit
ffe86284d2
1 changed files with 47 additions and 15 deletions
|
|
@ -55,22 +55,51 @@ export const PROXY_FAMILY_PRIORITY: ReadonlyArray<{
|
|||
match: RegExp;
|
||||
/** Canonical key used when matching settings.proxy.providerPriority overrides */
|
||||
prefix: string;
|
||||
/** True direct providers — the vendor or first-party endpoint. Tried first. */
|
||||
providers: string[];
|
||||
/**
|
||||
* Family-scoped failover providers — re-servers/proxies that serve this
|
||||
* family but aren't the vendor. Tried AFTER direct providers but BEFORE
|
||||
* the generic GLOBAL_PROVIDER_FALLBACK. Kept separate so the config is
|
||||
* honest about which endpoints are "native" vs "via intermediary".
|
||||
*/
|
||||
family_failover?: string[];
|
||||
}> = [
|
||||
// minimax direct (api.minimax.io) → CN endpoint
|
||||
// MiniMax direct (api.minimax.io) → CN endpoint as its direct pair
|
||||
{ match: /^MiniMax-/i, prefix: "MiniMax-", providers: ["minimax", "minimax-cn"] },
|
||||
// ZAI direct API for GLM
|
||||
{ match: /^glm-/i, prefix: "glm-", providers: ["zai"] },
|
||||
// Kimi Code direct API
|
||||
{ match: /^kimi-/i, prefix: "kimi-", providers: ["kimi-coding"] },
|
||||
// MiMo/Xiaomi — no direct provider; resolved entirely via global fallback
|
||||
{ match: /^mimo-|^XiaomiMiMo\//i, prefix: "mimo-", providers: [] },
|
||||
// Gemini/Gemma: free CLI (OAuth) first, then paid API tiers
|
||||
{ match: /^gemini-|^gemma-/i, prefix: "gemini-", providers: ["google-gemini-cli", "google", "google-vertex", "github-copilot"] },
|
||||
// Claude: Anthropic direct, then OAuth/delegated
|
||||
{ match: /^claude-/i, prefix: "claude-", providers: ["anthropic", "google-antigravity", "github-copilot"] },
|
||||
// GPT / o-series / codex
|
||||
{ match: /^gpt-|^o\d|^codex-/i, prefix: "gpt-", providers: ["openai", "azure-openai-responses", "github-copilot"] },
|
||||
// MiMo/Xiaomi — direct API via Xiaomi MiMo Open Platform (api.xiaomimimo.com)
|
||||
// or the Token Plan endpoint (token-plan-sgp.xiaomimimo.com). Both served
|
||||
// under the `xiaomi` provider namespace.
|
||||
{ match: /^mimo-|^XiaomiMiMo\//i, prefix: "mimo-", providers: ["xiaomi"] },
|
||||
// Gemini/Gemma: google-gemini-cli (OAuth), google (API key), google-vertex
|
||||
// are all FIRST-PARTY Google endpoints. github-copilot re-serves and is
|
||||
// failover only.
|
||||
{
|
||||
match: /^gemini-|^gemma-/i, prefix: "gemini-",
|
||||
providers: ["google-gemini-cli", "google", "google-vertex"],
|
||||
family_failover: ["github-copilot"],
|
||||
},
|
||||
// Claude: Anthropic is the ONLY direct provider.
|
||||
// google-antigravity and github-copilot re-serve Claude via Google's and
|
||||
// GitHub's own platforms — failover, not direct.
|
||||
{
|
||||
match: /^claude-/i, prefix: "claude-",
|
||||
providers: ["anthropic"],
|
||||
family_failover: ["google-antigravity", "github-copilot"],
|
||||
},
|
||||
// GPT / o-series / codex: OpenAI is direct. azure-openai-responses is
|
||||
// Microsoft's re-serving of OpenAI weights — treated as failover (it is
|
||||
// the same weights via a different legal/contractual relationship).
|
||||
// github-copilot likewise re-serves.
|
||||
{
|
||||
match: /^gpt-|^o\d|^codex-/i, prefix: "gpt-",
|
||||
providers: ["openai"],
|
||||
family_failover: ["azure-openai-responses", "openai-codex", "github-copilot"],
|
||||
},
|
||||
];
|
||||
|
||||
// ── Schema for OpenRouter routing preferences
|
||||
|
|
@ -822,13 +851,16 @@ export class ModelRegistry {
|
|||
|
||||
private buildCandidateOrder(modelId: string, overrides: Record<string, string[]>): string[] {
|
||||
const overrideEntry = Object.entries(overrides).find(([k]) => modelId.startsWith(k));
|
||||
const familyProviders =
|
||||
overrideEntry?.[1] ??
|
||||
PROXY_FAMILY_PRIORITY.find((r) => r.match.test(modelId))?.providers ??
|
||||
[];
|
||||
const seen = new Set(familyProviders);
|
||||
const familyEntry = PROXY_FAMILY_PRIORITY.find((r) => r.match.test(modelId));
|
||||
// Order: direct family providers → family-scoped failover → global fallback.
|
||||
// Overrides replace only the direct list (keeps family_failover + global
|
||||
// chain intact) so a user pinning "glm- → [zai]" still picks up
|
||||
// opencode-go / openrouter / ollama-cloud as last resort.
|
||||
const familyProviders = overrideEntry?.[1] ?? familyEntry?.providers ?? [];
|
||||
const familyFailover = familyEntry?.family_failover ?? [];
|
||||
const seen = new Set([...familyProviders, ...familyFailover]);
|
||||
const globalFallback = GLOBAL_PROVIDER_FALLBACK.filter((p) => !seen.has(p));
|
||||
return [...familyProviders, ...globalFallback];
|
||||
return [...familyProviders, ...familyFailover, ...globalFallback];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue