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:
Krish Chainani
2026-10-15 19:51:22 +00:00
committed by copyberry
parent 1ef30bcbe7
commit aabf13307a
64 changed files with 1229 additions and 125 deletions
@@ -27,6 +27,7 @@ use crate::protocol::v2::UserInput;
use crate::protocol::v2::WebSearchAction;
use crate::protocol::v2::WebSearchItem;
use crate::protocol::v2::web_search_action_from_core;
use codex_extension_items::image_generation::GeneratedImageRef;
use codex_extension_items::image_generation::ImageGenerationItem;
use codex_protocol::items::parse_hook_prompt_message;
use codex_protocol::protocol::AgentMessageEvent;
@@ -899,7 +900,9 @@ impl ThreadHistoryBuilder {
id: payload.call_id.clone(),
status: String::new(),
revised_prompt: None,
result: String::new(),
image: GeneratedImageRef::Inline {
result: String::new(),
},
transparent_background: None,
failure: None,
saved_path: None,
@@ -914,7 +917,7 @@ impl ThreadHistoryBuilder {
id: payload.call_id.clone(),
status: payload.status.clone(),
revised_prompt: payload.revised_prompt.clone(),
result: payload.result.clone(),
image: payload.image.clone(),
transparent_background: payload.transparent_background,
failure: payload.failure.clone(),
saved_path: payload.saved_path.clone(),
@@ -2348,7 +2351,9 @@ mod tests {
id: "image-1".to_string(),
status: "completed".to_string(),
revised_prompt: Some("A blue square".to_string()),
result: "cG5n".to_string(),
image: GeneratedImageRef::Inline {
result: "cG5n".to_string(),
},
transparent_background: Some(true),
failure: None,
saved_path: Some(saved_path.clone()),
@@ -2383,7 +2388,9 @@ mod tests {
id: "image-1".to_string(),
status: "completed".to_string(),
revised_prompt: Some("A blue square".to_string()),
result: "cG5n".to_string(),
image: GeneratedImageRef::Inline {
result: "cG5n".to_string(),
},
transparent_background: Some(true),
failure: None,
saved_path: Some(saved_path),
@@ -2648,7 +2655,7 @@ mod tests {
}
#[test]
fn replays_image_generation_end_events_into_turn_history() {
fn replays_file_backed_image_generation_end_events_into_turn_history() {
let items = vec![
RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {
turn_attribution: None,
@@ -2671,7 +2678,9 @@ mod tests {
call_id: "ig_123".into(),
status: "completed".into(),
revised_prompt: Some("final prompt".into()),
result: "Zm9v".into(),
image: GeneratedImageRef::File {
file_id: "file_123".into(),
},
transparent_background: Some(true),
failure: Some(
codex_extension_items::image_generation::ImageGenerationFailure::UsageLimitExceeded {
@@ -2719,7 +2728,9 @@ mod tests {
id: "ig_123".into(),
status: "completed".into(),
revised_prompt: Some("final prompt".into()),
result: "Zm9v".into(),
image: GeneratedImageRef::File {
file_id: "file_123".into(),
},
transparent_background: Some(true),
failure: Some(
codex_extension_items::image_generation::ImageGenerationFailure::UsageLimitExceeded {
@@ -15,6 +15,7 @@ use crate::protocol::item_builders::convert_patch_changes;
use crate::protocol::item_builders::review_output_text;
use codex_experimental_api_macros::ExperimentalApi;
use codex_extension_items::ExtensionItem;
pub use codex_extension_items::image_generation::GeneratedImageRef;
pub use codex_extension_items::image_generation::ImageGenerationFailure;
pub use codex_extension_items::image_generation::ImageGenerationItem;
pub use codex_extension_items::sleep::SleepItem;
@@ -1009,7 +1010,9 @@ impl From<CoreTurnItem> for ThreadItem {
id: image.id,
status: image.status,
revised_prompt: image.revised_prompt,
result: image.result,
image: GeneratedImageRef::Inline {
result: image.result,
},
transparent_background: None,
failure: None,
saved_path: image.saved_path,