From d1dab731afc1c15d5a995e2ab667a144a00a597b Mon Sep 17 00:00:00 2001 From: eibednejo <19763797+eibednejo@users.noreply.github.com> Date: Thu, 10 Sep 2026 12:06:22 +0000 Subject: [PATCH] feat(models): add input-modality overrides for catalog-gap vision models MODEL_INPUT_MODALITIES comes straight from the generated catalog, so a model published after the pinned CLI release falls back to ["text"] and image input is withheld on both transports: index.ts publishes input: ["text"] to the host and the generate transport rejects image blocks with "does not support image content". The override file only exposed an efforts hook, so there was no supported way to re-enable vision for such a model. Add MODEL_INPUT_MODALITIES_OVERRIDES next to the existing effort overrides and merge it the same way, with entries for deepseek/deepseek-v4.1-flash and xai/grok-4.6. Both are served by the Provider API and both are declared inputModalities:["text","image"] by command-code@1.53.0. Verified live on a Go subscription with the generated catalog untouched: deepseek/deepseek-v4.1-flash described a test image correctly (red circle, blue square, green triangle, text ZEBRA-42). xai/grok-4.6 requires GOAT or above (MODEL_NOT_IN_PLAN on Go), so its entry rests on the CLI registry declaration. --- CHANGELOG.md | 6 ++++++ src/commandcode-catalog-overrides.ts | 25 ++++++++++++++++++++++- src/models.ts | 15 +++++++++++--- tests/test-models.ts | 30 +++++++++++++++++++++++++++- 4 files changed, 71 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 879148f..5804ff3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +- Enable image input for models the pinned catalog predates. `src/commandcode-catalog.ts` is generated from a Command Code CLI release, so a model published afterwards is absent from `MODEL_INPUT_MODALITIES` and stays text-only until the next catalog sync: the provider transport publishes `input: ["text"]` to the host and the generate transport refuses image blocks. A new `MODEL_INPUT_MODALITIES_OVERRIDES` hook next to the effort overrides carries the modalities. It re-enables vision for `deepseek/deepseek-v4.1-flash` and `xai/grok-4.6`, both served by the Provider API and both declared `inputModalities:["text","image"]` by `command-code@1.53.0`. + +### Contributors + +- @eibednejo — added the input-modality override hook and the vision entries for the affected models. + ## 0.6.4 - 2026-09-03 - Refresh the generated Command Code capability catalog from `command-code@1.40.1` to `command-code@1.44.0`, adding current image-input, reasoning, effort, and output-limit metadata for newly published models. diff --git a/src/commandcode-catalog-overrides.ts b/src/commandcode-catalog-overrides.ts index cb4accd..1d244b3 100644 --- a/src/commandcode-catalog-overrides.ts +++ b/src/commandcode-catalog-overrides.ts @@ -1,4 +1,4 @@ -import type { CommandCodeReasoningEffort } from "./commandcode-catalog.ts" +import type { CommandCodeInputType, CommandCodeReasoningEffort } from "./commandcode-catalog.ts" /** * Manual reasoning-effort policy for models the official CLI marks as @@ -23,3 +23,26 @@ export const MODEL_EFFORT_OVERRIDES: Readonly< "meta/muse-spark-1.3": ["minimal", "low", "medium", "high", "xhigh"], "meta/muse-spark-1.3-contributor": ["minimal", "low", "medium", "high", "xhigh"], } + +/** + * Manual input-modality overrides for models the generated catalog predates. + * + * Same rationale as the effort overrides above: the generated catalog is pinned + * to one CLI release, so a model published afterwards is absent from + * `MODEL_INPUT_MODALITIES` and falls back to `["text"]`. Both transports honor + * the absence: the provider transport publishes `input: ["text"]` to the host + * and the generate transport refuses image blocks outright. + * + * The Provider API model list carries no modality metadata, so the CLI registry + * is the only source available for this. Add a model only when the CLI registry + * declares `image` for it; remove it once the generated catalog ships the same + * modalities. + */ +export const MODEL_INPUT_MODALITIES_OVERRIDES: Readonly< + Record +> = { + // Served by the Provider API and declared with + // `inputModalities:["text","image"]` by command-code@1.53.0. + "deepseek/deepseek-v4.1-flash": ["text", "image"], + "xai/grok-4.6": ["text", "image"], +} diff --git a/src/models.ts b/src/models.ts index 0d8ffe9..f096105 100644 --- a/src/models.ts +++ b/src/models.ts @@ -1,10 +1,13 @@ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises" import { dirname } from "node:path" -import { MODEL_EFFORT_OVERRIDES } from "./commandcode-catalog-overrides.ts" +import { + MODEL_EFFORT_OVERRIDES, + MODEL_INPUT_MODALITIES_OVERRIDES, +} from "./commandcode-catalog-overrides.ts" import { MODEL_EFFORTS as CATALOG_MODEL_EFFORTS, - MODEL_INPUT_MODALITIES, + MODEL_INPUT_MODALITIES as CATALOG_MODEL_INPUT_MODALITIES, MODEL_MAX_OUTPUT_TOKENS, MODEL_REASONING, type CommandCodeInputType, @@ -17,7 +20,13 @@ export const MODEL_EFFORTS: Readonly> = { + ...CATALOG_MODEL_INPUT_MODALITIES, + ...MODEL_INPUT_MODALITIES_OVERRIDES, +} + +export { MODEL_MAX_OUTPUT_TOKENS, MODEL_REASONING } export type { CommandCodeInputType } export const DEFAULT_PROVIDER_API_BASE = "https://api.commandcode.ai/provider/v1" diff --git a/tests/test-models.ts b/tests/test-models.ts index 821a2e6..545add4 100644 --- a/tests/test-models.ts +++ b/tests/test-models.ts @@ -4,10 +4,14 @@ import { tmpdir } from "node:os" import { join } from "node:path" import { describe, it } from "node:test" -import { MODEL_EFFORT_OVERRIDES } from "../src/commandcode-catalog-overrides.ts" +import { + MODEL_EFFORT_OVERRIDES, + MODEL_INPUT_MODALITIES_OVERRIDES, +} from "../src/commandcode-catalog-overrides.ts" import { COMMAND_CODE_CLI_VERSION, MODEL_EFFORTS as CATALOG_MODEL_EFFORTS, + MODEL_INPUT_MODALITIES as CATALOG_MODEL_INPUT_MODALITIES, } from "../src/commandcode-catalog.ts" import { apiForModelId, @@ -216,6 +220,30 @@ describe("commandCodeModelsFromApiResponse()", () => { } }) + it("merges manual input-modality overrides over the generated catalog", () => { + assert.ok(Object.keys(MODEL_INPUT_MODALITIES_OVERRIDES).length > 0) + for (const [modelId, modalities] of Object.entries(MODEL_INPUT_MODALITIES_OVERRIDES)) { + assert.equal( + CATALOG_MODEL_INPUT_MODALITIES[modelId], + undefined, + `${modelId} now has upstream modalities; drop the manual override`, + ) + assert.ok( + modalities.includes("image"), + `${modelId} override is pointless without image input`, + ) + assert.deepEqual(inputModalitiesForModel(modelId), modalities) + assert.equal(modelSupportsImageInput(modelId), true) + } + for (const [modelId, modalities] of Object.entries(CATALOG_MODEL_INPUT_MODALITIES)) { + assert.deepEqual( + MODEL_INPUT_MODALITIES[modelId], + modalities, + `${modelId} upstream modalities changed`, + ) + } + }) + it("builds separate canonical pi and OMP metadata", () => { for (const [modelId, efforts] of Object.entries(MODEL_EFFORTS)) { const metadata = thinkingMetadataForModel(modelId)