feat: retroactive image placeholder to prevent poisoning (#6774)

If an image can't be read by the API, it will poison the entire history,
preventing any new turn on the conversation.
This detect such cases and replace the image by a placeholder
This commit is contained in:
jif-oai
2025-12-03 11:35:56 +00:00
committed by GitHub
parent 42ae738f67
commit 51307eaf07
7 changed files with 171 additions and 8 deletions

View File

@@ -5,6 +5,8 @@ use crate::truncate::approx_token_count;
use crate::truncate::approx_tokens_from_byte_count;
use crate::truncate::truncate_function_output_items_with_policy;
use crate::truncate::truncate_text;
use codex_protocol::models::ContentItem;
use codex_protocol::models::FunctionCallOutputContentItem;
use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::ResponseItem;
use codex_protocol::protocol::TokenUsage;
@@ -118,6 +120,37 @@ impl ContextManager {
self.items = items;
}
pub(crate) fn replace_last_turn_images(&mut self, placeholder: &str) {
let Some(last_item) = self.items.last_mut() else {
return;
};
match last_item {
ResponseItem::Message { role, content, .. } if role == "user" => {
for item in content.iter_mut() {
if matches!(item, ContentItem::InputImage { .. }) {
*item = ContentItem::InputText {
text: placeholder.to_string(),
};
}
}
}
ResponseItem::FunctionCallOutput { output, .. } => {
let Some(content_items) = output.content_items.as_mut() else {
return;
};
for item in content_items.iter_mut() {
if matches!(item, FunctionCallOutputContentItem::InputImage { .. }) {
*item = FunctionCallOutputContentItem::InputText {
text: placeholder.to_string(),
};
}
}
}
_ => {}
}
}
pub(crate) fn update_token_info(
&mut self,
usage: &TokenUsage,