mirror of
https://github.com/openai/codex.git
synced 2026-11-03 16:23:51 +00:00
Stop retrying turns with invalid tool images (#34380)
## What changed Remove the fallback that replaced image content in the latest tool output with `Invalid image` and retried the model request. Invalid-image responses now use the existing bad-request error path without modifying conversation history. GitOrigin-RevId: f4ea782f16c5451feb261694caa73b77f24c3e4e
This commit is contained in:
1 parent
7e51abbbd1
commit
8431dc590a
4 files changed
-186
No files matched your search
@@ -202,40 +202,6 @@ impl ContextManager {
|
||||
self.world_state_baseline = None;
|
||||
}
|
||||
|
||||
/// Replace image content in the last turn if it originated from a tool output.
|
||||
/// Returns true when a tool image was replaced, false otherwise.
|
||||
pub(crate) fn replace_last_turn_images(&mut self, placeholder: &str) -> bool {
|
||||
let Some(index) = self.items.iter().rposition(|item| {
|
||||
matches!(item, ResponseItem::FunctionCallOutput { .. }) || is_user_turn_boundary(item)
|
||||
}) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
match &mut self.items[index] {
|
||||
ResponseItem::FunctionCallOutput { output, .. } => {
|
||||
let Some(content_items) = output.content_items_mut() else {
|
||||
return false;
|
||||
};
|
||||
let mut replaced = false;
|
||||
let placeholder = placeholder.to_string();
|
||||
for item in content_items.iter_mut() {
|
||||
if matches!(item, FunctionCallOutputContentItem::InputImage { .. }) {
|
||||
*item = FunctionCallOutputContentItem::InputText {
|
||||
text: placeholder.clone(),
|
||||
};
|
||||
replaced = true;
|
||||
}
|
||||
}
|
||||
if replaced {
|
||||
self.history_version = self.history_version.saturating_add(1);
|
||||
}
|
||||
replaced
|
||||
}
|
||||
ResponseItem::Message { .. } => false,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop the last `num_turns` instruction turns from this history.
|
||||
///
|
||||
/// Instruction turns are history messages that should behave like a new prompt boundary:
|
||||
|
||||
@@ -815,68 +815,6 @@ fn remove_first_item_removes_matching_call_for_output() {
|
||||
assert_eq!(h.raw_items(), vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replace_last_turn_images_replaces_tool_output_images() {
|
||||
let items = vec![
|
||||
user_input_text_msg("hi"),
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-1".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
body: FunctionCallOutputBody::ContentItems(vec![
|
||||
FunctionCallOutputContentItem::InputImage {
|
||||
image_url: "data:image/png;base64,AAA".to_string(),
|
||||
detail: Some(DEFAULT_IMAGE_DETAIL),
|
||||
},
|
||||
]),
|
||||
success: Some(true),
|
||||
},
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
];
|
||||
let mut history = create_history_with_items(items);
|
||||
|
||||
assert!(history.replace_last_turn_images("Invalid image"));
|
||||
|
||||
assert_eq!(
|
||||
history.raw_items(),
|
||||
vec![
|
||||
user_input_text_msg("hi"),
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-1".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
body: FunctionCallOutputBody::ContentItems(vec![
|
||||
FunctionCallOutputContentItem::InputText {
|
||||
text: "Invalid image".to_string(),
|
||||
},
|
||||
]),
|
||||
success: Some(true),
|
||||
},
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replace_last_turn_images_does_not_touch_user_images() {
|
||||
let items = vec![ResponseItem::Message {
|
||||
id: None,
|
||||
role: "user".to_string(),
|
||||
content: vec![ContentItem::InputImage {
|
||||
image_url: "data:image/png;base64,AAA".to_string(),
|
||||
detail: Some(DEFAULT_IMAGE_DETAIL),
|
||||
}],
|
||||
phase: None,
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
}];
|
||||
let mut history = create_history_with_items(items.clone());
|
||||
|
||||
assert!(!history.replace_last_turn_images("Invalid image"));
|
||||
assert_eq!(history.raw_items(), items);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_first_item_handles_local_shell_pair() {
|
||||
let items = vec![
|
||||
|
||||
@@ -432,16 +432,6 @@ pub(crate) async fn run_turn(
|
||||
return Err(err);
|
||||
}
|
||||
Err(codex_error @ CodexErr::InvalidImageRequest()) => {
|
||||
{
|
||||
let mut state = sess.state.lock().await;
|
||||
error_or_panic(
|
||||
"Invalid image detected; sanitizing tool output to prevent poisoning",
|
||||
);
|
||||
if state.history.replace_last_turn_images("Invalid image") {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
sess.track_turn_codex_error(turn_context.as_ref(), &codex_error);
|
||||
let error = CodexErrorInfo::BadRequest;
|
||||
sess.emit_turn_error_lifecycle(turn_context.as_ref(), error.clone())
|
||||
|
||||
Reference in new issue
Block a user