mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 22:32:12 +03:00
fix(playground): filter playground models by node prefix so custom-endpoint models appear (#3505) (#3581)
Integrated into release/v3.8.20
This commit is contained in:
committed by
GitHub
parent
9ca3c29633
commit
94a8883598
@@ -24,6 +24,7 @@
|
||||
- **fix(catalog):** PublicAI is no longer miscatalogued as keyless/free — it requires an API key (registry `authType:"apikey"`; signup grants a one-time credit, then it bills). The three PublicAI models moved from `freeType:"keyless"` (which could pick them into the no-auth pool and dispatch with no `Authorization` header) to `"one-time-initial"`, and the provider's `hasFree` flag is now `false` — matching `freeTierCatalog.ts`, which already excluded publicai. ([#3558](https://github.com/diegosouzapw/OmniRoute/issues/3558))
|
||||
- **fix(gemini-web):** a missing Playwright Chromium browser no longer loops and trips the provider breaker — when the browser binary is not installed, `chromium.launch()` threw an error surfaced as a retryable **500**, so accountFallback marked the account unavailable and retry-looped. It is now classified as a host/config problem and returns **503** with an actionable message (`npx playwright install chromium`) and the `X-Omni-Fallback-Hint: connection_cooldown` header, which skips the provider circuit breaker and applies a short non-exponential cooldown. ([#3516](https://github.com/diegosouzapw/OmniRoute/issues/3516))
|
||||
- **fix(proxy):** the SOCKS5 proxy option now follows the runtime `ENABLE_SOCKS5_PROXY` env instead of the build-time `NEXT_PUBLIC_ENABLE_SOCKS5_PROXY` — Next.js inlines `NEXT_PUBLIC_*` at build time, so a prebuilt Docker image ignored a runtime setting and the SOCKS5 type stayed hidden. The proxy modal now reads `socks5Enabled` from `GET /api/settings/proxies` (server-side `ENABLE_SOCKS5_PROXY`), with the build-time value kept only as a static-deploy fallback. ([#3508](https://github.com/diegosouzapw/OmniRoute/issues/3508))
|
||||
- **fix(playground):** the playground model selector now lists models from custom-endpoint (OpenAI/Anthropic-compatible) providers — it filtered `/v1/models` by the provider's connection id, but the catalog emits compatible-provider models under the node's custom prefix (`prefix/model`), so the list came up empty ("None"/"-"). The selector now filters by the node prefix (exposed additively as `modelPrefix` on provider options; the connection id is unchanged, so translator send/translate and connection lookups are unaffected). ([#3505](https://github.com/diegosouzapw/OmniRoute/issues/3505))
|
||||
- **fix(security):** route raw `err.message` through `sanitizeErrorMessage()` in five web executors (`adapta-web`, `deepseek-web`, `perplexity-web`, `qoder`, `veoaifree-web`) and the embeddings + search handlers (Hard Rule #12) — these built error response bodies from the raw upstream/exception message, which could leak internal detail. ([#3494](https://github.com/diegosouzapw/OmniRoute/issues/3494), [#3495](https://github.com/diegosouzapw/OmniRoute/issues/3495))
|
||||
- **fix(dashboard):** correct two dashboard fetches that hit non-existent routes (404) — `CustomHostsManager` called `/api/tools/traffic-inspector/custom-hosts` (the real route is `/hosts`), and `FeatureFlagsGrid`'s post-restart liveness probe called `/api/health` (the real lightweight endpoint is `/api/health/ping`). ([#3486](https://github.com/diegosouzapw/OmniRoute/issues/3486), [#3487](https://github.com/diegosouzapw/OmniRoute/issues/3487))
|
||||
- **chore(providers):** remove the dead `krutrim` registry entry — it was half-registered (present in `providerRegistry.ts` with a baseUrl + one model, but absent from `providers.ts`, with no executor/translator/OAuth), so it was never selectable. Dropped its `ProviderIcon` entry and the `KNOWN_REGISTRY_ONLY` exception. ([#3483](https://github.com/diegosouzapw/OmniRoute/issues/3483))
|
||||
|
||||
@@ -52,7 +52,14 @@ export default function StudioConfigPane({ configState, setConfigState }: Studio
|
||||
const { provider, setProvider, providerOptions, loading: loadingProviders } = useProviderOptions(
|
||||
configState.provider ?? ""
|
||||
);
|
||||
const { availableModels, loading: loadingModels } = useAvailableModels(provider || undefined);
|
||||
// #3505: filter models by the selected provider's catalog namespace. Compatible providers
|
||||
// emit models under their node prefix (e.g. "myprefix/gpt-4o"), not under the connection id,
|
||||
// so use the option's modelPrefix when present; fall back to the id for built-in providers.
|
||||
const selectedProviderOption = providerOptions.find(
|
||||
(opt: { value: string; modelPrefix?: string }) => opt.value === provider
|
||||
);
|
||||
const modelFilterKey = selectedProviderOption?.modelPrefix || provider || undefined;
|
||||
const { availableModels, loading: loadingModels } = useAvailableModels(modelFilterKey);
|
||||
|
||||
function update<K extends keyof ConfigState>(key: K, value: ConfigState[K]) {
|
||||
setConfigState({ ...configState, [key]: value });
|
||||
|
||||
@@ -25,6 +25,18 @@ const FORMAT_MODEL_PREFIXES = {
|
||||
* pickModelForFormat: (format: string) => string
|
||||
* }}
|
||||
*/
|
||||
/**
|
||||
* Filter the /v1/models id list to a provider's models. The `provider` key must be the model
|
||||
* NAMESPACE used in the catalog: built-in providers use their id (e.g. "openai"), while
|
||||
* compatible providers use the node's custom PREFIX (e.g. "myprefix"), NOT the node id — see
|
||||
* #3505. Pure + exported for testing.
|
||||
*/
|
||||
export function filterModelsByProvider(allModels: string[], provider?: string): string[] {
|
||||
return provider
|
||||
? allModels.filter((m) => m.startsWith(`${provider}/`) || m === provider)
|
||||
: allModels;
|
||||
}
|
||||
|
||||
export function useAvailableModels(provider?: string) {
|
||||
const [model, setModel] = useState("");
|
||||
const [allModels, setAllModels] = useState<string[]>([]);
|
||||
@@ -46,11 +58,10 @@ export function useAvailableModels(provider?: string) {
|
||||
fetchModels();
|
||||
}, []);
|
||||
|
||||
const availableModels = useMemo(() => {
|
||||
return provider
|
||||
? allModels.filter((m) => m.startsWith(`${provider}/`) || m === provider)
|
||||
: allModels;
|
||||
}, [allModels, provider]);
|
||||
const availableModels = useMemo(
|
||||
() => filterModelsByProvider(allModels, provider),
|
||||
[allModels, provider]
|
||||
);
|
||||
|
||||
/**
|
||||
* Pick the best model for a given format from the available models.
|
||||
|
||||
@@ -47,7 +47,12 @@ export function useProviderOptions(initialProvider = "openai") {
|
||||
label = node?.name || t("openaiCompatibleLabel");
|
||||
if (!info && (pid as string).startsWith(ANTHROPIC_COMPATIBLE_PREFIX))
|
||||
label = node?.name || t("anthropicCompatibleLabel");
|
||||
return { value: pid, label };
|
||||
// #3505: compatible providers emit catalog models under the node's custom prefix
|
||||
// (e.g. "myprefix/gpt-4o"), not under the connection id (`value`). Expose the prefix
|
||||
// so model-filter consumers (playground) can match; `value` stays the id for
|
||||
// connection lookups (translator send/translate, ApiTab).
|
||||
const modelPrefix = !info && node?.prefix ? String(node.prefix) : undefined;
|
||||
return { value: pid, label, modelPrefix };
|
||||
})
|
||||
.sort((a, b) => compareTr(a.label, b.label));
|
||||
|
||||
|
||||
30
tests/unit/playground-model-filter-3505.test.ts
Normal file
30
tests/unit/playground-model-filter-3505.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { filterModelsByProvider } from "../../src/app/(dashboard)/dashboard/translator/hooks/useAvailableModels.tsx";
|
||||
|
||||
// Regression for #3505: the playground model selector filtered the /v1/models list by the
|
||||
// provider OPTION VALUE (a compatible provider's node id, e.g. "openai-compatible-<uuid>"),
|
||||
// but the catalog emits compatible-provider models under the node's custom PREFIX
|
||||
// (e.g. "myprefix/gpt-4o"). So `startsWith("openai-compatible-<uuid>/")` matched nothing and
|
||||
// the selector showed "None"/"-". The fix passes the node prefix as the filter key. This locks
|
||||
// the filter behaviour: given the right key (the prefix), the models surface.
|
||||
|
||||
test("#3505 filters models by a custom node prefix (the catalog's model namespace)", () => {
|
||||
const all = ["myprefix/gpt-4o", "myprefix/llama-3", "openai/gpt-4o", "anthropic/claude-opus-4-8"];
|
||||
assert.deepEqual(filterModelsByProvider(all, "myprefix"), ["myprefix/gpt-4o", "myprefix/llama-3"]);
|
||||
});
|
||||
|
||||
test("#3505 a UUID-style node id (the old wrong key) matches nothing → empty (the bug)", () => {
|
||||
const all = ["myprefix/gpt-4o", "openai/gpt-4o"];
|
||||
assert.deepEqual(filterModelsByProvider(all, "openai-compatible-1234-uuid"), []);
|
||||
});
|
||||
|
||||
test("#3505 built-in provider filtering still works", () => {
|
||||
const all = ["openai/gpt-4o", "openai/gpt-4o-mini", "anthropic/claude-opus-4-8"];
|
||||
assert.deepEqual(filterModelsByProvider(all, "openai"), ["openai/gpt-4o", "openai/gpt-4o-mini"]);
|
||||
});
|
||||
|
||||
test("#3505 an exact bare match is included; no provider returns all", () => {
|
||||
assert.deepEqual(filterModelsByProvider(["auto", "openai/gpt-4o"], "auto"), ["auto"]);
|
||||
assert.deepEqual(filterModelsByProvider(["a", "b"], undefined), ["a", "b"]);
|
||||
});
|
||||
Reference in New Issue
Block a user