Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 1 addition & 17 deletions codex-rs/core/src/context_manager/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::context_manager::normalize;
use crate::event_mapping::has_non_contextual_dev_message_content;
use crate::event_mapping::is_contextual_dev_message_content;
use crate::event_mapping::is_contextual_user_message_content;
use crate::rollout_history::is_user_turn_boundary;
use crate::session::turn_context::TurnContext;
use base64::Engine;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
Expand All @@ -16,7 +17,6 @@ use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::ImageDetail;
use codex_protocol::models::ResponseItem;
use codex_protocol::openai_models::InputModality;
use codex_protocol::protocol::InterAgentCommunication;
use codex_protocol::protocol::TokenUsage;
use codex_protocol::protocol::TokenUsageInfo;
use codex_protocol::protocol::TurnContextItem;
Expand Down Expand Up @@ -747,22 +747,6 @@ fn is_model_generated_item(item: &ResponseItem) -> bool {
}
}

pub(crate) fn is_user_turn_boundary(item: &ResponseItem) -> bool {
if matches!(item, ResponseItem::AgentMessage { .. }) {
return true;
}
let ResponseItem::Message { role, content, .. } = item else {
return false;
};

(role == "user" && !is_contextual_user_message_content(content))
|| (role == "assistant" && is_inter_agent_instruction_content(content))
}

fn is_inter_agent_instruction_content(content: &[ContentItem]) -> bool {
InterAgentCommunication::is_message_content(content)
}

fn user_message_positions(items: &[ResponseItem]) -> Vec<usize> {
let mut positions = Vec::new();
for (idx, item) in items.iter().enumerate() {
Expand Down
1 change: 0 additions & 1 deletion codex-rs/core/src/context_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ mod normalize;
pub(crate) mod updates;

pub(crate) use history::ContextManager;
pub(crate) use history::is_user_turn_boundary;
pub(crate) use history::truncate_function_output_payload;
1 change: 1 addition & 0 deletions codex-rs/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ pub use agents_md::LOCAL_AGENTS_MD_FILENAME;
pub use agents_md::LoadedAgentsMd;
mod rollout;
mod rollout_budget;
pub mod rollout_history;
pub(crate) mod safety;
mod session_rollout_init_error;
pub mod shell;
Expand Down
23 changes: 23 additions & 0 deletions codex-rs/core/src/rollout_history.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use crate::event_mapping::is_contextual_user_message_content;
use codex_protocol::models::ResponseItem;
use codex_protocol::protocol::InterAgentCommunication;

/// Returns whether a response item starts a user-directed turn in stored history.
///
/// Context-only user messages do not start turns. Agent messages and structured
/// assistant instructions do, since they represent input that expects a response.
pub fn is_user_turn_boundary(item: &ResponseItem) -> bool {
if matches!(item, ResponseItem::AgentMessage { .. }) {
return true;
}
let ResponseItem::Message { role, content, .. } = item else {
return false;
};

(role == "user" && !is_contextual_user_message_content(content))
|| (role == "assistant" && InterAgentCommunication::is_message_content(content))
}

#[cfg(test)]
#[path = "rollout_history_tests.rs"]
mod tests;
68 changes: 68 additions & 0 deletions codex-rs/core/src/rollout_history_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use super::is_user_turn_boundary;
use codex_protocol::AgentPath;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ResponseItem;
use codex_protocol::protocol::InterAgentCommunication;

fn message(role: &str, content: ContentItem) -> ResponseItem {
serde_json::from_value(serde_json::json!({
"type": "message",
"role": role,
"content": [content],
}))
.expect("message response item should deserialize")
}

#[test]
fn classifies_user_directed_turn_boundaries() {
let user_message = message(
"user",
ContentItem::InputText {
text: "hello".to_string(),
},
);
let agent_message = serde_json::from_value(serde_json::json!({
"type": "agent_message",
"author": "/root",
"recipient": "/root/worker",
"content": [],
}))
.expect("agent message response item should deserialize");
let instruction = InterAgentCommunication::new(
AgentPath::root(),
AgentPath::root().join("worker").expect("valid agent path"),
Vec::new(),
"continue".to_string(),
/*trigger_turn*/ true,
);
let assistant_instruction = message(
"assistant",
ContentItem::OutputText {
text: serde_json::to_string(&instruction).expect("instruction should serialize"),
},
);

assert!(is_user_turn_boundary(&user_message));
assert!(is_user_turn_boundary(&agent_message));
assert!(is_user_turn_boundary(&assistant_instruction));
}

#[test]
fn excludes_context_and_non_input_items() {
let context_message = message(
"user",
ContentItem::InputText {
text: "<environment_context><cwd>/tmp</cwd></environment_context>".to_string(),
},
);
let assistant_message = message(
"assistant",
ContentItem::OutputText {
text: "done".to_string(),
},
);

assert!(!is_user_turn_boundary(&context_message));
assert!(!is_user_turn_boundary(&assistant_message));
assert!(!is_user_turn_boundary(&ResponseItem::Other));
}
2 changes: 1 addition & 1 deletion codex-rs/core/src/session/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ use codex_protocol::protocol::WarningEvent;
use codex_protocol::request_permissions::RequestPermissionsResponse;
use codex_protocol::request_user_input::RequestUserInputResponse;

use crate::context_manager::is_user_turn_boundary;
use crate::rollout_history::is_user_turn_boundary;
use codex_protocol::dynamic_tools::DynamicToolResponse;
use codex_protocol::mcp::RequestId as ProtocolRequestId;
use codex_rmcp_client::ElicitationAction;
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/session/rollout_reconstruction.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::*;
use crate::context::world_state::WorldStateSnapshot;
use crate::context_manager::is_user_turn_boundary;
use crate::rollout_history::is_user_turn_boundary;
use codex_protocol::protocol::SessionContextWindow;
use uuid::Uuid;

Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/thread_rollout_truncation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
//! In core, "user turns" are detected by scanning `ResponseItem::Message` items and
//! interpreting them via `event_mapping::parse_turn_item(...)`.

use crate::context_manager::is_user_turn_boundary;
use crate::event_mapping;
use crate::rollout_history::is_user_turn_boundary;
use codex_protocol::items::TurnItem;
use codex_protocol::models::ResponseItem;
use codex_protocol::protocol::EventMsg;
Expand Down
Loading