Codex Handbook
core/src/context_manager/history_tests.rs 2066 lines
use super::*;use base64::Engine;use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;use codex_protocol::AgentPath;use codex_protocol::models::BaseInstructions;use codex_protocol::models::ContentItem;use codex_protocol::models::DEFAULT_IMAGE_DETAIL;use codex_protocol::models::FunctionCallOutputBody;use codex_protocol::models::FunctionCallOutputContentItem;use codex_protocol::models::FunctionCallOutputPayload;use codex_protocol::models::ImageDetail;use codex_protocol::models::LocalShellAction;use codex_protocol::models::LocalShellExecAction;use codex_protocol::models::LocalShellStatus;use codex_protocol::models::ReasoningItemContent;use codex_protocol::models::ReasoningItemReasoningSummary;use codex_protocol::models::ResponseItemMetadata;use codex_protocol::openai_models::InputModality;use codex_protocol::openai_models::default_input_modalities;use codex_protocol::protocol::AskForApproval;use codex_protocol::protocol::InterAgentCommunication;use codex_protocol::protocol::SandboxPolicy;use codex_protocol::protocol::TurnContextItem;use codex_utils_output_truncation::TruncationPolicy;use codex_utils_output_truncation::truncate_text;use image::ImageBuffer;use image::ImageFormat;use image::Luma;use image::Rgba;use pretty_assertions::assert_eq;use regex_lite::Regex;use std::path::PathBuf;const EXEC_FORMAT_MAX_BYTES: usize = 10_000;const EXEC_FORMAT_MAX_TOKENS: usize = 2_500;fn assistant_msg(text: &str) -> ResponseItem {    ResponseItem::Message {        id: None,        role: "assistant".to_string(),        content: vec![ContentItem::OutputText {            text: text.to_string(),        }],        phase: None,        metadata: None,    }}fn inter_agent_assistant_msg(text: &str) -> ResponseItem {    let communication = InterAgentCommunication::new(        AgentPath::root(),        AgentPath::root().join("worker").unwrap(),        Vec::new(),        text.to_string(),        /*trigger_turn*/ true,    );    ResponseItem::Message {        id: None,        role: "assistant".to_string(),        content: vec![ContentItem::OutputText {            text: serde_json::to_string(&communication).unwrap(),        }],        phase: None,        metadata: None,    }}fn create_history_with_items(items: Vec<ResponseItem>) -> ContextManager {    let mut h = ContextManager::new();    // Use a generous but fixed token budget; tests only rely on truncation    // behavior, not on a specific model's token limit.    h.record_items(items.iter(), TruncationPolicy::Tokens(10_000));    h}fn user_msg(text: &str) -> ResponseItem {    ResponseItem::Message {        id: None,        role: "user".to_string(),        content: vec![ContentItem::OutputText {            text: text.to_string(),        }],        phase: None,        metadata: None,    }}fn user_input_text_msg(text: &str) -> ResponseItem {    ResponseItem::Message {        id: None,        role: "user".to_string(),        content: vec![ContentItem::InputText {            text: text.to_string(),        }],        phase: None,        metadata: None,    }}fn developer_msg(text: &str) -> ResponseItem {    ResponseItem::Message {        id: None,        role: "developer".to_string(),        content: vec![ContentItem::InputText {            text: text.to_string(),        }],        phase: None,        metadata: None,    }}fn developer_msg_with_fragments(texts: &[&str]) -> ResponseItem {    ResponseItem::Message {        id: None,        role: "developer".to_string(),        content: texts            .iter()            .map(|text| ContentItem::InputText {                text: (*text).to_string(),            })            .collect(),        phase: None,        metadata: None,    }}fn reference_context_item() -> TurnContextItem {    TurnContextItem {        turn_id: Some("reference-turn".to_string()),        cwd: PathBuf::from("/tmp/reference-cwd"),        workspace_roots: None,        current_date: Some("2026-03-23".to_string()),        timezone: Some("America/Los_Angeles".to_string()),        approval_policy: AskForApproval::OnRequest,        sandbox_policy: SandboxPolicy::new_read_only_policy(),        permission_profile: None,        network: None,        file_system_sandbox_policy: None,        model: "gpt-test".to_string(),        comp_hash: None,        personality: None,        collaboration_mode: None,        multi_agent_version: None,        realtime_active: Some(false),        effort: None,        summary: codex_protocol::config_types::ReasoningSummary::Auto,    }}fn custom_tool_call_output(call_id: &str, output: &str) -> ResponseItem {    ResponseItem::CustomToolCallOutput {        call_id: call_id.to_string(),        name: None,        output: FunctionCallOutputPayload::from_text(output.to_string()),        metadata: None,    }}fn reasoning_msg(text: &str) -> ResponseItem {    ResponseItem::Reasoning {        id: String::new(),        summary: vec![ReasoningItemReasoningSummary::SummaryText {            text: "summary".to_string(),        }],        content: Some(vec![ReasoningItemContent::ReasoningText {            text: text.to_string(),        }]),        encrypted_content: None,        metadata: None,    }}fn reasoning_with_encrypted_content(len: usize) -> ResponseItem {    ResponseItem::Reasoning {        id: String::new(),        summary: vec![ReasoningItemReasoningSummary::SummaryText {            text: "summary".to_string(),        }],        content: None,        encrypted_content: Some("a".repeat(len)),        metadata: None,    }}fn truncate_exec_output(content: &str) -> String {    truncate_text(content, TruncationPolicy::Tokens(EXEC_FORMAT_MAX_TOKENS))}fn approx_token_count_for_text(text: &str) -> i64 {    i64::try_from(text.len().saturating_add(3) / 4).unwrap_or(i64::MAX)}#[test]fn filters_non_api_messages() {    let mut h = ContextManager::default();    let policy = TruncationPolicy::Tokens(10_000);    // System message is not API messages; Other is ignored.    let system = ResponseItem::Message {        id: None,        role: "system".to_string(),        content: vec![ContentItem::OutputText {            text: "ignored".to_string(),        }],        phase: None,        metadata: None,    };    let reasoning = reasoning_msg("thinking...");    h.record_items([&system, &reasoning, &ResponseItem::Other], policy);    // User and assistant should be retained.    let u = user_msg("hi");    let a = assistant_msg("hello");    h.record_items([&u, &a], policy);    let items = h.raw_items();    assert_eq!(        items,        vec![            ResponseItem::Reasoning {                id: String::new(),                summary: vec![ReasoningItemReasoningSummary::SummaryText {                    text: "summary".to_string(),                }],                content: Some(vec![ReasoningItemContent::ReasoningText {                    text: "thinking...".to_string(),                }]),                encrypted_content: None,                metadata: None,            },            ResponseItem::Message {                id: None,                role: "user".to_string(),                content: vec![ContentItem::OutputText {                    text: "hi".to_string()                }],                phase: None,                metadata: None,            },            ResponseItem::Message {                id: None,                role: "assistant".to_string(),                content: vec![ContentItem::OutputText {                    text: "hello".to_string()                }],                phase: None,                metadata: None,            }        ]    );}#[test]fn non_last_reasoning_tokens_return_zero_when_no_user_messages() {    let history =        create_history_with_items(vec![reasoning_with_encrypted_content(/*len*/ 800)]);    assert_eq!(history.get_non_last_reasoning_items_tokens(), 0);}#[test]fn non_last_reasoning_tokens_ignore_entries_after_last_user() {    let history = create_history_with_items(vec![        reasoning_with_encrypted_content(/*len*/ 900),        user_msg("first"),        reasoning_with_encrypted_content(/*len*/ 1_000),        user_msg("second"),        reasoning_with_encrypted_content(/*len*/ 2_000),    ]);    // first: (900 * 0.75 - 650) / 4 = 6.25 tokens    // second: (1000 * 0.75 - 650) / 4 = 25 tokens    // first + second = 62.5    assert_eq!(history.get_non_last_reasoning_items_tokens(), 32);}#[test]fn items_after_last_model_generated_tokens_include_user_and_tool_output() {    let history = create_history_with_items(vec![        assistant_msg("already counted by API"),        user_msg("new user message"),        custom_tool_call_output("call-tail", "new tool output"),    ]);    let expected_tokens = estimate_item_token_count(&user_msg("new user message")).saturating_add(        estimate_item_token_count(&custom_tool_call_output("call-tail", "new tool output")),    );    assert_eq!(        history            .items_after_last_model_generated_item()            .iter()            .map(estimate_item_token_count)            .fold(0i64, i64::saturating_add),        expected_tokens    );}#[test]fn items_after_last_model_generated_tokens_are_zero_without_model_generated_items() {    let history = create_history_with_items(vec![user_msg("no model output yet")]);    assert_eq!(        history            .items_after_last_model_generated_item()            .iter()            .map(estimate_item_token_count)            .fold(0i64, i64::saturating_add),        0    );}#[test]fn inter_agent_assistant_messages_are_turn_boundaries() {    let item = inter_agent_assistant_msg("continue");    assert!(is_user_turn_boundary(&item));}#[test]fn for_prompt_preserves_inter_agent_assistant_messages() {    let item = inter_agent_assistant_msg("continue");    let history = create_history_with_items(vec![item.clone()]);    assert_eq!(history.raw_items(), std::slice::from_ref(&item));    assert_eq!(history.for_prompt(&default_input_modalities()), vec![item]);}#[test]fn drop_last_n_user_turns_treats_inter_agent_assistant_messages_as_instruction_turns() {    let first_turn = user_input_text_msg("first");    let first_reply = assistant_msg("done");    let inter_agent_turn = inter_agent_assistant_msg("continue");    let inter_agent_reply = assistant_msg("worker reply");    let mut history = create_history_with_items(vec![        first_turn.clone(),        first_reply.clone(),        inter_agent_turn,        inter_agent_reply,    ]);    history.drop_last_n_user_turns(/*num_turns*/ 1);    assert_eq!(history.raw_items(), &vec![first_turn, first_reply]);}#[test]fn legacy_inter_agent_assistant_messages_are_not_turn_boundaries() {    let item = assistant_msg(        "author: /root\nrecipient: /root/worker\nother_recipients: []\nContent: continue",    );    assert!(!is_user_turn_boundary(&item));}#[test]fn total_token_usage_includes_all_items_after_last_model_generated_item() {    let mut history = create_history_with_items(vec![assistant_msg("already counted by API")]);    history.update_token_info(        &TokenUsage {            total_tokens: 100,            ..Default::default()        },        /*model_context_window*/ None,    );    let added_user = user_msg("new user message");    let added_tool_output = custom_tool_call_output("tool-tail", "new tool output");    history.record_items(        [&added_user, &added_tool_output],        TruncationPolicy::Tokens(10_000),    );    assert_eq!(        history.get_total_token_usage(/*server_reasoning_included*/ true),        100 + estimate_item_token_count(&added_user)            + estimate_item_token_count(&added_tool_output)    );}#[test]fn for_prompt_strips_images_when_model_does_not_support_images() {    let items = vec![        ResponseItem::Message {            id: None,            role: "user".to_string(),            content: vec![                ContentItem::InputText {                    text: "look at this".to_string(),                },                ContentItem::InputImage {                    image_url: "https://example.com/img.png".to_string(),                    detail: Some(DEFAULT_IMAGE_DETAIL),                },                ContentItem::InputText {                    text: "caption".to_string(),                },            ],            phase: None,            metadata: None,        },        ResponseItem::FunctionCall {            id: None,            name: "view_image".to_string(),            namespace: None,            arguments: "{}".to_string(),            call_id: "call-1".to_string(),            metadata: None,        },        ResponseItem::FunctionCallOutput {            call_id: "call-1".to_string(),            output: FunctionCallOutputPayload::from_content_items(vec![                FunctionCallOutputContentItem::InputText {                    text: "image result".to_string(),                },                FunctionCallOutputContentItem::InputImage {                    image_url: "https://example.com/result.png".to_string(),                    detail: Some(DEFAULT_IMAGE_DETAIL),                },            ]),            metadata: None,        },        ResponseItem::CustomToolCall {            id: None,            status: None,            call_id: "tool-1".to_string(),            name: "js_repl".to_string(),            input: "view_image".to_string(),            metadata: None,        },        ResponseItem::CustomToolCallOutput {            call_id: "tool-1".to_string(),            name: None,            output: FunctionCallOutputPayload::from_content_items(vec![                FunctionCallOutputContentItem::InputText {                    text: "js repl result".to_string(),                },                FunctionCallOutputContentItem::InputImage {                    image_url: "https://example.com/js-repl-result.png".to_string(),                    detail: Some(DEFAULT_IMAGE_DETAIL),                },            ]),            metadata: None,        },    ];    let history = create_history_with_items(items);    let text_only_modalities = vec![InputModality::Text];    let stripped = history.for_prompt(&text_only_modalities);    let expected = vec![        ResponseItem::Message {            id: None,            role: "user".to_string(),            content: vec![                ContentItem::InputText {                    text: "look at this".to_string(),                },                ContentItem::InputText {                    text: "image content omitted because you do not support image input"                        .to_string(),                },                ContentItem::InputText {                    text: "caption".to_string(),                },            ],            phase: None,            metadata: None,        },        ResponseItem::FunctionCall {            id: None,            name: "view_image".to_string(),            namespace: None,            arguments: "{}".to_string(),            call_id: "call-1".to_string(),            metadata: None,        },        ResponseItem::FunctionCallOutput {            call_id: "call-1".to_string(),            output: FunctionCallOutputPayload::from_content_items(vec![                FunctionCallOutputContentItem::InputText {                    text: "image result".to_string(),                },                FunctionCallOutputContentItem::InputText {                    text: "image content omitted because you do not support image input"                        .to_string(),                },            ]),            metadata: None,        },        ResponseItem::CustomToolCall {            id: None,            status: None,            call_id: "tool-1".to_string(),            name: "js_repl".to_string(),            input: "view_image".to_string(),            metadata: None,        },        ResponseItem::CustomToolCallOutput {            call_id: "tool-1".to_string(),            name: None,            output: FunctionCallOutputPayload::from_content_items(vec![                FunctionCallOutputContentItem::InputText {                    text: "js repl result".to_string(),                },                FunctionCallOutputContentItem::InputText {                    text: "image content omitted because you do not support image input"                        .to_string(),                },            ]),            metadata: None,        },    ];    assert_eq!(stripped, expected);    // With image support, images are preserved    let modalities = default_input_modalities();    let with_images = create_history_with_items(vec![ResponseItem::Message {        id: None,        role: "user".to_string(),        content: vec![            ContentItem::InputText {                text: "look".to_string(),            },            ContentItem::InputImage {                image_url: "https://example.com/img.png".to_string(),                detail: Some(DEFAULT_IMAGE_DETAIL),            },        ],        phase: None,        metadata: None,    }]);    let preserved = with_images.for_prompt(&modalities);    assert_eq!(preserved.len(), 1);    if let ResponseItem::Message { content, .. } = &preserved[0] {        assert_eq!(content.len(), 2);        assert!(matches!(content[1], ContentItem::InputImage { .. }));    } else {        panic!("expected Message");    }}#[test]fn for_prompt_preserves_image_generation_calls_when_images_are_supported() {    let history = create_history_with_items(vec![        ResponseItem::ImageGenerationCall {            id: "ig_123".to_string(),            status: "generating".to_string(),            revised_prompt: Some("lobster".to_string()),            result: "Zm9v".to_string(),            metadata: None,        },        ResponseItem::Message {            id: None,            role: "user".to_string(),            content: vec![ContentItem::InputText {                text: "hi".to_string(),            }],            phase: None,            metadata: None,        },    ]);    assert_eq!(        history.for_prompt(&default_input_modalities()),        vec![            ResponseItem::ImageGenerationCall {                id: "ig_123".to_string(),                status: "generating".to_string(),                revised_prompt: Some("lobster".to_string()),                result: "Zm9v".to_string(),                metadata: None,            },            ResponseItem::Message {                id: None,                role: "user".to_string(),                content: vec![ContentItem::InputText {                    text: "hi".to_string(),                }],                phase: None,                metadata: None,            }        ]    );}#[test]fn for_prompt_clears_image_generation_result_when_images_are_unsupported() {    let history = create_history_with_items(vec![        ResponseItem::Message {            id: None,            role: "user".to_string(),            content: vec![ContentItem::InputText {                text: "generate a lobster".to_string(),            }],            phase: None,            metadata: None,        },        ResponseItem::ImageGenerationCall {            id: "ig_123".to_string(),            status: "completed".to_string(),            revised_prompt: Some("lobster".to_string()),            result: "Zm9v".to_string(),            metadata: None,        },    ]);    assert_eq!(        history.for_prompt(&[InputModality::Text]),        vec![            ResponseItem::Message {                id: None,                role: "user".to_string(),                content: vec![ContentItem::InputText {                    text: "generate a lobster".to_string(),                }],                phase: None,                metadata: None,            },            ResponseItem::ImageGenerationCall {                id: "ig_123".to_string(),                status: "completed".to_string(),                revised_prompt: Some("lobster".to_string()),                result: String::new(),                metadata: None,            },        ]    );}#[test]fn estimate_token_count_with_base_instructions_uses_provided_text() {    let history = create_history_with_items(vec![assistant_msg("hello from history")]);    let short_base = BaseInstructions {        text: "short".to_string(),    };    let long_base = BaseInstructions {        text: "x".repeat(1_000),    };    let short_estimate = history        .estimate_token_count_with_base_instructions(&short_base)        .expect("token estimate");    let long_estimate = history        .estimate_token_count_with_base_instructions(&long_base)        .expect("token estimate");    let expected_delta = approx_token_count_for_text(&long_base.text)        - approx_token_count_for_text(&short_base.text);    assert_eq!(long_estimate - short_estimate, expected_delta);}#[test]fn remove_first_item_removes_matching_output_for_function_call() {    let items = vec![        ResponseItem::FunctionCall {            id: None,            name: "do_it".to_string(),            namespace: None,            arguments: "{}".to_string(),            call_id: "call-1".to_string(),            metadata: None,        },        ResponseItem::FunctionCallOutput {            call_id: "call-1".to_string(),            output: FunctionCallOutputPayload::from_text("ok".to_string()),            metadata: None,        },    ];    let mut h = create_history_with_items(items);    h.remove_first_item();    assert_eq!(h.raw_items(), vec![]);}#[test]fn remove_first_item_removes_matching_call_for_output() {    let items = vec![        ResponseItem::FunctionCallOutput {            call_id: "call-2".to_string(),            output: FunctionCallOutputPayload::from_text("ok".to_string()),            metadata: None,        },        ResponseItem::FunctionCall {            id: None,            name: "do_it".to_string(),            namespace: None,            arguments: "{}".to_string(),            call_id: "call-2".to_string(),            metadata: None,        },    ];    let mut h = create_history_with_items(items);    h.remove_first_item();    assert_eq!(h.raw_items(), vec![]);}#[test]fn replace_last_turn_images_replaces_tool_output_images() {    let items = vec![        user_input_text_msg("hi"),        ResponseItem::FunctionCallOutput {            call_id: "call-1".to_string(),            output: FunctionCallOutputPayload {                body: FunctionCallOutputBody::ContentItems(vec![                    FunctionCallOutputContentItem::InputImage {                        image_url: "data:image/png;base64,AAA".to_string(),                        detail: Some(DEFAULT_IMAGE_DETAIL),                    },                ]),                success: Some(true),            },            metadata: None,        },    ];    let mut history = create_history_with_items(items);    assert!(history.replace_last_turn_images("Invalid image"));    assert_eq!(        history.raw_items(),        vec![            user_input_text_msg("hi"),            ResponseItem::FunctionCallOutput {                call_id: "call-1".to_string(),                output: FunctionCallOutputPayload {                    body: FunctionCallOutputBody::ContentItems(vec![                        FunctionCallOutputContentItem::InputText {                            text: "Invalid image".to_string(),                        },                    ]),                    success: Some(true),                },                metadata: None,            },        ]    );}#[test]fn replace_last_turn_images_does_not_touch_user_images() {    let items = vec![ResponseItem::Message {        id: None,        role: "user".to_string(),        content: vec![ContentItem::InputImage {            image_url: "data:image/png;base64,AAA".to_string(),            detail: Some(DEFAULT_IMAGE_DETAIL),        }],        phase: None,        metadata: None,    }];    let mut history = create_history_with_items(items.clone());    assert!(!history.replace_last_turn_images("Invalid image"));    assert_eq!(history.raw_items(), items);}#[test]fn remove_first_item_handles_local_shell_pair() {    let items = vec![        ResponseItem::LocalShellCall {            id: None,            call_id: Some("call-3".to_string()),            status: LocalShellStatus::Completed,            action: LocalShellAction::Exec(LocalShellExecAction {                command: vec!["echo".to_string(), "hi".to_string()],                timeout_ms: None,                working_directory: None,                env: None,                user: None,            }),            metadata: None,        },        ResponseItem::FunctionCallOutput {            call_id: "call-3".to_string(),            output: FunctionCallOutputPayload::from_text("ok".to_string()),            metadata: None,        },    ];    let mut h = create_history_with_items(items);    h.remove_first_item();    assert_eq!(h.raw_items(), vec![]);}#[test]fn drop_last_n_user_turns_preserves_prefix() {    let items = vec![        assistant_msg("session prefix item"),        user_msg("u1"),        assistant_msg("a1"),        user_msg("u2"),        assistant_msg("a2"),    ];    let modalities = default_input_modalities();    let mut history = create_history_with_items(items);    history.drop_last_n_user_turns(/*num_turns*/ 1);    assert_eq!(        history.for_prompt(&modalities),        vec![            assistant_msg("session prefix item"),            user_msg("u1"),            assistant_msg("a1"),        ]    );    let mut history = create_history_with_items(vec![        assistant_msg("session prefix item"),        user_msg("u1"),        assistant_msg("a1"),        user_msg("u2"),        assistant_msg("a2"),    ]);    history.drop_last_n_user_turns(/*num_turns*/ 99);    assert_eq!(        history.for_prompt(&modalities),        vec![assistant_msg("session prefix item")]    );}#[test]fn drop_last_n_user_turns_ignores_session_prefix_user_messages() {    let items = vec![        user_input_text_msg("<environment_context>ctx</environment_context>"),        user_input_text_msg(            "# AGENTS.md instructions for test_directory\n\n<INSTRUCTIONS>\ntest_text\n</INSTRUCTIONS>",        ),        user_input_text_msg(            "<skill>\n<name>demo</name>\n<path>skills/demo/SKILL.md</path>\nbody\n</skill>",        ),        user_input_text_msg("<user_shell_command>echo 42</user_shell_command>"),        user_input_text_msg(            "<subagent_notification>{\"agent_id\":\"a\",\"status\":\"completed\"}</subagent_notification>",        ),        user_input_text_msg("turn 1 user"),        assistant_msg("turn 1 assistant"),        user_input_text_msg("turn 2 user"),        assistant_msg("turn 2 assistant"),    ];    let modalities = default_input_modalities();    let mut history = create_history_with_items(items);    history.drop_last_n_user_turns(/*num_turns*/ 1);    let expected_prefix_and_first_turn = vec![        user_input_text_msg("<environment_context>ctx</environment_context>"),        user_input_text_msg(            "# AGENTS.md instructions for test_directory\n\n<INSTRUCTIONS>\ntest_text\n</INSTRUCTIONS>",        ),        user_input_text_msg(            "<skill>\n<name>demo</name>\n<path>skills/demo/SKILL.md</path>\nbody\n</skill>",        ),        user_input_text_msg("<user_shell_command>echo 42</user_shell_command>"),        user_input_text_msg(            "<subagent_notification>{\"agent_id\":\"a\",\"status\":\"completed\"}</subagent_notification>",        ),        user_input_text_msg("turn 1 user"),        assistant_msg("turn 1 assistant"),    ];    assert_eq!(        history.for_prompt(&modalities),        expected_prefix_and_first_turn    );    let expected_prefix_only = vec![        user_input_text_msg("<environment_context>ctx</environment_context>"),        user_input_text_msg(            "# AGENTS.md instructions for test_directory\n\n<INSTRUCTIONS>\ntest_text\n</INSTRUCTIONS>",        ),        user_input_text_msg(            "<skill>\n<name>demo</name>\n<path>skills/demo/SKILL.md</path>\nbody\n</skill>",        ),        user_input_text_msg("<user_shell_command>echo 42</user_shell_command>"),        user_input_text_msg(            "<subagent_notification>{\"agent_id\":\"a\",\"status\":\"completed\"}</subagent_notification>",        ),    ];    let mut history = create_history_with_items(vec![        user_input_text_msg("<environment_context>ctx</environment_context>"),        user_input_text_msg(            "# AGENTS.md instructions for test_directory\n\n<INSTRUCTIONS>\ntest_text\n</INSTRUCTIONS>",        ),        user_input_text_msg(            "<skill>\n<name>demo</name>\n<path>skills/demo/SKILL.md</path>\nbody\n</skill>",        ),        user_input_text_msg("<user_shell_command>echo 42</user_shell_command>"),        user_input_text_msg(            "<subagent_notification>{\"agent_id\":\"a\",\"status\":\"completed\"}</subagent_notification>",        ),        user_input_text_msg("turn 1 user"),        assistant_msg("turn 1 assistant"),        user_input_text_msg("turn 2 user"),        assistant_msg("turn 2 assistant"),    ]);    history.drop_last_n_user_turns(/*num_turns*/ 2);    assert_eq!(history.for_prompt(&modalities), expected_prefix_only);    let mut history = create_history_with_items(vec![        user_input_text_msg("<environment_context>ctx</environment_context>"),        user_input_text_msg(            "# AGENTS.md instructions for test_directory\n\n<INSTRUCTIONS>\ntest_text\n</INSTRUCTIONS>",        ),        user_input_text_msg(            "<skill>\n<name>demo</name>\n<path>skills/demo/SKILL.md</path>\nbody\n</skill>",        ),        user_input_text_msg("<user_shell_command>echo 42</user_shell_command>"),        user_input_text_msg(            "<subagent_notification>{\"agent_id\":\"a\",\"status\":\"completed\"}</subagent_notification>",        ),        user_input_text_msg("turn 1 user"),        assistant_msg("turn 1 assistant"),        user_input_text_msg("turn 2 user"),        assistant_msg("turn 2 assistant"),    ]);    history.drop_last_n_user_turns(/*num_turns*/ 3);    assert_eq!(history.for_prompt(&modalities), expected_prefix_only);}#[test]fn drop_last_n_user_turns_trims_context_updates_above_rolled_back_turn() {    let items = vec![        assistant_msg("session prefix item"),        user_input_text_msg("turn 1 user"),        assistant_msg("turn 1 assistant"),        developer_msg("Generated images are saved to /tmp as /tmp/image-1.png by default."),        developer_msg("<collaboration_mode>ROLLED_BACK_DEV_INSTRUCTIONS</collaboration_mode>"),        user_input_text_msg(            "<environment_context><cwd>PRETURN_CONTEXT_DIFF_CWD</cwd></environment_context>",        ),        user_input_text_msg("turn 2 user"),        assistant_msg("turn 2 assistant"),    ];    let modalities = default_input_modalities();    let mut history = create_history_with_items(items);    let reference_context_item = reference_context_item();    history.set_reference_context_item(Some(reference_context_item.clone()));    history.drop_last_n_user_turns(/*num_turns*/ 1);    assert_eq!(        history.clone().for_prompt(&modalities),        vec![            assistant_msg("session prefix item"),            user_input_text_msg("turn 1 user"),            assistant_msg("turn 1 assistant"),            developer_msg("Generated images are saved to /tmp as /tmp/image-1.png by default."),        ]    );    assert_eq!(        serde_json::to_value(history.reference_context_item())            .expect("serialize retained reference context item"),        serde_json::to_value(Some(reference_context_item))            .expect("serialize expected reference context item")    );}#[test]fn drop_last_n_user_turns_clears_reference_context_for_mixed_developer_context_bundles() {    let items = vec![        user_input_text_msg("turn 1 user"),        assistant_msg("turn 1 assistant"),        developer_msg_with_fragments(&[            "<permissions instructions>contextual permissions</permissions instructions>",            "persistent plugin instructions",        ]),        user_input_text_msg(            "<environment_context><cwd>PRETURN_CONTEXT_DIFF_CWD</cwd></environment_context>",        ),        user_input_text_msg("turn 2 user"),        assistant_msg("turn 2 assistant"),    ];    let modalities = default_input_modalities();    let mut history = create_history_with_items(items);    history.set_reference_context_item(Some(reference_context_item()));    history.drop_last_n_user_turns(/*num_turns*/ 1);    assert_eq!(        history.clone().for_prompt(&modalities),        vec![            user_input_text_msg("turn 1 user"),            assistant_msg("turn 1 assistant"),        ]    );    assert!(history.reference_context_item().is_none());}#[test]fn remove_first_item_handles_custom_tool_pair() {    let items = vec![        ResponseItem::CustomToolCall {            id: None,            status: None,            call_id: "tool-1".to_string(),            name: "my_tool".to_string(),            input: "{}".to_string(),            metadata: None,        },        ResponseItem::CustomToolCallOutput {            call_id: "tool-1".to_string(),            name: None,            output: FunctionCallOutputPayload::from_text("ok".to_string()),            metadata: None,        },    ];    let mut h = create_history_with_items(items);    h.remove_first_item();    assert_eq!(h.raw_items(), vec![]);}#[test]fn normalization_retains_local_shell_outputs() {    let items = vec![        ResponseItem::LocalShellCall {            id: None,            call_id: Some("shell-1".to_string()),            status: LocalShellStatus::Completed,            action: LocalShellAction::Exec(LocalShellExecAction {                command: vec!["echo".to_string(), "hi".to_string()],                timeout_ms: None,                working_directory: None,                env: None,                user: None,            }),            metadata: None,        },        ResponseItem::FunctionCallOutput {            call_id: "shell-1".to_string(),            output: FunctionCallOutputPayload::from_text("Total output lines: 1\n\nok".to_string()),            metadata: None,        },    ];    let modalities = default_input_modalities();    let history = create_history_with_items(items.clone());    let normalized = history.for_prompt(&modalities);    assert_eq!(normalized, items);}#[test]fn record_items_truncates_function_call_output_content() {    let mut history = ContextManager::new();    // Any reasonably small token budget works; the test only cares that    // truncation happens and the marker is present.    let policy = TruncationPolicy::Tokens(1_000);    let long_line = "a very long line to trigger truncation\n";    let long_output = long_line.repeat(2_500);    let item = ResponseItem::FunctionCallOutput {        call_id: "call-100".to_string(),        output: FunctionCallOutputPayload {            body: FunctionCallOutputBody::Text(long_output.clone()),            success: Some(true),        },        metadata: Some(ResponseItemMetadata {            turn_id: Some("turn-1".to_string()),        }),    };    history.record_items([&item], policy);    assert_eq!(history.items.len(), 1);    match &history.items[0] {        ResponseItem::FunctionCallOutput { output, .. } => {            let content = output.text_content().unwrap_or_default();            assert_ne!(content, long_output);            assert!(                content.contains("tokens truncated"),                "expected token-based truncation marker, got {content}"            );            assert!(                content.contains("tokens truncated"),                "expected truncation marker, got {content}"            );        }        other => panic!("unexpected history item: {other:?}"),    }    assert_eq!(history.items[0].turn_id(), Some("turn-1"));}#[test]fn record_items_truncates_custom_tool_call_output_content() {    let mut history = ContextManager::new();    let policy = TruncationPolicy::Tokens(1_000);    let line = "custom output that is very long\n";    let long_output = line.repeat(2_500);    let item = ResponseItem::CustomToolCallOutput {        call_id: "tool-200".to_string(),        name: None,        output: FunctionCallOutputPayload::from_text(long_output.clone()),        metadata: None,    };    history.record_items([&item], policy);    assert_eq!(history.items.len(), 1);    match &history.items[0] {        ResponseItem::CustomToolCallOutput { output, .. } => {            let output = output.text_content().unwrap_or_default();            assert_ne!(output, long_output);            assert!(                output.contains("tokens truncated"),                "expected token-based truncation marker, got {output}"            );            assert!(                output.contains("tokens truncated") || output.contains("bytes truncated"),                "expected truncation marker, got {output}"            );        }        other => panic!("unexpected history item: {other:?}"),    }}#[test]fn record_items_respects_custom_token_limit() {    let mut history = ContextManager::new();    let policy = TruncationPolicy::Tokens(10);    let long_output = "tokenized content repeated many times ".repeat(200);    let item = ResponseItem::FunctionCallOutput {        call_id: "call-custom-limit".to_string(),        output: FunctionCallOutputPayload {            body: FunctionCallOutputBody::Text(long_output),            success: Some(true),        },        metadata: None,    };    history.record_items([&item], policy);    let stored = match &history.items[0] {        ResponseItem::FunctionCallOutput { output, .. } => output,        other => panic!("unexpected history item: {other:?}"),    };    assert!(        stored            .text_content()            .is_some_and(|content| content.contains("tokens truncated"))    );}fn assert_truncated_message_matches(message: &str, line: &str, expected_removed: usize) {    let pattern = truncated_message_pattern(line);    let regex = Regex::new(&pattern).unwrap_or_else(|err| {        panic!("failed to compile regex {pattern}: {err}");    });    let captures = regex        .captures(message)        .unwrap_or_else(|| panic!("message failed to match pattern {pattern}: {message}"));    let body = captures        .name("body")        .expect("missing body capture")        .as_str();    assert!(        body.len() <= EXEC_FORMAT_MAX_BYTES,        "body exceeds byte limit: {} bytes",        body.len()    );    let removed: usize = captures        .name("removed")        .expect("missing removed capture")        .as_str()        .parse()        .unwrap_or_else(|err| panic!("invalid removed tokens: {err}"));    assert_eq!(removed, expected_removed, "mismatched removed token count");}fn truncated_message_pattern(line: &str) -> String {    let escaped_line = regex_lite::escape(line);    format!(r"(?s)^(?P<body>{escaped_line}.*?)(?:\r?)?…(?P<removed>\d+) tokens truncated…(?:.*)?$")}#[test]fn format_exec_output_truncates_large_error() {    let line = "very long execution error line that should trigger truncation\n";    let large_error = line.repeat(2_500); // way beyond both byte and line limits    let truncated = truncate_exec_output(&large_error);    assert_truncated_message_matches(&truncated, line, /*expected_removed*/ 36250);    assert_ne!(truncated, large_error);}#[test]fn format_exec_output_marks_byte_truncation_without_omitted_lines() {    let long_line = "a".repeat(EXEC_FORMAT_MAX_BYTES + 10000);    let truncated = truncate_exec_output(&long_line);    assert_ne!(truncated, long_line);    assert_truncated_message_matches(&truncated, "a", /*expected_removed*/ 2500);    assert!(        !truncated.contains("omitted"),        "line omission marker should not appear when no lines were dropped: {truncated}"    );}#[test]fn format_exec_output_returns_original_when_within_limits() {    let content = "example output\n".repeat(10);    assert_eq!(truncate_exec_output(&content), content);}#[test]fn format_exec_output_reports_omitted_lines_and_keeps_head_and_tail() {    let total_lines = 2_000;    let filler = "x".repeat(64);    let content: String = (0..total_lines)        .map(|idx| format!("line-{idx}-{filler}\n"))        .collect();    let truncated = truncate_exec_output(&content);    assert_truncated_message_matches(&truncated, "line-0-", /*expected_removed*/ 34_723);    assert!(        truncated.contains("line-0-"),        "expected head line to remain: {truncated}"    );    let last_line = format!("line-{}-", total_lines - 1);    assert!(        truncated.contains(&last_line),        "expected tail line to remain: {truncated}"    );}#[test]fn format_exec_output_prefers_line_marker_when_both_limits_exceeded() {    let total_lines = 300;    let long_line = "x".repeat(256);    let content: String = (0..total_lines)        .map(|idx| format!("line-{idx}-{long_line}\n"))        .collect();    let truncated = truncate_exec_output(&content);    assert_truncated_message_matches(&truncated, "line-0-", /*expected_removed*/ 17_423);}#[cfg(not(debug_assertions))]#[test]fn normalize_adds_missing_output_for_function_call() {    let items = vec![ResponseItem::FunctionCall {        id: None,        name: "do_it".to_string(),        namespace: None,        arguments: "{}".to_string(),        call_id: "call-x".to_string(),        metadata: None,    }];    let mut h = create_history_with_items(items);    h.normalize_history(&default_input_modalities());    assert_eq!(        h.raw_items(),        vec![            ResponseItem::FunctionCall {                id: None,                name: "do_it".to_string(),                namespace: None,                arguments: "{}".to_string(),                call_id: "call-x".to_string(),                metadata: None,            },            ResponseItem::FunctionCallOutput {                call_id: "call-x".to_string(),                output: FunctionCallOutputPayload::from_text("aborted".to_string()),                metadata: None,            },        ]    );}#[cfg(not(debug_assertions))]#[test]fn normalize_adds_missing_output_for_custom_tool_call() {    let items = vec![ResponseItem::CustomToolCall {        id: None,        status: None,        call_id: "tool-x".to_string(),        name: "custom".to_string(),        input: "{}".to_string(),        metadata: None,    }];    let mut h = create_history_with_items(items);    h.normalize_history(&default_input_modalities());    assert_eq!(        h.raw_items(),        vec![            ResponseItem::CustomToolCall {                id: None,                status: None,                call_id: "tool-x".to_string(),                name: "custom".to_string(),                input: "{}".to_string(),                metadata: None,            },            ResponseItem::CustomToolCallOutput {                call_id: "tool-x".to_string(),                name: None,                output: FunctionCallOutputPayload::from_text("aborted".to_string()),                metadata: None,            },        ]    );}#[cfg(not(debug_assertions))]#[test]fn normalize_adds_missing_output_for_local_shell_call_with_id() {    let items = vec![ResponseItem::LocalShellCall {        id: None,        call_id: Some("shell-1".to_string()),        status: LocalShellStatus::Completed,        action: LocalShellAction::Exec(LocalShellExecAction {            command: vec!["echo".to_string(), "hi".to_string()],            timeout_ms: None,            working_directory: None,            env: None,            user: None,        }),        metadata: None,    }];    let mut h = create_history_with_items(items);    h.normalize_history(&default_input_modalities());    assert_eq!(        h.raw_items(),        vec![            ResponseItem::LocalShellCall {                id: None,                call_id: Some("shell-1".to_string()),                status: LocalShellStatus::Completed,                action: LocalShellAction::Exec(LocalShellExecAction {                    command: vec!["echo".to_string(), "hi".to_string()],                    timeout_ms: None,                    working_directory: None,                    env: None,                    user: None,                }),                metadata: None,            },            ResponseItem::FunctionCallOutput {                call_id: "shell-1".to_string(),                output: FunctionCallOutputPayload::from_text("aborted".to_string()),                metadata: None,            },        ]    );}#[cfg(not(debug_assertions))]#[test]fn normalize_removes_orphan_function_call_output() {    let items = vec![ResponseItem::FunctionCallOutput {        call_id: "orphan-1".to_string(),        output: FunctionCallOutputPayload::from_text("ok".to_string()),        metadata: None,    }];    let mut h = create_history_with_items(items);    h.normalize_history(&default_input_modalities());    assert_eq!(h.raw_items(), vec![]);}#[cfg(not(debug_assertions))]#[test]fn normalize_removes_orphan_custom_tool_call_output() {    let items = vec![ResponseItem::CustomToolCallOutput {        call_id: "orphan-2".to_string(),        name: None,        output: FunctionCallOutputPayload::from_text("ok".to_string()),        metadata: None,    }];    let mut h = create_history_with_items(items);    h.normalize_history(&default_input_modalities());    assert_eq!(h.raw_items(), vec![]);}#[cfg(not(debug_assertions))]#[test]fn normalize_mixed_inserts_and_removals() {    let items = vec![        // Will get an inserted output        ResponseItem::FunctionCall {            id: None,            name: "f1".to_string(),            namespace: None,            arguments: "{}".to_string(),            call_id: "c1".to_string(),            metadata: None,        },        // Orphan output that should be removed        ResponseItem::FunctionCallOutput {            call_id: "c2".to_string(),            output: FunctionCallOutputPayload::from_text("ok".to_string()),            metadata: None,        },        // Will get an inserted custom tool output        ResponseItem::CustomToolCall {            id: None,            status: None,            call_id: "t1".to_string(),            name: "tool".to_string(),            input: "{}".to_string(),            metadata: None,        },        // Local shell call also gets an inserted function call output        ResponseItem::LocalShellCall {            id: None,            call_id: Some("s1".to_string()),            status: LocalShellStatus::Completed,            action: LocalShellAction::Exec(LocalShellExecAction {                command: vec!["echo".to_string()],                timeout_ms: None,                working_directory: None,                env: None,                user: None,            }),            metadata: None,        },    ];    let mut h = create_history_with_items(items);    h.normalize_history(&default_input_modalities());    assert_eq!(        h.raw_items(),        vec![            ResponseItem::FunctionCall {                id: None,                name: "f1".to_string(),                namespace: None,                arguments: "{}".to_string(),                call_id: "c1".to_string(),                metadata: None,            },            ResponseItem::FunctionCallOutput {                call_id: "c1".to_string(),                output: FunctionCallOutputPayload::from_text("aborted".to_string()),                metadata: None,            },            ResponseItem::CustomToolCall {                id: None,                status: None,                call_id: "t1".to_string(),                name: "tool".to_string(),                input: "{}".to_string(),                metadata: None,            },            ResponseItem::CustomToolCallOutput {                call_id: "t1".to_string(),                name: None,                output: FunctionCallOutputPayload::from_text("aborted".to_string()),                metadata: None,            },            ResponseItem::LocalShellCall {                id: None,                call_id: Some("s1".to_string()),                status: LocalShellStatus::Completed,                action: LocalShellAction::Exec(LocalShellExecAction {                    command: vec!["echo".to_string()],                    timeout_ms: None,                    working_directory: None,                    env: None,                    user: None,                }),                metadata: None,            },            ResponseItem::FunctionCallOutput {                call_id: "s1".to_string(),                output: FunctionCallOutputPayload::from_text("aborted".to_string()),                metadata: None,            },        ]    );}#[test]fn normalize_adds_missing_output_for_function_call_inserts_output() {    let items = vec![ResponseItem::FunctionCall {        id: None,        name: "do_it".to_string(),        namespace: None,        arguments: "{}".to_string(),        call_id: "call-x".to_string(),        metadata: None,    }];    let mut h = create_history_with_items(items);    h.normalize_history(&default_input_modalities());    assert_eq!(        h.raw_items(),        vec![            ResponseItem::FunctionCall {                id: None,                name: "do_it".to_string(),                namespace: None,                arguments: "{}".to_string(),                call_id: "call-x".to_string(),                metadata: None,            },            ResponseItem::FunctionCallOutput {                call_id: "call-x".to_string(),                output: FunctionCallOutputPayload::from_text("aborted".to_string()),                metadata: None,            },        ]    );}#[test]fn normalize_adds_missing_output_for_tool_search_call() {    let items = vec![ResponseItem::ToolSearchCall {        id: None,        call_id: Some("search-call-x".to_string()),        status: Some("completed".to_string()),        execution: "client".to_string(),        arguments: "{}".into(),        metadata: None,    }];    let mut h = create_history_with_items(items);    h.normalize_history(&default_input_modalities());    assert_eq!(        h.raw_items(),        vec![            ResponseItem::ToolSearchCall {                id: None,                call_id: Some("search-call-x".to_string()),                status: Some("completed".to_string()),                execution: "client".to_string(),                arguments: "{}".into(),                metadata: None,            },            ResponseItem::ToolSearchOutput {                call_id: Some("search-call-x".to_string()),                status: "completed".to_string(),                execution: "client".to_string(),                tools: Vec::new(),                metadata: None,            },        ]    );}#[cfg(debug_assertions)]#[test]#[should_panic]fn normalize_adds_missing_output_for_custom_tool_call_panics_in_debug() {    let items = vec![ResponseItem::CustomToolCall {        id: None,        status: None,        call_id: "tool-x".to_string(),        name: "custom".to_string(),        input: "{}".to_string(),        metadata: None,    }];    let mut h = create_history_with_items(items);    h.normalize_history(&default_input_modalities());}#[cfg(debug_assertions)]#[test]#[should_panic]fn normalize_adds_missing_output_for_local_shell_call_with_id_panics_in_debug() {    let items = vec![ResponseItem::LocalShellCall {        id: None,        call_id: Some("shell-1".to_string()),        status: LocalShellStatus::Completed,        action: LocalShellAction::Exec(LocalShellExecAction {            command: vec!["echo".to_string(), "hi".to_string()],            timeout_ms: None,            working_directory: None,            env: None,            user: None,        }),        metadata: None,    }];    let mut h = create_history_with_items(items);    h.normalize_history(&default_input_modalities());}#[cfg(debug_assertions)]#[test]#[should_panic]fn normalize_removes_orphan_function_call_output_panics_in_debug() {    let items = vec![ResponseItem::FunctionCallOutput {        call_id: "orphan-1".to_string(),        output: FunctionCallOutputPayload::from_text("ok".to_string()),        metadata: None,    }];    let mut h = create_history_with_items(items);    h.normalize_history(&default_input_modalities());}#[cfg(debug_assertions)]#[test]#[should_panic]fn normalize_removes_orphan_custom_tool_call_output_panics_in_debug() {    let items = vec![ResponseItem::CustomToolCallOutput {        call_id: "orphan-2".to_string(),        name: None,        output: FunctionCallOutputPayload::from_text("ok".to_string()),        metadata: None,    }];    let mut h = create_history_with_items(items);    h.normalize_history(&default_input_modalities());}#[cfg(not(debug_assertions))]#[test]fn normalize_removes_orphan_client_tool_search_output() {    let items = vec![ResponseItem::ToolSearchOutput {        call_id: Some("orphan-search".to_string()),        status: "completed".to_string(),        execution: "client".to_string(),        tools: Vec::new(),        metadata: None,    }];    let mut h = create_history_with_items(items);    h.normalize_history(&default_input_modalities());    assert_eq!(h.raw_items(), vec![]);}#[cfg(debug_assertions)]#[test]#[should_panic]fn normalize_removes_orphan_client_tool_search_output_panics_in_debug() {    let items = vec![ResponseItem::ToolSearchOutput {        call_id: Some("orphan-search".to_string()),        status: "completed".to_string(),        execution: "client".to_string(),        tools: Vec::new(),        metadata: None,    }];    let mut h = create_history_with_items(items);    h.normalize_history(&default_input_modalities());}#[test]fn normalize_keeps_server_tool_search_output_without_matching_call() {    let items = vec![ResponseItem::ToolSearchOutput {        call_id: Some("server-search".to_string()),        status: "completed".to_string(),        execution: "server".to_string(),        tools: Vec::new(),        metadata: None,    }];    let mut h = create_history_with_items(items);    h.normalize_history(&default_input_modalities());    assert_eq!(        h.raw_items(),        vec![ResponseItem::ToolSearchOutput {            call_id: Some("server-search".to_string()),            status: "completed".to_string(),            execution: "server".to_string(),            tools: Vec::new(),            metadata: None,        }]    );}#[cfg(debug_assertions)]#[test]#[should_panic]fn normalize_mixed_inserts_and_removals_panics_in_debug() {    let items = vec![        ResponseItem::FunctionCall {            id: None,            name: "f1".to_string(),            namespace: None,            arguments: "{}".to_string(),            call_id: "c1".to_string(),            metadata: None,        },        ResponseItem::FunctionCallOutput {            call_id: "c2".to_string(),            output: FunctionCallOutputPayload::from_text("ok".to_string()),            metadata: None,        },        ResponseItem::CustomToolCall {            id: None,            status: None,            call_id: "t1".to_string(),            name: "tool".to_string(),            input: "{}".to_string(),            metadata: None,        },        ResponseItem::LocalShellCall {            id: None,            call_id: Some("s1".to_string()),            status: LocalShellStatus::Completed,            action: LocalShellAction::Exec(LocalShellExecAction {                command: vec!["echo".to_string()],                timeout_ms: None,                working_directory: None,                env: None,                user: None,            }),            metadata: None,        },    ];    let mut h = create_history_with_items(items);    h.normalize_history(&default_input_modalities());}#[test]fn image_data_url_payload_does_not_dominate_message_estimate() {    let payload = "A".repeat(100_000);    let image_url = format!("data:image/png;base64,{payload}");    let image_item = ResponseItem::Message {        id: None,        role: "user".to_string(),        content: vec![            ContentItem::InputText {                text: "Here is the screenshot".to_string(),            },            ContentItem::InputImage {                image_url,                detail: Some(DEFAULT_IMAGE_DETAIL),            },        ],        phase: None,        metadata: None,    };    let text_only_item = ResponseItem::Message {        id: None,        role: "user".to_string(),        content: vec![ContentItem::InputText {            text: "Here is the screenshot".to_string(),        }],        phase: None,        metadata: None,    };    let raw_len = serde_json::to_string(&image_item).unwrap().len() as i64;    let estimated = estimate_response_item_model_visible_bytes(&image_item);    let expected = raw_len - payload.len() as i64 + RESIZED_IMAGE_BYTES_ESTIMATE;    let text_only_estimated = estimate_response_item_model_visible_bytes(&text_only_item);    assert_eq!(estimated, expected);    assert!(estimated < raw_len);    assert!(estimated > text_only_estimated);}#[test]fn image_data_url_payload_does_not_dominate_function_call_output_estimate() {    let payload = "B".repeat(50_000);    let image_url = format!("data:image/png;base64,{payload}");    let item = ResponseItem::FunctionCallOutput {        call_id: "call-abc".to_string(),        output: FunctionCallOutputPayload::from_content_items(vec![            FunctionCallOutputContentItem::InputText {                text: "Screenshot captured".to_string(),            },            FunctionCallOutputContentItem::InputImage {                image_url,                detail: Some(DEFAULT_IMAGE_DETAIL),            },        ]),        metadata: None,    };    let raw_len = serde_json::to_string(&item).unwrap().len() as i64;    let estimated = estimate_response_item_model_visible_bytes(&item);    let expected = raw_len - payload.len() as i64 + RESIZED_IMAGE_BYTES_ESTIMATE;    assert_eq!(estimated, expected);    assert!(estimated < raw_len);}#[test]fn image_data_url_payload_does_not_dominate_custom_tool_call_output_estimate() {    let payload = "C".repeat(50_000);    let image_url = format!("data:image/png;base64,{payload}");    let item = ResponseItem::CustomToolCallOutput {        call_id: "call-js-repl".to_string(),        name: None,        output: FunctionCallOutputPayload::from_content_items(vec![            FunctionCallOutputContentItem::InputText {                text: "Screenshot captured".to_string(),            },            FunctionCallOutputContentItem::InputImage {                image_url,                detail: Some(DEFAULT_IMAGE_DETAIL),            },        ]),        metadata: None,    };    let raw_len = serde_json::to_string(&item).unwrap().len() as i64;    let estimated = estimate_response_item_model_visible_bytes(&item);    let expected = raw_len - payload.len() as i64 + RESIZED_IMAGE_BYTES_ESTIMATE;    assert_eq!(estimated, expected);    assert!(estimated < raw_len);}#[test]fn non_base64_image_urls_are_unchanged() {    let message_item = ResponseItem::Message {        id: None,        role: "user".to_string(),        content: vec![ContentItem::InputImage {            image_url: "https://example.com/foo.png".to_string(),            detail: Some(DEFAULT_IMAGE_DETAIL),        }],        phase: None,        metadata: None,    };    let function_output_item = ResponseItem::FunctionCallOutput {        call_id: "call-1".to_string(),        output: FunctionCallOutputPayload::from_content_items(vec![            FunctionCallOutputContentItem::InputImage {                image_url: "file:///tmp/foo.png".to_string(),                detail: Some(DEFAULT_IMAGE_DETAIL),            },        ]),        metadata: None,    };    assert_eq!(        estimate_response_item_model_visible_bytes(&message_item),        serde_json::to_string(&message_item).unwrap().len() as i64    );    assert_eq!(        estimate_response_item_model_visible_bytes(&function_output_item),        serde_json::to_string(&function_output_item).unwrap().len() as i64    );}#[test]fn encrypted_function_output_uses_plaintext_byte_estimate() {    let encrypted_content = "A".repeat(1_868);    let item = ResponseItem::FunctionCallOutput {        call_id: "call-encrypted".to_string(),        output: FunctionCallOutputPayload::from_content_items(vec![            FunctionCallOutputContentItem::EncryptedContent {                encrypted_content: encrypted_content.clone(),            },        ]),        metadata: None,    };    let raw_len = serde_json::to_string(&item).unwrap().len() as i64;    let estimated = estimate_response_item_model_visible_bytes(&item);    let expected = raw_len - encrypted_content.len() as i64        + estimate_encrypted_function_output_length(encrypted_content.len()) as i64;    assert_eq!(estimated, expected);}#[test]fn data_url_without_base64_marker_is_unchanged() {    let item = ResponseItem::Message {        id: None,        role: "user".to_string(),        content: vec![ContentItem::InputImage {            image_url: "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>".to_string(),            detail: Some(DEFAULT_IMAGE_DETAIL),        }],        phase: None,        metadata: None,    };    assert_eq!(        estimate_response_item_model_visible_bytes(&item),        serde_json::to_string(&item).unwrap().len() as i64    );}#[test]fn non_image_base64_data_url_is_unchanged() {    let payload = "C".repeat(4_096);    let image_url = format!("data:application/octet-stream;base64,{payload}");    let item = ResponseItem::FunctionCallOutput {        call_id: "call-octet".to_string(),        output: FunctionCallOutputPayload::from_content_items(vec![            FunctionCallOutputContentItem::InputImage {                image_url,                detail: Some(DEFAULT_IMAGE_DETAIL),            },        ]),        metadata: None,    };    let raw_len = serde_json::to_string(&item).unwrap().len() as i64;    let estimated = estimate_response_item_model_visible_bytes(&item);    assert_eq!(estimated, raw_len);}#[test]fn mixed_case_data_url_markers_are_adjusted() {    let payload = "F".repeat(1_024);    let image_url = format!("DATA:image/png;BASE64,{payload}");    let item = ResponseItem::Message {        id: None,        role: "user".to_string(),        content: vec![ContentItem::InputImage {            image_url,            detail: Some(DEFAULT_IMAGE_DETAIL),        }],        phase: None,        metadata: None,    };    let raw_len = serde_json::to_string(&item).unwrap().len() as i64;    let estimated = estimate_response_item_model_visible_bytes(&item);    let expected = raw_len - payload.len() as i64 + RESIZED_IMAGE_BYTES_ESTIMATE;    assert_eq!(estimated, expected);}#[test]fn multiple_inline_images_apply_multiple_fixed_costs() {    let payload_one = "D".repeat(100);    let payload_two = "E".repeat(200);    let image_url_one = format!("data:image/png;base64,{payload_one}");    let image_url_two = format!("data:image/jpeg;base64,{payload_two}");    let item = ResponseItem::Message {        id: None,        role: "user".to_string(),        content: vec![            ContentItem::InputText {                text: "images".to_string(),            },            ContentItem::InputImage {                image_url: image_url_one,                detail: Some(DEFAULT_IMAGE_DETAIL),            },            ContentItem::InputImage {                image_url: image_url_two,                detail: Some(DEFAULT_IMAGE_DETAIL),            },        ],        phase: None,        metadata: None,    };    let raw_len = serde_json::to_string(&item).unwrap().len() as i64;    let payload_sum = (payload_one.len() + payload_two.len()) as i64;    let estimated = estimate_response_item_model_visible_bytes(&item);    let expected = raw_len - payload_sum + (2 * RESIZED_IMAGE_BYTES_ESTIMATE);    assert_eq!(estimated, expected);}#[test]fn original_detail_images_scale_with_dimensions() {    // 2304x864 at 32px patches yields 72 * 27 = 1,944 patches.    // The byte heuristic uses 4 bytes per token, so the replacement cost is 7,776 bytes.    const EXPECTED_ORIGINAL_DETAIL_IMAGE_BYTES: i64 = 7_776;    let width = 2304;    let height = 864;    let image = ImageBuffer::from_pixel(width, height, Rgba([12u8, 34, 56, 255]));    let mut bytes = std::io::Cursor::new(Vec::new());    image        .write_to(&mut bytes, ImageFormat::Png)        .expect("encode png");    let payload = BASE64_STANDARD.encode(bytes.get_ref());    let image_url = format!("data:image/png;base64,{payload}");    let item = ResponseItem::FunctionCallOutput {        call_id: "call-original".to_string(),        output: FunctionCallOutputPayload::from_content_items(vec![            FunctionCallOutputContentItem::InputImage {                image_url,                detail: Some(ImageDetail::Original),            },        ]),        metadata: None,    };    let raw_len = serde_json::to_string(&item).unwrap().len() as i64;    let estimated = estimate_response_item_model_visible_bytes(&item);    let expected = raw_len - payload.len() as i64 + EXPECTED_ORIGINAL_DETAIL_IMAGE_BYTES;    assert_eq!(estimated, expected);}#[test]fn original_detail_images_are_capped_at_max_patch_count() {    // 3201x3201 at 32px patches yields 101 * 101 = 10,201 patches,    // which exceeds the original-detail patch budget.    let width = 3201;    let height = 3201;    let image = ImageBuffer::from_pixel(width, height, Luma([12u8]));    let mut bytes = std::io::Cursor::new(Vec::new());    image        .write_to(&mut bytes, ImageFormat::Png)        .expect("encode png");    let payload = BASE64_STANDARD.encode(bytes.get_ref());    let image_url = format!("data:image/png;base64,{payload}");    let item = ResponseItem::FunctionCallOutput {        call_id: "call-original-capped".to_string(),        output: FunctionCallOutputPayload::from_content_items(vec![            FunctionCallOutputContentItem::InputImage {                image_url,                detail: Some(ImageDetail::Original),            },        ]),        metadata: None,    };    let raw_len = serde_json::to_string(&item).unwrap().len() as i64;    let estimated = estimate_response_item_model_visible_bytes(&item);    let capped_original_detail_image_bytes =        i64::try_from(approx_bytes_for_tokens(ORIGINAL_IMAGE_MAX_PATCHES)).unwrap();    let expected = raw_len - payload.len() as i64 + capped_original_detail_image_bytes;    assert_eq!(estimated, expected);}#[test]fn original_detail_webp_images_scale_with_dimensions() {    // Same dimensions as the PNG case above, so the patch-based replacement cost is the same.    const EXPECTED_ORIGINAL_DETAIL_IMAGE_BYTES: i64 = 7_776;    let width = 2304;    let height = 864;    let image = ImageBuffer::from_pixel(width, height, Rgba([12u8, 34, 56, 255]));    let mut bytes = std::io::Cursor::new(Vec::new());    image        .write_to(&mut bytes, ImageFormat::WebP)        .expect("encode webp");    let payload = BASE64_STANDARD.encode(bytes.get_ref());    let image_url = format!("data:image/webp;base64,{payload}");    let item = ResponseItem::FunctionCallOutput {        call_id: "call-original-webp".to_string(),        output: FunctionCallOutputPayload::from_content_items(vec![            FunctionCallOutputContentItem::InputImage {                image_url,                detail: Some(ImageDetail::Original),            },        ]),        metadata: None,    };    let raw_len = serde_json::to_string(&item).unwrap().len() as i64;    let estimated = estimate_response_item_model_visible_bytes(&item);    let expected = raw_len - payload.len() as i64 + EXPECTED_ORIGINAL_DETAIL_IMAGE_BYTES;    assert_eq!(estimated, expected);}#[test]fn text_only_items_unchanged() {    let item = ResponseItem::Message {        id: None,        role: "assistant".to_string(),        content: vec![ContentItem::OutputText {            text: "Hello world, this is a response.".to_string(),        }],        phase: None,        metadata: None,    };    let estimated = estimate_response_item_model_visible_bytes(&item);    let raw_len = serde_json::to_string(&item).unwrap().len() as i64;    assert_eq!(estimated, raw_len);}