diff --git a/packages/ai/src/providers/vision-guard.ts b/packages/ai/src/providers/vision-guard.ts index 72981343c..5a85baf27 100644 --- a/packages/ai/src/providers/vision-guard.ts +++ b/packages/ai/src/providers/vision-guard.ts @@ -55,10 +55,12 @@ export function isDashscopeCompatibleModeTextOnlyQwen(model: Model<"openai-compl if (!isQwenModelId(model.id)) return false; const id = model.id.toLowerCase(); if (/\bqwen(?:[\d.]+)?-coder\b/.test(id)) return true; - const maxMatch = id.match(/\bqwen(\d+(?:\.\d+)?)?-max\b/); + const maxMatch = id.match(/\bqwen(?:(\d+)(?:\.(\d+))?)?-max\b/); if (!maxMatch) return false; - // Bare `qwen-max` (undefined version) is the text-only 2.5-era flagship; - // treat missing/pre-3.8 versions as text-only, 3.8+ as multimodal. - const version = maxMatch[1] ? Number.parseFloat(maxMatch[1]) : 0; - return version < 3.8; + // Bare `qwen-max` (no version) is the text-only 2.5-era flagship. Compare + // major/minor component-wise, not as a decimal float, so `qwen3.10-max` + // sorts after `qwen3.8-max`: text-only through 3.7, multimodal from 3.8 on. + const major = maxMatch[1] ? Number.parseInt(maxMatch[1], 10) : 0; + const minor = maxMatch[2] ? Number.parseInt(maxMatch[2], 10) : 0; + return major < 3 || (major === 3 && minor < 8); } diff --git a/packages/ai/test/openai-completions-tool-result-images.test.ts b/packages/ai/test/openai-completions-tool-result-images.test.ts index 87f459095..1219f8e30 100644 --- a/packages/ai/test/openai-completions-tool-result-images.test.ts +++ b/packages/ai/test/openai-completions-tool-result-images.test.ts @@ -394,9 +394,11 @@ describe("openai-completions convertMessages", () => { it("preserves image_url for DashScope compatible-mode multimodal Qwen models", () => { // Counter-cases for the issue #1859 guard: DashScope also exposes // genuinely multimodal Qwen ids without `vl` in the name (`qwen3.7-plus`), - // and Qwen-Max is multimodal from `qwen3.8-max` onward (issue #8305), so - // the text-only override must be limited to known text-only families. - for (const id of ["qwen3.7-plus", "qwen-vl-max", "qwen3.8-max", "qwen3.8-max-preview"]) { + // and Qwen-Max is multimodal from `qwen3.8-max` onward (issue #8305) — + // including `qwen3.10-max`, which a decimal-float compare would wrongly + // sort below 3.8 — so the text-only override must stay limited to the + // known text-only families. + for (const id of ["qwen3.7-plus", "qwen-vl-max", "qwen3.8-max", "qwen3.8-max-preview", "qwen3.10-max"]) { const baseModel = getBundledModel("openai", "gpt-4o-mini") as Model<"openai-completions">; const model: Model<"openai-completions"> = { ...baseModel,