From 658d69e1a4327e416bc1894fe09b556f7f8c33ed Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Thu, 10 Jul 2025 19:37:50 -0700 Subject: [PATCH 1/6] add summary operation --- codex-rs/core/src/codex.rs | 22 ++++++++++++++++++++++ codex-rs/core/src/protocol.rs | 5 +++++ 2 files changed, 27 insertions(+) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 52c37c51ee..c27a78cc97 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -782,6 +782,28 @@ async fn submission_loop( } }); } + + Op::SummarizeContext => { + let sess = match sess.as_ref() { + Some(sess) => sess, + None => { + send_no_session_event(sub.id).await; + continue; + } + }; + + // Create a summarization request as user input + let summarization_prompt = vec![InputItem::Text { + text: "Please provide a summary of our conversation so far, highlighting key points, decisions made, and any important context that would be useful for future reference. This summary will be used to replace our conversation history with a more concise version so choose what details you will need to continue your work.".to_string(), + }]; + + // attempt to inject input into current task + if let Err(items) = sess.inject_input(summarization_prompt) { + // no current task, spawn a new one + let task = AgentTask::spawn(Arc::clone(sess), sub.id, items); + sess.set_task(task); + } + } } } debug!("Agent loop exited"); diff --git a/codex-rs/core/src/protocol.rs b/codex-rs/core/src/protocol.rs index fa25a2fe38..dafd001dd6 100644 --- a/codex-rs/core/src/protocol.rs +++ b/codex-rs/core/src/protocol.rs @@ -108,6 +108,11 @@ pub enum Op { /// Request a single history entry identified by `log_id` + `offset`. GetHistoryEntryRequest { offset: usize, log_id: u64 }, + + /// Request the agent to summarize the current conversation context. + /// The agent will use its existing context (either conversation history or previous response id) + /// to generate a summary which will be returned as an AgentMessage event. + SummarizeContext, } /// Determines the conditions under which the user is consulted to approve From f77fab3d2dba11229ce128d0e7c458fd5f831db7 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Thu, 10 Jul 2025 20:53:07 -0700 Subject: [PATCH 2/6] adding tests --- codex-rs/core/src/codex.rs | 14 ++- codex-rs/core/tests/summarize_context.rs | 109 +++++++++++++++++++++++ 2 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 codex-rs/core/tests/summarize_context.rs diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index c27a78cc97..f408a99cb4 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -793,13 +793,21 @@ async fn submission_loop( }; // Create a summarization request as user input + const SUMMARIZATION_PROMPT: &str = concat!( + "Please provide a summary of our conversation so far, highlighting key points, ", + "decisions made, and any important context that would be useful for future reference. ", + "This summary will be used to replace our conversation history with a more concise ", + "version so choose what details you will need to continue your work. ", + "Provide the summary directly without main title." + ); + let summarization_prompt = vec![InputItem::Text { - text: "Please provide a summary of our conversation so far, highlighting key points, decisions made, and any important context that would be useful for future reference. This summary will be used to replace our conversation history with a more concise version so choose what details you will need to continue your work.".to_string(), + text: SUMMARIZATION_PROMPT.to_string(), }]; - // attempt to inject input into current task + // Attempt to inject input into current task if let Err(items) = sess.inject_input(summarization_prompt) { - // no current task, spawn a new one + // No current task, spawn a new one let task = AgentTask::spawn(Arc::clone(sess), sub.id, items); sess.set_task(task); } diff --git a/codex-rs/core/tests/summarize_context.rs b/codex-rs/core/tests/summarize_context.rs new file mode 100644 index 0000000000..c358df6d35 --- /dev/null +++ b/codex-rs/core/tests/summarize_context.rs @@ -0,0 +1,109 @@ +#![expect(clippy::unwrap_used, clippy::expect_used)] + +//! Tests for the `Op::SummarizeContext` operation added to verify that +//! summarization requests are properly handled and injected as user input. + +use std::time::Duration; + +use codex_core::Codex; +use codex_core::protocol::EventMsg; +use codex_core::protocol::InputItem; +use codex_core::protocol::Op; +mod test_support; +use tempfile::TempDir; +use test_support::load_default_config_for_test; +use tokio::sync::Notify; +use tokio::time::timeout; + +/// Helper function to set up a codex session and wait for it to be configured +async fn setup_configured_codex_session() -> Codex { + let codex_home = TempDir::new().unwrap(); + let config = load_default_config_for_test(&codex_home); + let (codex, _init_id) = Codex::spawn(config, std::sync::Arc::new(Notify::new())) + .await + .unwrap(); + + // Wait for session configured + loop { + let event = timeout(Duration::from_secs(5), codex.next_event()) + .await + .expect("timeout waiting for session configured") + .expect("codex closed"); + + if matches!(event.msg, EventMsg::SessionConfigured(_)) { + break; + } + } + + codex +} + +#[tokio::test] +async fn test_summarize_context_spawns_new_agent_task() { + // Test the specific behavior: when there's no current task, + // SummarizeContext should spawn a new AgentTask with the summarization prompt + let codex = setup_configured_codex_session().await; + + // At this point, there should be no current task running + // Submit SummarizeContext operation - this should trigger: + // if let Err(items) = sess.inject_input(summarization_prompt) { + // let task = AgentTask::spawn(Arc::clone(sess), sub.id, items); + // sess.set_task(task); + // } + let _sub_id = codex.submit(Op::SummarizeContext).await.unwrap(); + + // Should receive a TaskStarted event indicating a new AgentTask was spawned + let event = timeout(Duration::from_secs(5), codex.next_event()) + .await + .expect("timeout waiting for task started event") + .expect("codex closed"); + + assert!( + matches!(event.msg, EventMsg::TaskStarted), + "Expected TaskStarted when no current task exists - should spawn new AgentTask" + ); +} + +#[tokio::test] +async fn test_summarize_context_injects_into_running_task() { + // Test that when a task IS running, SummarizeContext injects into the existing task + let codex = setup_configured_codex_session().await; + + // First, start a task by submitting user input + let _input_sub_id = codex + .submit(Op::UserInput { + items: vec![InputItem::Text { + text: "Hello, this should start a task".to_string(), + }], + }) + .await + .unwrap(); + + // Wait for the task to start + let event = timeout(Duration::from_secs(5), codex.next_event()) + .await + .expect("timeout waiting for task started") + .expect("codex closed"); + + assert!( + matches!(event.msg, EventMsg::TaskStarted), + "First task should start" + ); + + // Now submit SummarizeContext while a task is running + // This should test the inject_input SUCCESS path (not the spawn new task path) + let _summary_sub_id = codex.submit(Op::SummarizeContext).await.unwrap(); + + // The summarization prompt should be injected into the existing task + // rather than spawning a new one. We shouldn't get another TaskStarted event + let result = timeout(Duration::from_millis(500), codex.next_event()).await; + + // If we get an event, it should NOT be TaskStarted (since we're injecting into existing task) + if let Ok(Ok(event)) = result { + assert!( + !matches!(event.msg, EventMsg::TaskStarted), + "Should not spawn new task when one is already running - should inject instead" + ); + } + // If we timeout, that's expected - no immediate event for successful injection +} From 99df99d006c42c9b8c1b1e92b74c406752ad0dca Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 11 Jul 2025 16:19:55 -0700 Subject: [PATCH 3/6] progress --- codex-rs/core/src/codex.rs | 14 +- codex-rs/core/tests/summarize_context.rs | 209 +++++++++++++++++++---- 2 files changed, 180 insertions(+), 43 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index f408a99cb4..c45153e596 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -793,13 +793,13 @@ async fn submission_loop( }; // Create a summarization request as user input - const SUMMARIZATION_PROMPT: &str = concat!( - "Please provide a summary of our conversation so far, highlighting key points, ", - "decisions made, and any important context that would be useful for future reference. ", - "This summary will be used to replace our conversation history with a more concise ", - "version so choose what details you will need to continue your work. ", - "Provide the summary directly without main title." - ); + const SUMMARIZATION_PROMPT: &str = r#" + Please provide a summary of our conversation so far, highlighting key points, + decisions made, and any important context that would be useful for future reference. + This summary will be used to replace our conversation history with a more concise + version so choose what details you will need to continue your work. + Provide the summary directly without main title. + "#; let summarization_prompt = vec![InputItem::Text { text: SUMMARIZATION_PROMPT.to_string(), diff --git a/codex-rs/core/tests/summarize_context.rs b/codex-rs/core/tests/summarize_context.rs index c358df6d35..2f3d6a0d96 100644 --- a/codex-rs/core/tests/summarize_context.rs +++ b/codex-rs/core/tests/summarize_context.rs @@ -6,36 +6,48 @@ use std::time::Duration; use codex_core::Codex; +use codex_core::ModelProviderInfo; +use codex_core::exec::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR; +use codex_core::protocol::AgentMessageEvent; use codex_core::protocol::EventMsg; use codex_core::protocol::InputItem; use codex_core::protocol::Op; mod test_support; use tempfile::TempDir; use test_support::load_default_config_for_test; -use tokio::sync::Notify; use tokio::time::timeout; +use wiremock::Mock; +use wiremock::MockServer; +use wiremock::Request; +use wiremock::Respond; +use wiremock::ResponseTemplate; +use wiremock::matchers::method; +use wiremock::matchers::path; /// Helper function to set up a codex session and wait for it to be configured async fn setup_configured_codex_session() -> Codex { let codex_home = TempDir::new().unwrap(); let config = load_default_config_for_test(&codex_home); - let (codex, _init_id) = Codex::spawn(config, std::sync::Arc::new(Notify::new())) - .await - .unwrap(); - - // Wait for session configured - loop { - let event = timeout(Duration::from_secs(5), codex.next_event()) - .await - .expect("timeout waiting for session configured") - .expect("codex closed"); + let (codex, _, _) = codex_core::codex_wrapper::init_codex(config).await.unwrap(); + codex +} - if matches!(event.msg, EventMsg::SessionConfigured(_)) { - break; - } - } +/// Build SSE response with a message but WITHOUT completed marker (keeps task running) +fn sse_message_no_complete(message: &str) -> String { + format!( + "event: response.output_item.done\n\ +data: {{\"type\":\"response.output_item.done\",\"item\":{{\"type\":\"message\",\"role\":\"assistant\",\"content\":[{{\"type\":\"output_text\",\"text\":\"{message}\"}}]}}}}\n\n" + ) +} - codex +/// Build SSE response with a message AND completed marker +fn sse_message_with_complete(id: &str, message: &str) -> String { + format!( + "event: response.output_item.done\n\ +data: {{\"type\":\"response.output_item.done\",\"item\":{{\"type\":\"message\",\"role\":\"assistant\",\"content\":[{{\"type\":\"output_text\",\"text\":\"{message}\"}}]}}}}\n\n\ +event: response.completed\n\ +data: {{\"type\":\"response.completed\",\"response\":{{\"id\":\"{id}\",\"output\":[]}}}}\n\n" + ) } #[tokio::test] @@ -45,11 +57,6 @@ async fn test_summarize_context_spawns_new_agent_task() { let codex = setup_configured_codex_session().await; // At this point, there should be no current task running - // Submit SummarizeContext operation - this should trigger: - // if let Err(items) = sess.inject_input(summarization_prompt) { - // let task = AgentTask::spawn(Arc::clone(sess), sub.id, items); - // sess.set_task(task); - // } let _sub_id = codex.submit(Op::SummarizeContext).await.unwrap(); // Should receive a TaskStarted event indicating a new AgentTask was spawned @@ -66,14 +73,103 @@ async fn test_summarize_context_spawns_new_agent_task() { #[tokio::test] async fn test_summarize_context_injects_into_running_task() { - // Test that when a task IS running, SummarizeContext injects into the existing task - let codex = setup_configured_codex_session().await; + if std::env::var(CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR).is_ok() { + println!( + "Skipping test because it cannot execute when network is disabled in a Codex sandbox." + ); + return; + } + + // Set up mock server + let server = MockServer::start().await; + + // Custom responder that tracks request count and responds differently + struct SequentialResponder; + impl Respond for SequentialResponder { + fn respond(&self, req: &Request) -> ResponseTemplate { + use std::sync::atomic::AtomicUsize; + use std::sync::atomic::Ordering; + static CALLS: AtomicUsize = AtomicUsize::new(0); + let n = CALLS.fetch_add(1, Ordering::SeqCst); + + println!( + "Mock server received request #{n}: {}", + std::str::from_utf8(&req.body).unwrap_or("invalid utf8") + ); + + if n == 0 { + // First request: respond to initial message but DON'T complete the task + let response = sse_message_no_complete( + "I understand you need help with a coding task. Please go ahead and explain what you're trying to do.", + ); + println!("Sending response without complete: {}", response); + ResponseTemplate::new(200) + .insert_header("content-type", "text/event-stream") + .set_body_raw(response, "text/event-stream") + } else { + // Second request: this should be the summary request + let response = sse_message_with_complete( + "resp2", + "Here's a summary of our conversation: You mentioned needing help with a coding task and were about to explain what you're trying to do.", + ); + println!("Sending response with complete: {}", response); + ResponseTemplate::new(200) + .insert_header("content-type", "text/event-stream") + .set_body_raw(response, "text/event-stream") + } + } + } + + Mock::given(method("POST")) + .and(path("/v1/responses")) + .respond_with(SequentialResponder {}) + .expect(2) + .mount(&server) + .await; + + // Configure environment + unsafe { + std::env::set_var("OPENAI_REQUEST_MAX_RETRIES", "0"); + std::env::set_var("OPENAI_STREAM_MAX_RETRIES", "0"); + } + + let model_provider = ModelProviderInfo { + name: "openai".into(), + base_url: format!("{}/v1", server.uri()), + env_key: Some("PATH".into()), + env_key_instructions: None, + wire_api: codex_core::WireApi::Responses, + query_params: None, + http_headers: None, + env_http_headers: None, + }; + + // Set up codex with mock configuration + let codex_home = TempDir::new().unwrap(); + let mut config = load_default_config_for_test(&codex_home); + config.model_provider = model_provider; + let ctrl_c = std::sync::Arc::new(tokio::sync::Notify::new()); + let (codex, _) = Codex::spawn(config, ctrl_c).await.unwrap(); + + // Wait for SessionConfigured event + let event = timeout(Duration::from_secs(5), codex.next_event()) + .await + .expect("timeout waiting for session configured") + .expect("codex closed"); + + assert!( + matches!(event.msg, EventMsg::SessionConfigured(_)), + "Expected SessionConfigured event, got: {:?}", + event.msg + ); // First, start a task by submitting user input let _input_sub_id = codex .submit(Op::UserInput { items: vec![InputItem::Text { - text: "Hello, this should start a task".to_string(), + text: + "I need help with a coding task. First, let me explain what I'm trying to do..." + .to_string(), }], }) .await @@ -85,25 +181,66 @@ async fn test_summarize_context_injects_into_running_task() { .expect("timeout waiting for task started") .expect("codex closed"); + println!("Got event after UserInput: {:?}", event.msg); + assert!( matches!(event.msg, EventMsg::TaskStarted), "First task should start" ); - // Now submit SummarizeContext while a task is running - // This should test the inject_input SUCCESS path (not the spawn new task path) + // Wait for the initial response message + let mut got_initial_response = false; + while !got_initial_response { + let event = timeout(Duration::from_secs(5), codex.next_event()) + .await + .expect("timeout waiting for initial response") + .expect("codex closed"); + + match event.msg { + EventMsg::AgentReasoning(_) | EventMsg::TokenCount(_) => continue, + EventMsg::AgentMessage(_) => { + got_initial_response = true; + } + EventMsg::TaskComplete(_) => { + panic!( + "Task should NOT complete after first message - mock should keep it running" + ); + } + other => panic!("Unexpected event: {other:?}"), + } + } + + // Now submit SummarizeContext while the task is still running let _summary_sub_id = codex.submit(Op::SummarizeContext).await.unwrap(); - // The summarization prompt should be injected into the existing task - // rather than spawning a new one. We shouldn't get another TaskStarted event - let result = timeout(Duration::from_millis(500), codex.next_event()).await; + // We should NOT get a new TaskStarted event (that would mean a new task was spawned) + // Instead we should get an AgentMessage with the summary + loop { + let event = timeout(Duration::from_secs(5), codex.next_event()) + .await + .expect("timeout waiting for summary message") + .expect("codex closed"); - // If we get an event, it should NOT be TaskStarted (since we're injecting into existing task) - if let Ok(Ok(event)) = result { - assert!( - !matches!(event.msg, EventMsg::TaskStarted), - "Should not spawn new task when one is already running - should inject instead" - ); + match event.msg { + EventMsg::TaskStarted => { + panic!( + "Got TaskStarted - summary request spawned a new task instead of injecting into existing one!" + ); + } + EventMsg::AgentReasoning(_) | EventMsg::TokenCount(_) => continue, + EventMsg::AgentMessage(AgentMessageEvent { message }) => { + // Verify this is the summary message + assert!( + message.contains("summary") || message.contains("conversation"), + "Expected summary content, got: {message}" + ); + break; + } + EventMsg::TaskComplete(_) => { + // This is OK after we get the summary message + break; + } + other => panic!("Unexpected event: {other:?}"), + } } - // If we timeout, that's expected - no immediate event for successful injection } From f8d6e9745073ddcc0950092d51870a96aff196b6 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 11 Jul 2025 17:23:45 -0700 Subject: [PATCH 4/6] review --- codex-rs/core/tests/summarize_context.rs | 174 ----------------------- 1 file changed, 174 deletions(-) diff --git a/codex-rs/core/tests/summarize_context.rs b/codex-rs/core/tests/summarize_context.rs index 2f3d6a0d96..19bee6ff71 100644 --- a/codex-rs/core/tests/summarize_context.rs +++ b/codex-rs/core/tests/summarize_context.rs @@ -70,177 +70,3 @@ async fn test_summarize_context_spawns_new_agent_task() { "Expected TaskStarted when no current task exists - should spawn new AgentTask" ); } - -#[tokio::test] -async fn test_summarize_context_injects_into_running_task() { - if std::env::var(CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR).is_ok() { - println!( - "Skipping test because it cannot execute when network is disabled in a Codex sandbox." - ); - return; - } - - // Set up mock server - let server = MockServer::start().await; - - // Custom responder that tracks request count and responds differently - struct SequentialResponder; - impl Respond for SequentialResponder { - fn respond(&self, req: &Request) -> ResponseTemplate { - use std::sync::atomic::AtomicUsize; - use std::sync::atomic::Ordering; - static CALLS: AtomicUsize = AtomicUsize::new(0); - let n = CALLS.fetch_add(1, Ordering::SeqCst); - - println!( - "Mock server received request #{n}: {}", - std::str::from_utf8(&req.body).unwrap_or("invalid utf8") - ); - - if n == 0 { - // First request: respond to initial message but DON'T complete the task - let response = sse_message_no_complete( - "I understand you need help with a coding task. Please go ahead and explain what you're trying to do.", - ); - println!("Sending response without complete: {}", response); - ResponseTemplate::new(200) - .insert_header("content-type", "text/event-stream") - .set_body_raw(response, "text/event-stream") - } else { - // Second request: this should be the summary request - let response = sse_message_with_complete( - "resp2", - "Here's a summary of our conversation: You mentioned needing help with a coding task and were about to explain what you're trying to do.", - ); - println!("Sending response with complete: {}", response); - ResponseTemplate::new(200) - .insert_header("content-type", "text/event-stream") - .set_body_raw(response, "text/event-stream") - } - } - } - - Mock::given(method("POST")) - .and(path("/v1/responses")) - .respond_with(SequentialResponder {}) - .expect(2) - .mount(&server) - .await; - - // Configure environment - unsafe { - std::env::set_var("OPENAI_REQUEST_MAX_RETRIES", "0"); - std::env::set_var("OPENAI_STREAM_MAX_RETRIES", "0"); - } - - let model_provider = ModelProviderInfo { - name: "openai".into(), - base_url: format!("{}/v1", server.uri()), - env_key: Some("PATH".into()), - env_key_instructions: None, - wire_api: codex_core::WireApi::Responses, - query_params: None, - http_headers: None, - env_http_headers: None, - }; - - // Set up codex with mock configuration - let codex_home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&codex_home); - config.model_provider = model_provider; - let ctrl_c = std::sync::Arc::new(tokio::sync::Notify::new()); - let (codex, _) = Codex::spawn(config, ctrl_c).await.unwrap(); - - // Wait for SessionConfigured event - let event = timeout(Duration::from_secs(5), codex.next_event()) - .await - .expect("timeout waiting for session configured") - .expect("codex closed"); - - assert!( - matches!(event.msg, EventMsg::SessionConfigured(_)), - "Expected SessionConfigured event, got: {:?}", - event.msg - ); - - // First, start a task by submitting user input - let _input_sub_id = codex - .submit(Op::UserInput { - items: vec![InputItem::Text { - text: - "I need help with a coding task. First, let me explain what I'm trying to do..." - .to_string(), - }], - }) - .await - .unwrap(); - - // Wait for the task to start - let event = timeout(Duration::from_secs(5), codex.next_event()) - .await - .expect("timeout waiting for task started") - .expect("codex closed"); - - println!("Got event after UserInput: {:?}", event.msg); - - assert!( - matches!(event.msg, EventMsg::TaskStarted), - "First task should start" - ); - - // Wait for the initial response message - let mut got_initial_response = false; - while !got_initial_response { - let event = timeout(Duration::from_secs(5), codex.next_event()) - .await - .expect("timeout waiting for initial response") - .expect("codex closed"); - - match event.msg { - EventMsg::AgentReasoning(_) | EventMsg::TokenCount(_) => continue, - EventMsg::AgentMessage(_) => { - got_initial_response = true; - } - EventMsg::TaskComplete(_) => { - panic!( - "Task should NOT complete after first message - mock should keep it running" - ); - } - other => panic!("Unexpected event: {other:?}"), - } - } - - // Now submit SummarizeContext while the task is still running - let _summary_sub_id = codex.submit(Op::SummarizeContext).await.unwrap(); - - // We should NOT get a new TaskStarted event (that would mean a new task was spawned) - // Instead we should get an AgentMessage with the summary - loop { - let event = timeout(Duration::from_secs(5), codex.next_event()) - .await - .expect("timeout waiting for summary message") - .expect("codex closed"); - - match event.msg { - EventMsg::TaskStarted => { - panic!( - "Got TaskStarted - summary request spawned a new task instead of injecting into existing one!" - ); - } - EventMsg::AgentReasoning(_) | EventMsg::TokenCount(_) => continue, - EventMsg::AgentMessage(AgentMessageEvent { message }) => { - // Verify this is the summary message - assert!( - message.contains("summary") || message.contains("conversation"), - "Expected summary content, got: {message}" - ); - break; - } - EventMsg::TaskComplete(_) => { - // This is OK after we get the summary message - break; - } - other => panic!("Unexpected event: {other:?}"), - } - } -} From 133ad67ce0167744be33220c32fdc50f51524c5e Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 11 Jul 2025 17:26:02 -0700 Subject: [PATCH 5/6] review --- codex-rs/core/tests/summarize_context.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/codex-rs/core/tests/summarize_context.rs b/codex-rs/core/tests/summarize_context.rs index 19bee6ff71..9e97f2ad18 100644 --- a/codex-rs/core/tests/summarize_context.rs +++ b/codex-rs/core/tests/summarize_context.rs @@ -59,7 +59,6 @@ async fn test_summarize_context_spawns_new_agent_task() { // At this point, there should be no current task running let _sub_id = codex.submit(Op::SummarizeContext).await.unwrap(); - // Should receive a TaskStarted event indicating a new AgentTask was spawned let event = timeout(Duration::from_secs(5), codex.next_event()) .await .expect("timeout waiting for task started event") From f30e25aa117e3027e3877abcf80ab711e121b0b1 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Sat, 12 Jul 2025 11:26:22 -0700 Subject: [PATCH 6/6] warnings --- codex-rs/core/src/codex.rs | 2 +- codex-rs/core/tests/summarize_context.rs | 29 ------------------------ 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index c45153e596..4abde39e08 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -808,7 +808,7 @@ async fn submission_loop( // Attempt to inject input into current task if let Err(items) = sess.inject_input(summarization_prompt) { // No current task, spawn a new one - let task = AgentTask::spawn(Arc::clone(sess), sub.id, items); + let task = AgentTask::spawn(sess.clone(), sub.id, items); sess.set_task(task); } } diff --git a/codex-rs/core/tests/summarize_context.rs b/codex-rs/core/tests/summarize_context.rs index 9e97f2ad18..ecf84c4fdf 100644 --- a/codex-rs/core/tests/summarize_context.rs +++ b/codex-rs/core/tests/summarize_context.rs @@ -6,23 +6,12 @@ use std::time::Duration; use codex_core::Codex; -use codex_core::ModelProviderInfo; -use codex_core::exec::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR; -use codex_core::protocol::AgentMessageEvent; use codex_core::protocol::EventMsg; -use codex_core::protocol::InputItem; use codex_core::protocol::Op; mod test_support; use tempfile::TempDir; use test_support::load_default_config_for_test; use tokio::time::timeout; -use wiremock::Mock; -use wiremock::MockServer; -use wiremock::Request; -use wiremock::Respond; -use wiremock::ResponseTemplate; -use wiremock::matchers::method; -use wiremock::matchers::path; /// Helper function to set up a codex session and wait for it to be configured async fn setup_configured_codex_session() -> Codex { @@ -32,24 +21,6 @@ async fn setup_configured_codex_session() -> Codex { codex } -/// Build SSE response with a message but WITHOUT completed marker (keeps task running) -fn sse_message_no_complete(message: &str) -> String { - format!( - "event: response.output_item.done\n\ -data: {{\"type\":\"response.output_item.done\",\"item\":{{\"type\":\"message\",\"role\":\"assistant\",\"content\":[{{\"type\":\"output_text\",\"text\":\"{message}\"}}]}}}}\n\n" - ) -} - -/// Build SSE response with a message AND completed marker -fn sse_message_with_complete(id: &str, message: &str) -> String { - format!( - "event: response.output_item.done\n\ -data: {{\"type\":\"response.output_item.done\",\"item\":{{\"type\":\"message\",\"role\":\"assistant\",\"content\":[{{\"type\":\"output_text\",\"text\":\"{message}\"}}]}}}}\n\n\ -event: response.completed\n\ -data: {{\"type\":\"response.completed\",\"response\":{{\"id\":\"{id}\",\"output\":[]}}}}\n\n" - ) -} - #[tokio::test] async fn test_summarize_context_spawns_new_agent_task() { // Test the specific behavior: when there's no current task,