From d9aefa41599cbf987d8f0965c2f69ecb9f20da8f Mon Sep 17 00:00:00 2001 From: Josh Wright Date: Thu, 11 Jun 2026 15:58:28 -0700 Subject: [PATCH] [imagegen] preserve image generation start events --- codex-rs/core/src/stream_events_utils.rs | 3 +++ codex-rs/core/tests/common/responses.rs | 11 +++++++++++ codex-rs/core/tests/suite/items.rs | 6 ++++++ codex-rs/protocol/src/models.rs | 21 +++++++++++++++++++++ 4 files changed, 41 insertions(+) diff --git a/codex-rs/core/src/stream_events_utils.rs b/codex-rs/core/src/stream_events_utils.rs index 81beea5be1..b0def9ab9c 100644 --- a/codex-rs/core/src/stream_events_utils.rs +++ b/codex-rs/core/src/stream_events_utils.rs @@ -133,6 +133,9 @@ pub(crate) async fn persist_image_generation_item( image_item: &mut ImageGenerationItem, ) -> Option { image_item.saved_path = None; + if image_item.result.is_empty() { + return None; + } let session_id = sess.thread_id.to_string(); match save_image_generation_result( &turn_context.config.codex_home, diff --git a/codex-rs/core/tests/common/responses.rs b/codex-rs/core/tests/common/responses.rs index c917436b6d..e5831a92e8 100644 --- a/codex-rs/core/tests/common/responses.rs +++ b/codex-rs/core/tests/common/responses.rs @@ -812,6 +812,17 @@ pub fn ev_image_generation_call( }) } +pub fn ev_image_generation_call_added(id: &str, status: &str) -> Value { + serde_json::json!({ + "type": "response.output_item.added", + "item": { + "type": "image_generation_call", + "id": id, + "status": status, + } + }) +} + pub fn ev_function_call(call_id: &str, name: &str, arguments: &str) -> Value { serde_json::json!({ "type": "response.output_item.done", diff --git a/codex-rs/core/tests/suite/items.rs b/codex-rs/core/tests/suite/items.rs index a56b2ab95f..f4c2cea524 100644 --- a/codex-rs/core/tests/suite/items.rs +++ b/codex-rs/core/tests/suite/items.rs @@ -20,6 +20,7 @@ use codex_utils_absolute_path::AbsolutePathBuf; use core_test_support::responses::ev_assistant_message; use core_test_support::responses::ev_completed; use core_test_support::responses::ev_image_generation_call; +use core_test_support::responses::ev_image_generation_call_added; use core_test_support::responses::ev_message_item_added; use core_test_support::responses::ev_output_text_delta; use core_test_support::responses::ev_reasoning_item; @@ -372,6 +373,7 @@ async fn image_generation_call_event_is_emitted() -> anyhow::Result<()> { let first_response = sse(vec![ ev_response_created("resp-1"), + ev_image_generation_call_added(call_id, "in_progress"), ev_image_generation_call(call_id, "completed", "A tiny blue square", "Zm9v"), ev_completed("resp-1"), ]); @@ -422,6 +424,10 @@ async fn image_generation_call_event_is_emitted() -> anyhow::Result<()> { assert_eq!(begin.call_id, call_id); assert_eq!(started.0.id, call_id); + assert_eq!(started.0.status, "in_progress"); + assert_eq!(started.0.revised_prompt, None); + assert_eq!(started.0.result, ""); + assert_eq!(started.0.saved_path, None); assert!(started.1 > 0); assert_eq!(completed.0.id, call_id); assert!(completed.1 > 0); diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index 517c33cac9..24ba05ae5b 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -892,6 +892,7 @@ pub enum ResponseItem { #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] revised_prompt: Option, + #[serde(default)] result: String, }, #[serde(alias = "compaction_summary")] @@ -1811,6 +1812,26 @@ mod tests { ); } + #[test] + fn response_item_parses_in_progress_image_generation_call_without_result() { + let item = serde_json::from_value::(serde_json::json!({ + "id": "ig_123", + "type": "image_generation_call", + "status": "in_progress", + })) + .expect("in-progress image generation item should deserialize"); + + assert_eq!( + item, + ResponseItem::ImageGenerationCall { + id: "ig_123".to_string(), + status: "in_progress".to_string(), + revised_prompt: None, + result: String::new(), + } + ); + } + #[test] fn additional_permission_profile_is_empty_when_all_fields_are_none() { assert_eq!(AdditionalPermissionProfile::default().is_empty(), true);