Codex Handbook
core/src/tools/context_tests.rs 463 lines
use super::*;use codex_protocol::models::DEFAULT_IMAGE_DETAIL;use codex_protocol::models::SearchToolCallParams;use core_test_support::assert_regex_match;use pretty_assertions::assert_eq;use serde_json::json;#[test]fn custom_tool_calls_should_roundtrip_as_custom_outputs() {    let payload = ToolPayload::Custom {        input: "patch".to_string(),    };    let response = FunctionToolOutput::from_text("patched".to_string(), Some(true))        .to_response_item("call-42", &payload);    match response {        ResponseInputItem::CustomToolCallOutput {            call_id, output, ..        } => {            assert_eq!(call_id, "call-42");            assert_eq!(output.content_items(), None);            assert_eq!(output.body.to_text().as_deref(), Some("patched"));            assert_eq!(output.success, Some(true));        }        other => panic!("expected CustomToolCallOutput, got {other:?}"),    }}#[test]fn function_payloads_remain_function_outputs() {    let payload = ToolPayload::Function {        arguments: "{}".to_string(),    };    let response = FunctionToolOutput::from_text("ok".to_string(), Some(true))        .to_response_item("fn-1", &payload);    match response {        ResponseInputItem::FunctionCallOutput { call_id, output } => {            assert_eq!(call_id, "fn-1");            assert_eq!(output.content_items(), None);            assert_eq!(output.body.to_text().as_deref(), Some("ok"));            assert_eq!(output.success, Some(true));        }        other => panic!("expected FunctionCallOutput, got {other:?}"),    }}#[test]fn mcp_code_mode_result_serializes_full_call_tool_result() {    let output = CallToolResult {        content: vec![serde_json::json!({            "type": "text",            "text": "ignored",        })],        structured_content: Some(serde_json::json!({            "threadId": "thread_123",            "content": "done",        })),        is_error: Some(false),        meta: Some(serde_json::json!({            "source": "mcp",        })),    };    let result = output.code_mode_result(&ToolPayload::Function {        arguments: "{}".to_string(),    });    assert_eq!(        result,        serde_json::json!({            "content": [{                "type": "text",                "text": "ignored",            }],            "structuredContent": {                "threadId": "thread_123",                "content": "done",            },            "isError": false,            "_meta": {                "source": "mcp",            },        })    );}#[test]fn mcp_tool_output_response_item_includes_wall_time() {    let output = McpToolOutput {        result: CallToolResult {            content: vec![serde_json::json!({                "type": "text",                "text": "done",            })],            structured_content: None,            is_error: Some(false),            meta: None,        },        tool_input: json!({}),        wall_time: std::time::Duration::from_millis(1250),        original_image_detail_supported: false,        truncation_policy: TruncationPolicy::Bytes(1024),    };    let response = output.to_response_item(        "mcp-call-1",        &ToolPayload::Function {            arguments: "{}".to_string(),        },    );    match response {        ResponseInputItem::FunctionCallOutput { call_id, output } => {            assert_eq!(call_id, "mcp-call-1");            assert_eq!(output.success, Some(true));            let Some(text) = output.body.to_text() else {                panic!("MCP output should serialize as text");            };            let Some(payload) = text.strip_prefix("Wall time: 1.2500 seconds\nOutput:\n") else {                panic!("MCP output should include wall-time header: {text}");            };            let parsed: serde_json::Value = serde_json::from_str(payload).unwrap_or_else(|err| {                panic!("MCP output should serialize JSON content: {err}");            });            assert_eq!(                parsed,                json!([{                    "type": "text",                    "text": "done",                }])            );        }        other => panic!("expected FunctionCallOutput, got {other:?}"),    }}#[test]fn mcp_tool_output_response_item_truncates_large_structured_content() {    let output = McpToolOutput {        result: CallToolResult {            content: vec![serde_json::json!({                "type": "text",                "text": "ignored when structured content is present",            })],            structured_content: Some(serde_json::json!({                "items": "large structured value ".repeat(1_000),            })),            is_error: Some(false),            meta: None,        },        tool_input: json!({}),        wall_time: std::time::Duration::from_millis(1250),        original_image_detail_supported: false,        truncation_policy: TruncationPolicy::Bytes(128),    };    let response = output.to_response_item(        "mcp-call-large",        &ToolPayload::Function {            arguments: "{}".to_string(),        },    );    match response {        ResponseInputItem::FunctionCallOutput { call_id, output } => {            assert_eq!(call_id, "mcp-call-large");            assert_eq!(output.success, Some(true));            let text = output                .body                .to_text()                .expect("MCP output should serialize as text");            assert!(text.starts_with("Wall time: 1.2500 seconds\nOutput:\n"));            assert!(text.contains("chars truncated"));            assert!(!text.contains("ignored when structured content is present"));        }        other => panic!("expected FunctionCallOutput, got {other:?}"),    }}#[test]fn mcp_tool_output_response_item_preserves_content_items() {    let image_url = "data:image/png;base64,AAA";    let output = McpToolOutput {        result: CallToolResult {            content: vec![serde_json::json!({                "type": "image",                "mimeType": "image/png",                "data": "AAA",            })],            structured_content: None,            is_error: Some(false),            meta: None,        },        tool_input: json!({}),        wall_time: std::time::Duration::from_millis(500),        original_image_detail_supported: false,        truncation_policy: TruncationPolicy::Bytes(1024),    };    let response = output.to_response_item(        "mcp-call-2",        &ToolPayload::Function {            arguments: "{}".to_string(),        },    );    match response {        ResponseInputItem::FunctionCallOutput { output, .. } => {            assert_eq!(                output.content_items(),                Some(                    vec![                        FunctionCallOutputContentItem::InputText {                            text: "Wall time: 0.5000 seconds\nOutput:".to_string(),                        },                        FunctionCallOutputContentItem::InputImage {                            image_url: image_url.to_string(),                            detail: Some(DEFAULT_IMAGE_DETAIL),                        },                    ]                    .as_slice()                )            );            assert_eq!(                output.body.to_text().as_deref(),                Some("Wall time: 0.5000 seconds\nOutput:")            );        }        other => panic!("expected FunctionCallOutput, got {other:?}"),    }}#[test]fn mcp_tool_output_code_mode_result_stays_raw_call_tool_result() {    let large_content = "large structured value ".repeat(1_000);    let output = McpToolOutput {        result: CallToolResult {            content: vec![serde_json::json!({                "type": "text",                "text": "ignored",            })],            structured_content: Some(serde_json::json!({                "content": large_content,            })),            is_error: Some(false),            meta: None,        },        tool_input: json!({}),        wall_time: std::time::Duration::from_millis(1250),        original_image_detail_supported: false,        truncation_policy: TruncationPolicy::Bytes(64),    };    let result = output.code_mode_result(&ToolPayload::Function {        arguments: "{}".to_string(),    });    assert_eq!(        result,        serde_json::json!({            "content": [{                "type": "text",                "text": "ignored",            }],            "structuredContent": {                "content": "large structured value ".repeat(1_000),            },            "isError": false,        })    );}#[test]fn custom_tool_calls_can_derive_text_from_content_items() {    let payload = ToolPayload::Custom {        input: "patch".to_string(),    };    let response = FunctionToolOutput::from_content(        vec![            FunctionCallOutputContentItem::InputText {                text: "line 1".to_string(),            },            FunctionCallOutputContentItem::InputImage {                image_url: "data:image/png;base64,AAA".to_string(),                detail: Some(DEFAULT_IMAGE_DETAIL),            },            FunctionCallOutputContentItem::InputText {                text: "line 2".to_string(),            },        ],        Some(true),    )    .to_response_item("call-99", &payload);    match response {        ResponseInputItem::CustomToolCallOutput {            call_id, output, ..        } => {            let expected = vec![                FunctionCallOutputContentItem::InputText {                    text: "line 1".to_string(),                },                FunctionCallOutputContentItem::InputImage {                    image_url: "data:image/png;base64,AAA".to_string(),                    detail: Some(DEFAULT_IMAGE_DETAIL),                },                FunctionCallOutputContentItem::InputText {                    text: "line 2".to_string(),                },            ];            assert_eq!(call_id, "call-99");            assert_eq!(output.content_items(), Some(expected.as_slice()));            assert_eq!(output.body.to_text().as_deref(), Some("line 1\nline 2"));            assert_eq!(output.success, Some(true));        }        other => panic!("expected CustomToolCallOutput, got {other:?}"),    }}#[test]fn tool_search_payloads_roundtrip_as_tool_search_outputs() {    let payload = ToolPayload::ToolSearch {        arguments: SearchToolCallParams {            query: "calendar".to_string(),            limit: None,        },    };    let response = ToolSearchOutput {        tools: vec![LoadableToolSpec::Function(codex_tools::ResponsesApiTool {            name: "create_event".to_string(),            description: String::new(),            strict: false,            defer_loading: Some(true),            parameters: codex_tools::JsonSchema::object(                /*properties*/ Default::default(),                /*required*/ None,                /*additional_properties*/ None,            ),            output_schema: None,        })],    }    .to_response_item("search-1", &payload);    match response {        ResponseInputItem::ToolSearchOutput {            call_id,            status,            execution,            tools,        } => {            assert_eq!(call_id, "search-1");            assert_eq!(status, "completed");            assert_eq!(execution, "client");            assert_eq!(                tools,                vec![json!({                    "type": "function",                    "name": "create_event",                    "description": "",                    "strict": false,                    "defer_loading": true,                    "parameters": {                        "type": "object",                        "properties": {}                    }                })]            );        }        other => panic!("expected ToolSearchOutput, got {other:?}"),    }}#[test]fn log_preview_uses_content_items_when_plain_text_is_missing() {    let output = FunctionToolOutput::from_content(        vec![FunctionCallOutputContentItem::InputText {            text: "preview".to_string(),        }],        Some(true),    );    assert_eq!(output.log_preview(), "preview");    assert_eq!(        function_call_output_content_items_to_text(&output.body),        Some("preview".to_string())    );}#[test]fn telemetry_preview_returns_original_within_limits() {    let content = "short output";    assert_eq!(telemetry_preview(content), content);}#[test]fn telemetry_preview_truncates_by_bytes() {    let content = "x".repeat(TELEMETRY_PREVIEW_MAX_BYTES + 8);    let preview = telemetry_preview(&content);    assert!(preview.contains(TELEMETRY_PREVIEW_TRUNCATION_NOTICE));    assert!(        preview.len()            <= TELEMETRY_PREVIEW_MAX_BYTES + TELEMETRY_PREVIEW_TRUNCATION_NOTICE.len() + 1    );}#[test]fn telemetry_preview_truncates_by_lines() {    let content = (0..(TELEMETRY_PREVIEW_MAX_LINES + 5))        .map(|idx| format!("line {idx}"))        .collect::<Vec<_>>()        .join("\n");    let preview = telemetry_preview(&content);    let lines: Vec<&str> = preview.lines().collect();    assert!(lines.len() <= TELEMETRY_PREVIEW_MAX_LINES + 1);    assert_eq!(lines.last(), Some(&TELEMETRY_PREVIEW_TRUNCATION_NOTICE));}#[test]fn exec_command_tool_output_formats_truncated_response() {    let payload = ToolPayload::Function {        arguments: "{}".to_string(),    };    let response = ExecCommandToolOutput {        event_call_id: "call-42".to_string(),        chunk_id: "abc123".to_string(),        wall_time: std::time::Duration::from_millis(1250),        raw_output: b"token one token two token three token four token five".to_vec(),        truncation_policy: TruncationPolicy::Tokens(10_000),        max_output_tokens: Some(4),        process_id: None,        exit_code: Some(0),        original_token_count: Some(10),        hook_command: None,    }    .to_response_item("call-42", &payload);    match response {        ResponseInputItem::FunctionCallOutput { call_id, output } => {            assert_eq!(call_id, "call-42");            assert_eq!(output.success, Some(true));            let text = output                .body                .to_text()                .expect("exec output should serialize as text");            assert_regex_match(                r#"(?sx)                    ^Chunk\ ID:\ abc123                    \nWall\ time:\ \d+\.\d{4}\ seconds                    \nProcess\ exited\ with\ code\ 0                    \nOriginal\ token\ count:\ 10                    \nOutput:                    \n.*tokens\ truncated.*                    $"#,                &text,            );        }        other => panic!("expected FunctionCallOutput, got {other:?}"),    }}