Files
codex/codex-rs/tools/src/image_detail_tests.rs
Curtis 'Fjord' Hawthorne 8543e39885 Preserve image detail in app-server inputs (#20693)
## Summary

- Add optional image detail to user image inputs across core, app-server
v2, thread history/event mapping, and the generated app-server
schemas/types.
- Preserve requested detail when serializing Responses image inputs:
omitted detail stays on the existing `high` default, while explicit
`original` keeps local images on the original-resolution path.
- Support `high`/`original` consistently for tool image outputs,
including MCP `codex/imageDetail`, code-mode image helpers, and
`view_image`.
2026-05-15 15:04:04 -07:00

114 lines
3.5 KiB
Rust

use super::*;
use codex_protocol::models::DEFAULT_IMAGE_DETAIL;
use codex_protocol::models::FunctionCallOutputContentItem;
use codex_protocol::models::ImageDetail;
use codex_protocol::openai_models::ModelInfo;
use pretty_assertions::assert_eq;
use serde_json::json;
fn model_info() -> ModelInfo {
serde_json::from_value(json!({
"slug": "test-model",
"display_name": "Test Model",
"description": null,
"supported_reasoning_levels": [],
"shell_type": "shell_command",
"visibility": "list",
"supported_in_api": true,
"priority": 1,
"availability_nux": null,
"upgrade": null,
"base_instructions": "base",
"model_messages": null,
"supports_reasoning_summaries": false,
"default_reasoning_summary": "auto",
"support_verbosity": false,
"default_verbosity": null,
"apply_patch_tool_type": null,
"truncation_policy": {
"mode": "bytes",
"limit": 10000
},
"supports_parallel_tool_calls": false,
"supports_image_detail_original": true,
"context_window": null,
"auto_compact_token_limit": null,
"effective_context_window_percent": 95,
"experimental_supported_tools": [],
"input_modalities": ["text", "image"],
"supports_search_tool": false
}))
.expect("deserialize test model")
}
#[test]
fn explicit_original_is_allowed_when_model_supports_it() {
let model_info = model_info();
assert!(can_request_original_image_detail(&model_info));
assert_eq!(
normalize_output_image_detail(&model_info, Some(ImageDetail::Original)),
Some(ImageDetail::Original)
);
assert_eq!(
normalize_output_image_detail(&model_info, /*detail*/ None),
None
);
}
#[test]
fn explicit_original_is_dropped_without_model_support() {
let mut model_info = model_info();
model_info.supports_image_detail_original = false;
assert_eq!(
normalize_output_image_detail(&model_info, Some(ImageDetail::Original)),
None
);
}
#[test]
fn explicit_non_original_detail_is_preserved() {
let model_info = model_info();
assert_eq!(
normalize_output_image_detail(&model_info, Some(ImageDetail::High)),
Some(ImageDetail::High)
);
}
#[test]
fn sanitize_original_falls_back_to_high_without_support() {
let mut items = vec![
FunctionCallOutputContentItem::InputText {
text: "header".to_string(),
},
FunctionCallOutputContentItem::InputImage {
image_url: "data:image/png;base64,AAA".to_string(),
detail: Some(ImageDetail::Original),
},
FunctionCallOutputContentItem::InputImage {
image_url: "data:image/png;base64,BBB".to_string(),
detail: Some(ImageDetail::High),
},
];
sanitize_original_image_detail(/*can_request_original_image_detail*/ false, &mut items);
assert_eq!(
items,
vec![
FunctionCallOutputContentItem::InputText {
text: "header".to_string(),
},
FunctionCallOutputContentItem::InputImage {
image_url: "data:image/png;base64,AAA".to_string(),
detail: Some(DEFAULT_IMAGE_DETAIL),
},
FunctionCallOutputContentItem::InputImage {
image_url: "data:image/png;base64,BBB".to_string(),
detail: Some(ImageDetail::High),
},
]
);
}