From 26f0d221c008eca6a889e772d7cac28c5c035e72 Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 28 Jun 2026 17:22:35 +0800 Subject: [PATCH] fix(codex): reject web_search for Qwen3-Coder and correct LongCat context window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `qwen3-coder` to the web_search reject model-prefix blacklist so the native qwen3-coder-plus direct-connect preset suppresses the built-in tool (百炼 rejects it for the coder series), while general Qwen models sharing the DashScope host keep web_search enabled. Matched on the model axis, not host. - Correct LongCat-2.0-Preview context window from 128K to its real 1M (1048576), aligning with the MiMo/Qwen 2^20 convention. - Tighten native Responses preset tests to assert exact model→contextWindow catalogs instead of only checking catalog presence. --- src-tauri/src/codex_config.rs | 42 ++++++++++++----- src/config/codexProviderPresets.ts | 2 +- tests/config/codexChatProviderPresets.test.ts | 47 +++++++++++++++---- 3 files changed, 67 insertions(+), 24 deletions(-) diff --git a/src-tauri/src/codex_config.rs b/src-tauri/src/codex_config.rs index 801c349f9..a9b75a237 100644 --- a/src-tauri/src/codex_config.rs +++ b/src-tauri/src/codex_config.rs @@ -37,10 +37,11 @@ const CODEX_WEB_SEARCH_DISABLED: &str = "disabled"; /// - `base_url` host substring, and /// - the model id's brand prefix (after stripping any `vendor/` path segment). /// -/// Verified 2026-06-27 doc audit — reject: MiMo (hard 400), LongCat (official +/// Verified 2026-06-28 doc audit — reject: MiMo (hard 400), LongCat (official /// config ships `web_search = "disabled"`), MiniMax (tool-type enum `['function']` -/// only). Deliberately NOT listed (they support it): 火山方舟豆包, 阿里百炼 Qwen, -/// and all GPT-native relays. +/// only), and Qwen3-Coder models (百炼 marks built-in tools unsupported for +/// the coder series). Deliberately NOT listed by host: 火山方舟豆包, general +/// 阿里百炼 Qwen models that support built-in web_search, and GPT-native relays. const CODEX_WEB_SEARCH_REJECT_HOSTS: &[&str] = &[ "xiaomimimo.com", // Xiaomi MiMo (api.xiaomimimo.com, token-plan-cn.xiaomimimo.com) "longcat.chat", // Meituan LongCat (api.longcat.chat) @@ -52,7 +53,8 @@ const CODEX_WEB_SEARCH_REJECT_HOSTS: &[&str] = &[ /// against the model id's last `/`-segment so aggregator ids like /// `MiniMaxAI/MiniMax-M3` are caught. Exact brand names (not a fuzzy heuristic), /// so a supporting gateway is never wrongly matched. -const CODEX_WEB_SEARCH_REJECT_MODEL_PREFIXES: &[&str] = &["mimo", "longcat", "minimax"]; +const CODEX_WEB_SEARCH_REJECT_MODEL_PREFIXES: &[&str] = + &["mimo", "longcat", "minimax", "qwen3-coder"]; /// Top-level `model` id from a Codex `config.toml`. fn codex_top_level_model(config_text: &str) -> Option { @@ -79,7 +81,8 @@ fn codex_native_gateway_rejects_web_search(config_text: &str) -> bool { } if let Some(model) = codex_top_level_model(config_text) { let model = model.to_ascii_lowercase(); - // Strip any aggregator "vendor/" prefix, e.g. "MiniMaxAI/MiniMax-M3". + // Strip any aggregator "vendor/" prefix, e.g. "MiniMaxAI/MiniMax-M3" + // or "qwen/qwen3-coder-plus". let model = model.rsplit('/').next().unwrap_or(model.as_str()); if CODEX_WEB_SEARCH_REJECT_MODEL_PREFIXES .iter() @@ -948,8 +951,9 @@ pub fn prepare_codex_config_text_with_model_catalog( if let Some(catalog) = codex_model_catalog_from_settings(settings, config_text, profile)? { let config_text = set_codex_model_catalog_json_field(config_text, Some(&catalog_path))?; // Disable web_search only for native gateways on the reject blacklist - // (MiMo/LongCat/MiniMax, by host or model brand). Everything else — - // relays, DouBao/Qwen, unknown providers — keeps Codex's default. + // (MiMo/LongCat/MiniMax by host or model brand; Qwen3-Coder by model). + // Everything else — relays, DouBao, web-search-capable Qwen models, + // unknown providers — keeps Codex's default. let disable_web_search = profile == CodexCatalogToolProfile::NativeResponses && codex_native_gateway_rejects_web_search(&config_text); let config_text = set_codex_native_web_search_field(&config_text, disable_web_search)?; @@ -2622,6 +2626,10 @@ web_search = "disabled" ("MiniMax-M3", "https://api.siliconflow.cn/v1"), ("MiniMaxAI/MiniMax-M3", "https://api.siliconflow.cn/v1"), ("mimo-v2.5-pro", "https://some-aggregator.example/v1"), + ( + "qwen/qwen3-coder-plus", + "https://some-aggregator.example/v1", + ), ] { assert!( codex_native_gateway_rejects_web_search(&cfg(model, host)), @@ -2629,8 +2637,20 @@ web_search = "disabled" ); } - // NOT blacklisted → keep Codex default (relays/GPT, DouBao, Qwen, and any - // unknown provider incl. an aggregator serving a non-reject model). + // Qwen3-Coder is blacklisted by model, not by DashScope host. This keeps + // general Qwen models that support built-in web_search on the same host + // enabled while protecting the native qwen3-coder-plus preset. + assert!(codex_native_gateway_rejects_web_search(&cfg( + "qwen3-coder-plus", + "https://dashscope.aliyuncs.com/compatible-mode/v1", + ))); + assert!(!codex_native_gateway_rejects_web_search(&cfg( + "qwen3.7-plus", + "https://dashscope.aliyuncs.com/compatible-mode/v1", + ))); + + // NOT blacklisted → keep Codex default (relays/GPT, DouBao, general Qwen, + // and any unknown provider incl. an aggregator serving a non-reject model). for (model, host) in [ ("gpt-5.5", "https://www.packyapi.com/v1"), ("gpt-5-codex", "https://aihubmix.com/v1"), @@ -2638,10 +2658,6 @@ web_search = "disabled" "doubao-seed-2-1-pro", "https://ark.cn-beijing.volces.com/api/v3", ), - ( - "qwen3-coder-plus", - "https://dashscope.aliyuncs.com/compatible-mode/v1", - ), ("Pro/moonshotai/Kimi-K2.6", "https://api.siliconflow.cn/v1"), ] { assert!( diff --git a/src/config/codexProviderPresets.ts b/src/config/codexProviderPresets.ts index 0ab8a21c6..babc0a7f2 100644 --- a/src/config/codexProviderPresets.ts +++ b/src/config/codexProviderPresets.ts @@ -625,7 +625,7 @@ requires_openai_auth = true`, { model: "LongCat-2.0-Preview", displayName: "LongCat 2.0 Preview", - contextWindow: 131072, + contextWindow: 1048576, }, ]), category: "cn_official", diff --git a/tests/config/codexChatProviderPresets.test.ts b/tests/config/codexChatProviderPresets.test.ts index 4f4c4fa2c..47cae427e 100644 --- a/tests/config/codexChatProviderPresets.test.ts +++ b/tests/config/codexChatProviderPresets.test.ts @@ -154,17 +154,36 @@ describe("Codex Chat provider presets", () => { }); it("uses native Responses API for migrated CN providers without local route mapping", () => { - const nativeResponsesPresets = [ - "DouBaoSeed", - "Bailian", - "Longcat", - "MiniMax", - "MiniMax en", - "Xiaomi MiMo", - "Xiaomi MiMo Token Plan (China)", - ]; + const nativeResponsesPresets = new Map< + string, + { contextWindows: Record } + >([ + ["DouBaoSeed", { contextWindows: { "doubao-seed-2-1-pro": 262144 } }], + ["Bailian", { contextWindows: { "qwen3-coder-plus": 1048576 } }], + ["Longcat", { contextWindows: { "LongCat-2.0-Preview": 1048576 } }], + ["MiniMax", { contextWindows: { "MiniMax-M3": 1000000 } }], + ["MiniMax en", { contextWindows: { "MiniMax-M3": 1000000 } }], + [ + "Xiaomi MiMo", + { + contextWindows: { + "mimo-v2.5-pro": 1048576, + "mimo-v2.5": 1048576, + }, + }, + ], + [ + "Xiaomi MiMo Token Plan (China)", + { + contextWindows: { + "mimo-v2.5-pro": 1048576, + "mimo-v2.5": 1048576, + }, + }, + ], + ]); - for (const name of nativeResponsesPresets) { + for (const [name, expected] of nativeResponsesPresets) { const preset = codexProviderPresets.find((item) => item.name === name); expect(preset, `${name} preset`).toBeDefined(); @@ -174,6 +193,14 @@ describe("Codex Chat provider presets", () => { // apply_patch)。带 catalog 不再强制开“本地路由映射”——前端已按 // apiFormat 解耦(openai_responses 默认不开接管)。 expect((preset?.modelCatalog ?? []).length).toBeGreaterThan(0); + expect( + Object.fromEntries( + (preset?.modelCatalog ?? []).map((model) => [ + model.model, + model.contextWindow, + ]), + ), + ).toEqual(expected.contextWindows); // 原生(直连)不走 Chat 转换,因此不需要 codexChatReasoning。 expect(preset?.codexChatReasoning).toBeUndefined(); }