From c977f32dcb5bad2e343177d4650728061182bc55 Mon Sep 17 00:00:00 2001 From: xinzhihong-ship-it <254184831+xinzhihong-ship-it@users.noreply.github.com> Date: Fri, 18 Sep 2026 15:24:33 +0800 Subject: [PATCH] fix(registry): declare deepseek/deepseek-v4.1-flash natively image-capable on CommandCode Move the id from COMMAND_CODE_TEXT_ONLY_MODELS to COMMAND_CODE_IMAGE_MODELS. The gateway route was unverified when #4505 classified it text-only; the upstream probe that issue asked for is now run end to end: - A 3x3 random-color grid (180x180 PNG, six candidate colors) delivered as a user-message image and as a tool_result image both came back 9/9 correct (~1e-7 by guessing), with no vision-sidecar call in either request window. - Both requests: HTTP 200, adapter command-code, on an existing 2.58.0 install. Update the pinned regression tests for the new classification; opencode-go's route keeps its text-only declaration (no probe evidence) and the text-only mechanism stays in place for the next route that measures text-only. --- src/providers/registry/model-seeds.ts | 30 ++++++----- .../flash-route-image-modalities.test.ts | 50 ++++++++++--------- 2 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/providers/registry/model-seeds.ts b/src/providers/registry/model-seeds.ts index 9125c9738f9..62517025e53 100644 --- a/src/providers/registry/model-seeds.ts +++ b/src/providers/registry/model-seeds.ts @@ -315,6 +315,13 @@ export const DEEPSEEK_VISION_PREVIEW_MODEL = "deepseek-v4-flash-vision-exp"; */ export const COMMAND_CODE_IMAGE_MODELS = [ `deepseek/${DEEPSEEK_VISION_PREVIEW_MODEL}`, + // Probed 2026-09-18 through a running 2.58.0 proxy: a 3x3 random-color grid + // (180x180 PNG, six candidate colors) came back 9/9 correct both as a user + // message and as a tool_result, and the request logs show the route served + // the image natively — no vision-sidecar call in either window. #4505 asked + // for exactly this upstream probe before promoting the id. The sibling + // deepseek/deepseek-v4-flash route remains verified-negative above. + "deepseek/deepseek-v4.1-flash", "gpt-5.6-luna", "gpt-5.6-sol", "MiniMaxAI/MiniMax-M3", @@ -334,20 +341,19 @@ export const COMMAND_CODE_IMAGE_MODELS = [ /** * Native image stays sourced from COMMAND_CODE_IMAGE_MODELS. Text-only routes * sit beside that list so the catalog can still advertise sidecar coverage - * without claiming the gateway itself accepts a picture. - * - * The gateway-prefixed DeepSeek V4.1 Flash route has no verified native image - * support, so declaring it image-capable would hand it a picture it drops. A - * positive text-only declaration makes it a vision-sidecar consumer + * without claiming the gateway itself accepts a picture. A positive text-only + * declaration makes the route a vision-sidecar consumer * (src/vision/eligibility.ts), so the catalog advertises image input on its - * behalf and the four-target combo in #4505 intersects to ["text","image"] - * instead of ["text"] — without claiming native vision. modelInputModalities - * is per-key filled, so this reaches an existing install even when - * noVisionModels was persisted before the id joined that list. + * behalf — without claiming native vision — and modelInputModalities is + * per-key filled, so that reaches an existing install even when noVisionModels + * was persisted before the id joined a list. + * + * Empty as of 2026-09-18. Its only entry, deepseek/deepseek-v4.1-flash, moved + * to COMMAND_CODE_IMAGE_MODELS once the #4505-requested probe passed on both + * the user-message and tool-result paths (see the note at that entry). The + * mechanism stays for the next route that measures text-only. */ -export const COMMAND_CODE_TEXT_ONLY_MODELS = [ - "deepseek/deepseek-v4.1-flash", -] as const; +export const COMMAND_CODE_TEXT_ONLY_MODELS = [] as const; export const COMMAND_CODE_MODEL_INPUT_MODALITIES: Record = { ...Object.fromEntries(COMMAND_CODE_IMAGE_MODELS.map(id => [id, ["text", "image"] as ["text", "image"]])), ...Object.fromEntries(COMMAND_CODE_TEXT_ONLY_MODELS.map(id => [id, ["text"] as ["text"]])), diff --git a/tests/providers/flash-route-image-modalities.test.ts b/tests/providers/flash-route-image-modalities.test.ts index e115cd97240..a5f74edffe2 100644 --- a/tests/providers/flash-route-image-modalities.test.ts +++ b/tests/providers/flash-route-image-modalities.test.ts @@ -2,12 +2,15 @@ * Flash-route image modality declarations (#4505). * * opencode-go and command-code each serve a GLM-5.3-Flash route (native VLM) and a - * DeepSeek V4.1-Flash route (text-only upstream, covered by the vision sidecar). - * None of the four declared input modalities, so a failover combo over them - * intersected to ["text"] in deriveComboCatalogModel and the Codex app refused - * image attachments for the whole combo — combo image routing was silently - * disabled even though every member can accept an image (two natively, two - * through the sidecar). + * DeepSeek V4.1-Flash route. None of the four declared input modalities, so a + * failover combo over them intersected to ["text"] in deriveComboCatalogModel + * and the Codex app refused image attachments for the whole combo — combo image + * routing was silently disabled even though every member can accept an image. + * + * command-code's DeepSeek route was promoted from sidecar-covered to native + * image on 2026-09-18 after the upstream probe #4505 asked for passed on both + * the user-message and tool-result paths (see model-seeds.ts); opencode-go's + * route remains text-only and sidecar-covered. * * The fix is positive per-route modelInputModalities declarations, not a * noVisionModels union: a text-only declaration makes the route a sidecar @@ -34,7 +37,7 @@ import type { CatalogModel, OcxConfig, OcxProviderConfig } from "../../src/types const OPENCODE_GO_NATIVE = "glm-5.3-flash"; const OPENCODE_GO_SIDECAR = "deepseek-v4.1-flash"; const COMMAND_CODE_NATIVE = "z-ai/glm-5.3-flash"; -const COMMAND_CODE_SIDECAR = "deepseek/deepseek-v4.1-flash"; +const COMMAND_CODE_DEEPSEEK = "deepseek/deepseek-v4.1-flash"; /** Seeded provider config, shaped the way an install persists it. */ function seeded(provider: string): OcxProviderConfig { @@ -53,38 +56,37 @@ describe("flash-route registry modality declarations (#4505)", () => { expect(entry?.modelInputModalities?.[OPENCODE_GO_SIDECAR]).toEqual(["text"]); }); - test("command-code declares z-ai/glm-5.3-flash in the image set and deepseek/deepseek-v4.1-flash text-only", () => { + test("command-code declares both flash routes image-capable (deepseek probed 2026-09-18)", () => { const entry = PROVIDER_REGISTRY.find(e => e.id === "command-code"); expect(entry?.modelInputModalities?.[COMMAND_CODE_NATIVE]).toEqual(["text", "image"]); - // The text-only declaration must NOT come from the image allowlist: putting - // a DeepSeek route in COMMAND_CODE_IMAGE_MODELS would advertise native - // vision for a model that drops the image upstream. - expect(entry?.modelInputModalities?.[COMMAND_CODE_SIDECAR]).toEqual(["text"]); + // Promoted from COMMAND_CODE_TEXT_ONLY_MODELS: the upstream probe #4505 + // asked for passed on both the user-message and tool-result paths. + expect(entry?.modelInputModalities?.[COMMAND_CODE_DEEPSEEK]).toEqual(["text", "image"]); }); }); describe("flash-route native vs sidecar distinction (#4505)", () => { // WHY: the issue requires routes needing a sidecar to stay distinguishable - // from native vision. A wrong fix that declares DeepSeek ["text","image"] - // passes an "everything says image" test while the upstream model silently - // drops the picture — the sidecar distinction is what keeps the image on a - // path that actually reads it. + // from native vision. The distinction must follow measured upstream behavior: + // command-code's route now carries probe evidence (user message + tool + // result) for native reading, while opencode-go's route has none and stays on + // the sidecar path. test("the two glm-5.3-flash routes are NOT sidecar consumers (native VLM)", () => { expect(isModelVisionSidecarConsumer(seeded("opencode-go"), OPENCODE_GO_NATIVE)).toBe(false); expect(isModelVisionSidecarConsumer(seeded("command-code"), COMMAND_CODE_NATIVE)).toBe(false); }); - test("the two deepseek-v4.1-flash routes ARE sidecar consumers (text-only upstream)", () => { + test("opencode-go's deepseek route is a sidecar consumer; command-code's is native", () => { expect(isModelVisionSidecarConsumer(seeded("opencode-go"), OPENCODE_GO_SIDECAR)).toBe(true); - expect(isModelVisionSidecarConsumer(seeded("command-code"), COMMAND_CODE_SIDECAR)).toBe(true); + expect(isModelVisionSidecarConsumer(seeded("command-code"), COMMAND_CODE_DEEPSEEK)).toBe(false); }); }); describe("flash-route catalog advertisement (#4505)", () => { // WHY: the Codex app gates attachments client-side on input_modalities, so the // catalog row is where the combo's image capability is actually won or lost. - // The DeepSeek rows must pick up "image" from the sidecar hint, not from a - // native claim. + // The opencode-go DeepSeek row picks up "image" from the sidecar hint; the + // command-code row now carries a native declaration. test("applyProviderConfigHints advertises image for sidecar-covered deepseek-v4.1-flash on opencode-go", () => { const hinted = applyProviderConfigHints("opencode-go", seeded("opencode-go"), { id: OPENCODE_GO_SIDECAR, @@ -98,7 +100,7 @@ describe("flash-route catalog advertisement (#4505)", () => { ["opencode-go", OPENCODE_GO_NATIVE], ["opencode-go", OPENCODE_GO_SIDECAR], ["command-code", COMMAND_CODE_NATIVE], - ["command-code", COMMAND_CODE_SIDECAR], + ["command-code", COMMAND_CODE_DEEPSEEK], ]; for (const [provider, id] of cases) { const hinted = applyProviderConfigHints(provider, seeded(provider), { id, provider }); @@ -118,7 +120,7 @@ describe("flash-route combo intersection (#4505)", () => { { provider: "opencode-go", model: OPENCODE_GO_NATIVE }, { provider: "opencode-go", model: OPENCODE_GO_SIDECAR }, { provider: "command-code", model: COMMAND_CODE_NATIVE }, - { provider: "command-code", model: COMMAND_CODE_SIDECAR }, + { provider: "command-code", model: COMMAND_CODE_DEEPSEEK }, ], defaultEffort: "high", } as never; @@ -135,7 +137,7 @@ describe("flash-route combo intersection (#4505)", () => { hintedMember("opencode-go", OPENCODE_GO_NATIVE), hintedMember("opencode-go", OPENCODE_GO_SIDECAR), hintedMember("command-code", COMMAND_CODE_NATIVE), - hintedMember("command-code", COMMAND_CODE_SIDECAR), + hintedMember("command-code", COMMAND_CODE_DEEPSEEK), ]; const derived = deriveComboCatalogModel("flash_failover", combo, members); expect(derived?.inputModalities).toEqual(["text", "image"]); @@ -149,7 +151,7 @@ describe("flash-route combo intersection (#4505)", () => { hintedMember("opencode-go", OPENCODE_GO_NATIVE), hintedMember("opencode-go", OPENCODE_GO_SIDECAR), hintedMember("command-code", COMMAND_CODE_NATIVE), - { ...hintedMember("command-code", COMMAND_CODE_SIDECAR), inputModalities: ["text"] }, + { ...hintedMember("command-code", COMMAND_CODE_DEEPSEEK), inputModalities: ["text"] }, ]; const derived = deriveComboCatalogModel("flash_failover", combo, members); expect(derived?.inputModalities).toEqual(["text"]);