fix(codex): reject web_search for Qwen3-Coder and correct LongCat context window

- 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.
This commit is contained in:
Jason
2026-06-28 17:22:35 +08:00
parent d380b410af
commit 26f0d221c0
3 changed files with 67 additions and 24 deletions
+29 -13
View File
@@ -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<String> {
@@ -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!(
+1 -1
View File
@@ -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",
+37 -10
View File
@@ -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<string, number> }
>([
["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();
}