From e0c21f85ba20aad3b3ca5b2bebcda0ca6784db52 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Fri, 20 Mar 2026 09:34:05 -0700 Subject: [PATCH] Revert "Remove js_repl emit-image view_image integration test" This reverts commit 757579935ac44e5ef4a2e57f99e3446238e7299d. --- codex-rs/core/tests/suite/code_mode.rs | 12 ++- codex-rs/core/tests/suite/view_image.rs | 123 ++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 5 deletions(-) diff --git a/codex-rs/core/tests/suite/code_mode.rs b/codex-rs/core/tests/suite/code_mode.rs index 2650d5d5b8..3f7771a2de 100644 --- a/codex-rs/core/tests/suite/code_mode.rs +++ b/codex-rs/core/tests/suite/code_mode.rs @@ -1815,14 +1815,16 @@ async fn code_mode_can_use_view_image_result_with_image_helper() -> Result<()> { }); let test = builder.build_remote_aware(&server).await?; - let code = r#" + let code = format!( + r#" const pngBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg=="; -await tools.exec_command({ +await tools.exec_command({{ cmd: "printf '%s' '" + pngBase64 + "' | base64 --decode > code_mode_view_image.png" -}); -const out = await tools.view_image({ path: "code_mode_view_image.png", detail: "original" }); +}}); +const out = await tools.view_image({{ path: "code_mode_view_image.png", detail: "original" }}); image(out); -"#.to_string(); +"# + ); responses::mount_sse_once( &server, diff --git a/codex-rs/core/tests/suite/view_image.rs b/codex-rs/core/tests/suite/view_image.rs index c5c8cd095d..a91efb6a28 100644 --- a/codex-rs/core/tests/suite/view_image.rs +++ b/codex-rs/core/tests/suite/view_image.rs @@ -866,6 +866,129 @@ async fn view_image_tool_does_not_force_original_resolution_with_capability_feat Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn js_repl_emit_image_attaches_local_image() -> anyhow::Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let mut builder = test_codex().with_config(|config| { + config + .features + .enable(Feature::JsRepl) + .expect("test config should allow feature update"); + }); + let test = builder.build_remote_aware(&server).await?; + let TestCodex { + codex, + config, + session_configured, + .. + } = &test; + + let call_id = "js-repl-view-image"; + let js_input = r#" +const path = await import("node:path"); +const pngBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg=="; +const imagePath = path.join(codex.cwd, "js-repl-view-image.png"); +await codex.tool("shell_command", { + command: `mkdir -p ${JSON.stringify(path.dirname(imagePath))} && printf '%s' '${pngBase64}' | base64 --decode > ${JSON.stringify(imagePath)}`, +}); +const out = await codex.tool("view_image", { path: imagePath }); +await codex.emitImage(out); +"#; + + let first_response = sse(vec![ + ev_response_created("resp-1"), + ev_custom_tool_call(call_id, "js_repl", js_input), + ev_completed("resp-1"), + ]); + responses::mount_sse_once(&server, first_response).await; + + let second_response = sse(vec![ + ev_assistant_message("msg-1", "done"), + ev_completed("resp-2"), + ]); + let mock = responses::mount_sse_once(&server, second_response).await; + + let session_model = session_configured.model.clone(); + codex + .submit(Op::UserTurn { + items: vec![UserInput::Text { + text: "use js_repl to write an image and attach it".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + cwd: config.cwd.clone(), + approval_policy: AskForApproval::Never, + sandbox_policy: SandboxPolicy::DangerFullAccess, + model: session_model, + effort: None, + summary: None, + service_tier: None, + collaboration_mode: None, + personality: None, + }) + .await?; + + let mut tool_event = None; + wait_for_event_with_timeout( + codex, + |event| match event { + EventMsg::ViewImageToolCall(_) => { + tool_event = Some(event.clone()); + false + } + EventMsg::TurnComplete(_) => true, + _ => false, + }, + Duration::from_secs(10), + ) + .await; + match tool_event { + Some(EventMsg::ViewImageToolCall(event)) => { + assert!( + event.path.ends_with("js-repl-view-image.png"), + "unexpected image path: {}", + event.path.display() + ); + } + other if !remote_test_env_enabled() => { + panic!("expected ViewImageToolCall event, got {other:?}") + } + _ => {} + } + + let req = mock.single_request(); + let body = req.body_json(); + assert_eq!( + image_messages(&body).len(), + 0, + "js_repl view_image should not inject a pending input image message" + ); + + let custom_output = req.custom_tool_call_output(call_id); + match custom_output.get("output").and_then(Value::as_array) { + Some(output_items) => { + let image_url = output_items + .iter() + .find_map(|item| { + (item.get("type").and_then(Value::as_str) == Some("input_image")) + .then(|| item.get("image_url").and_then(Value::as_str)) + .flatten() + }) + .expect("image_url present in js_repl custom tool output"); + assert!( + image_url.starts_with("data:image/png;base64,"), + "expected png data URL, got {image_url}" + ); + } + None if remote_test_env_enabled() => {} + None => panic!("custom_tool_call_output should be a content item array"), + } + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn js_repl_view_image_requires_explicit_emit() -> anyhow::Result<()> { skip_if_no_network!(Ok(()));