use codex_extension_api::ResponseItem; use codex_protocol::models::AgentMessageInputContent; use codex_protocol::models::ContentItem; use codex_protocol::models::FunctionCallOutputContentItem; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::ReasoningItemContent; use codex_protocol::models::ReasoningItemReasoningSummary; use codex_protocol::protocol::TruncationPolicy; use pretty_assertions::assert_eq; use super::MANUAL_APPROVAL_DEVELOPER_PREFIX; use super::MAX_MESSAGE_ENTRY_TOKENS; use super::MAX_MESSAGE_TRANSCRIPT_TOKENS; use super::MAX_TOOL_ENTRY_TOKENS; use super::MAX_TOOL_TRANSCRIPT_TOKENS; use super::TranscriptConfig; use super::TranscriptSource; use super::truncate_entry; #[test] fn transcript_keeps_conversation_and_configured_sources() { let items = vec![ ResponseItem::Message { id: None, role: "user".to_string(), content: vec![ContentItem::InputText { text: "Inspect the workspace.".to_string(), }], phase: None, internal_chat_message_metadata_passthrough: None, }, ResponseItem::FunctionCall { id: None, name: "exec_command".to_string(), namespace: None, arguments: "{}".to_string(), encrypted_function_args: None, call_id: "call-1".to_string(), internal_chat_message_metadata_passthrough: None, }, ResponseItem::FunctionCallOutput { id: None, call_id: "call-1".to_string(), output: FunctionCallOutputPayload::from_text("Workspace inspected.".to_string()), internal_chat_message_metadata_passthrough: None, }, ResponseItem::Reasoning { id: None, summary: vec![ReasoningItemReasoningSummary::SummaryText { text: "Review the current files.".to_string(), }], content: Some(vec![ReasoningItemContent::ReasoningText { text: "Plaintext reasoning.".to_string(), }]), encrypted_content: Some("encrypted-reasoning-blob".to_string()), internal_chat_message_metadata_passthrough: None, }, ResponseItem::Message { id: None, role: "developer".to_string(), content: Vec::new(), phase: None, internal_chat_message_metadata_passthrough: None, }, ]; let transcript = TranscriptConfig::default().build(&items); assert_eq!( transcript, vec![ "[1] user: Inspect the workspace.\n", "[2] tool exec_command call: {}\n", "[3] tool exec_command result: Workspace inspected.\n", ] ); let output_and_reasoning = TranscriptConfig { sources: vec![TranscriptSource::ToolOutputs, TranscriptSource::Reasoning], ..TranscriptConfig::default() }; let transcript = output_and_reasoning.build(&items); assert_eq!( transcript, vec![ "[1] user: Inspect the workspace.\n", "[2] tool exec_command result: Workspace inspected.\n", "[3] reasoning: Review the current files.\nPlaintext reasoning.\n", ] ); let calls_only = TranscriptConfig { sources: vec![TranscriptSource::ToolCalls], ..TranscriptConfig::default() }; let transcript = calls_only.build(&items); assert_eq!( transcript, vec![ "[1] user: Inspect the workspace.\n", "[2] tool exec_command call: {}\n", ] ); } #[test] fn transcript_truncates_oversized_entries_without_splitting_characters() { let prefix = "start é"; let suffix = "é end"; let oversized_message = format!( "{prefix}{}{suffix}", "é".repeat(TruncationPolicy::Tokens(MAX_MESSAGE_ENTRY_TOKENS).byte_budget()) ); let items = vec![ ResponseItem::Message { id: None, role: "user".to_string(), content: vec![ContentItem::InputText { text: oversized_message, }], phase: None, internal_chat_message_metadata_passthrough: None, }, ResponseItem::Message { id: None, role: "assistant".to_string(), content: vec![ContentItem::OutputText { text: "latest response".to_string(), }], phase: None, internal_chat_message_metadata_passthrough: None, }, ]; let transcript = TranscriptConfig::default().build(&items); assert_eq!(transcript.len(), 2); let user_entry = &transcript[0]; assert!(user_entry.starts_with(&format!("[1] user: {prefix}"))); assert!(user_entry.contains(">(); items.push(ResponseItem::Message { id: None, role: "assistant".to_string(), content: vec![ContentItem::OutputText { text: "Most recent assistant context.".to_string(), }], phase: None, internal_chat_message_metadata_passthrough: None, }); let transcript = TranscriptConfig::default().build(&items); assert!(transcript[0].starts_with("[1] user: user turn 0:")); assert!( transcript .iter() .any(|entry| entry.starts_with("[8] user: user turn 7:")) ); assert!( !transcript .iter() .any(|entry| entry.contains("user turn 1:")) ); assert!( transcript .iter() .any(|entry| entry.contains("user turn 6:")) ); assert_eq!( transcript.last().map(String::as_str), Some("[9] assistant: Most recent assistant context.\n") ); assert!( transcript .iter() .map(|entry| TruncationPolicy::Bytes(entry.len()).token_budget()) .sum::() <= MAX_MESSAGE_TRANSCRIPT_TOKENS ); } #[test] fn transcript_reserves_separate_budget_for_recent_tool_evidence() { let mut items = (0..8) .map(|index| ResponseItem::Message { id: None, role: "user".to_string(), content: vec![ContentItem::InputText { text: format!("user turn {index}: {}", "authorization ".repeat(1_000)), }], phase: None, internal_chat_message_metadata_passthrough: None, }) .collect::>(); items.extend((0..12).map(|index| ResponseItem::FunctionCall { id: None, name: "exec_command".to_string(), namespace: None, arguments: format!("tool evidence {index}: {}", "signal ".repeat(1_000)), encrypted_function_args: None, call_id: format!("call-{index}"), internal_chat_message_metadata_passthrough: None, })); let transcript = TranscriptConfig::default().build(&items); assert!(transcript[0].contains("user turn 0:")); assert!( transcript .iter() .any(|entry| entry.contains("user turn 7:")) ); assert!( transcript.last().is_some_and( |entry| entry.starts_with("[20] tool exec_command call: tool evidence 11:") ) ); assert!( !transcript .iter() .any(|entry| entry.contains("tool evidence 0:")) ); let tool_entries = transcript .iter() .filter(|entry| entry.contains("tool exec_command call:")) .collect::>(); assert!(tool_entries.len() < 12); assert!(tool_entries.iter().all(|entry| { entry.len() <= TruncationPolicy::Tokens(MAX_TOOL_ENTRY_TOKENS).byte_budget() + "[20] tool exec_command call: \n".len() })); assert!( tool_entries .iter() .map(|entry| TruncationPolicy::Bytes(entry.len()).token_budget()) .sum::() <= MAX_TOOL_TRANSCRIPT_TOKENS ); } #[test] fn transcript_truncates_tool_results_using_standard_budget() { let output = format!("first {} last", "evidence ".repeat(5_000)); let items = vec![ ResponseItem::FunctionCall { id: None, name: "node_repl__run".to_owned(), namespace: None, arguments: "{}".to_owned(), encrypted_function_args: None, call_id: "call-1".to_owned(), internal_chat_message_metadata_passthrough: None, }, ResponseItem::FunctionCallOutput { id: None, call_id: "call-1".to_owned(), output: FunctionCallOutputPayload::from_text(output), internal_chat_message_metadata_passthrough: None, }, ]; let transcript = TranscriptConfig::default().build(&items); let result = transcript .iter() .find(|entry| entry.contains(" result: ")) .expect("the tool result should be retained"); let prefix = "[2] tool node_repl__run result: "; assert!(result.starts_with(&format!("{prefix}first "))); assert!(result.contains(">(); items.push(ResponseItem::Reasoning { id: None, summary: vec![ReasoningItemReasoningSummary::SummaryText { text: "Recent reasoning evidence.".to_string(), }], content: None, encrypted_content: None, internal_chat_message_metadata_passthrough: None, }); let transcript = TranscriptConfig { sources: vec![TranscriptSource::Reasoning], ..TranscriptConfig::default() } .build(&items); assert!(transcript[0].contains("user turn 0:")); assert!( transcript .iter() .any(|entry| entry.contains("user turn 7:")) ); assert_eq!( transcript.last().map(String::as_str), Some("[9] reasoning: Recent reasoning evidence.\n") ); assert!( transcript .iter() .map(|entry| TruncationPolicy::Bytes(entry.len()).token_budget()) .sum::() <= MAX_MESSAGE_TRANSCRIPT_TOKENS ); } #[test] fn truncate_entry_preserves_prefix_suffix_and_utf8_boundaries() { let text = format!("prefix é{}é suffix", "🦀".repeat(2_000)); let truncated = truncate_entry(&text, /*max_tokens*/ 200); assert!(truncated.starts_with("prefix é")); assert!(truncated.contains("