From 7bd70cfd94afbd129434da55d27b5d584dd54e18 Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Wed, 26 Aug 2026 12:56:11 +0900 Subject: [PATCH] fix(registry): retain verified CommandCode image capabilities Share one modality table across OAuth and API-key presets so catalog drift cannot silently disable image input. Keep routes that drop images absent and cover full upstream IDs plus preset parity. --- src/providers/registry.ts | 32 +++++++++++++++++++++------- tests/command-code-provider.test.ts | 33 +++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/providers/registry.ts b/src/providers/registry.ts index 5ca6c036857..913ca82af1e 100644 --- a/src/providers/registry.ts +++ b/src/providers/registry.ts @@ -495,6 +495,28 @@ const DEEPSEEK_THINKING_MODELS = ["deepseek-v4-pro", "deepseek-v4-flash"]; * at which point this id retires the same way deepseek-chat/reasoner did. */ const DEEPSEEK_VISION_PREVIEW_MODEL = "deepseek-v4-flash-vision-exp"; +/** + * CommandCode routes verified to accept image input end-to-end (#2406). + * + * Verified-negative and therefore deliberately ABSENT: deepseek/deepseek-v4-flash, + * deepseek/deepseek-v4-pro, zai-org/GLM-5.2, zai-org/GLM-5.3, xai/grok-4.6. Those + * routes accept the request and drop the image, which is worse than declining it — the + * model answers about an image it never saw. Do not add an id here on family resemblance; + * capability intersection trusts this map. + */ +const COMMAND_CODE_IMAGE_MODELS = [ + "stealth/ox-alpha", + "openai/ox-alpha", + `deepseek/${DEEPSEEK_VISION_PREVIEW_MODEL}`, + "gpt-5.6-luna", + "gpt-5.6-sol", + "MiniMaxAI/MiniMax-M3", + "moonshotai/Kimi-K3", + "meta/muse-spark-1.2", + "meta/muse-spark-1.2-contributor", +] as const; +const COMMAND_CODE_MODEL_INPUT_MODALITIES: Record = + Object.fromEntries(COMMAND_CODE_IMAGE_MODELS.map(id => [id, ["text", "image"]])); const OPENCODE_FREE_DEEPSEEK_MODELS = ["deepseek-v4-flash-free"]; /* * OpenCode Zen's free slug for the OpenRouter stealth model "Ox Alpha" @@ -1181,10 +1203,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [ "stealth/ox-alpha": OX_ALPHA_CONTEXT_WINDOW, [`deepseek/${DEEPSEEK_VISION_PREVIEW_MODEL}`]: 1_048_576, }, - modelInputModalities: { - "stealth/ox-alpha": ["text", "image"], - [`deepseek/${DEEPSEEK_VISION_PREVIEW_MODEL}`]: ["text", "image"], - }, + modelInputModalities: COMMAND_CODE_MODEL_INPUT_MODALITIES, defaultMaxOutputTokens: 64_000, // The proprietary generate wire has no verified per-request serialization flag. parallelToolCalls: false, @@ -1922,10 +1941,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [ "stealth/ox-alpha": OX_ALPHA_CONTEXT_WINDOW, [`deepseek/${DEEPSEEK_VISION_PREVIEW_MODEL}`]: 1_048_576, }, - modelInputModalities: { - "stealth/ox-alpha": ["text", "image"], - [`deepseek/${DEEPSEEK_VISION_PREVIEW_MODEL}`]: ["text", "image"], - }, + modelInputModalities: COMMAND_CODE_MODEL_INPUT_MODALITIES, modelDiscovery: { path: "models", maxResponseBytes: 256 * 1024, diff --git a/tests/command-code-provider.test.ts b/tests/command-code-provider.test.ts index 3453fc6d77c..b342b56da53 100644 --- a/tests/command-code-provider.test.ts +++ b/tests/command-code-provider.test.ts @@ -83,6 +83,39 @@ describe("Command Code provider", () => { }); }); + test("OAuth and API-key presets share only verified image capabilities", () => { + const oauth = PROVIDER_REGISTRY.find(row => row.id === "command-code"); + const apiKey = PROVIDER_REGISTRY.find(row => row.id === "commandcode"); + const verifiedImageModels = [ + "stealth/ox-alpha", + "openai/ox-alpha", + "deepseek/deepseek-v4-flash-vision-exp", + "gpt-5.6-luna", + "gpt-5.6-sol", + "MiniMaxAI/MiniMax-M3", + "moonshotai/Kimi-K3", + "meta/muse-spark-1.2", + "meta/muse-spark-1.2-contributor", + ]; + const verifiedTextOnlyModels = [ + "deepseek/deepseek-v4-flash", + "deepseek/deepseek-v4-pro", + "zai-org/GLM-5.2", + "zai-org/GLM-5.3", + "xai/grok-4.6", + ]; + + expect(apiKey?.modelInputModalities).toEqual(oauth?.modelInputModalities); + for (const preset of [oauth, apiKey]) { + for (const id of verifiedImageModels) { + expect(preset?.modelInputModalities?.[id]).toEqual(["text", "image"]); + } + for (const id of verifiedTextOnlyModels) { + expect(preset?.modelInputModalities?.[id]).toBeUndefined(); + } + } + }); + test("validates callback shape and state without exposing the key", () => { const secret = "super-secret-callback-key"; const parsedCallback = parseCommandCodeCallback({ apiKey: secret, state: "state", userId: "u", userName: "name", keyName: "cli" }, "state");