From 8d918d0459392e09a1c10afa9362feed61830e42 Mon Sep 17 00:00:00 2001 From: zjm54321 Date: Tue, 11 Aug 2026 00:30:00 +0800 Subject: [PATCH 1/2] fix(ai): serialize image edits with images array --- .../src/formats/openai/image/request.rs | 71 ++++++++++++++++--- 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/crates/aether-ai/formats/src/formats/openai/image/request.rs b/crates/aether-ai/formats/src/formats/openai/image/request.rs index 45b6aa827..6048fda26 100644 --- a/crates/aether-ai/formats/src/formats/openai/image/request.rs +++ b/crates/aether-ai/formats/src/formats/openai/image/request.rs @@ -542,6 +542,20 @@ pub fn build_openai_image_api_provider_request_body( request: &NormalizedOpenAiImageRequest, mapped_model: Option<&str>, upstream_is_stream: bool, +) -> Option { + build_openai_image_api_provider_request_body_with_edit_images( + request, + mapped_model, + upstream_is_stream, + true, + ) +} + +fn build_openai_image_api_provider_request_body_with_edit_images( + request: &NormalizedOpenAiImageRequest, + mapped_model: Option<&str>, + upstream_is_stream: bool, + use_plural_edit_images: bool, ) -> Option { let model = mapped_model .map(str::trim) @@ -578,12 +592,16 @@ pub fn build_openai_image_api_provider_request_body( .or_insert_with(|| response_format.clone()); } insert_standard_openai_image_inputs(&mut body, request.images.clone()); - project_openai_image_api_request_body( + let mut body = project_openai_image_api_request_body( &Value::Object(body), model, request.operation, request.max_generation_count, - ) + )?; + if use_plural_edit_images && request.operation == OpenAiImageOperation::Edit { + insert_standard_openai_images_edit_inputs(body.as_object_mut()?, request.images.clone())?; + } + Some(body) } pub fn build_codex_openai_image_api_provider_request_body( @@ -591,8 +609,12 @@ pub fn build_codex_openai_image_api_provider_request_body( mapped_model: Option<&str>, upstream_is_stream: bool, ) -> Option { - let body = - build_openai_image_api_provider_request_body(request, mapped_model, upstream_is_stream)?; + let body = build_openai_image_api_provider_request_body_with_edit_images( + request, + mapped_model, + upstream_is_stream, + false, + )?; project_codex_openai_image_api_request_body(&body, request.operation) } @@ -898,6 +920,31 @@ pub(crate) fn insert_standard_openai_image_inputs( object.insert("image".to_string(), image); } +fn insert_standard_openai_images_edit_inputs( + object: &mut Map, + images: Vec, +) -> Option<()> { + let images = images + .into_iter() + .map(|image| { + let image = image.as_object()?; + image + .get("image_url") + .cloned() + .map(|image_url| json!({ "image_url": image_url })) + .or_else(|| { + image + .get("file_id") + .cloned() + .map(|file_id| json!({ "file_id": file_id })) + }) + }) + .collect::>>()?; + object.remove("image"); + object.insert("images".to_string(), Value::Array(images)); + Some(()) +} + fn openai_image_api_inputs(object: &Map) -> Option> { match (object.get("image"), object.get("images")) { (Some(_), Some(_)) => None, @@ -2316,9 +2363,10 @@ mod tests { assert_eq!(provider_request_body["response_format"], "url"); assert_eq!(provider_request_body["user"], "user-123"); assert_eq!( - provider_request_body["image"]["image_url"], - "data:image/png;base64,aW1hZ2U=" + provider_request_body["images"], + json!([{"image_url": "data:image/png;base64,aW1hZ2U="}]) ); + assert!(provider_request_body.get("image").is_none()); assert_eq!( provider_request_body["mask"]["image_url"], "data:image/png;base64,bWFzaw==" @@ -2329,7 +2377,7 @@ mod tests { } #[test] - fn build_image_api_provider_edit_request_uses_one_standard_image_field() { + fn build_image_api_provider_edit_request_uses_plural_images_field() { let parts = request_parts("/v1/images/edits", Some("application/json")); let request = normalize_openai_image_request( &parts, @@ -2350,10 +2398,13 @@ mod tests { .expect("standard Images edit body should project"); assert_eq!( - provider_request_body["image"].as_array().map(Vec::len), - Some(2) + provider_request_body["images"], + json!([ + {"image_url": "data:image/png;base64,Zm9v"}, + {"image_url": "https://example.test/reference.png"} + ]) ); - assert!(provider_request_body.get("images").is_none()); + assert!(provider_request_body.get("image").is_none()); } #[test] From 41e93858e163187f8f231dc81969a10b60af6f5b Mon Sep 17 00:00:00 2001 From: zjm54321 Date: Tue, 11 Aug 2026 00:36:33 +0800 Subject: [PATCH 2/2] refactor(ai): simplify image edit serialization --- .../src/formats/openai/image/request.rs | 79 ++++++------------- 1 file changed, 26 insertions(+), 53 deletions(-) diff --git a/crates/aether-ai/formats/src/formats/openai/image/request.rs b/crates/aether-ai/formats/src/formats/openai/image/request.rs index 6048fda26..923784aa7 100644 --- a/crates/aether-ai/formats/src/formats/openai/image/request.rs +++ b/crates/aether-ai/formats/src/formats/openai/image/request.rs @@ -542,20 +542,6 @@ pub fn build_openai_image_api_provider_request_body( request: &NormalizedOpenAiImageRequest, mapped_model: Option<&str>, upstream_is_stream: bool, -) -> Option { - build_openai_image_api_provider_request_body_with_edit_images( - request, - mapped_model, - upstream_is_stream, - true, - ) -} - -fn build_openai_image_api_provider_request_body_with_edit_images( - request: &NormalizedOpenAiImageRequest, - mapped_model: Option<&str>, - upstream_is_stream: bool, - use_plural_edit_images: bool, ) -> Option { let model = mapped_model .map(str::trim) @@ -592,16 +578,12 @@ fn build_openai_image_api_provider_request_body_with_edit_images( .or_insert_with(|| response_format.clone()); } insert_standard_openai_image_inputs(&mut body, request.images.clone()); - let mut body = project_openai_image_api_request_body( + project_openai_image_api_request_body( &Value::Object(body), model, request.operation, request.max_generation_count, - )?; - if use_plural_edit_images && request.operation == OpenAiImageOperation::Edit { - insert_standard_openai_images_edit_inputs(body.as_object_mut()?, request.images.clone())?; - } - Some(body) + ) } pub fn build_codex_openai_image_api_provider_request_body( @@ -609,12 +591,8 @@ pub fn build_codex_openai_image_api_provider_request_body( mapped_model: Option<&str>, upstream_is_stream: bool, ) -> Option { - let body = build_openai_image_api_provider_request_body_with_edit_images( - request, - mapped_model, - upstream_is_stream, - false, - )?; + let body = + build_openai_image_api_provider_request_body(request, mapped_model, upstream_is_stream)?; project_codex_openai_image_api_request_body(&body, request.operation) } @@ -654,7 +632,27 @@ pub fn project_openai_image_api_request_body( projected.remove("image"); projected.remove("images"); let image_input_count = image_inputs.len(); - insert_standard_openai_image_inputs(&mut projected, image_inputs); + if operation == OpenAiImageOperation::Edit { + let images = image_inputs + .into_iter() + .map(|image| { + let image = image.as_object()?; + image + .get("image_url") + .cloned() + .map(|image_url| json!({ "image_url": image_url })) + .or_else(|| { + image + .get("file_id") + .cloned() + .map(|file_id| json!({ "file_id": file_id })) + }) + }) + .collect::>>()?; + projected.insert("images".to_string(), Value::Array(images)); + } else { + insert_standard_openai_image_inputs(&mut projected, image_inputs); + } match operation { OpenAiImageOperation::Generate if image_input_count > 0 || projected.contains_key("mask") => @@ -920,31 +918,6 @@ pub(crate) fn insert_standard_openai_image_inputs( object.insert("image".to_string(), image); } -fn insert_standard_openai_images_edit_inputs( - object: &mut Map, - images: Vec, -) -> Option<()> { - let images = images - .into_iter() - .map(|image| { - let image = image.as_object()?; - image - .get("image_url") - .cloned() - .map(|image_url| json!({ "image_url": image_url })) - .or_else(|| { - image - .get("file_id") - .cloned() - .map(|file_id| json!({ "file_id": file_id })) - }) - }) - .collect::>>()?; - object.remove("image"); - object.insert("images".to_string(), Value::Array(images)); - Some(()) -} - fn openai_image_api_inputs(object: &Map) -> Option> { match (object.get("image"), object.get("images")) { (Some(_), Some(_)) => None, @@ -1025,7 +998,7 @@ pub fn project_codex_openai_image_api_request_body( | "n" | "quality" | "size" - | "image" + | "images" | "response_format" | "stream" )