fix(ci): clear remaining base-reds on release/v3.8.50 (refs #9985) (#10749)

* 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 <mail@hartmark.se>
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-08-19 12:09:43 -03:00
committed by GitHub
parent 65e1960029
commit da0088df99
9 changed files with 62 additions and 22 deletions

View File

@@ -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).

View File

@@ -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.

View File

@@ -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" },

View File

@@ -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,
});
}

View File

@@ -6350,7 +6350,7 @@
"autoFetchModels": "自動獲取上游模型",
"autoFetchModelsDisabled": "上游模型自動獲取已禁用",
"autoFetchModelsEnabled": "上游模型自動獲取已啟用",
"autoFetchModelsTooltip": "在需要時獲取並緩存上游模型",
"autoFetchModelsTooltip": "在需要時獲取並快取上游模型",
"autoFetchModelsToggleFailed": "無法切換上游模型自動獲取",
"autoFetchModelsPartialFailure": "某些連接已更新,但上游模型自動獲取並未在所有地方更改",
"overridesUpstreamModel": "覆蓋上游",

View File

@@ -126,5 +126,3 @@ export const zedHosted = {
},
}),
};
export default zedHosted;

View File

@@ -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)"],

View File

@@ -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;

View File

@@ -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) => ({