From a4872ce1de6151cf513fa4e64925902dc6b7eec5 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Fri, 3 Jul 2026 00:14:28 -0300 Subject: [PATCH] feat(providers): refresh The Old LLM (Free) model catalog (#5181) --- CHANGELOG.md | 1 + .../providers/registry/theoldllm/index.ts | 28 +++++- open-sse/executors/theoldllm.ts | 39 +++++++- .../unit/theoldllm-model-refresh-5181.test.ts | 90 +++++++++++++++++++ 4 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 tests/unit/theoldllm-model-refresh-5181.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 88bcac19ca..ccd76660b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - **feat(api):** add `/v1/ocr` endpoint (Mistral OCR), an OCR provider category, and Mistral moderation support. (thanks @waguriagentic) - **Discovery tool (Phase 2):** add the `discoveryResults` DB module (CRUD over the `discovery_results` table, migration 074) and wire the opt-in provider-discovery service to persist and read findings through it (`persistDiscoveryResult`, `getDiscoveryResults`, `getDiscoveryResultById`, `markVerified`, `deleteDiscoveryResult`) with `(provider, method, endpoint)` upsert de-duplication. Adds the `/api/discovery/*` HTTP surface — `GET /results`, `GET|DELETE /results/:id`, `POST /scan`, `POST /verify/:id` — under **strict loopback-only** authorization (`/api/discovery/` is in `LOCAL_ONLY_API_PREFIXES` and is NOT manage-scope-bypassable, so the `scan` route's outbound probes can never be reached from a tunnel/remote origin). Adds a **dashboard UI tab** (Tools → Discovery, `/dashboard/discovery`) to run scans and review, verify, or delete findings. The service stays **opt-in / default-off**. - **feat(proxy):** add Webshare proxy pool import and sync — a `WebshareProvider` (`FreeProxyProvider`) that paginates `proxy.webshare.io/api/v2/proxy/list/` gated on `FREE_PROXY_WEBSHARE_API_KEY`, SSRF-guards imported hosts, and tombstones retired proxy IDs via `pruneStaleFreeProxies()`. (thanks @ricatix) +- **feat(providers):** refresh The Old LLM (Free) model catalog ([#5181](https://github.com/diegosouzapw/OmniRoute/issues/5181)) — seed the current free `/api/chatgpt` tier (GPT-5/5.1/5.2/5.3/5.4, o3/o4-mini, Gemini 3 Pro / 2.5 Pro / 2.0 Flash / 1.5 Flash, Claude 4.6 Opus/Sonnet & 4.5 Haiku, GPT-4o, Grok 4, DeepSeek V3/R1, Sonar Pro) while keeping the legacy alias IDs for saved-preference compatibility. Also fixes a latent routing bug: `mapModel()` now passes known upstream IDs through unchanged, so Gemini/o-series/Grok/DeepSeek/Sonar models no longer silently collapse onto `GPT_5_4`. Regression guard: `tests/unit/theoldllm-model-refresh-5181.test.ts`. (thanks @WslzGmzs) ### 🔧 Bug Fixes diff --git a/open-sse/config/providers/registry/theoldllm/index.ts b/open-sse/config/providers/registry/theoldllm/index.ts index 1b1c7691cb..a22d9901ae 100644 --- a/open-sse/config/providers/registry/theoldllm/index.ts +++ b/open-sse/config/providers/registry/theoldllm/index.ts @@ -11,15 +11,41 @@ export const theoldllmProvider: RegistryEntry = { authType: "none", authHeader: "none", defaultContextLength: 200000, + // Catalog seed. `passthroughModels: true` means live /api/chatgpt discovery is + // authoritative; this list is the curated display/fallback set. The upstream IDs + // (GPT_5_*, gemini_*, CLAUDE_4_*, openrouter_*, etc.) mirror the site's free + // "chatgpt" tier and MUST match `CHATGPT_UPSTREAM_MODELS` in the executor so they + // route unchanged. Legacy alias IDs (GPT_4o, claude_opus_4, …) are kept for + // backward compatibility with saved model preferences (mapped in the executor). models: [ + // ── Current free tier (refreshed for #5181) ── { id: "GPT_5_4", name: "GPT-5.4 (The Old LLM 🆓)", contextLength: 400000 }, + { id: "GPT_5_3", name: "GPT-5.3 (The Old LLM 🆓)", contextLength: 400000 }, + { id: "GPT_5_2", name: "GPT-5.2 (The Old LLM 🆓)", contextLength: 400000 }, + { id: "GPT_5_1", name: "GPT-5.1 (The Old LLM 🆓)", contextLength: 400000 }, + { id: "GPT_5", name: "GPT-5 (The Old LLM 🆓)", contextLength: 400000 }, + { id: "GPT_o4_mini", name: "o4-mini (The Old LLM 🆓)" }, + { id: "GPT_o3_mini", name: "o3-mini (The Old LLM 🆓)" }, + { id: "gemini_3_pro", name: "Gemini 3 Pro (The Old LLM 🆓)", contextLength: 1000000 }, + { id: "gemini_2_5_pro", name: "Gemini 2.5 Pro (The Old LLM 🆓)", contextLength: 1000000 }, + { id: "gemini_2_0_flash", name: "Gemini 2.0 Flash (The Old LLM 🆓)", contextLength: 1000000 }, + { id: "gemini_1_5_flash", name: "Gemini 1.5 Flash (The Old LLM 🆓)", contextLength: 1000000 }, + { id: "CLAUDE_4_6_OPUS", name: "Claude 4.6 Opus (The Old LLM 🆓)", contextLength: 200000 }, + { id: "CLAUDE_4_6_SONNET", name: "Claude 4.6 Sonnet (The Old LLM 🆓)", contextLength: 200000 }, + { id: "CLAUDE_4_5_HAIKU", name: "Claude 4.5 Haiku (The Old LLM 🆓)", contextLength: 200000 }, + { id: "openrouter_gpt_4_o", name: "GPT-4o (The Old LLM 🆓)" }, + { id: "openrouter_gpt_4_o_mini", name: "GPT-4o mini (The Old LLM 🆓)" }, + { id: "openrouter_grok_4", name: "Grok 4 (The Old LLM 🆓)" }, + { id: "together_deepseek_v3", name: "DeepSeek V3 (The Old LLM 🆓)" }, + { id: "openrouter_deepseek_r1", name: "DeepSeek R1 (The Old LLM 🆓)" }, + { id: "sonar-pro", name: "Sonar Pro (The Old LLM 🆓)" }, + // ── Legacy alias IDs (kept for saved-preference backward compatibility) ── { id: "GPT_4o", name: "GPT-4o (The Old LLM 🆓)" }, { id: "claude_opus_4", name: "Claude Opus 4 (The Old LLM 🆓)", contextLength: 200000 }, { id: "claude_sonnet_4", name: "Claude Sonnet 4 (The Old LLM 🆓)", contextLength: 200000 }, { id: "claude_haiku_3_5", name: "Claude Haiku 3.5 (The Old LLM 🆓)", contextLength: 200000 }, { id: "deepseek_v4", name: "DeepSeek V4 (The Old LLM 🆓)", contextLength: 200000 }, { id: "gemini_3_flash", name: "Gemini 3 Flash (The Old LLM 🆓)", contextLength: 1000000 }, - { id: "gemini_3_pro", name: "Gemini 3 Pro (The Old LLM 🆓)", contextLength: 1000000 }, ], passthroughModels: true, }; diff --git a/open-sse/executors/theoldllm.ts b/open-sse/executors/theoldllm.ts index 46804cdf77..64e985b96c 100644 --- a/open-sse/executors/theoldllm.ts +++ b/open-sse/executors/theoldllm.ts @@ -39,7 +39,44 @@ const CLAUDE_NAMES: Record = { "claude haiku 3.5": "CLAUDE_4_5_HAIKU", }; -function mapModel(model: string): string { +// Canonical upstream model IDs served by theoldllm's /api/chatgpt proxy +// (apiProvider "chatgpt" in the site's model catalog — the free, reachable tier). +// Source: https://theoldllm.vercel.app model list (reported in #5181). +// These pass through mapModel() UNCHANGED — critical for non-GPT/Claude models +// (Gemini, o-series, Grok, DeepSeek, Sonar) which would otherwise fall through +// to the GPT_5_4 default and silently misroute. +export const CHATGPT_UPSTREAM_MODELS: ReadonlySet = new Set([ + "GPT_5_4", + "GPT_5_3", + "GPT_5_2", + "GPT_5_1", + "GPT_5", + "GPT_o4_mini", + "GPT_o3_mini", + "gemini_3_pro", + "gemini_2_5_pro", + "gemini_2_0_flash", + "gemini_1_5_flash", + "CLAUDE_4_6_OPUS", + "CLAUDE_4_6_SONNET", + "CLAUDE_4_5_HAIKU", + "openrouter_gpt_4_o", + "openrouter_gpt_4_o_mini", + "openrouter_gpt_4", + "openrouter_grok_4", + "together_deepseek_r1", + "openrouter_deepseek_r1", + "together_deepseek_v3", + "openrouter_deepseek_v3", + "sonar-deep-research", + "sonar-pro", + "openrouter_web_search", +]); + +export function mapModel(model: string): string { + const trimmed = model.trim(); + // Known upstream IDs (from live discovery / refreshed catalog) route as-is. + if (CHATGPT_UPSTREAM_MODELS.has(trimmed)) return trimmed; const n = model.toLowerCase().trim(); const gptKey = n.replace(/[_\s]+/g, "-"); if (GPT_MODELS[gptKey]) return GPT_MODELS[gptKey]; diff --git a/tests/unit/theoldllm-model-refresh-5181.test.ts b/tests/unit/theoldllm-model-refresh-5181.test.ts new file mode 100644 index 0000000000..f41003268e --- /dev/null +++ b/tests/unit/theoldllm-model-refresh-5181.test.ts @@ -0,0 +1,90 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +// Feature guard for #5181 — "Update The Old LLM (Free) model list". +// +// Two things this proves, both of which fail on the pre-#5181 code: +// 1. mapModel() now passes KNOWN upstream IDs through UNCHANGED. Before the fix, +// any non-GPT/Claude id (Gemini, o-series, Grok, DeepSeek, Sonar) fell through +// to the `return "GPT_5_4"` default and silently misrouted every request. +// 2. The registry catalog is refreshed with the current free-tier models while +// keeping the legacy alias IDs for saved-preference backward compatibility. +const { mapModel, CHATGPT_UPSTREAM_MODELS } = await import( + "../../open-sse/executors/theoldllm.ts" +); +const { getRegistryEntry } = await import("../../open-sse/config/providerRegistry.ts"); + +function catalogIds(): string[] { + const entry = getRegistryEntry("theoldllm"); + assert.ok(entry, "theoldllm registry entry must exist"); + return (entry.models ?? []).map((m) => m.id); +} + +test("#5181 known upstream IDs pass through mapModel unchanged (Gemini no longer misroutes to GPT_5_4)", () => { + // These are the exact cases the old default clause broke. + assert.equal(mapModel("gemini_3_pro"), "gemini_3_pro"); + assert.equal(mapModel("gemini_2_5_pro"), "gemini_2_5_pro"); + assert.equal(mapModel("gemini_2_0_flash"), "gemini_2_0_flash"); + assert.equal(mapModel("openrouter_grok_4"), "openrouter_grok_4"); + assert.equal(mapModel("together_deepseek_v3"), "together_deepseek_v3"); + assert.equal(mapModel("sonar-pro"), "sonar-pro"); + assert.equal(mapModel("GPT_o4_mini"), "GPT_o4_mini"); + // Every declared upstream id must round-trip through mapModel unchanged. + for (const id of CHATGPT_UPSTREAM_MODELS) { + assert.equal(mapModel(id), id, `${id} must route unchanged`); + } +}); + +test("#5181 legacy alias IDs still map to available upstream models (backward compatibility)", () => { + assert.equal(mapModel("claude_opus_4"), "CLAUDE_4_6_OPUS"); + assert.equal(mapModel("claude_sonnet_4"), "CLAUDE_4_6_SONNET"); + assert.equal(mapModel("claude_haiku_3_5"), "CLAUDE_4_5_HAIKU"); + assert.equal(mapModel("gpt-5.4"), "GPT_5_4"); + assert.equal(mapModel("gpt-4o"), "GPT_4O"); +}); + +test("#5181 catalog is refreshed with the current free-tier models", () => { + const ids = catalogIds(); + for (const id of [ + "GPT_5_3", + "GPT_5_2", + "GPT_5_1", + "GPT_5", + "GPT_o4_mini", + "GPT_o3_mini", + "gemini_2_5_pro", + "gemini_2_0_flash", + "gemini_1_5_flash", + "CLAUDE_4_6_OPUS", + "CLAUDE_4_6_SONNET", + "CLAUDE_4_5_HAIKU", + "openrouter_grok_4", + "sonar-pro", + ]) { + assert.ok(ids.includes(id), `catalog must include refreshed model ${id}`); + } +}); + +test("#5181 legacy catalog entries are preserved (no breaking removal of saved-preference IDs)", () => { + const ids = catalogIds(); + for (const id of ["GPT_5_4", "GPT_4o", "claude_opus_4", "gemini_3_pro"]) { + assert.ok(ids.includes(id), `legacy catalog id ${id} must be preserved`); + } +}); + +test("#5181 every refreshed catalog id routes to a valid upstream model", () => { + // No catalog id may fall through to the GPT_5_4 default unless it is genuinely a + // GPT-5 alias — Gemini/Grok/DeepSeek/Sonar/Claude entries must resolve to their + // own upstream id, not silently collapse onto GPT_5_4. + const nonGptExpectations: Record = { + gemini_2_5_pro: "gemini_2_5_pro", + gemini_2_0_flash: "gemini_2_0_flash", + gemini_1_5_flash: "gemini_1_5_flash", + CLAUDE_4_6_OPUS: "CLAUDE_4_6_OPUS", + openrouter_grok_4: "openrouter_grok_4", + "sonar-pro": "sonar-pro", + }; + for (const [id, expected] of Object.entries(nonGptExpectations)) { + assert.equal(mapModel(id), expected); + } +});