mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +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 parent
9df70a0772
commit
ac3237721e
3 files changed
+55
-33
No files matched your search
Generated
-1
@@ -1491,7 +1491,6 @@ name = "codex-protocol"
|
|||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
|
||||||
"codex-git",
|
"codex-git",
|
||||||
"codex-utils-image",
|
"codex-utils-image",
|
||||||
"icu_decimal",
|
"icu_decimal",
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
codex-git = { workspace = true }
|
codex-git = { workspace = true }
|
||||||
|
|
||||||
base64 = { workspace = true }
|
|
||||||
codex-utils-image = { workspace = true }
|
codex-utils-image = { workspace = true }
|
||||||
icu_decimal = { workspace = true }
|
icu_decimal = { workspace = true }
|
||||||
icu_locale_core = { workspace = true }
|
icu_locale_core = { workspace = true }
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use base64::Engine;
|
|
||||||
use codex_utils_image::load_and_resize_to_fit;
|
use codex_utils_image::load_and_resize_to_fit;
|
||||||
use mcp_types::CallToolResult;
|
use mcp_types::CallToolResult;
|
||||||
use mcp_types::ContentBlock;
|
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 {
|
impl From<ResponseInputItem> for ResponseItem {
|
||||||
fn from(item: ResponseInputItem) -> Self {
|
fn from(item: ResponseInputItem) -> Self {
|
||||||
match item {
|
match item {
|
||||||
@@ -285,37 +294,20 @@ impl From<Vec<UserInput>> for ResponseInputItem {
|
|||||||
} else if err.is_invalid_image() {
|
} else if err.is_invalid_image() {
|
||||||
invalid_image_error_placeholder(&path, &err)
|
invalid_image_error_placeholder(&path, &err)
|
||||||
} else {
|
} else {
|
||||||
match std::fs::read(&path) {
|
let Some(mime_guess) = mime_guess::from_path(&path).first() else {
|
||||||
Ok(bytes) => {
|
return local_image_error_placeholder(
|
||||||
let Some(mime_guess) = mime_guess::from_path(&path).first()
|
&path,
|
||||||
else {
|
"unsupported MIME type (unknown)",
|
||||||
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(
|
||||||
let mime = mime_guess.essence_str().to_owned();
|
&path,
|
||||||
if !mime.starts_with("image/") {
|
format!("unsupported MIME type `{mime}`"),
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
unsupported_image_error_placeholder(&path, &mime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -823,4 +815,36 @@ mod tests {
|
|||||||
|
|
||||||
Ok(())
|
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