Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/provider/openai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -720,7 +734,7 @@ impl OpenAIProvider {
native_compaction_threshold: Option<usize>,
) -> 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" }));
}

Expand Down
64 changes: 64 additions & 0 deletions src/provider/openai_tests/payloads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down