From 770b5db58f4e632cf6ee006dad94b136c9876f54 Mon Sep 17 00:00:00 2001 From: quangdang46 Date: Fri, 22 May 2026 00:47:18 +0700 Subject: [PATCH] fix(openai): suppress native image_generation tool for Codex models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In ChatGPT mode, build_response_request unconditionally pushed { "type": "image_generation" } onto the request's tool array. The OpenAI Responses API rejects that tool for codex-family models (gpt-5.x-codex*, including the [1m] long-context suffix variants) with HTTP 400 ("unsupported tool image_generation for model …"), which aborted the entire session for ChatGPT-mode codex users. Add Self::supports_native_image_generation(model_id) returning false for any model id whose lower-cased / [1m]-stripped form contains "codex". Gate the tools.push site by that helper. Non-codex ChatGPT models (gpt-5.x non-codex, gpt-4o, etc.) still receive the tool. Two regression tests in src/provider/openai_tests/payloads.rs: - test_chatgpt_payload_includes_native_image_generation_for_non_codex_models - test_chatgpt_payload_excludes_native_image_generation_for_codex_models iterating gpt-5.3-codex, gpt-5.3-codex-spark, and gpt-5.3-codex-spark[1m]. Ports upstream PR https://github.com/1jehuang/jcode/pull/148. Closes #115 --- src/provider/openai.rs | 16 ++++++- src/provider/openai_tests/payloads.rs | 64 +++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/provider/openai.rs b/src/provider/openai.rs index f3b793291..7c5881ceb 100644 --- a/src/provider/openai.rs +++ b/src/provider/openai.rs @@ -702,6 +702,20 @@ impl OpenAIProvider { format!("{}/compact", Self::responses_url(credentials)) } + /// Codex-family models (`gpt-5.x-codex*`, including the `[1m]` long-context + /// suffix variants) do not accept the native `image_generation` tool. The + /// OpenAI Responses API rejects requests with that tool attached and the + /// session aborts with a 400. Other ChatGPT-mode models (gpt-5.x non-codex, + /// gpt-4o, …) still support it, so we only suppress the tool for codex + /// model ids. + fn supports_native_image_generation(model_id: &str) -> bool { + let normalized = model_id + .strip_suffix("[1m]") + .unwrap_or(model_id) + .to_ascii_lowercase(); + !normalized.contains("codex") + } + #[expect( clippy::too_many_arguments, reason = "request construction threads explicit per-request OpenAI settings without hidden state" @@ -720,7 +734,7 @@ impl OpenAIProvider { native_compaction_threshold: Option, ) -> Value { let mut tools = api_tools.to_vec(); - if is_chatgpt_mode { + if is_chatgpt_mode && Self::supports_native_image_generation(model_id) { tools.push(serde_json::json!({ "type": "image_generation" })); } diff --git a/src/provider/openai_tests/payloads.rs b/src/provider/openai_tests/payloads.rs index 891dfba67..54070519c 100644 --- a/src/provider/openai_tests/payloads.rs +++ b/src/provider/openai_tests/payloads.rs @@ -17,6 +17,70 @@ fn test_build_response_request_includes_stream_for_http() { assert_eq!(request["store"], serde_json::json!(false)); } +#[test] +fn test_chatgpt_payload_includes_native_image_generation_for_non_codex_models() { + // Regression for issue #115: ChatGPT mode adds a native `image_generation` + // tool. Non-codex models (gpt-5.x, gpt-4o, …) accept it and benefit. + let request = OpenAIProvider::build_response_request( + "gpt-5.5", + "system".to_string(), + &[], + &[], + true, + Some(DEFAULT_MAX_OUTPUT_TOKENS), + None, + None, + None, + None, + None, + ); + + assert!( + request["tools"] + .as_array() + .expect("tools array") + .iter() + .any(|tool| tool["type"] == "image_generation"), + "non-codex models should still receive image_generation in ChatGPT mode" + ); +} + +#[test] +fn test_chatgpt_payload_excludes_native_image_generation_for_codex_models() { + // Regression for issue #115: codex-family models reject the native + // image_generation tool with a 400 from the OpenAI Responses API. Suppress + // it for any model id whose normalized form contains "codex", including + // the `[1m]` long-context suffix variant. + for model in [ + "gpt-5.3-codex", + "gpt-5.3-codex-spark", + "gpt-5.3-codex-spark[1m]", + ] { + let request = OpenAIProvider::build_response_request( + model, + "system".to_string(), + &[], + &[], + true, + Some(DEFAULT_MAX_OUTPUT_TOKENS), + None, + None, + None, + None, + None, + ); + + assert!( + request["tools"] + .as_array() + .expect("tools array") + .iter() + .all(|tool| tool["type"] != "image_generation"), + "{model} should not receive the unsupported native image_generation tool" + ); + } +} + #[test] fn test_websocket_payload_strips_stream_and_background() { let mut request = OpenAIProvider::build_response_request(