diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index ef5a02e8fc..fe64b5da23 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -694,6 +694,17 @@ impl Codex { self.session.steer_input(input, expected_turn_id).await } + /// Use sparingly: inject response input into the active turn. + /// + /// Returns the input unchanged when there is no active turn. + #[doc(hidden)] + pub async fn inject_response_items( + &self, + input: Vec, + ) -> Result<(), Vec> { + self.session.inject_response_items(input).await + } + pub(crate) async fn set_app_server_client_name( &self, app_server_client_name: Option, diff --git a/codex-rs/core/src/codex_thread.rs b/codex-rs/core/src/codex_thread.rs index 7a243f9f74..57653400a2 100644 --- a/codex-rs/core/src/codex_thread.rs +++ b/codex-rs/core/src/codex_thread.rs @@ -88,6 +88,17 @@ impl CodexThread { self.codex.steer_input(input, expected_turn_id).await } + /// Use sparingly: inject response input into the active turn. + /// + /// Returns the input unchanged when there is no active turn. + #[doc(hidden)] + pub async fn inject_response_items( + &self, + input: Vec, + ) -> Result<(), Vec> { + self.codex.inject_response_items(input).await + } + pub async fn set_app_server_client_name( &self, app_server_client_name: Option, diff --git a/codex-rs/core/tests/suite/items.rs b/codex-rs/core/tests/suite/items.rs index f08cbfa3fd..269cd22f47 100644 --- a/codex-rs/core/tests/suite/items.rs +++ b/codex-rs/core/tests/suite/items.rs @@ -68,30 +68,6 @@ fn user_message_item_by_text<'a>(input: &'a [Value], text: &str) -> &'a Value { .unwrap_or_else(|| panic!("submitted user message input item not found for text: {text}")) } -fn user_message_item_containing<'a>(input: &'a [Value], needle: &str) -> &'a Value { - input - .iter() - .find(|item| { - if item.get("type").and_then(Value::as_str) != Some("message") - || item.get("role").and_then(Value::as_str) != Some("user") - { - return false; - } - item.get("content") - .and_then(Value::as_array) - .is_some_and(|content| { - content.iter().any(|entry| { - entry.get("type").and_then(Value::as_str) == Some("input_text") - && entry - .get("text") - .and_then(Value::as_str) - .is_some_and(|text| text.contains(needle)) - }) - }) - }) - .unwrap_or_else(|| panic!("submitted user message input item not found containing: {needle}")) -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn user_message_item_is_emitted() -> anyhow::Result<()> { skip_if_no_network!(Ok(())); @@ -458,20 +434,18 @@ async fn user_message_type_prompt_queued_metadata_is_emitted_when_feature_enable .await; let queued_text = "queued metadata check"; - codex - .submit(Op::RunUserShellCommand { - command: format!("printf '{queued_text}'"), - }) - .await?; - wait_for_event_match(&codex, |ev| match ev { - EventMsg::ExecCommandEnd(event) - if event.source == codex_protocol::protocol::ExecCommandSource::UserShell => - { - Some(()) - } - _ => None, - }) - .await; + assert!( + codex + .inject_response_items(vec![codex_protocol::models::ResponseInputItem::Message { + role: "user".into(), + content: vec![codex_protocol::models::ContentItem::InputText { + text: queued_text.into(), + }], + }]) + .await + .is_ok(), + "inject_response_items should succeed on active turn" + ); std::fs::write(&unblock_path, "go")?; @@ -498,7 +472,7 @@ async fn user_message_type_prompt_queued_metadata_is_emitted_when_feature_enable .get("input") .and_then(Value::as_array) .expect("request input array"); - let queued_message = user_message_item_containing(input, queued_text); + let queued_message = user_message_item_by_text(input, queued_text); assert_eq!( queued_message .get("metadata")