Skip to content

Commit 770b5db

Browse files
committed
fix(openai): suppress native image_generation tool for Codex models
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 1jehuang#148. Closes #115
1 parent e1886bb commit 770b5db

2 files changed

Lines changed: 79 additions & 1 deletion

File tree

src/provider/openai.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,20 @@ impl OpenAIProvider {
702702
format!("{}/compact", Self::responses_url(credentials))
703703
}
704704

705+
/// Codex-family models (`gpt-5.x-codex*`, including the `[1m]` long-context
706+
/// suffix variants) do not accept the native `image_generation` tool. The
707+
/// OpenAI Responses API rejects requests with that tool attached and the
708+
/// session aborts with a 400. Other ChatGPT-mode models (gpt-5.x non-codex,
709+
/// gpt-4o, …) still support it, so we only suppress the tool for codex
710+
/// model ids.
711+
fn supports_native_image_generation(model_id: &str) -> bool {
712+
let normalized = model_id
713+
.strip_suffix("[1m]")
714+
.unwrap_or(model_id)
715+
.to_ascii_lowercase();
716+
!normalized.contains("codex")
717+
}
718+
705719
#[expect(
706720
clippy::too_many_arguments,
707721
reason = "request construction threads explicit per-request OpenAI settings without hidden state"
@@ -720,7 +734,7 @@ impl OpenAIProvider {
720734
native_compaction_threshold: Option<usize>,
721735
) -> Value {
722736
let mut tools = api_tools.to_vec();
723-
if is_chatgpt_mode {
737+
if is_chatgpt_mode && Self::supports_native_image_generation(model_id) {
724738
tools.push(serde_json::json!({ "type": "image_generation" }));
725739
}
726740

src/provider/openai_tests/payloads.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,70 @@ fn test_build_response_request_includes_stream_for_http() {
1717
assert_eq!(request["store"], serde_json::json!(false));
1818
}
1919

20+
#[test]
21+
fn test_chatgpt_payload_includes_native_image_generation_for_non_codex_models() {
22+
// Regression for issue #115: ChatGPT mode adds a native `image_generation`
23+
// tool. Non-codex models (gpt-5.x, gpt-4o, …) accept it and benefit.
24+
let request = OpenAIProvider::build_response_request(
25+
"gpt-5.5",
26+
"system".to_string(),
27+
&[],
28+
&[],
29+
true,
30+
Some(DEFAULT_MAX_OUTPUT_TOKENS),
31+
None,
32+
None,
33+
None,
34+
None,
35+
None,
36+
);
37+
38+
assert!(
39+
request["tools"]
40+
.as_array()
41+
.expect("tools array")
42+
.iter()
43+
.any(|tool| tool["type"] == "image_generation"),
44+
"non-codex models should still receive image_generation in ChatGPT mode"
45+
);
46+
}
47+
48+
#[test]
49+
fn test_chatgpt_payload_excludes_native_image_generation_for_codex_models() {
50+
// Regression for issue #115: codex-family models reject the native
51+
// image_generation tool with a 400 from the OpenAI Responses API. Suppress
52+
// it for any model id whose normalized form contains "codex", including
53+
// the `[1m]` long-context suffix variant.
54+
for model in [
55+
"gpt-5.3-codex",
56+
"gpt-5.3-codex-spark",
57+
"gpt-5.3-codex-spark[1m]",
58+
] {
59+
let request = OpenAIProvider::build_response_request(
60+
model,
61+
"system".to_string(),
62+
&[],
63+
&[],
64+
true,
65+
Some(DEFAULT_MAX_OUTPUT_TOKENS),
66+
None,
67+
None,
68+
None,
69+
None,
70+
None,
71+
);
72+
73+
assert!(
74+
request["tools"]
75+
.as_array()
76+
.expect("tools array")
77+
.iter()
78+
.all(|tool| tool["type"] != "image_generation"),
79+
"{model} should not receive the unsupported native image_generation tool"
80+
);
81+
}
82+
}
83+
2084
#[test]
2185
fn test_websocket_payload_strips_stream_and_background() {
2286
let mut request = OpenAIProvider::build_response_request(

0 commit comments

Comments
 (0)