Always enable original image detail on supported models (#17665)

## Summary

This PR removes `image_detail_original` as a runtime experiment and
makes original image detail available whenever the selected model
supports it.

Concretely, this change:
- drops the `image_detail_original` feature flag from the feature
registry and generated config schema
- makes tool-emitted image detail depend only on
`ModelInfo.supports_image_detail_original`
- updates `view_image` and `code_mode`/`js_repl` image emission to use
that capability check directly
- removes now-redundant experiment-specific tests and instruction
coverage
- keeps backward compatibility for existing configs by silently ignoring
a stale `features.image_detail_original` entry

The net effect is that `detail: "original"` is always available on
supported models, without requiring an experiment toggle.
This commit is contained in:
Curtis 'Fjord' Hawthorne
2026-04-14 08:15:56 -07:00
committed by GitHub
parent e6947f85f6
commit f030ab62eb
15 changed files with 48 additions and 222 deletions

View File

@@ -1,19 +1,16 @@
use codex_features::Feature;
use codex_features::Features;
use codex_protocol::models::ImageDetail;
use codex_protocol::openai_models::ModelInfo;
pub fn can_request_original_image_detail(features: &Features, model_info: &ModelInfo) -> bool {
model_info.supports_image_detail_original && features.enabled(Feature::ImageDetailOriginal)
pub fn can_request_original_image_detail(model_info: &ModelInfo) -> bool {
model_info.supports_image_detail_original
}
pub fn normalize_output_image_detail(
features: &Features,
model_info: &ModelInfo,
detail: Option<ImageDetail>,
) -> Option<ImageDetail> {
match detail {
Some(ImageDetail::Original) if can_request_original_image_detail(features, model_info) => {
Some(ImageDetail::Original) if can_request_original_image_detail(model_info) => {
Some(ImageDetail::Original)
}
Some(ImageDetail::Original) | Some(_) | None => None,

View File

@@ -1,6 +1,4 @@
use super::*;
use codex_features::Feature;
use codex_features::Features;
use codex_protocol::models::ImageDetail;
use codex_protocol::openai_models::ModelInfo;
use pretty_assertions::assert_eq;
@@ -42,37 +40,26 @@ fn model_info() -> ModelInfo {
}
#[test]
fn image_detail_original_feature_enables_explicit_original_without_force() {
fn explicit_original_is_allowed_when_model_supports_it() {
let model_info = model_info();
let mut features = Features::with_defaults();
features.enable(Feature::ImageDetailOriginal);
assert!(can_request_original_image_detail(&features, &model_info));
assert!(can_request_original_image_detail(&model_info));
assert_eq!(
normalize_output_image_detail(&features, &model_info, Some(ImageDetail::Original)),
normalize_output_image_detail(&model_info, Some(ImageDetail::Original)),
Some(ImageDetail::Original)
);
assert_eq!(
normalize_output_image_detail(&features, &model_info, /*detail*/ None),
normalize_output_image_detail(&model_info, /*detail*/ None),
None
);
}
#[test]
fn explicit_original_is_dropped_without_feature_or_model_support() {
fn explicit_original_is_dropped_without_model_support() {
let mut model_info = model_info();
let features = Features::with_defaults();
assert_eq!(
normalize_output_image_detail(&features, &model_info, Some(ImageDetail::Original)),
None
);
let mut features = Features::with_defaults();
features.enable(Feature::ImageDetailOriginal);
model_info.supports_image_detail_original = false;
assert_eq!(
normalize_output_image_detail(&features, &model_info, Some(ImageDetail::Original)),
normalize_output_image_detail(&model_info, Some(ImageDetail::Original)),
None
);
}
@@ -80,11 +67,9 @@ fn explicit_original_is_dropped_without_feature_or_model_support() {
#[test]
fn unsupported_non_original_detail_is_dropped() {
let model_info = model_info();
let mut features = Features::with_defaults();
features.enable(Feature::ImageDetailOriginal);
assert_eq!(
normalize_output_image_detail(&features, &model_info, Some(ImageDetail::Low)),
normalize_output_image_detail(&model_info, Some(ImageDetail::Low)),
None
);
}

View File

@@ -153,7 +153,7 @@ impl ToolsConfig {
let include_tool_suggest = features.enabled(Feature::ToolSuggest)
&& features.enabled(Feature::Apps)
&& features.enabled(Feature::Plugins);
let include_original_image_detail = can_request_original_image_detail(features, model_info);
let include_original_image_detail = can_request_original_image_detail(model_info);
// API-key auth bypasses Codex backend entitlement/tool normalization, so
// callers must confirm ChatGPT auth before exposing the built-in tool.
let include_image_gen_tool = *image_generation_tool_auth_allowed

View File

@@ -362,9 +362,9 @@ fn test_build_specs_enable_fanout_enables_agent_jobs_and_collab_tools() {
}
#[test]
fn view_image_tool_omits_detail_without_original_detail_feature() {
fn view_image_tool_omits_detail_without_original_detail_support() {
let mut model_info = model_info();
model_info.supports_image_detail_original = true;
model_info.supports_image_detail_original = false;
let features = Features::with_defaults();
let available_models = Vec::new();
let tools_config = ToolsConfig::new(&ToolsConfigParams {
@@ -392,11 +392,10 @@ fn view_image_tool_omits_detail_without_original_detail_feature() {
}
#[test]
fn view_image_tool_includes_detail_with_original_detail_feature() {
fn view_image_tool_includes_detail_with_original_detail_support() {
let mut model_info = model_info();
model_info.supports_image_detail_original = true;
let mut features = Features::with_defaults();
features.enable(Feature::ImageDetailOriginal);
let features = Features::with_defaults();
let available_models = Vec::new();
let tools_config = ToolsConfig::new(&ToolsConfigParams {
model_info: &model_info,

View File

@@ -53,7 +53,3 @@ fn view_image_output_schema() -> Value {
"additionalProperties": false
})
}
#[cfg(test)]
#[path = "view_image_tests.rs"]
mod tests;

View File

@@ -1,54 +0,0 @@
use super::*;
use crate::JsonSchema;
use pretty_assertions::assert_eq;
use std::collections::BTreeMap;
#[test]
fn view_image_tool_omits_detail_without_original_detail_feature() {
assert_eq!(
create_view_image_tool(ViewImageToolOptions {
can_request_original_image_detail: false,
}),
ToolSpec::Function(ResponsesApiTool {
name: "view_image".to_string(),
description: "View a local image from the filesystem (only use if given a full filepath by the user, and the image isn't already attached to the thread context within <image ...> tags)."
.to_string(),
strict: false,
defer_loading: None,
parameters: JsonSchema::object(BTreeMap::from([(
"path".to_string(),
JsonSchema::string(Some("Local filesystem path to an image file".to_string()),),
)]), Some(vec!["path".to_string()]), Some(false.into())),
output_schema: Some(view_image_output_schema()),
})
);
}
#[test]
fn view_image_tool_includes_detail_with_original_detail_feature() {
assert_eq!(
create_view_image_tool(ViewImageToolOptions {
can_request_original_image_detail: true,
}),
ToolSpec::Function(ResponsesApiTool {
name: "view_image".to_string(),
description: "View a local image from the filesystem (only use if given a full filepath by the user, and the image isn't already attached to the thread context within <image ...> tags)."
.to_string(),
strict: false,
defer_loading: None,
parameters: JsonSchema::object(BTreeMap::from([
(
"detail".to_string(),
JsonSchema::string(Some(
"Optional detail override. The only supported value is `original`; omit this field for default resized behavior. Use `original` to preserve the file's original resolution instead of resizing to fit. This is important when high-fidelity image perception or precise localization is needed, especially for CUA agents.".to_string(),
),),
),
(
"path".to_string(),
JsonSchema::string(Some("Local filesystem path to an image file".to_string()),),
),
]), Some(vec!["path".to_string()]), Some(false.into())),
output_schema: Some(view_image_output_schema()),
})
);
}