fix(ai): compare qwen -max version components, not decimal floats

Number.parseFloat("3.10") is 3.1, so a future qwen3.10-max flagship would
sort below 3.8 and get its images stripped. Parse major/minor separately and
compare component-wise so 3.10 > 3.8. Locks the contract with a qwen3.10-max
counter-case.

Fixes #8305
This commit is contained in:
roboomp
2026-08-12 03:27:46 +00:00
parent a556ad61f8
commit 6ad4b02a8e
2 changed files with 12 additions and 8 deletions
+7 -5
View File
@@ -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);
}
@@ -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,