From bafc65aed3bc367cda79697a4e214a7d759f2744 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Tue, 30 Jun 2026 12:55:12 -0300 Subject: [PATCH] fix: static model catalog for jules/linkup/ollama/searchapi search providers (#5569, #5571, #5573, #5575) (#5672) --- CHANGELOG.md | 2 ++ src/lib/providers/staticModels.ts | 25 ++++++++++++++++++++++++ tests/unit/provider-models-route.test.ts | 19 ++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4a9ee1251..69b1fb80e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ ### 🔧 Bug Fixes +- **dashboard (provider add):** non-LLM search/agent providers no longer fail the model-import step with a red `Provider does not support models listing`. **Jules** (Google Labs coding agent), **linkup-search** (Linkup web search), **ollama-search** (Ollama Cloud web search — distinct from the local Ollama LLM), and **searchapi-search** (SearchAPI SERP) have no `/v1/models` endpoint, so the import surfaced a failure for expected behavior. Each now ships a small static catalog of its selectable capability ids — Linkup's `fast`/`standard`/`deep` search depths, SearchAPI's `google`/`bing`/`youtube`/… engines, a single Jules/Ollama-web-search entry — so the import step returns a usable list (`source: local_catalog`) instead of an error. Regression guard: `tests/unit/provider-models-route.test.ts`. ([#5569](https://github.com/diegosouzapw/OmniRoute/issues/5569), [#5571](https://github.com/diegosouzapw/OmniRoute/issues/5571), [#5573](https://github.com/diegosouzapw/OmniRoute/issues/5573), [#5575](https://github.com/diegosouzapw/OmniRoute/issues/5575)) + - **dashboard (provider add):** providers without a live key/cookie validator (e.g. **LMArena (Free)**, **PiAPI**) can now be saved. The Add-connection modal treated the backend's `"Provider validation not supported"` response as a hard **Invalid** state and blocked Save entirely, leaving those providers impossible to add. The validate route now returns `unsupported: true` alongside the message, and the modal treats that as a non-blocking warning — the "Check" badge still shows "validation not supported" (informational), but Save persists the credential as-is. Regression guards: `tests/unit/ui/add-api-key-modal-unsupported-save-5565.test.tsx` (Save proceeds) and `tests/unit/providers-validate-route.test.ts` (wire-format). ([#5565](https://github.com/diegosouzapw/OmniRoute/issues/5565), [#5567](https://github.com/diegosouzapw/OmniRoute/issues/5567)) - **providers (codex):** fix the **Codex Responses WebSocket** path (`/v1/responses`), which regressed in v3.8.40 with a client-visible `Invalid JSON body` and bypassed the configured proxy. (1) #5591 — PR #5237 bumped the impersonation TLS profile to `chrome_149`, but `wreq-js@2.3.1` only supports up to `chrome_147`; the unknown profile produced a degenerate fingerprint and ChatGPT rejected the upstream upgrade. The Codex WS path is reverted to the proven `chrome_142` (the v3.8.39 value), and the over-bumped `grok-web`/`claude-web` profiles (masked by their circuit-breaker but silently dropping TLS impersonation) are restored to `chrome_146`. A new regression guard asserts every configured `chrome_*` profile exists in the installed `wreq-js` typings (`tests/unit/tls-profiles-valid-5591.test.mjs`). (2) #5611 — the upstream `wreq-js.websocket()` connect ignored the Proxy Registry, so a no-direct-egress Docker container failed with a DNS error; the prepare route now resolves the Global/provider proxy and threads it through to the WS connect. Regression guard in `tests/unit/responses-ws-proxy.test.mjs`. ([#5591](https://github.com/diegosouzapw/OmniRoute/issues/5591), [#5611](https://github.com/diegosouzapw/OmniRoute/issues/5611)) diff --git a/src/lib/providers/staticModels.ts b/src/lib/providers/staticModels.ts index 56aa1bed00..e1b921e7e4 100644 --- a/src/lib/providers/staticModels.ts +++ b/src/lib/providers/staticModels.ts @@ -71,6 +71,31 @@ const STATIC_MODEL_PROVIDERS: Record Array<{ id: string; name: str name: model.name || model.id, })), qoder: () => getStaticQoderModels(), + // Non-LLM providers with no /v1/models endpoint — expose their selectable + // capability ids as a static catalog so the model-import step shows a usable + // list instead of a red "does not support models listing" failure. + jules: () => [ + // Google Labs async coding agent — single async session, no model selection. + { id: "jules", name: "Jules (Google Labs coding agent)" }, + ], + "linkup-search": () => [ + // Linkup web search — the "model" is the search depth (docs.linkup.so #5571). + { id: "standard", name: "Standard (single-iteration agentic search)" }, + { id: "deep", name: "Deep (multi-iteration search & scrape)" }, + { id: "fast", name: "Fast (sub-second, no LLM)" }, + ], + "ollama-search": () => [ + // ollama.com/api/web_search (cloud web search, not the local Ollama LLM) #5573. + { id: "web_search", name: "Ollama Web Search" }, + ], + "searchapi-search": () => [ + // SearchAPI (searchapi.io) is a SERP API — the "model" is the engine #5575. + { id: "google", name: "Google" }, + { id: "bing", name: "Bing" }, + { id: "youtube", name: "YouTube" }, + { id: "google_scholar", name: "Google Scholar" }, + { id: "duckduckgo", name: "DuckDuckGo" }, + ], }; export function getStaticModelsForProvider(provider: string): LocalCatalogModel[] | undefined { diff --git a/tests/unit/provider-models-route.test.ts b/tests/unit/provider-models-route.test.ts index 8693774a08..cfe8f3ff9f 100644 --- a/tests/unit/provider-models-route.test.ts +++ b/tests/unit/provider-models-route.test.ts @@ -61,6 +61,25 @@ test.after(async () => { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); }); +test("provider models route returns a static local catalog for non-LLM search/agent providers (#5569/#5571/#5573/#5575)", async () => { + const cases = [ + { provider: "jules", expectId: "jules" }, + { provider: "linkup-search", expectId: "standard" }, + { provider: "ollama-search", expectId: "web_search" }, + { provider: "searchapi-search", expectId: "google" }, + ]; + for (const { provider, expectId } of cases) { + const connection = await seedConnection(provider, { apiKey: `${provider}-key` }); + const response = await callRoute(connection.id); + // RED before the fix: these had no static catalog → 400 "does not support models listing". + assert.equal(response.status, 200, `${provider} should not 400 on model import`); + const body = await response.json(); + assert.equal(body.source, "local_catalog", `${provider} should serve a local catalog`); + const ids = (body.models || []).map((m) => m.id); + assert.ok(ids.includes(expectId), `${provider} should list "${expectId}"; got: ${ids.join(", ")}`); + } +}); + test("provider models route returns 404 for unknown connections", async () => { const response = await callRoute("missing-connection");