fix(models): derive model discovery config from registry modelsUrl (#5087)

Integrated into release/v3.8.38 (leva 5)
This commit is contained in:
Hernan Javier Ardila Sanchez
2026-06-26 16:14:53 +02:00
committed by GitHub
parent 7256d34a23
commit 57d10ea9f7
6 changed files with 74 additions and 2 deletions

View File

@@ -29,6 +29,7 @@ _In development — bullets added per PR; finalized at release._
- **fix(sse): don't cool down a healthy connection on a self-inflicted upstream timeout (504)** — when OmniRoute's own deadline elapses (surfaced as `TimeoutError`/`BodyTimeoutError` → 504), the connection is no longer disabled/failed-over, so a slow-but-healthy provider isn't penalised for our timeout. Genuine upstream 5xx/429 still trigger cooldown; antigravity keeps its own policy. (thanks @costaeder)
- **fix(sse): robust Anthropic `/v1/messages` streaming — real ping keepalive + client-disconnect guard** — slow first tokens on reasoning models could trip strict clients' idle-read watchdog; the route now keeps the stream warm with a real `event: ping` (Anthropic clients ignore SSE comments) from the very first frame, and a client disconnect (AbortError / controller-closed) no longer counts as a provider failure (no failover/cooldown). (thanks @costaeder)
- **fix: preserve model hidden flags (`isHidden`) across model sync**`replaceCustomModels` pruned the compat-override list to the new custom-model ids, silently wiping the `isHidden` flag of eye-hidden SYNCED models on every periodic sync / import (all hidden models turned back on). The redundant cleanup is removed (per-model removal already handles its own compat cleanup), so eye-hidden models stay hidden across re-sync. (#4389, thanks @herjarsa)
- **fix(models): derive model-discovery config from the registry `modelsUrl`** — providers absent from the hardcoded `PROVIDER_MODELS_CONFIG` but carrying a registry `modelsUrl` (e.g. MiniMax) now get an auto-derived Bearer `/v1/models` discovery config, so "discover models" works instead of returning nothing. (thanks @herjarsa)
---

View File

@@ -7,6 +7,7 @@ export const minimax_cnProvider: RegistryEntry = {
format: "claude",
executor: "default",
baseUrl: "https://api.minimaxi.com/anthropic/v1/messages",
modelsUrl: "https://api.minimaxi.com/v1/models",
urlSuffix: "?beta=true",
authType: "apikey",
authHeader: "bearer",

View File

@@ -7,6 +7,7 @@ export const minimaxProvider: RegistryEntry = {
format: "claude",
executor: "default",
baseUrl: "https://api.minimax.io/anthropic/v1/messages",
modelsUrl: "https://api.minimax.io/v1/models",
urlSuffix: "?beta=true",
authType: "apikey",
authHeader: "bearer",

View File

@@ -0,0 +1,35 @@
import { getRegistryEntry } from "@omniroute/open-sse/config/providerRegistry.ts";
/**
* Derive a models-discovery config from the provider's registry `modelsUrl`
* when the provider is absent from the hardcoded PROVIDER_MODELS_CONFIG.
*
* Returns a config object with Bearer auth suitable for fetching an
* OpenAI-compatible `/v1/models` endpoint, or `undefined` when the
* registry entry has no `modelsUrl`.
*/
export function deriveConfigFromRegistryModelsUrl(provider: string):
| {
url: string;
method: "GET";
headers: Record<string, string>;
authHeader?: string;
authPrefix?: string;
authQuery?: string;
body?: unknown;
parseResponse: (data: any) => any;
}
| undefined {
const entry = getRegistryEntry(provider);
if (typeof entry?.modelsUrl === "string" && entry.modelsUrl.length > 0) {
return {
url: entry.modelsUrl,
method: "GET",
authHeader: "Authorization",
authPrefix: "Bearer ",
headers: { "Content-Type": "application/json" },
parseResponse: (data) => data.data || data.models || [],
};
}
return undefined;
}

View File

@@ -25,6 +25,7 @@ import {
import { getProviderOutboundGuard } from "@/shared/network/outboundUrlGuard";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
import { getStaticQoderModels } from "@omniroute/open-sse/services/qoderCli.ts";
import { deriveConfigFromRegistryModelsUrl } from "./discoveryConfig";
import { fetchGitHubCopilotModels } from "@omniroute/open-sse/services/githubCopilotModels.ts";
import { fetchKiroAvailableModels } from "@omniroute/open-sse/services/kiroModels.ts";
import { getAntigravityHeaders } from "@omniroute/open-sse/services/antigravityHeaders.ts";
@@ -2390,8 +2391,7 @@ export async function GET(
const config =
provider in PROVIDER_MODELS_CONFIG
? PROVIDER_MODELS_CONFIG[provider as keyof typeof PROVIDER_MODELS_CONFIG]
: undefined;
: deriveConfigFromRegistryModelsUrl(provider);
// Static model providers (no remote /models API)
// Qwen OAuth Fallback: The Dashscope /models API rejects OAuth tokens with 401
if (provider === "qwen" && connection.authType === "oauth") {

View File

@@ -0,0 +1,34 @@
/**
* #5087 — derive model-discovery config from a provider's registry `modelsUrl`.
*
* When a provider is absent from the hardcoded PROVIDER_MODELS_CONFIG but its
* registry entry carries a `modelsUrl`, `deriveConfigFromRegistryModelsUrl`
* builds a Bearer `/v1/models` discovery config so the dashboard's "discover
* models" path works (e.g. MiniMax). Providers without `modelsUrl` return
* `undefined` (caller falls back to its existing undefined handling).
*/
import test from "node:test";
import assert from "node:assert/strict";
import { deriveConfigFromRegistryModelsUrl } from "../../src/app/api/providers/[id]/models/discoveryConfig.ts";
test("minimax (registry modelsUrl present) yields a Bearer /v1/models config", () => {
const config = deriveConfigFromRegistryModelsUrl("minimax");
assert.ok(config, "minimax must yield a config");
assert.equal(config.url, "https://api.minimax.io/v1/models");
assert.equal(config.method, "GET");
assert.equal(config.authHeader, "Authorization");
assert.equal(config.authPrefix, "Bearer ");
// parseResponse tolerates both {data:[...]} and {models:[...]} shapes.
assert.deepEqual(config.parseResponse({ data: [{ id: "abab6.5" }] }), [{ id: "abab6.5" }]);
assert.deepEqual(config.parseResponse({ models: [{ id: "m" }] }), [{ id: "m" }]);
});
test("a provider without a registry modelsUrl returns undefined", () => {
// `baseten` is a registry provider with no `modelsUrl`.
assert.equal(deriveConfigFromRegistryModelsUrl("baseten"), undefined);
});
test("an unknown provider returns undefined", () => {
assert.equal(deriveConfigFromRegistryModelsUrl("not-a-real-provider-xyz"), undefined);
});