diff --git a/codex-rs/core/src/image_preparation.rs b/codex-rs/core/src/image_preparation.rs index d510f220df..78170db319 100644 --- a/codex-rs/core/src/image_preparation.rs +++ b/codex-rs/core/src/image_preparation.rs @@ -19,7 +19,6 @@ use codex_protocol::models::ResponseItem; use codex_protocol::openai_models::ModelInfo; use codex_utils_image::ImageProcessingError; use codex_utils_image::PromptImageMode; -use codex_utils_image::PromptImageResizeLimits; use codex_utils_image::load_data_url_for_prompt; use tracing::warn; @@ -31,15 +30,6 @@ const UNSUPPORTED_LOW_DETAIL_PLACEHOLDER: &str = "image content omitted because const REMOTE_IMAGE_URL_PLACEHOLDER: &str = "image content omitted because remote image URLs are not supported"; -const HIGH_DETAIL_LIMITS: PromptImageResizeLimits = PromptImageResizeLimits { - max_dimension: 2048, - max_patches: 2_500, -}; -const UNIFIED_IMAGE_LIMITS: PromptImageResizeLimits = PromptImageResizeLimits { - max_dimension: 6000, - max_patches: 10_000, -}; - #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum ImagePreparationMode { DetailBased, @@ -288,17 +278,23 @@ fn prepare_image( return Ok(None); } - let (effective_detail, limits) = match mode { - ImagePreparationMode::UnifiedBudget => (ImageDetailSetting::Original, UNIFIED_IMAGE_LIMITS), + let (effective_detail, image_mode) = match mode { + ImagePreparationMode::UnifiedBudget => ( + ImageDetailSetting::Original, + PromptImageMode::ORIGINAL_DETAIL, + ), ImagePreparationMode::DetailBased => match detail { None | Some(ImageDetail::Auto | ImageDetail::High) => { - (ImageDetailSetting::High, HIGH_DETAIL_LIMITS) + (ImageDetailSetting::High, PromptImageMode::HIGH_DETAIL) } - Some(ImageDetail::Original) => (ImageDetailSetting::Original, UNIFIED_IMAGE_LIMITS), + Some(ImageDetail::Original) => ( + ImageDetailSetting::Original, + PromptImageMode::ORIGINAL_DETAIL, + ), Some(ImageDetail::Low) => return Err(ImagePreparationError::UnsupportedLowDetail), }, }; - let image = load_data_url_for_prompt(image_url, PromptImageMode::ResizeWithLimits(limits))?; + let image = load_data_url_for_prompt(image_url, image_mode)?; metadata.push(ImagePreparationMetadata { message_role: origin.message_role.map(str::to_string), item_id: origin.item_id.map(str::to_string), diff --git a/codex-rs/utils/image/src/image_tests.rs b/codex-rs/utils/image/src/image_tests.rs index 35264fba20..0527de0f90 100644 --- a/codex-rs/utils/image/src/image_tests.rs +++ b/codex-rs/utils/image/src/image_tests.rs @@ -276,31 +276,37 @@ fn data_url_processing_rejects_malformed_input() { } } +/// The shared detail modes apply the policies used by external byte-oriented callers. #[tokio::test(flavor = "multi_thread")] -async fn resize_with_limits_respects_dimension_and_patch_budgets() { - let image = ImageBuffer::from_pixel(2048, 2048, Rgba([200u8, 10, 10, 255])); - let original_bytes = image_bytes(&image, ImageFormat::Png); - let limits = PromptImageResizeLimits { - max_dimension: 2048, - max_patches: 2_500, - }; +async fn detail_modes_apply_expected_budgets() { + for (mode, input_dimensions, expected_dimensions) in [ + (PromptImageMode::HIGH_DETAIL, (2048, 2048), (1600, 1600)), + (PromptImageMode::ORIGINAL_DETAIL, (6401, 100), (6000, 94)), + ] { + let image = ImageBuffer::from_pixel( + input_dimensions.0, + input_dimensions.1, + Rgba([200u8, 10, 10, 255]), + ); + let original_bytes = image_bytes(&image, ImageFormat::Png); + let processed = load_for_prompt_bytes(Path::new("in-memory-image"), original_bytes, mode) + .expect("process image with detail mode"); - let processed = load_for_prompt_bytes( - Path::new("in-memory-image"), - original_bytes, - PromptImageMode::ResizeWithLimits(limits), - ) - .expect("process image with explicit limits"); - - assert_eq!( - ( - processed.source_width, - processed.source_height, - processed.width, - processed.height, - ), - (2048, 2048, 1600, 1600) - ); + assert_eq!( + ( + processed.source_width, + processed.source_height, + processed.width, + processed.height, + ), + ( + input_dimensions.0, + input_dimensions.1, + expected_dimensions.0, + expected_dimensions.1, + ) + ); + } } #[tokio::test(flavor = "multi_thread")] diff --git a/codex-rs/utils/image/src/lib.rs b/codex-rs/utils/image/src/lib.rs index 603d19e6aa..58b5050ace 100644 --- a/codex-rs/utils/image/src/lib.rs +++ b/codex-rs/utils/image/src/lib.rs @@ -70,6 +70,19 @@ pub struct PromptImageResizeLimits { pub max_patches: usize, } +impl PromptImageMode { + /// Resize policy for high-detail prompt images. + pub const HIGH_DETAIL: Self = Self::ResizeWithLimits(PromptImageResizeLimits { + max_dimension: 2048, + max_patches: 2_500, + }); + /// Resize policy for original-detail prompt images. + pub const ORIGINAL_DETAIL: Self = Self::ResizeWithLimits(PromptImageResizeLimits { + max_dimension: 6000, + max_patches: 10_000, + }); +} + struct ImageMetadata { icc_profile: Option>, exif: Option>,