diff --git a/CHANGELOG.md b/CHANGELOG.md index d07aee0839..8f4750ed3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ _In development — bullets added per PR; finalized at release._ - **fix(dashboard):** on OAuth providers (e.g. GLM Coding), "Test all models" with auto-hide-failed now switches the model list to the "visible" filter after the run, so just-hidden failed models actually disappear on-screen — parity with the passthrough-provider path (#3610). Previously they were hidden in the DB but stayed visible under the "All" filter, so it looked like nothing was hidden. (#4887) - **fix(pollinations):** stop forcing `jsonMode` on every request. Pollinations treats `jsonMode=true` as "the model MUST return JSON" and rejects (HTTP 400 "messages must contain the word 'json'") any normal chat request whose messages don't mention "json", so all non-JSON chat was broken. `jsonMode` is now only enabled when the caller actually requests JSON output (`response_format.type` of `json_object` or `json_schema`). (#3981) - **fix(antigravity):** default `safetySettings` to all-OFF for parity with the native Gemini paths. The Antigravity (Google Cloud Code) request builder set `safetySettings: undefined`, which `JSON.stringify` drops — so no safety settings reached Google and its server-side defaults false-flagged benign technical prompts as `prohibited_content` (HTTP 200 + blocked body, which combo failover treats as terminal). Now honors a caller-supplied value and otherwise defaults to `DEFAULT_SAFETY_SETTINGS`, matching the claude-to-gemini / openai-to-gemini paths (#5003) +- **fix(chatgpt-web):** map the advertised `gpt-5.5`, `gpt-5.5-pro`, `gpt-5.4-pro` and `gpt-5.2-pro` catalog ids to their dash-form ChatGPT backend slugs. They were missing from `MODEL_MAP`, so the executor sent the dot-form id verbatim, which the ChatGPT backend silently ignored and served the default Plus model instead of the requested one. Adds a drift guard asserting no advertised dot-form id reaches the backend verbatim. (#4665) --- diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index 0ffbd9bf51..46d0145309 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -240,7 +240,7 @@ "tests/unit/cc-compatible-provider.test.ts": 1179, "tests/unit/chatcore-sanitization.test.ts": 829, "tests/unit/chatcore-translation-paths.test.ts": 2810, - "tests/unit/chatgpt-web.test.ts": 2809, + "tests/unit/chatgpt-web.test.ts": 2855, "tests/unit/combo-routing-engine.test.ts": 3213, "tests/unit/combo-strategy-fallbacks.test.ts": 880, "tests/unit/db-core-init.test.ts": 867, diff --git a/open-sse/executors/chatgpt-web.ts b/open-sse/executors/chatgpt-web.ts index f269ee14d1..498ef604cb 100644 --- a/open-sse/executors/chatgpt-web.ts +++ b/open-sse/executors/chatgpt-web.ts @@ -82,9 +82,13 @@ const MODEL_MAP: Record = { "gpt-5.3-instant": "gpt-5-3-instant", "gpt-5.3": "gpt-5-3", "gpt-5.3-mini": "gpt-5-3-mini", + "gpt-5.5-pro": "gpt-5-5-pro", "gpt-5.5-thinking": "gpt-5-5-thinking", + "gpt-5.5": "gpt-5-5", + "gpt-5.4-pro": "gpt-5-4-pro", "gpt-5.4-thinking": "gpt-5-4-thinking", "gpt-5.4-thinking-mini": "gpt-5-4-t-mini", + "gpt-5.2-pro": "gpt-5-2-pro", "gpt-5.2-instant": "gpt-5-2-instant", "gpt-5.2": "gpt-5-2", "gpt-5.2-thinking": "gpt-5-2-thinking", diff --git a/tests/unit/chatgpt-web.test.ts b/tests/unit/chatgpt-web.test.ts index b1766c7069..a0547d6589 100644 --- a/tests/unit/chatgpt-web.test.ts +++ b/tests/unit/chatgpt-web.test.ts @@ -1119,6 +1119,13 @@ test("Executor MODEL_MAP: dot-form OmniRoute IDs translate to dash-form ChatGPT ["gpt-5.4-thinking-mini", "gpt-5-4-t-mini"], ["gpt-5.2-thinking", "gpt-5-2-thinking"], ["o3", "o3"], + // Regression #4665: these advertised catalog ids were missing from + // MODEL_MAP and fell through as their dot-form slug verbatim, which the + // ChatGPT backend-api silently rejects → served the default Plus model. + ["gpt-5.5", "gpt-5-5"], + ["gpt-5.5-pro", "gpt-5-5-pro"], + ["gpt-5.4-pro", "gpt-5-4-pro"], + ["gpt-5.2-pro", "gpt-5-2-pro"], ]; for (const [omniId, expectedSlug] of cases) { m.calls.urls.length = 0; @@ -1141,6 +1148,45 @@ test("Executor MODEL_MAP: dot-form OmniRoute IDs translate to dash-form ChatGPT } }); +test("MODEL_MAP drift guard: every advertised dot-form catalog id resolves to a dash-form backend slug (no verbatim fall-through) (#4665)", async () => { + reset(); + const { getRegistryEntry } = await import("../../open-sse/config/providerRegistry.ts"); + const ids = (getRegistryEntry("chatgpt-web")?.models || []).map((m) => m.id); + // Dot-form ids must never reach the ChatGPT backend verbatim — the backend + // only accepts dash-form slugs. (Ids that are already dash-form, e.g. + // "gpt-4-5", legitimately pass through unchanged and are exempt.) + const dotFormIds = ids.filter((id) => id.includes(".")); + const m = installMockFetch(); + try { + for (const omniId of dotFormIds) { + m.calls.urls.length = 0; + m.calls.bodies.length = 0; + const executor = new ChatGptWebExecutor(); + await executor.execute({ + model: omniId, + body: { messages: [{ role: "user", content: "hi" }] }, + stream: false, + credentials: { apiKey: "test" }, + signal: AbortSignal.timeout(10_000), + log: null, + }); + const convIdx = m.calls.urls.findIndex((u) => u.endsWith("/backend-api/f/conversation")); + const body = JSON.parse(m.calls.bodies[convIdx]); + assert.ok( + !body.model.includes("."), + `${omniId} reached the backend as "${body.model}" (still dot-form) — missing MODEL_MAP entry causes silent model substitution`, + ); + assert.notEqual( + body.model, + omniId, + `${omniId} fell through MODEL_MAP verbatim — add a dash-form mapping`, + ); + } + } finally { + m.restore(); + } +}); + // ─── thinking_effort PATCH user_last_used_model_config ───────────────────── test("thinking_effort: high → PATCH user_last_used_model_config with extended", async () => {