mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
Store generated image items as attachment references (#54398)
## What changed - Upload generated images to the attachment store and expose `fileId` in image-generation items when available, with inline `result` fallback on upload or decoding failure. - Preserve file references through legacy events, persisted rollouts, thread history, and task output summaries. Keep model-facing image preparation separate. - Add attachment request tags and mark generated-image uploads with `usage=image_gen`, preserving the thread's ephemeral setting. - Update public schemas and SDK types to accept either image representation, retain the Python `ImageGenerationThreadItem` alias, and prefer `fileId` when reading records containing both fields. ## Testing Add coverage for file-backed generation and rollout persistence, legacy history replay, inline and transitional serialization, task output visibility, and Python SDK alias compatibility. GitOrigin-RevId: f7a2bb5707e2f277348a547a988bc33c8e96d891
This commit is contained in:
committed by
copyberry
parent
1ef30bcbe7
commit
aabf13307a
@@ -282,3 +282,7 @@ fn persisted_item_completed_event<'a>(
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "policy_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
use codex_extension_items::ExtensionItem;
|
||||
use codex_extension_items::image_generation::GeneratedImageRef;
|
||||
use codex_extension_items::image_generation::ImageGenerationItem;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::ImageGenerationEndEvent;
|
||||
use codex_protocol::protocol::ItemCompletedEvent;
|
||||
use codex_protocol::protocol::ThreadHistoryMode;
|
||||
|
||||
use super::persisted_event_msg;
|
||||
|
||||
fn image_item(image: GeneratedImageRef) -> ImageGenerationItem {
|
||||
ImageGenerationItem {
|
||||
id: "image-1".to_string(),
|
||||
status: "completed".to_string(),
|
||||
revised_prompt: Some("paint a blue whale".to_string()),
|
||||
image,
|
||||
transparent_background: Some(false),
|
||||
failure: None,
|
||||
saved_path: None,
|
||||
imagegen_request_id: None,
|
||||
generation_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paginated_image_generation_preserves_file_reference() {
|
||||
let event = EventMsg::ItemCompleted(ItemCompletedEvent {
|
||||
thread_id: ThreadId::new(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
item: TurnItem::Extension(ExtensionItem::ImageGeneration(image_item(
|
||||
GeneratedImageRef::File {
|
||||
file_id: "file-1".to_string(),
|
||||
},
|
||||
))),
|
||||
started_at_ms: Some(1),
|
||||
completed_at_ms: 2,
|
||||
});
|
||||
|
||||
let persisted = persisted_event_msg(&event, ThreadHistoryMode::Paginated)
|
||||
.expect("expected persisted image-generation item");
|
||||
let EventMsg::ItemCompleted(persisted) = persisted.as_ref() else {
|
||||
panic!("expected persisted image-generation item");
|
||||
};
|
||||
let TurnItem::Extension(ExtensionItem::ImageGeneration(image)) = &persisted.item else {
|
||||
panic!("expected image-generation extension item");
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
image,
|
||||
&image_item(GeneratedImageRef::File {
|
||||
file_id: "file-1".to_string(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_image_generation_preserves_file_reference() {
|
||||
let event = EventMsg::ImageGenerationEnd(ImageGenerationEndEvent {
|
||||
call_id: "image-1".to_string(),
|
||||
status: "completed".to_string(),
|
||||
revised_prompt: Some("paint a blue whale".to_string()),
|
||||
image: GeneratedImageRef::File {
|
||||
file_id: "file-1".to_string(),
|
||||
},
|
||||
transparent_background: Some(false),
|
||||
failure: None,
|
||||
saved_path: None,
|
||||
});
|
||||
|
||||
let persisted = persisted_event_msg(&event, ThreadHistoryMode::Legacy)
|
||||
.expect("expected persisted legacy image-generation event");
|
||||
let EventMsg::ImageGenerationEnd(persisted) = persisted.as_ref() else {
|
||||
panic!("expected persisted legacy image-generation event");
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
persisted.image,
|
||||
GeneratedImageRef::File {
|
||||
file_id: "file-1".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inline_image_generation_result_is_preserved() {
|
||||
let event = EventMsg::ItemCompleted(ItemCompletedEvent {
|
||||
thread_id: ThreadId::new(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
item: TurnItem::Extension(ExtensionItem::ImageGeneration(image_item(
|
||||
GeneratedImageRef::Inline {
|
||||
result: "base64-image".to_string(),
|
||||
},
|
||||
))),
|
||||
started_at_ms: Some(1),
|
||||
completed_at_ms: 2,
|
||||
});
|
||||
|
||||
let persisted = persisted_event_msg(&event, ThreadHistoryMode::Paginated)
|
||||
.expect("expected persisted inline image-generation item");
|
||||
let EventMsg::ItemCompleted(persisted) = persisted.as_ref() else {
|
||||
panic!("expected persisted inline image-generation item");
|
||||
};
|
||||
let TurnItem::Extension(ExtensionItem::ImageGeneration(image)) = &persisted.item else {
|
||||
panic!("expected inline image-generation extension item");
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
image,
|
||||
&image_item(GeneratedImageRef::Inline {
|
||||
result: "base64-image".to_string(),
|
||||
})
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user