From 4711ed92aa5cd448e6c809bd90039483f531e346 Mon Sep 17 00:00:00 2001 From: Xmon Dai Date: Sat, 19 Sep 2026 23:20:27 +0800 Subject: [PATCH] fix(providers): stop rewriting GA qwen3.8-max to preview on opencode-go (#14181) OpenCode Go now serves a GA qwen3.8-max, but the built-in deprecation table (written when the model shipped only under the -preview id) rewrote it to qwen3.8-max-preview before dispatch, and the upstream rejects the preview id with a 401. The provider-aware exemption in resolveModelAlias never fired because the opencode-go static registry lacked the GA id. - declare qwen3.8-max in the opencode-go registry (qwen family there is text-only and routes through the Claude translator per #2292) - give the GA model its own MODEL_SPECS row instead of aliasing it to the preview spec The rewrite stays for preview-only providers (qoder, bailian-coding-plan, qwen-cloud-token-plan) and for callers with no provider in hand. --- .../providers/registry/opencode/go/index.ts | 12 ++++ open-sse/services/modelDeprecation.ts | 11 ++-- src/shared/constants/modelSpecs.ts | 12 +++- .../14181-opencode-go-qwen-ga-alias.test.ts | 58 +++++++++++++++++++ 4 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 tests/unit/14181-opencode-go-qwen-ga-alias.test.ts diff --git a/open-sse/config/providers/registry/opencode/go/index.ts b/open-sse/config/providers/registry/opencode/go/index.ts index 8bf7c65e998..7a2c2353ab6 100644 --- a/open-sse/config/providers/registry/opencode/go/index.ts +++ b/open-sse/config/providers/registry/opencode/go/index.ts @@ -121,6 +121,18 @@ export const opencode_goProvider: RegistryEntry = { supportsVision: false, supportsReasoning: true, }, + // #14181: OpenCode Go now serves a GA `qwen3.8-max` alongside the preview. + // Without this row the provider-aware exemption in resolveModelAlias could not + // see it, and the stale built-in rewrite to `qwen3.8-max-preview` (which the + // upstream rejects with a 401) fired before dispatch. Base id only — no + // effort-tier variants are advertised upstream yet. + { + id: "qwen3.8-max", + name: "Qwen3.8 Max", + targetFormat: "claude", + supportsVision: false, + supportsReasoning: true, + }, // qwen3.6-plus / qwen3.5-plus base ids declared identically on opencode-zen — see // OPENCODE_ZEN_GO_SHARED_MODELS. { diff --git a/open-sse/services/modelDeprecation.ts b/open-sse/services/modelDeprecation.ts index eefb2fce9cb..a800f4517e3 100644 --- a/open-sse/services/modelDeprecation.ts +++ b/open-sse/services/modelDeprecation.ts @@ -48,11 +48,12 @@ const BUILT_IN_ALIASES: Record = { "fireworks/accounts/fireworks/models/kimi-k2": "moonshotai/Kimi-K2", "kimi-k2": "moonshotai/Kimi-K2", - // Qwen — the model ships only under the `-preview` id (bailian-coding-plan, qoder, - // qwen-cloud-token-plan). Without this, the bare id missed MODEL_SPECS and - // the context preflight fell back to contextManager's `default: 128000`, rejecting - // prompts the model's real 1M window accepts. Drop this line if Alibaba ever ships a - // distinct GA `qwen3.8-max` — it would no longer be the same model. + // Qwen — preview-only providers (bailian-coding-plan, qoder, qwen-cloud-token-plan) + // still serve the model solely under the `-preview` id; without this rewrite the + // bare id missed MODEL_SPECS there and the context preflight fell back to + // contextManager's `default: 128000`. Providers that list the GA id as-is + // (alibaba, qwen-cloud, kilocode, clinepass, xkiro, opencode-go — #14181) are + // exempted by the provider-aware check in resolveModelAlias. "qwen3.8-max": "qwen3.8-max-preview", // Mistral short aliases diff --git a/src/shared/constants/modelSpecs.ts b/src/shared/constants/modelSpecs.ts index 0498d988423..3f84b77ef46 100644 --- a/src/shared/constants/modelSpecs.ts +++ b/src/shared/constants/modelSpecs.ts @@ -576,6 +576,17 @@ export const MODEL_SPECS: Record = { supportsVision: true, aliases: ["qwen3.7-max", "qwen3-max-2026-01-23"], }, + // #14181: the GA `qwen3.8-max` is a distinct model served by opencode-go (and + // listed bare by alibaba/qwen-cloud/kilocode/clinepass/xkiro) — it gets its own + // spec row instead of aliasing to the preview, which remains a separate model. + "qwen3.8-max": { + maxOutputTokens: 65536, + contextWindow: 1000000, + thinkingBudgetCap: 38912, + supportsThinking: true, + supportsTools: true, + supportsVision: true, + }, "qwen3.8-max-preview": { maxOutputTokens: 65536, contextWindow: 1000000, @@ -583,7 +594,6 @@ export const MODEL_SPECS: Record = { supportsThinking: true, supportsTools: true, supportsVision: true, - aliases: ["qwen3.8-max"], }, "qwen3.6-plus": { maxOutputTokens: 65536, diff --git a/tests/unit/14181-opencode-go-qwen-ga-alias.test.ts b/tests/unit/14181-opencode-go-qwen-ga-alias.test.ts new file mode 100644 index 00000000000..6bd668e9867 --- /dev/null +++ b/tests/unit/14181-opencode-go-qwen-ga-alias.test.ts @@ -0,0 +1,58 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { resolveModelAlias } from "../../open-sse/services/modelDeprecation.ts"; +import { opencode_goProvider } from "../../open-sse/config/providers/registry/opencode/go/index.ts"; +import { + findModelSpecIdByExactOrAlias, + getModelSpec, +} from "../../src/shared/constants/modelSpecs.ts"; + +/** + * #14181 — OpenCode Go now serves a GA `qwen3.8-max`, but OmniRoute's built-in + * deprecation table (`qwen3.8-max` → `qwen3.8-max-preview`, written when the model + * shipped only under the `-preview` id) rewrote the id before dispatch, and the + * upstream rejects the preview id with `401 Model qwen3.8-max-preview is not + * supported`. + * + * The rewrite is skipped only when `hasKnownProviderModel(provider, id)` finds the + * id in the provider's static registry — and the opencode-go registry never + * declared `qwen3.8-max`, so the exemption never fired for it (alibaba/qwen-cloud/ + * kilocode/clinepass/xkiro already declare the bare GA id and were unaffected). + * + * Fix: declare the GA id in the opencode-go registry (qwen family on opencode-go + * is text-only and routes through the Claude translator per #2292), and give the + * GA model its own MODEL_SPECS entry instead of aliasing it to the preview spec. + */ +test("opencode-go registry declares the GA qwen3.8-max id (#14181)", () => { + const entry = (opencode_goProvider.models || []).find((m) => m.id === "qwen3.8-max"); + assert.ok( + entry, + "opencode-go registry must list qwen3.8-max so the provider-aware alias exemption fires" + ); + assert.equal( + entry.targetFormat, + "claude", + "qwen models on opencode-go reject oa-compat (#2292) — the GA id must route through the Claude translator like its qwen3.7-max sibling" + ); + assert.equal(entry.supportsVision, false, "text-only on opencode-go (#2822)"); +}); + +test("resolveModelAlias leaves qwen3.8-max untouched for opencode-go (#14181)", () => { + assert.equal(resolveModelAlias("qwen3.8-max", "opencode-go"), "qwen3.8-max"); +}); + +test("resolveModelAlias still rewrites qwen3.8-max for preview-only providers (#14181)", () => { + // qoder / bailian-coding-plan serve only the -preview id: the built-in rewrite + // remains the correct behaviour there (and for callers with no provider in hand). + assert.equal(resolveModelAlias("qwen3.8-max", "qoder"), "qwen3.8-max-preview"); + assert.equal(resolveModelAlias("qwen3.8-max"), "qwen3.8-max-preview"); +}); + +test("GA qwen3.8-max resolves to its own MODEL_SPECS entry, not the preview alias (#14181)", () => { + assert.equal(findModelSpecIdByExactOrAlias("qwen3.8-max"), "qwen3.8-max"); + const ga = getModelSpec("qwen3.8-max"); + const preview = getModelSpec("qwen3.8-max-preview"); + assert.ok(ga && preview, "both GA and preview specs must exist"); + assert.equal(ga.contextWindow, preview.contextWindow); + assert.equal(ga.maxOutputTokens, preview.maxOutputTokens); +});