fix(opencode): qwen3.x max/plus models lack vision support (#2822) (#2836)

Integrated into release/v3.8.6.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-05-29 01:00:24 -03:00
committed by GitHub
parent 283c05d3d0
commit fee1c17d51
3 changed files with 122 additions and 5 deletions

View File

@@ -14,6 +14,7 @@
### 🔧 Bug Fixes
- **fix(opencode-go,opencode-zen):** mark qwen3.7-max / 3.6-plus / 3.5-plus as supportsVision:false to stop forwarding image blocks to vision-incapable upstream models ([#2822])
- **nous-research:** append /chat/completions to provider baseUrl so DefaultExecutor's default URL builder hits the correct endpoint instead of returning 404 ([#2826])
- **fix(quota):** honor explicit per-connection `quotaPreflightEnabled: false` even when the provider has global window defaults — adds early-return guard before the AND-of-negations gate in auth.ts ([#2831])
- **api:** include noAuth providers (opencode, etc.) in `/v1/models` active aliases so their models surface without a DB connection row (#2798)

View File

@@ -1296,9 +1296,12 @@ export const REGISTRY: Record<string, RegistryEntry> = {
// ("Model qwen3.x-* is not supported for format oa-compat") — same
// upstream behavior already declared for opencode-zen. Route them
// through /messages with the Claude translator.
{ id: "qwen3.7-max", name: "Qwen3.7 Max", targetFormat: "claude" },
{ id: "qwen3.6-plus", name: "Qwen3.6 Plus", targetFormat: "claude" },
{ id: "qwen3.5-plus", name: "Qwen3.5 Plus", targetFormat: "claude" },
// Issue #2822: These models are text-only — mark supportsVision: false
// so combo routing skips them when the request contains image blocks,
// preventing image content from reaching a vision-incapable upstream.
{ id: "qwen3.7-max", name: "Qwen3.7 Max", targetFormat: "claude", supportsVision: false },
{ id: "qwen3.6-plus", name: "Qwen3.6 Plus", targetFormat: "claude", supportsVision: false },
{ id: "qwen3.5-plus", name: "Qwen3.5 Plus", targetFormat: "claude", supportsVision: false },
{ id: "hy3-preview", name: "Hunyuan3 Preview" },
{ id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", supportsReasoning: true },
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", supportsReasoning: true },
@@ -1374,8 +1377,10 @@ export const REGISTRY: Record<string, RegistryEntry> = {
// Issue #2292: Qwen models return Claude-format SSE bodies even
// when hitting /chat/completions. targetFormat: "claude" routes
// through /messages and the Claude translator.
{ id: "qwen3.5-plus", name: "Qwen3.5 Plus", targetFormat: "claude" },
{ id: "qwen3.6-plus", name: "Qwen3.6 Plus", targetFormat: "claude" },
// Issue #2822: These models are text-only — supportsVision: false
// ensures combo routing skips them on image-bearing requests.
{ id: "qwen3.5-plus", name: "Qwen3.5 Plus", targetFormat: "claude", supportsVision: false },
{ id: "qwen3.6-plus", name: "Qwen3.6 Plus", targetFormat: "claude", supportsVision: false },
// ── Free Tier ──────────────────────────────────────────────
{ id: "deepseek-v4-flash-free", name: "DeepSeek V4 Flash Free", supportsReasoning: true },

View File

@@ -0,0 +1,111 @@
/**
* Issue #2822 — qwen3.7-max (opencode-go) retorna 500 em inputs com imagem.
*
* qwen3.7-max, qwen3.6-plus e qwen3.5-plus nos providers opencode-go e
* opencode-zen não possuíam supportsVision: false. Isso fazia com que
* blocos de imagem chegassem ao upstream (que não suporta visão),
* gerando 500s que esgotavam todo o orçamento de retentativas.
*
* Este teste garante que todos os modelos afetados tenham
* supportsVision !== true (i.e. false ou não definido como true).
*/
import test from "node:test";
import assert from "node:assert/strict";
const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts");
type ModelEntry = {
id: string;
supportsVision?: boolean;
[key: string]: unknown;
};
type ProviderEntry = {
models?: ModelEntry[];
[key: string]: unknown;
};
function getModel(providerId: string, modelId: string): ModelEntry | undefined {
const provider = (REGISTRY as Record<string, ProviderEntry>)[providerId];
if (!provider) return undefined;
return provider.models?.find((m) => m.id === modelId);
}
// ── opencode-go ─────────────────────────────────────────────────────────────
test("#2822 opencode-go/qwen3.7-max deve ter supportsVision !== true", () => {
const model = getModel("opencode-go", "qwen3.7-max");
assert.ok(model, "qwen3.7-max deve estar registrado em opencode-go");
assert.notEqual(
model.supportsVision,
true,
"opencode-go/qwen3.7-max não suporta visão — supportsVision deve ser false ou ausente"
);
assert.strictEqual(
model.supportsVision,
false,
"opencode-go/qwen3.7-max deve ter supportsVision: false explícito para bloquear seleção em combo com imagens"
);
});
test("#2822 opencode-go/qwen3.6-plus deve ter supportsVision !== true", () => {
const model = getModel("opencode-go", "qwen3.6-plus");
assert.ok(model, "qwen3.6-plus deve estar registrado em opencode-go");
assert.notEqual(
model.supportsVision,
true,
"opencode-go/qwen3.6-plus não suporta visão — supportsVision deve ser false ou ausente"
);
assert.strictEqual(
model.supportsVision,
false,
"opencode-go/qwen3.6-plus deve ter supportsVision: false explícito para bloquear seleção em combo com imagens"
);
});
test("#2822 opencode-go/qwen3.5-plus deve ter supportsVision !== true", () => {
const model = getModel("opencode-go", "qwen3.5-plus");
assert.ok(model, "qwen3.5-plus deve estar registrado em opencode-go");
assert.notEqual(
model.supportsVision,
true,
"opencode-go/qwen3.5-plus não suporta visão — supportsVision deve ser false ou ausente"
);
assert.strictEqual(
model.supportsVision,
false,
"opencode-go/qwen3.5-plus deve ter supportsVision: false explícito para bloquear seleção em combo com imagens"
);
});
// ── opencode-zen ─────────────────────────────────────────────────────────────
test("#2822 opencode-zen/qwen3.5-plus deve ter supportsVision !== true", () => {
const model = getModel("opencode-zen", "qwen3.5-plus");
assert.ok(model, "qwen3.5-plus deve estar registrado em opencode-zen");
assert.notEqual(
model.supportsVision,
true,
"opencode-zen/qwen3.5-plus não suporta visão — supportsVision deve ser false ou ausente"
);
assert.strictEqual(
model.supportsVision,
false,
"opencode-zen/qwen3.5-plus deve ter supportsVision: false explícito para bloquear seleção em combo com imagens"
);
});
test("#2822 opencode-zen/qwen3.6-plus deve ter supportsVision !== true", () => {
const model = getModel("opencode-zen", "qwen3.6-plus");
assert.ok(model, "qwen3.6-plus deve estar registrado em opencode-zen");
assert.notEqual(
model.supportsVision,
true,
"opencode-zen/qwen3.6-plus não suporta visão — supportsVision deve ser false ou ausente"
);
assert.strictEqual(
model.supportsVision,
false,
"opencode-zen/qwen3.6-plus deve ter supportsVision: false explícito para bloquear seleção em combo com imagens"
);
});