mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
Fix: gracefully error out for unsupported images (#7478)
Fix for #7459 ## What Since codex errors out for unsupported images, stop attempting to base64/attach them and instead emit a clear placeholder when the file isn’t a supported image MIME. ## Why Local uploads for unsupported formats (e.g., SVG/GIF/etc.) were dead-ending after decode failures because of the 400 retry loop. Users now get an explicit “cannot attach … unsupported image format …” response. ## How Replace the fallback read/encode path with MIME detection that bails out for non-image or unsupported image types, returning a consistent placeholder. Unreadable and invalid images still produce their existing error placeholders.
This commit is contained in:
1
codex-rs/Cargo.lock
generated
1
codex-rs/Cargo.lock
generated
@@ -1491,7 +1491,6 @@ name = "codex-protocol"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"codex-git",
|
||||
"codex-utils-image",
|
||||
"icu_decimal",
|
||||
|
||||
@@ -14,7 +14,6 @@ workspace = true
|
||||
[dependencies]
|
||||
codex-git = { workspace = true }
|
||||
|
||||
base64 = { workspace = true }
|
||||
codex-utils-image = { workspace = true }
|
||||
icu_decimal = { workspace = true }
|
||||
icu_locale_core = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use base64::Engine;
|
||||
use codex_utils_image::load_and_resize_to_fit;
|
||||
use mcp_types::CallToolResult;
|
||||
use mcp_types::ContentBlock;
|
||||
@@ -175,6 +174,16 @@ fn invalid_image_error_placeholder(
|
||||
}
|
||||
}
|
||||
|
||||
fn unsupported_image_error_placeholder(path: &std::path::Path, mime: &str) -> ContentItem {
|
||||
ContentItem::InputText {
|
||||
text: format!(
|
||||
"Codex cannot attach image at `{}`: unsupported image format `{}`.",
|
||||
path.display(),
|
||||
mime
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResponseInputItem> for ResponseItem {
|
||||
fn from(item: ResponseInputItem) -> Self {
|
||||
match item {
|
||||
@@ -285,37 +294,20 @@ impl From<Vec<UserInput>> for ResponseInputItem {
|
||||
} else if err.is_invalid_image() {
|
||||
invalid_image_error_placeholder(&path, &err)
|
||||
} else {
|
||||
match std::fs::read(&path) {
|
||||
Ok(bytes) => {
|
||||
let Some(mime_guess) = mime_guess::from_path(&path).first()
|
||||
else {
|
||||
return local_image_error_placeholder(
|
||||
&path,
|
||||
"unsupported MIME type (unknown)",
|
||||
);
|
||||
};
|
||||
let mime = mime_guess.essence_str().to_owned();
|
||||
if !mime.starts_with("image/") {
|
||||
return local_image_error_placeholder(
|
||||
&path,
|
||||
format!("unsupported MIME type `{mime}`"),
|
||||
);
|
||||
}
|
||||
let encoded =
|
||||
base64::engine::general_purpose::STANDARD.encode(bytes);
|
||||
ContentItem::InputImage {
|
||||
image_url: format!("data:{mime};base64,{encoded}"),
|
||||
}
|
||||
}
|
||||
Err(read_err) => {
|
||||
tracing::warn!(
|
||||
"Skipping image {} – could not read file: {}",
|
||||
path.display(),
|
||||
read_err
|
||||
);
|
||||
local_image_error_placeholder(&path, &read_err)
|
||||
}
|
||||
let Some(mime_guess) = mime_guess::from_path(&path).first() else {
|
||||
return local_image_error_placeholder(
|
||||
&path,
|
||||
"unsupported MIME type (unknown)",
|
||||
);
|
||||
};
|
||||
let mime = mime_guess.essence_str().to_owned();
|
||||
if !mime.starts_with("image/") {
|
||||
return local_image_error_placeholder(
|
||||
&path,
|
||||
format!("unsupported MIME type `{mime}`"),
|
||||
);
|
||||
}
|
||||
unsupported_image_error_placeholder(&path, &mime)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -823,4 +815,36 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_image_unsupported_image_format_adds_placeholder() -> Result<()> {
|
||||
let dir = tempdir()?;
|
||||
let svg_path = dir.path().join("example.svg");
|
||||
std::fs::write(
|
||||
&svg_path,
|
||||
br#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg>"#,
|
||||
)?;
|
||||
|
||||
let item = ResponseInputItem::from(vec![UserInput::LocalImage {
|
||||
path: svg_path.clone(),
|
||||
}]);
|
||||
|
||||
match item {
|
||||
ResponseInputItem::Message { content, .. } => {
|
||||
assert_eq!(content.len(), 1);
|
||||
let expected = format!(
|
||||
"Codex cannot attach image at `{}`: unsupported image format `image/svg+xml`.",
|
||||
svg_path.display()
|
||||
);
|
||||
match &content[0] {
|
||||
ContentItem::InputText { text } => assert_eq!(text, &expected),
|
||||
other => panic!("expected placeholder text but found {other:?}"),
|
||||
}
|
||||
}
|
||||
other => panic!("expected message response but got {other:?}"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user