From e92323ce350c435c109e12c2692b5ede64e71ea2 Mon Sep 17 00:00:00 2001 From: ZZR <1206302772@qq.com> Date: Wed, 9 Sep 2026 14:45:00 +0800 Subject: [PATCH 1/2] fix: always emit wire_api = "responses" in generated Codex config Codex 26.901 dropped support for wire_api = "chat" (openai/codex discussion #7782): if the generated config.toml contains it, the whole file is treated as invalid and Codex silently falls back to its built-in default models. Chat Completions upstreams are transparently converted by the local protocol proxy, so the config exposed to Codex must always declare wire_api = "responses" regardless of the selected relay protocol. Applied to all config write paths: relay profile completion, provider import and CC Switch import. --- crates/codex-plus-core/src/ccs_import.rs | 8 ++++---- crates/codex-plus-core/src/provider_import.rs | 9 +++++---- crates/codex-plus-core/src/relay_config.rs | 13 +++++-------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/crates/codex-plus-core/src/ccs_import.rs b/crates/codex-plus-core/src/ccs_import.rs index 4f275e960..8e58e1bd5 100644 --- a/crates/codex-plus-core/src/ccs_import.rs +++ b/crates/codex-plus-core/src/ccs_import.rs @@ -322,10 +322,10 @@ fn extract_toml_string_value(text: &str, key: &str) -> Option { } fn build_config_toml(base_url: &str, api_key: &str, protocol: RelayProtocol) -> String { - let wire_api = match protocol { - RelayProtocol::Responses => "responses", - RelayProtocol::ChatCompletions => "chat", - }; + // Codex 26.901+ 不再支持 wire_api = "chat"(openai/codex discussion #7782), + // Chat Completions 上游由本地协议代理转换,对 Codex 始终暴露 "responses"。 + let _ = protocol; + let wire_api = "responses"; [ "model_provider = \"CodexPlusPlus\"".to_string(), String::new(), diff --git a/crates/codex-plus-core/src/provider_import.rs b/crates/codex-plus-core/src/provider_import.rs index e0775e114..d1c465b52 100644 --- a/crates/codex-plus-core/src/provider_import.rs +++ b/crates/codex-plus-core/src/provider_import.rs @@ -255,10 +255,11 @@ fn relay_mode(value: &str) -> RelayMode { } fn build_config_toml(base_url: &str, api_key: &str, protocol: RelayProtocol) -> String { - let wire_api = match protocol { - RelayProtocol::Responses => "responses", - RelayProtocol::ChatCompletions => "chat", - }; + // Codex 26.901+ 不再支持 wire_api = "chat"(openai/codex discussion #7782), + // 写出 "chat" 会导致整份 config.toml 无效并回退默认。Chat Completions 上游 + // 一律经由本地协议代理转换,对 Codex 始终暴露 "responses"。 + let _ = protocol; + let wire_api = "responses"; [ "model_provider = \"CodexPlusPlus\"".to_string(), String::new(), diff --git a/crates/codex-plus-core/src/relay_config.rs b/crates/codex-plus-core/src/relay_config.rs index d2be4bf32..387ec1433 100644 --- a/crates/codex-plus-core/src/relay_config.rs +++ b/crates/codex-plus-core/src/relay_config.rs @@ -2911,14 +2911,11 @@ fn complete_relay_profile_config(profile: &RelayProfile) -> anyhow::Result Date: Thu, 10 Sep 2026 09:05:35 +0800 Subject: [PATCH 2/2] fix: derive custom Responses detection from profile.protocol apply_model_catalog_to_config used custom_responses_provider(), which reads wire_api from the generated config. Since wire_api is now always "responses" for Codex 26.901 compatibility, that signal is polluted: Chat Completions upstreams were misdetected as Responses and their generated catalogs lost use_responses_lite (Lite behavior belongs to the chat side of the protocol proxy). Determine custom_responses from profile.protocol (the real upstream protocol) instead of the rewritten config text. The chat-profile test now declares protocol: ChatCompletions explicitly, matching its intent. --- crates/codex-plus-core/src/relay_config.rs | 26 +++++--------------- crates/codex-plus-core/tests/relay_config.rs | 3 +++ 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/crates/codex-plus-core/src/relay_config.rs b/crates/codex-plus-core/src/relay_config.rs index 387ec1433..85e5dc85d 100644 --- a/crates/codex-plus-core/src/relay_config.rs +++ b/crates/codex-plus-core/src/relay_config.rs @@ -1896,7 +1896,12 @@ fn apply_model_catalog_to_config( || entries .iter() .any(|entry| entry.suffix_window.is_some() || entry.auto_compact_percent.is_some()); - let custom_responses = custom_responses_provider(&config_text); + // custom_responses_provider(&config_text) 读取的是生成后 config 里的 wire_api; + // 自对 Codex 恒写 "responses" 起,该信号已失真(chat 上游也会读到 responses)。 + // catalog 是否按 Responses 语义生成取决于真实上游协议,只能由 profile.protocol 判定。 + let custom_responses = profile.protocol == RelayProtocol::Responses + && active_provider_id(&parse_toml_document(&config_text)?) + .is_some_and(|provider_id| is_custom_provider_id(&provider_id)); // Catalog capabilities must follow the effective config, not stale profile URLs. let official_deepseek_responses = uses_official_deepseek_responses_for_config(profile, &config_text); @@ -2193,25 +2198,6 @@ fn uses_official_deepseek_responses_for_config(profile: &RelayProfile, config_te uses_official_deepseek_responses(profile) } -fn custom_responses_provider(config_text: &str) -> bool { - let Ok(doc) = parse_toml_document(config_text) else { - return false; - }; - let Some(provider_id) = active_provider_id(&doc) else { - return false; - }; - if !is_custom_provider_id(&provider_id) { - return false; - } - doc.get("model_providers") - .and_then(Item::as_table) - .and_then(|providers| providers.get(&provider_id)) - .and_then(Item::as_table_like) - .and_then(|provider| provider.get("wire_api")) - .and_then(Item::as_str) - .is_some_and(|wire_api| wire_api.trim().eq_ignore_ascii_case("responses")) -} - fn copy_standard_responses_catalog( home: &Path, source: &str, diff --git a/crates/codex-plus-core/tests/relay_config.rs b/crates/codex-plus-core/tests/relay_config.rs index bcb7c7b83..c2e7b868d 100644 --- a/crates/codex-plus-core/tests/relay_config.rs +++ b/crates/codex-plus-core/tests/relay_config.rs @@ -4786,6 +4786,9 @@ fn apply_custom_chat_profile_preserves_generated_catalog_lite_behavior() { id: "relay-gpt56-chat".to_string(), model: "gpt-5.6-sol".to_string(), relay_mode: RelayMode::PureApi, + // 恒写 wire_api="responses" 后,生成 config 不再携带真实上游协议; + // catalog 的 Lite 判定改由 profile.protocol 驱动,故此处必须显式声明 Chat。 + protocol: RelayProtocol::ChatCompletions, config_contents: r#"model = "gpt-5.6-sol" model_provider = "custom"