@@ -154,6 +154,26 @@ fn developer_input_texts(items: &[ResponseItem]) -> Vec<&str> {
154154 . collect ( )
155155}
156156
157+ fn default_image_save_developer_message_text ( ) -> String {
158+ let image_output_dir = crate :: stream_events_utils:: default_image_generation_output_dir ( ) ;
159+ format ! (
160+ "Generated images are saved to {} as {} by default." ,
161+ image_output_dir. display( ) ,
162+ image_output_dir. join( "<image_id>.png" ) . display( ) ,
163+ )
164+ }
165+
166+ fn test_tool_runtime ( session : Arc < Session > , turn_context : Arc < TurnContext > ) -> ToolCallRuntime {
167+ let router = Arc :: new ( ToolRouter :: from_config (
168+ & turn_context. tools_config ,
169+ None ,
170+ None ,
171+ turn_context. dynamic_tools . as_slice ( ) ,
172+ ) ) ;
173+ let tracker = Arc :: new ( tokio:: sync:: Mutex :: new ( TurnDiffTracker :: new ( ) ) ) ;
174+ ToolCallRuntime :: new ( router, session, turn_context, tracker)
175+ }
176+
157177fn make_connector ( id : & str , name : & str ) -> AppInfo {
158178 AppInfo {
159179 id : id. to_string ( ) ,
@@ -3123,6 +3143,114 @@ async fn build_initial_context_uses_previous_realtime_state() {
31233143 ) ;
31243144}
31253145
3146+ #[ tokio:: test]
3147+ async fn build_initial_context_omits_default_image_save_location_with_image_history ( ) {
3148+ let ( session, turn_context) = make_session_and_context ( ) . await ;
3149+ session
3150+ . replace_history (
3151+ vec ! [ ResponseItem :: ImageGenerationCall {
3152+ id: "ig-test" . to_string( ) ,
3153+ status: "completed" . to_string( ) ,
3154+ revised_prompt: Some ( "a tiny blue square" . to_string( ) ) ,
3155+ result: "Zm9v" . to_string( ) ,
3156+ } ] ,
3157+ None ,
3158+ )
3159+ . await ;
3160+
3161+ let initial_context = session. build_initial_context ( & turn_context) . await ;
3162+ let developer_texts = developer_input_texts ( & initial_context) ;
3163+ assert ! (
3164+ !developer_texts
3165+ . iter( )
3166+ . any( |text| text. contains( "Generated images are saved to" ) ) ,
3167+ "expected initial context to omit image save instructions even with image history, got {developer_texts:?}"
3168+ ) ;
3169+ }
3170+
3171+ #[ tokio:: test]
3172+ async fn build_initial_context_omits_default_image_save_location_without_image_history ( ) {
3173+ let ( session, turn_context) = make_session_and_context ( ) . await ;
3174+
3175+ let initial_context = session. build_initial_context ( & turn_context) . await ;
3176+ let developer_texts = developer_input_texts ( & initial_context) ;
3177+
3178+ assert ! (
3179+ !developer_texts
3180+ . iter( )
3181+ . any( |text| text. contains( "Generated images are saved to" ) ) ,
3182+ "expected initial context to omit image save instructions without image history, got {developer_texts:?}"
3183+ ) ;
3184+ }
3185+
3186+ #[ tokio:: test]
3187+ async fn handle_output_item_done_records_image_save_message_after_successful_save ( ) {
3188+ let ( session, turn_context) = make_session_and_context ( ) . await ;
3189+ let session = Arc :: new ( session) ;
3190+ let turn_context = Arc :: new ( turn_context) ;
3191+ let call_id = "ig_history_records_message" ;
3192+ let expected_saved_path = crate :: stream_events_utils:: default_image_generation_output_dir ( )
3193+ . join ( format ! ( "{call_id}.png" ) ) ;
3194+ let _ = std:: fs:: remove_file ( & expected_saved_path) ;
3195+ let item = ResponseItem :: ImageGenerationCall {
3196+ id : call_id. to_string ( ) ,
3197+ status : "completed" . to_string ( ) ,
3198+ revised_prompt : Some ( "a tiny blue square" . to_string ( ) ) ,
3199+ result : "Zm9v" . to_string ( ) ,
3200+ } ;
3201+
3202+ let mut ctx = HandleOutputCtx {
3203+ sess : Arc :: clone ( & session) ,
3204+ turn_context : Arc :: clone ( & turn_context) ,
3205+ tool_runtime : test_tool_runtime ( Arc :: clone ( & session) , Arc :: clone ( & turn_context) ) ,
3206+ cancellation_token : CancellationToken :: new ( ) ,
3207+ } ;
3208+ handle_output_item_done ( & mut ctx, item. clone ( ) , None )
3209+ . await
3210+ . expect ( "image generation item should succeed" ) ;
3211+
3212+ let history = session. clone_history ( ) . await ;
3213+ let expected_message: ResponseItem =
3214+ DeveloperInstructions :: new ( default_image_save_developer_message_text ( ) ) . into ( ) ;
3215+ assert_eq ! ( history. raw_items( ) , & [ expected_message, item] ) ;
3216+ assert_eq ! (
3217+ std:: fs:: read( & expected_saved_path) . expect( "saved file" ) ,
3218+ b"foo"
3219+ ) ;
3220+ let _ = std:: fs:: remove_file ( & expected_saved_path) ;
3221+ }
3222+
3223+ #[ tokio:: test]
3224+ async fn handle_output_item_done_skips_image_save_message_when_save_fails ( ) {
3225+ let ( session, turn_context) = make_session_and_context ( ) . await ;
3226+ let session = Arc :: new ( session) ;
3227+ let turn_context = Arc :: new ( turn_context) ;
3228+ let call_id = "ig_history_no_message" ;
3229+ let expected_saved_path = crate :: stream_events_utils:: default_image_generation_output_dir ( )
3230+ . join ( format ! ( "{call_id}.png" ) ) ;
3231+ let _ = std:: fs:: remove_file ( & expected_saved_path) ;
3232+ let item = ResponseItem :: ImageGenerationCall {
3233+ id : call_id. to_string ( ) ,
3234+ status : "completed" . to_string ( ) ,
3235+ revised_prompt : Some ( "broken payload" . to_string ( ) ) ,
3236+ result : "_-8" . to_string ( ) ,
3237+ } ;
3238+
3239+ let mut ctx = HandleOutputCtx {
3240+ sess : Arc :: clone ( & session) ,
3241+ turn_context : Arc :: clone ( & turn_context) ,
3242+ tool_runtime : test_tool_runtime ( Arc :: clone ( & session) , Arc :: clone ( & turn_context) ) ,
3243+ cancellation_token : CancellationToken :: new ( ) ,
3244+ } ;
3245+ handle_output_item_done ( & mut ctx, item. clone ( ) , None )
3246+ . await
3247+ . expect ( "image generation item should still complete" ) ;
3248+
3249+ let history = session. clone_history ( ) . await ;
3250+ assert_eq ! ( history. raw_items( ) , & [ item] ) ;
3251+ assert ! ( !expected_saved_path. exists( ) ) ;
3252+ }
3253+
31263254#[ tokio:: test]
31273255async fn build_initial_context_uses_previous_turn_settings_for_realtime_end ( ) {
31283256 let ( session, turn_context) = make_session_and_context ( ) . await ;
0 commit comments