diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index d23981b95..47e252307 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -831,6 +831,36 @@ 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 + 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(), + }]; + + // 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(sess.clone(), 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 08d55b974..aa6728bb0 100644 --- a/codex-rs/core/src/protocol.rs +++ b/codex-rs/core/src/protocol.rs @@ -112,6 +112,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 diff --git a/codex-rs/core/tests/summarize_context.rs b/codex-rs/core/tests/summarize_context.rs new file mode 100644 index 000000000..ecf84c4fd --- /dev/null +++ b/codex-rs/core/tests/summarize_context.rs @@ -0,0 +1,42 @@ +#![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::Op; +mod test_support; +use tempfile::TempDir; +use test_support::load_default_config_for_test; +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, _, _) = codex_core::codex_wrapper::init_codex(config).await.unwrap(); + 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 + let _sub_id = codex.submit(Op::SummarizeContext).await.unwrap(); + + 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" + ); +}