Codex Handbook
core/src/event_mapping_tests.rs 566 lines
use super::has_non_contextual_dev_message_content;use super::is_contextual_dev_message_content;use super::parse_turn_item;use crate::context::ContextualUserFragment;use crate::context::InternalContextSource;use crate::context::InternalModelContextFragment;use codex_protocol::items::AgentMessageContent;use codex_protocol::items::HookPromptFragment;use codex_protocol::items::TurnItem;use codex_protocol::items::WebSearchItem;use codex_protocol::items::build_hook_prompt_message;use codex_protocol::models::ContentItem;use codex_protocol::models::DEFAULT_IMAGE_DETAIL;use codex_protocol::models::ReasoningItemContent;use codex_protocol::models::ReasoningItemReasoningSummary;use codex_protocol::models::ResponseItem;use codex_protocol::models::WebSearchAction;use codex_protocol::protocol::SKILLS_INSTRUCTIONS_OPEN_TAG;use codex_protocol::user_input::UserInput;use pretty_assertions::assert_eq;#[test]fn recognizes_skills_instructions_as_contextual_developer_content() {    assert!(is_contextual_dev_message_content(&[        ContentItem::InputText {            text: format!("{SKILLS_INSTRUCTIONS_OPEN_TAG}\n## Skills"),        },    ]));}#[test]fn recognizes_token_budget_as_contextual_developer_content() {    let content = vec![ContentItem::InputText {        text: "<token_budget>\nYou have 710 tokens left in this context window.\n</token_budget>"            .to_string(),    }];    assert!(is_contextual_dev_message_content(&content));    assert!(!has_non_contextual_dev_message_content(&content));}#[test]fn parses_user_message_with_text_and_two_images() {    let img1 = "https://example.com/one.png".to_string();    let img2 = "https://example.com/two.jpg".to_string();    let item = ResponseItem::Message {        id: None,        role: "user".to_string(),        content: vec![            ContentItem::InputText {                text: "Hello world".to_string(),            },            ContentItem::InputImage {                image_url: img1.clone(),                detail: Some(DEFAULT_IMAGE_DETAIL),            },            ContentItem::InputImage {                image_url: img2.clone(),                detail: Some(DEFAULT_IMAGE_DETAIL),            },        ],        phase: None,        metadata: None,    };    let turn_item = parse_turn_item(&item).expect("expected user message turn item");    match turn_item {        TurnItem::UserMessage(user) => {            let expected_content = vec![                UserInput::Text {                    text: "Hello world".to_string(),                    text_elements: Vec::new(),                },                UserInput::Image {                    image_url: img1,                    detail: Some(DEFAULT_IMAGE_DETAIL),                },                UserInput::Image {                    image_url: img2,                    detail: Some(DEFAULT_IMAGE_DETAIL),                },            ];            assert_eq!(user.content, expected_content);        }        other => panic!("expected TurnItem::UserMessage, got {other:?}"),    }}#[test]fn skips_local_image_label_text() {    let image_url = "data:image/png;base64,abc".to_string();    let label = r#"<image name=[Image #1] path="/tmp/local.png">"#.to_string();    let user_text = "Please review this image.".to_string();    let item = ResponseItem::Message {        id: None,        role: "user".to_string(),        content: vec![            ContentItem::InputText { text: label },            ContentItem::InputImage {                image_url: image_url.clone(),                detail: Some(DEFAULT_IMAGE_DETAIL),            },            ContentItem::InputText {                text: "</image>".to_string(),            },            ContentItem::InputText {                text: user_text.clone(),            },        ],        phase: None,        metadata: None,    };    let turn_item = parse_turn_item(&item).expect("expected user message turn item");    match turn_item {        TurnItem::UserMessage(user) => {            let expected_content = vec![                UserInput::Image {                    image_url,                    detail: Some(DEFAULT_IMAGE_DETAIL),                },                UserInput::Text {                    text: user_text,                    text_elements: Vec::new(),                },            ];            assert_eq!(user.content, expected_content);        }        other => panic!("expected TurnItem::UserMessage, got {other:?}"),    }}#[test]fn parses_assistant_message_input_text_for_backward_compatibility() {    let item = ResponseItem::Message {        id: None,        role: "assistant".to_string(),        content: vec![ContentItem::InputText {            text: "author: /root\nrecipient: /root/worker\nother_recipients: []\nContent: continue"                .to_string(),        }],        phase: None,        metadata: None,    };    let turn_item = parse_turn_item(&item).expect("expected assistant message turn item");    match turn_item {        TurnItem::AgentMessage(message) => {            let rendered = message                .content                .into_iter()                .map(|content| {                    let AgentMessageContent::Text { text } = content;                    text                })                .collect::<Vec<_>>();            assert_eq!(                rendered,                vec![                    "author: /root\nrecipient: /root/worker\nother_recipients: []\nContent: continue"                        .to_string()                ]            );        }        other => panic!("expected TurnItem::AgentMessage, got {other:?}"),    }}#[test]fn skips_unnamed_image_label_text() {    let image_url = "data:image/png;base64,abc".to_string();    let label = codex_protocol::models::image_open_tag_text();    let user_text = "Please review this image.".to_string();    let item = ResponseItem::Message {        id: None,        role: "user".to_string(),        content: vec![            ContentItem::InputText { text: label },            ContentItem::InputImage {                image_url: image_url.clone(),                detail: Some(DEFAULT_IMAGE_DETAIL),            },            ContentItem::InputText {                text: codex_protocol::models::image_close_tag_text(),            },            ContentItem::InputText {                text: user_text.clone(),            },        ],        phase: None,        metadata: None,    };    let turn_item = parse_turn_item(&item).expect("expected user message turn item");    match turn_item {        TurnItem::UserMessage(user) => {            let expected_content = vec![                UserInput::Image {                    image_url,                    detail: Some(DEFAULT_IMAGE_DETAIL),                },                UserInput::Text {                    text: user_text,                    text_elements: Vec::new(),                },            ];            assert_eq!(user.content, expected_content);        }        other => panic!("expected TurnItem::UserMessage, got {other:?}"),    }}#[test]fn skips_user_instructions_and_env() {    let items = vec![            ResponseItem::Message {                id: None,                role: "user".to_string(),                content: vec![ContentItem::InputText {                    text: "# AGENTS.md instructions for test_directory\n\n<INSTRUCTIONS>\ntest_text\n</INSTRUCTIONS>".to_string(),                }],            phase: None,                metadata: None,},            ResponseItem::Message {                id: None,                role: "user".to_string(),                content: vec![ContentItem::InputText {                    text: "<environment_context>test_text</environment_context>".to_string(),                }],            phase: None,                metadata: None,},            ResponseItem::Message {                id: None,                role: "user".to_string(),                content: vec![ContentItem::InputText {                    text: "# AGENTS.md instructions for test_directory\n\n<INSTRUCTIONS>\ntest_text\n</INSTRUCTIONS>".to_string(),                }],            phase: None,                metadata: None,},            ResponseItem::Message {                id: None,                role: "user".to_string(),                content: vec![ContentItem::InputText {                    text: "<skill>\n<name>demo</name>\n<path>skills/demo/SKILL.md</path>\nbody\n</skill>"                        .to_string(),                }],            phase: None,                metadata: None,},            ResponseItem::Message {                id: None,                role: "user".to_string(),                content: vec![ContentItem::InputText {                    text: "<user_shell_command>echo 42</user_shell_command>".to_string(),                }],            phase: None,                metadata: None,},            ResponseItem::Message {                id: None,                role: "user".to_string(),                content: vec![                    ContentItem::InputText {                        text: "<environment_context>ctx</environment_context>".to_string(),                    },                    ContentItem::InputText {                        text:                            "# AGENTS.md instructions for dir\n\n<INSTRUCTIONS>\nbody\n</INSTRUCTIONS>"                                .to_string(),                    },                ],                phase: None,                metadata: None,},        ];    for item in items {        let turn_item = parse_turn_item(&item);        assert!(turn_item.is_none(), "expected none, got {turn_item:?}");    }}#[test]fn parses_hook_prompt_message_as_distinct_turn_item() {    let item = build_hook_prompt_message(&[HookPromptFragment::from_single_hook(        "Retry with exactly the phrase meow meow meow.",        "hook-run-1",    )])    .expect("hook prompt message");    let turn_item = parse_turn_item(&item).expect("expected hook prompt turn item");    match turn_item {        TurnItem::HookPrompt(hook_prompt) => {            assert_eq!(hook_prompt.fragments.len(), 1);            assert_eq!(                hook_prompt.fragments[0],                HookPromptFragment {                    text: "Retry with exactly the phrase meow meow meow.".to_string(),                    hook_run_id: "hook-run-1".to_string(),                }            );        }        other => panic!("expected TurnItem::HookPrompt, got {other:?}"),    }}#[test]fn parses_hook_prompt_and_hides_other_contextual_fragments() {    let item = ResponseItem::Message {        id: Some("msg-1".to_string()),        role: "user".to_string(),        content: vec![            ContentItem::InputText {                text: "<environment_context>ctx</environment_context>".to_string(),            },            ContentItem::InputText {                text:                    "<hook_prompt hook_run_id=\"hook-run-1\">Retry with care &amp; joy.</hook_prompt>"                        .to_string(),            },        ],        phase: None,        metadata: None,};    let turn_item = parse_turn_item(&item).expect("expected hook prompt turn item");    match turn_item {        TurnItem::HookPrompt(hook_prompt) => {            assert_eq!(hook_prompt.id, "msg-1");            assert_eq!(                hook_prompt.fragments,                vec![HookPromptFragment {                    text: "Retry with care & joy.".to_string(),                    hook_run_id: "hook-run-1".to_string(),                }]            );        }        other => panic!("expected TurnItem::HookPrompt, got {other:?}"),    }}#[test]fn internal_model_context_does_not_parse_as_visible_turn_item() {    let item = ResponseItem::Message {        id: Some("msg-1".to_string()),        role: "user".to_string(),        content: vec![ContentItem::InputText {            text: InternalModelContextFragment::new(                InternalContextSource::from_static("extension"),                "Internal steering.",            )            .render(),        }],        phase: None,        metadata: None,    };    assert!(parse_turn_item(&item).is_none());}#[test]fn parses_agent_message() {    let item = ResponseItem::Message {        id: Some("msg-1".to_string()),        role: "assistant".to_string(),        content: vec![ContentItem::OutputText {            text: "Hello from Codex".to_string(),        }],        phase: None,        metadata: None,    };    let turn_item = parse_turn_item(&item).expect("expected agent message turn item");    match turn_item {        TurnItem::AgentMessage(message) => {            let Some(AgentMessageContent::Text { text }) = message.content.first() else {                panic!("expected agent message text content");            };            assert_eq!(text, "Hello from Codex");        }        other => panic!("expected TurnItem::AgentMessage, got {other:?}"),    }}#[test]fn parses_reasoning_summary_and_raw_content() {    let item = ResponseItem::Reasoning {        id: "reasoning_1".to_string(),        summary: vec![            ReasoningItemReasoningSummary::SummaryText {                text: "Step 1".to_string(),            },            ReasoningItemReasoningSummary::SummaryText {                text: "Step 2".to_string(),            },        ],        content: Some(vec![ReasoningItemContent::ReasoningText {            text: "raw details".to_string(),        }]),        encrypted_content: None,        metadata: None,    };    let turn_item = parse_turn_item(&item).expect("expected reasoning turn item");    match turn_item {        TurnItem::Reasoning(reasoning) => {            assert_eq!(                reasoning.summary_text,                vec!["Step 1".to_string(), "Step 2".to_string()]            );            assert_eq!(reasoning.raw_content, vec!["raw details".to_string()]);        }        other => panic!("expected TurnItem::Reasoning, got {other:?}"),    }}#[test]fn parses_reasoning_including_raw_content() {    let item = ResponseItem::Reasoning {        id: "reasoning_2".to_string(),        summary: vec![ReasoningItemReasoningSummary::SummaryText {            text: "Summarized step".to_string(),        }],        content: Some(vec![            ReasoningItemContent::ReasoningText {                text: "raw step".to_string(),            },            ReasoningItemContent::Text {                text: "final thought".to_string(),            },        ]),        encrypted_content: None,        metadata: None,    };    let turn_item = parse_turn_item(&item).expect("expected reasoning turn item");    match turn_item {        TurnItem::Reasoning(reasoning) => {            assert_eq!(reasoning.summary_text, vec!["Summarized step".to_string()]);            assert_eq!(                reasoning.raw_content,                vec!["raw step".to_string(), "final thought".to_string()]            );        }        other => panic!("expected TurnItem::Reasoning, got {other:?}"),    }}#[test]fn parses_web_search_call() {    let item = ResponseItem::WebSearchCall {        id: Some("ws_1".to_string()),        status: Some("completed".to_string()),        action: Some(WebSearchAction::Search {            query: Some("weather".to_string()),            queries: None,        }),        metadata: None,    };    let turn_item = parse_turn_item(&item).expect("expected web search turn item");    match turn_item {        TurnItem::WebSearch(search) => assert_eq!(            search,            WebSearchItem {                id: "ws_1".to_string(),                query: "weather".to_string(),                action: WebSearchAction::Search {                    query: Some("weather".to_string()),                    queries: None,                },            }        ),        other => panic!("expected TurnItem::WebSearch, got {other:?}"),    }}#[test]fn parses_web_search_open_page_call() {    let item = ResponseItem::WebSearchCall {        id: Some("ws_open".to_string()),        status: Some("completed".to_string()),        action: Some(WebSearchAction::OpenPage {            url: Some("https://example.com".to_string()),        }),        metadata: None,    };    let turn_item = parse_turn_item(&item).expect("expected web search turn item");    match turn_item {        TurnItem::WebSearch(search) => assert_eq!(            search,            WebSearchItem {                id: "ws_open".to_string(),                query: "https://example.com".to_string(),                action: WebSearchAction::OpenPage {                    url: Some("https://example.com".to_string()),                },            }        ),        other => panic!("expected TurnItem::WebSearch, got {other:?}"),    }}#[test]fn parses_web_search_find_in_page_call() {    let item = ResponseItem::WebSearchCall {        id: Some("ws_find".to_string()),        status: Some("completed".to_string()),        action: Some(WebSearchAction::FindInPage {            url: Some("https://example.com".to_string()),            pattern: Some("needle".to_string()),        }),        metadata: None,    };    let turn_item = parse_turn_item(&item).expect("expected web search turn item");    match turn_item {        TurnItem::WebSearch(search) => assert_eq!(            search,            WebSearchItem {                id: "ws_find".to_string(),                query: "'needle' in https://example.com".to_string(),                action: WebSearchAction::FindInPage {                    url: Some("https://example.com".to_string()),                    pattern: Some("needle".to_string()),                },            }        ),        other => panic!("expected TurnItem::WebSearch, got {other:?}"),    }}#[test]fn parses_partial_web_search_call_without_action_as_other() {    let item = ResponseItem::WebSearchCall {        id: Some("ws_partial".to_string()),        status: Some("in_progress".to_string()),        action: None,        metadata: None,    };    let turn_item = parse_turn_item(&item).expect("expected web search turn item");    match turn_item {        TurnItem::WebSearch(search) => assert_eq!(            search,            WebSearchItem {                id: "ws_partial".to_string(),                query: String::new(),                action: WebSearchAction::Other,            }        ),        other => panic!("expected TurnItem::WebSearch, got {other:?}"),    }}