From da0088df99ea442b9ee80a7e4348fc4927eb0954 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Wed, 19 Aug 2026 12:09:43 -0300 Subject: [PATCH] fix(ci): clear remaining base-reds on release/v3.8.50 (refs #9985) (#10749) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(ci): route the Gemini Web b64_json download error through sanitizeErrorMessage() open-sse/handlers/imageGeneration/providers/geminiWeb.ts embedded a raw err.message in the b64_json download-failure response, tripping check:error-helper (Hard Rule #12) on release/v3.8.50. Refs #9985. * fix(tests): drain test-drift base-reds left by #10603/#10537 and a stale qwen-web catalog id Several base-reds on release/v3.8.50 (#9985) share one root cause: a legitimate product change landed without updating the test asserting the old behavior. - tests/unit/glm-provider-model-import-route.test.ts (12 tests) and tests/unit/model-sync-route.test.ts (2 tests) predate #10603, which made upstream model sync opt-in (isAutoFetchModelsEnabled() now requires providerSpecificData.autoFetchModels === true) and made manual custom-model overrides survive a sync instead of being demoted. Updated both files to opt in / assert the new preserve-manual-overrides behavior, with a comment citing #10603. - tests/unit/antigravity-model-aliases.test.ts predates #10537, which retired the collapsed 'gemini-3.7-flash' alias (upstream 'gemini-3.7-flash-tiered') in favor of the three directly-callable tiered ids. Dropped the retired id from EXPECTED_FLASH_TIERS. - open-sse/config/freeModelCatalog.data.ts: the qwen-web free-catalog entry still listed the retired 'qwen3.8-max-preview' id instead of the current 'qwen3.8-max' (open-sse/config/providers/registry/qwen/web/index.ts and the executor's compat alias both confirm 'qwen3.8-max' is canonical). Real data drift, not test drift. - src/i18n/messages/zh-TW.json: providers.autoFetchModelsTooltip (added by #10603) used the mainland term 緩存 instead of the zh-TW glossary-canonical 快取, tripping the i18n-glossary-consistency-check base-red. - src/lib/oauth/providers/zed-hosted.ts: removed an unused default export (the named export already covers every consumer) — shaves one symbol off the check:dead-code ratchet (419 -> 418; baseline 415, 3 still outstanding). Refs #9985. --------- Co-authored-by: Markus Hartung --- ...se-v3850-basereds-error-helper-20260819.md | 3 ++ ...lease-v3850-basereds-testdrift-20260819.md | 12 ++++++++ open-sse/config/freeModelCatalog.data.ts | 2 +- .../imageGeneration/providers/geminiWeb.ts | 3 +- src/i18n/messages/zh-TW.json | 2 +- src/lib/oauth/providers/zed-hosted.ts | 2 -- tests/unit/antigravity-model-aliases.test.ts | 5 +++- .../glm-provider-model-import-route.test.ts | 30 +++++++++++-------- tests/unit/model-sync-route.test.ts | 25 +++++++++++++--- 9 files changed, 62 insertions(+), 22 deletions(-) create mode 100644 changelog.d/maintenance/release-v3850-basereds-error-helper-20260819.md create mode 100644 changelog.d/maintenance/release-v3850-basereds-testdrift-20260819.md diff --git a/changelog.d/maintenance/release-v3850-basereds-error-helper-20260819.md b/changelog.d/maintenance/release-v3850-basereds-error-helper-20260819.md new file mode 100644 index 0000000000..e89d53ab49 --- /dev/null +++ b/changelog.d/maintenance/release-v3850-basereds-error-helper-20260819.md @@ -0,0 +1,3 @@ +- **fix(ci):** route `open-sse/handlers/imageGeneration/providers/geminiWeb.ts`'s b64_json + download-failure message through `sanitizeErrorMessage()` instead of embedding a raw + `err.message`, clearing the `check:error-helper` base-red on `release/v3.8.50` (#9985). diff --git a/changelog.d/maintenance/release-v3850-basereds-testdrift-20260819.md b/changelog.d/maintenance/release-v3850-basereds-testdrift-20260819.md new file mode 100644 index 0000000000..f4deae6aae --- /dev/null +++ b/changelog.d/maintenance/release-v3850-basereds-testdrift-20260819.md @@ -0,0 +1,12 @@ +- **fix(tests):** drain several base-reds on `release/v3.8.50` (#9985) that were all instances + of the same pattern — a legitimate product change landed without updating the test that + asserted the old behavior: `tests/unit/glm-provider-model-import-route.test.ts` (12 tests) + and `tests/unit/model-sync-route.test.ts` (2 tests) predate #10603's "upstream model sync is + opt-in and manual overrides are preserved" change; `tests/unit/antigravity-model-aliases.test.ts` + predated #10537 retiring the collapsed `gemini-3.7-flash` alias in favor of its three tiered + ids. Also fixes a real data drift in `open-sse/config/freeModelCatalog.data.ts` (the `qwen-web` + free-catalog entry still pointed at the retired `qwen3.8-max-preview` id instead of the + current `qwen3.8-max`), corrects the zh-TW `providers.autoFetchModelsTooltip` string to the + glossary-canonical 快取 instead of 緩存, and removes an unused default export from + `src/lib/oauth/providers/zed-hosted.ts` (the named export already covers every consumer) to + shave one symbol off the `check:dead-code` ratchet regression. diff --git a/open-sse/config/freeModelCatalog.data.ts b/open-sse/config/freeModelCatalog.data.ts index decef67c50..3d1777ed65 100644 --- a/open-sse/config/freeModelCatalog.data.ts +++ b/open-sse/config/freeModelCatalog.data.ts @@ -361,7 +361,7 @@ export const FREE_MODEL_BUDGETS: FreeModelBudget[] = [ { provider: "qoder", modelId: "deepseek-v4-pro", displayName: "DeepSeek-V4-Pro", monthlyTokens: 0, creditTokens: 1000000, freeType: "one-time-initial", poolKey: "qoder", tos: "caution" }, { provider: "qoder", modelId: "deepseek-v4-flash", displayName: "DeepSeek-V4-Flash", monthlyTokens: 0, creditTokens: 1000000, freeType: "one-time-initial", poolKey: "qoder", tos: "caution" }, { provider: "qoder", modelId: "minimax-m3", displayName: "MiniMax-M3", monthlyTokens: 0, creditTokens: 1000000, freeType: "one-time-initial", poolKey: "qoder", tos: "caution" }, - { provider: "qwen-web", modelId: "qwen3.8-max-preview", displayName: "Qwen3.8 Max Preview", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "qwen-web", tos: "avoid" }, + { provider: "qwen-web", modelId: "qwen3.8-max", displayName: "Qwen3.8 Max", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "qwen-web", tos: "avoid" }, { provider: "qwen-web", modelId: "qwen3.7-max", displayName: "Qwen3.7 Max", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "qwen-web", tos: "avoid" }, { provider: "qwen-web", modelId: "qwen3.7-plus", displayName: "Qwen3.7 Plus", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "qwen-web", tos: "avoid" }, { provider: "qwen-web", modelId: "qwen3.6-plus", displayName: "Qwen3.6 Plus", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "qwen-web", tos: "avoid" }, diff --git a/open-sse/handlers/imageGeneration/providers/geminiWeb.ts b/open-sse/handlers/imageGeneration/providers/geminiWeb.ts index 43c91de8fc..8f131fe83d 100644 --- a/open-sse/handlers/imageGeneration/providers/geminiWeb.ts +++ b/open-sse/handlers/imageGeneration/providers/geminiWeb.ts @@ -20,6 +20,7 @@ import { GeminiWebExecutor } from "../../../executors/gemini-web.ts"; import { fetchRemoteImage } from "@/shared/network/remoteImageFetch"; import { saveImageErrorResult, saveImageSuccessResult } from "../../imageGeneration.ts"; +import { sanitizeErrorMessage } from "../../../utils/error.ts"; /** Each image is one gemini.google.com turn (~30-60s). Cap like chatgpt-web. */ const GEMINI_WEB_IMAGE_N_MAX = 4; @@ -210,7 +211,7 @@ export async function handleGeminiWebImageGeneration({ model, status: 502, startTime, - error: `Gemini Web generated an image but OmniRoute could not download it for b64_json conversion: ${err instanceof Error ? err.message : String(err)}`, + error: `Gemini Web generated an image but OmniRoute could not download it for b64_json conversion: ${sanitizeErrorMessage(err instanceof Error ? err.message : String(err))}`, requestBody, }); } diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 627b7b4383..a7ac344ae8 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -6350,7 +6350,7 @@ "autoFetchModels": "自動獲取上游模型", "autoFetchModelsDisabled": "上游模型自動獲取已禁用", "autoFetchModelsEnabled": "上游模型自動獲取已啟用", - "autoFetchModelsTooltip": "在需要時獲取並緩存上游模型", + "autoFetchModelsTooltip": "在需要時獲取並快取上游模型", "autoFetchModelsToggleFailed": "無法切換上游模型自動獲取", "autoFetchModelsPartialFailure": "某些連接已更新,但上游模型自動獲取並未在所有地方更改", "overridesUpstreamModel": "覆蓋上游", diff --git a/src/lib/oauth/providers/zed-hosted.ts b/src/lib/oauth/providers/zed-hosted.ts index 46a4c1b320..b37c157163 100644 --- a/src/lib/oauth/providers/zed-hosted.ts +++ b/src/lib/oauth/providers/zed-hosted.ts @@ -126,5 +126,3 @@ export const zedHosted = { }, }), }; - -export default zedHosted; diff --git a/tests/unit/antigravity-model-aliases.test.ts b/tests/unit/antigravity-model-aliases.test.ts index c821766058..542573b3a4 100644 --- a/tests/unit/antigravity-model-aliases.test.ts +++ b/tests/unit/antigravity-model-aliases.test.ts @@ -17,8 +17,11 @@ function getPublicModel(id: string) { return ANTIGRAVITY_PUBLIC_MODELS.find((model) => model.id === id) as any; } +// #10537 retired the single-alias `gemini-3.7-flash` (which mapped to the upstream +// `gemini-3.7-flash-tiered`) in favor of three directly-callable tiered public models — +// the suffixed ids now work upstream without the collapsing alias. Keep this list in sync +// with ANTIGRAVITY_PUBLIC_MODELS/ANTIGRAVITY_MODEL_ALIASES instead of the retired bare id. const EXPECTED_FLASH_TIERS = [ - ["gemini-3.7-flash", "Gemini 3.7 Flash"], ["gemini-3.7-flash-high", "Gemini 3.7 Flash (High)"], ["gemini-3.7-flash-medium", "Gemini 3.7 Flash (Medium)"], ["gemini-3.7-flash-low", "Gemini 3.7 Flash (Low)"], diff --git a/tests/unit/glm-provider-model-import-route.test.ts b/tests/unit/glm-provider-model-import-route.test.ts index dd174c93c6..b86c115e70 100644 --- a/tests/unit/glm-provider-model-import-route.test.ts +++ b/tests/unit/glm-provider-model-import-route.test.ts @@ -4,6 +4,12 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +// #10603 made upstream model sync opt-in (isAutoFetchModelsEnabled() now requires +// providerSpecificData.autoFetchModels === true) so remote discovery doesn't overwrite +// manual catalog overrides by default. Every connection below sets it explicitly so the +// mocked `fetch` in each test actually gets called — without it, the route short-circuits +// to the local/cached catalog before ever reaching the network call these tests assert on. + const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-glm-models-")); process.env.DATA_DIR = TEST_DATA_DIR; @@ -29,7 +35,7 @@ test("GLM import uses international coding endpoint when apiRegion is internatio authType: "apikey", name: "glm-intl", apiKey: "glm-key", - providerSpecificData: { apiRegion: "international" }, + providerSpecificData: { apiRegion: "international", autoFetchModels: true }, }); const originalFetch = globalThis.fetch; @@ -88,7 +94,7 @@ test("GLM import normalizes custom coding models URLs without duplicating endpoi authType: "apikey", name: `glm-custom-${index}`, apiKey: testCase.apiKey, - providerSpecificData: { baseUrl: testCase.baseUrl }, + providerSpecificData: { baseUrl: testCase.baseUrl, autoFetchModels: true }, }) ); } @@ -129,7 +135,7 @@ test("GLM import falls back to Anthropic model discovery when coding discovery f authType: "apikey", name: "glm-discovery-fallback", apiKey: "glm-key", - providerSpecificData: { apiRegion: "international" }, + providerSpecificData: { apiRegion: "international", autoFetchModels: true }, }); const originalFetch = globalThis.fetch; @@ -178,7 +184,7 @@ test("GLM import preserves auth failures instead of falling back across transpor authType: "apikey", name: "glm-auth-fail", apiKey: "bad-key", - providerSpecificData: { apiRegion: "international" }, + providerSpecificData: { apiRegion: "international", autoFetchModels: true }, }); const originalFetch = globalThis.fetch; @@ -207,7 +213,7 @@ test("GLMT import shares the GLM coding models endpoint and surfaces provider me authType: "apikey", name: "glmt-intl", apiKey: "glmt-key", - providerSpecificData: { apiRegion: "international" }, + providerSpecificData: { apiRegion: "international", autoFetchModels: true }, }); const originalFetch = globalThis.fetch; @@ -241,7 +247,7 @@ test("GLM import uses China coding endpoint when apiRegion is china", async () = authType: "apikey", name: "glm-cn", apiKey: "glm-cn-key", - providerSpecificData: { apiRegion: "china" }, + providerSpecificData: { apiRegion: "china", autoFetchModels: true }, }); const originalFetch = globalThis.fetch; @@ -272,7 +278,7 @@ test("GLM China provider import uses the specialized GLM discovery path", async authType: "apikey", name: "glm-cn-provider", apiKey: "glm-cn-key", - providerSpecificData: {}, + providerSpecificData: { autoFetchModels: true }, }); const originalFetch = globalThis.fetch; @@ -304,7 +310,7 @@ test("GLM import defaults to international endpoint when apiRegion is missing", authType: "apikey", name: "glm-default", apiKey: "glm-key", - providerSpecificData: {}, + providerSpecificData: { autoFetchModels: true }, }); const originalFetch = globalThis.fetch; @@ -331,7 +337,7 @@ test("GLM import defaults to international endpoint when apiRegion is invalid", authType: "apikey", name: "glm-bogus", apiKey: "glm-key", - providerSpecificData: { apiRegion: "bogus" }, + providerSpecificData: { apiRegion: "bogus", autoFetchModels: true }, }); const originalFetch = globalThis.fetch; @@ -359,7 +365,7 @@ test("GLM import prefers apiKey over accessToken and sends only Authorization Be name: "glm-both-tokens", apiKey: "glm-api-key", accessToken: "glm-access-token", - providerSpecificData: { apiRegion: "international" }, + providerSpecificData: { apiRegion: "international", autoFetchModels: true }, }); const originalFetch = globalThis.fetch; @@ -387,7 +393,7 @@ test("GLM import falls back to accessToken when apiKey is absent", async () => { authType: "apikey", name: "glm-access-only", accessToken: "glm-access-token", - providerSpecificData: { apiRegion: "international" }, + providerSpecificData: { apiRegion: "international", autoFetchModels: true }, }); const originalFetch = globalThis.fetch; @@ -414,7 +420,7 @@ test("GLM import falls back to the local catalog on upstream non-OK status codes authType: "apikey", name: "glm-error", apiKey: "glm-key", - providerSpecificData: { apiRegion: "international" }, + providerSpecificData: { apiRegion: "international", autoFetchModels: true }, }); const originalFetch = globalThis.fetch; diff --git a/tests/unit/model-sync-route.test.ts b/tests/unit/model-sync-route.test.ts index 0e71b96c17..ece60f730d 100644 --- a/tests/unit/model-sync-route.test.ts +++ b/tests/unit/model-sync-route.test.ts @@ -471,6 +471,10 @@ test("model sync route writes synced available models for non-Gemini providers t ]); }); +// #10603 ("make upstream model sync opt-in and preserve manual overrides") also applies to +// import/merge mode: a manual custom-model row sharing an id with a discovered model is kept +// as the user-owned overlay instead of being demoted/removed. See the comment above the +// "reports synced managed models separately from preserved manual models" test. test("model sync route import mode merges discovered models without deleting manual models", async () => { await resetStorage(); @@ -512,10 +516,13 @@ test("model sync route import mode merges discovered models without deleting man assert.equal(body.updatedCount, 0); assert.equal(body.syncedAliases, 1); assert.deepEqual(body.modelChanges, { added: 1, removed: 0, updated: 0, total: 1 }); - assert.deepEqual(body.customModelChanges, { added: 0, removed: 1, updated: 0, total: 1 }); + assert.deepEqual(body.customModelChanges, { added: 0, removed: 0, updated: 0, total: 0 }); assert.deepEqual( body.models.map((model) => ({ id: model.id, source: model.source })), - [{ id: "manual-only", source: "manual" }] + [ + { id: "manual-only", source: "manual" }, + { id: "router-v4", source: "manual" }, + ] ); assert.deepEqual( body.importedModels.map((model) => ({ id: model.id, source: model.source })), @@ -820,6 +827,13 @@ test("model sync route forwards cookies, filters built-ins, and syncs aliases fo assert.equal(logs[0].account, "External Sync"); }); +// #10603 ("make upstream model sync opt-in and preserve manual overrides") changed +// importManagedModels() so a manually configured custom-model row that shares an id with +// a synced upstream model is no longer demoted/removed — it stays as the user-owned +// metadata overlay and is merged over the synced base at read time (see the comment in +// src/lib/providerModels/managedModelImport.ts). Only rows already tagged as an +// imported/auto-sync source get pruned. This test predates that change; its expectations +// below reflect the current preserve-manual-overrides behavior. test("model sync route reports synced managed models separately from preserved manual models", async () => { await resetStorage(); @@ -858,10 +872,13 @@ test("model sync route reports synced managed models separately from preserved m assert.equal(body.availableModelsCount, 2); assert.equal(body.importedCount, 1); assert.equal(body.updatedCount, 0); - assert.deepEqual(body.customModelChanges, { added: 0, removed: 1, updated: 0, total: 1 }); + assert.deepEqual(body.customModelChanges, { added: 0, removed: 0, updated: 0, total: 0 }); assert.deepEqual( body.models.map((model) => ({ id: model.id, source: model.source })), - [{ id: "manual-only", source: "manual" }] + [ + { id: "manual-only", source: "manual" }, + { id: "router-v4", source: "manual" }, + ] ); assert.deepEqual( (await modelsDb.getSyncedAvailableModels("openrouter")).map((model) => ({