From e66f864f670a8b43143c137ddab5636c897e931f Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Sat, 15 Aug 2026 01:49:10 +0200 Subject: [PATCH] fix(providers): recover static model discovery --- gui/.eslint/i18n-allowlist.ts | 3 +- .../provider-workspace/ProviderSettings.tsx | 22 ++-- gui/src/provider-workspace/catalog.ts | 17 +++ ...r-settings-live-models-provenance.test.tsx | 43 +++++++ src/adapters/mimo-free.ts | 17 +++ src/providers/derive.ts | 15 ++- src/providers/registry.ts | 6 +- src/providers/static-model-discovery.ts | 86 +++++++++++++ src/router.ts | 23 +++- tests/cline-pass-provider.test.ts | 3 + tests/mimo-free-provider.test.ts | 4 +- tests/provider-static-model-discovery.test.ts | 118 ++++++++++++++++++ 12 files changed, 335 insertions(+), 22 deletions(-) create mode 100644 src/providers/static-model-discovery.ts create mode 100644 tests/provider-static-model-discovery.test.ts diff --git a/gui/.eslint/i18n-allowlist.ts b/gui/.eslint/i18n-allowlist.ts index 83974db5a8..7332fe522d 100644 --- a/gui/.eslint/i18n-allowlist.ts +++ b/gui/.eslint/i18n-allowlist.ts @@ -97,7 +97,8 @@ export function isTechnicalLiteral(value: string): boolean { if (/^ocx\b/i.test(trimmed)) return true; if (/^codex\b/i.test(trimmed)) return true; - // HTTP headers / auth schemes + // HTTP protocol / headers / auth schemes + if (/^HTTP$/i.test(trimmed)) return true; if (/^Authorization\b/i.test(trimmed)) return true; if (/^Bearer\b/i.test(trimmed)) return true; if (/^Content-Type\b/i.test(trimmed)) return true; diff --git a/gui/src/components/provider-workspace/ProviderSettings.tsx b/gui/src/components/provider-workspace/ProviderSettings.tsx index 26ca85fa35..873d6f0730 100644 --- a/gui/src/components/provider-workspace/ProviderSettings.tsx +++ b/gui/src/components/provider-workspace/ProviderSettings.tsx @@ -14,6 +14,7 @@ import { useT } from "../../i18n/shared"; import { IconLock } from "../../icons"; import { isCatalogProviderId } from "../../provider-icons"; import { openAiAccountProviderState } from "../../provider-payload"; +import { providerSupportsLiveModelDiscovery } from "../../provider-workspace/catalog"; import type { CatalogPreset } from "../provider-catalog/provider-presets"; import { authModeLabel } from "./ProviderRail"; import type { WorkspaceItem, ProviderUpdatePatch } from "./types"; @@ -37,6 +38,8 @@ export default function ProviderSettings({ }) { const t = useT(); const initialAuth = String(item.authMode ?? (item.keyOptional ? "local" : "key")); + const liveModelDiscoverySupported = providerSupportsLiveModelDiscovery(item.name, item); + const savedLiveModels = liveModelDiscoverySupported ? item.liveModels !== false : false; const [adapter, setAdapter] = useState(item.adapter); const [baseUrl, setBaseUrl] = useState(item.baseUrl); const [defaultModel, setDefaultModel] = useState(item.defaultModel ?? ""); @@ -44,7 +47,7 @@ export default function ProviderSettings({ const [apiKeyTransport, setApiKeyTransport] = useState(item.apiKeyTransport ?? "x-api-key"); const [note, setNote] = useState(item.note ?? ""); const [allowPrivateNetwork, setAllowPrivateNetwork] = useState(item.allowPrivateNetwork ?? false); - const [liveModels, setLiveModels] = useState(item.liveModels !== false); + const [liveModels, setLiveModels] = useState(savedLiveModels); const [saving, setSaving] = useState(false); const [msg, setMsg] = useState<{ ok: boolean; text: string } | null>(null); const [accountMode, setAccountMode] = useState<"pool" | "direct">(item.codexAccountMode ?? "pool"); @@ -63,11 +66,11 @@ export default function ProviderSettings({ setApiKeyTransport(item.apiKeyTransport ?? "x-api-key"); setNote(item.note ?? ""); setAllowPrivateNetwork(item.allowPrivateNetwork ?? false); - setLiveModels(item.liveModels !== false); + setLiveModels(savedLiveModels); setMsg(null); setModeMsg(null); queueMicrotask(() => setEndpointChoice(matchChoiceId(baseUrlChoices, item.baseUrl))); - }, [item.adapter, item.baseUrl, item.defaultModel, item.authMode, item.apiKeyTransport, item.keyOptional, item.note, item.allowPrivateNetwork, item.liveModels, baseUrlChoices]); + }, [item.adapter, item.baseUrl, item.defaultModel, item.authMode, item.apiKeyTransport, item.keyOptional, item.note, item.allowPrivateNetwork, savedLiveModels, baseUrlChoices]); /* eslint-enable react-hooks/set-state-in-effect */ // Account mode syncs on its own: a mode PATCH refresh must not reset an in-progress @@ -116,7 +119,7 @@ export default function ProviderSettings({ || (adapter.trim() === "anthropic" && authMode === "key" && apiKeyTransport !== (item.apiKeyTransport ?? "x-api-key")) || note.trim() !== (item.note ?? "") || allowPrivateNetwork !== (item.allowPrivateNetwork ?? false) - || liveModels !== (item.liveModels !== false); + || liveModels !== savedLiveModels; useEffect(() => { onDirtyChange?.(dirty); return () => onDirtyChange?.(false); }, [dirty, onDirtyChange]); @@ -155,7 +158,7 @@ export default function ProviderSettings({ const patch: ProviderUpdatePatch = { adapter: adapter.trim(), baseUrl: nextBaseUrl, defaultModel: defaultModel.trim(), authMode, note: note.trim(), allowPrivateNetwork }; // Keep omitted legacy values omitted unless the user actually changes this toggle. // Otherwise an unrelated settings save manufactures `liveModels: true` provenance. - if (liveModels !== (item.liveModels !== false)) patch.liveModels = liveModels; + if (liveModelDiscoverySupported && liveModels !== (item.liveModels !== false)) patch.liveModels = liveModels; if (supportsApiKeyTransport) patch.apiKeyTransport = apiKeyTransport; else if (item.apiKeyTransport !== undefined) patch.apiKeyTransport = ""; const res = await onUpdateProvider(item.name, patch); @@ -200,7 +203,7 @@ export default function ProviderSettings({ setAdapter(item.adapter); setBaseUrl(item.baseUrl); setDefaultModel(item.defaultModel ?? ""); setAuthMode(initialAuth); setApiKeyTransport(item.apiKeyTransport ?? "x-api-key"); - setNote(item.note ?? ""); setAllowPrivateNetwork(item.allowPrivateNetwork ?? false); setLiveModels(item.liveModels !== false); setMsg(null); + setNote(item.note ?? ""); setAllowPrivateNetwork(item.allowPrivateNetwork ?? false); setLiveModels(savedLiveModels); setMsg(null); setEndpointChoice(matchChoiceId(baseUrlChoices, item.baseUrl)); }; @@ -335,7 +338,12 @@ export default function ProviderSettings({ {t("pws.allowPrivateNetwork")}