unifying all image saves to /tmp to bug-proof (#14149)

image-gen feature will have the model saving to /tmp by default + at all
times
This commit is contained in:
Won Park
2026-03-10 15:13:12 -07:00
committed by GitHub
parent 2895d3571b
commit 28934762d0
8 changed files with 214 additions and 82 deletions

View File

@@ -154,6 +154,26 @@ fn developer_input_texts(items: &[ResponseItem]) -> Vec<&str> {
.collect()
}
fn default_image_save_developer_message_text() -> String {
let image_output_dir = crate::stream_events_utils::default_image_generation_output_dir();
format!(
"Generated images are saved to {} as {} by default.",
image_output_dir.display(),
image_output_dir.join("<image_id>.png").display(),
)
}
fn test_tool_runtime(session: Arc<Session>, turn_context: Arc<TurnContext>) -> ToolCallRuntime {
let router = Arc::new(ToolRouter::from_config(
&turn_context.tools_config,
None,
None,
turn_context.dynamic_tools.as_slice(),
));
let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));
ToolCallRuntime::new(router, session, turn_context, tracker)
}
fn make_connector(id: &str, name: &str) -> AppInfo {
AppInfo {
id: id.to_string(),
@@ -3123,6 +3143,114 @@ async fn build_initial_context_uses_previous_realtime_state() {
);
}
#[tokio::test]
async fn build_initial_context_omits_default_image_save_location_with_image_history() {
let (session, turn_context) = make_session_and_context().await;
session
.replace_history(
vec![ResponseItem::ImageGenerationCall {
id: "ig-test".to_string(),
status: "completed".to_string(),
revised_prompt: Some("a tiny blue square".to_string()),
result: "Zm9v".to_string(),
}],
None,
)
.await;
let initial_context = session.build_initial_context(&turn_context).await;
let developer_texts = developer_input_texts(&initial_context);
assert!(
!developer_texts
.iter()
.any(|text| text.contains("Generated images are saved to")),
"expected initial context to omit image save instructions even with image history, got {developer_texts:?}"
);
}
#[tokio::test]
async fn build_initial_context_omits_default_image_save_location_without_image_history() {
let (session, turn_context) = make_session_and_context().await;
let initial_context = session.build_initial_context(&turn_context).await;
let developer_texts = developer_input_texts(&initial_context);
assert!(
!developer_texts
.iter()
.any(|text| text.contains("Generated images are saved to")),
"expected initial context to omit image save instructions without image history, got {developer_texts:?}"
);
}
#[tokio::test]
async fn handle_output_item_done_records_image_save_message_after_successful_save() {
let (session, turn_context) = make_session_and_context().await;
let session = Arc::new(session);
let turn_context = Arc::new(turn_context);
let call_id = "ig_history_records_message";
let expected_saved_path = crate::stream_events_utils::default_image_generation_output_dir()
.join(format!("{call_id}.png"));
let _ = std::fs::remove_file(&expected_saved_path);
let item = ResponseItem::ImageGenerationCall {
id: call_id.to_string(),
status: "completed".to_string(),
revised_prompt: Some("a tiny blue square".to_string()),
result: "Zm9v".to_string(),
};
let mut ctx = HandleOutputCtx {
sess: Arc::clone(&session),
turn_context: Arc::clone(&turn_context),
tool_runtime: test_tool_runtime(Arc::clone(&session), Arc::clone(&turn_context)),
cancellation_token: CancellationToken::new(),
};
handle_output_item_done(&mut ctx, item.clone(), None)
.await
.expect("image generation item should succeed");
let history = session.clone_history().await;
let expected_message: ResponseItem =
DeveloperInstructions::new(default_image_save_developer_message_text()).into();
assert_eq!(history.raw_items(), &[expected_message, item]);
assert_eq!(
std::fs::read(&expected_saved_path).expect("saved file"),
b"foo"
);
let _ = std::fs::remove_file(&expected_saved_path);
}
#[tokio::test]
async fn handle_output_item_done_skips_image_save_message_when_save_fails() {
let (session, turn_context) = make_session_and_context().await;
let session = Arc::new(session);
let turn_context = Arc::new(turn_context);
let call_id = "ig_history_no_message";
let expected_saved_path = crate::stream_events_utils::default_image_generation_output_dir()
.join(format!("{call_id}.png"));
let _ = std::fs::remove_file(&expected_saved_path);
let item = ResponseItem::ImageGenerationCall {
id: call_id.to_string(),
status: "completed".to_string(),
revised_prompt: Some("broken payload".to_string()),
result: "_-8".to_string(),
};
let mut ctx = HandleOutputCtx {
sess: Arc::clone(&session),
turn_context: Arc::clone(&turn_context),
tool_runtime: test_tool_runtime(Arc::clone(&session), Arc::clone(&turn_context)),
cancellation_token: CancellationToken::new(),
};
handle_output_item_done(&mut ctx, item.clone(), None)
.await
.expect("image generation item should still complete");
let history = session.clone_history().await;
assert_eq!(history.raw_items(), &[item]);
assert!(!expected_saved_path.exists());
}
#[tokio::test]
async fn build_initial_context_uses_previous_turn_settings_for_realtime_end() {
let (session, turn_context) = make_session_and_context().await;