Codex Handbook
core/src/client_common_tests.rs 258 lines
use codex_api::OpenAiVerbosity;use codex_api::ResponsesApiRequest;use codex_api::TextControls;use codex_api::create_text_param_for_request;use codex_protocol::config_types::ServiceTier;use codex_protocol::models::FunctionCallOutputPayload;use codex_protocol::models::ImageDetail;use pretty_assertions::assert_eq;use super::*;fn prompt_with_image_outputs() -> Prompt {    Prompt {        input: vec![            ResponseItem::Message {                id: None,                role: "user".to_string(),                content: vec![ContentItem::InputImage {                    image_url: "https://example.com/image.png".to_string(),                    detail: Some(ImageDetail::Original),                }],                phase: None,                metadata: None,            },            ResponseItem::FunctionCallOutput {                call_id: "function-call".to_string(),                output: FunctionCallOutputPayload::from_content_items(vec![                    FunctionCallOutputContentItem::InputImage {                        image_url: "data:image/png;base64,function".to_string(),                        detail: Some(ImageDetail::High),                    },                ]),                metadata: None,            },            ResponseItem::CustomToolCallOutput {                call_id: "custom-call".to_string(),                name: None,                output: FunctionCallOutputPayload::from_content_items(vec![                    FunctionCallOutputContentItem::InputImage {                        image_url: "data:image/png;base64,custom".to_string(),                        detail: Some(ImageDetail::Auto),                    },                ]),                metadata: None,            },        ],        ..Default::default()    }}#[test]fn responses_lite_request_copies_strip_image_details() {    let prompt = prompt_with_image_outputs();    let original = prompt.input.clone();    let stripped = prompt.get_formatted_input_for_request(/*use_responses_lite*/ true);    assert_eq!(        stripped,        vec![            ResponseItem::Message {                id: None,                role: "user".to_string(),                content: vec![ContentItem::InputImage {                    image_url: "https://example.com/image.png".to_string(),                    detail: None,                }],                phase: None,                metadata: None,            },            ResponseItem::FunctionCallOutput {                call_id: "function-call".to_string(),                output: FunctionCallOutputPayload::from_content_items(vec![                    FunctionCallOutputContentItem::InputImage {                        image_url: "data:image/png;base64,function".to_string(),                        detail: None,                    },                ]),                metadata: None,            },            ResponseItem::CustomToolCallOutput {                call_id: "custom-call".to_string(),                name: None,                output: FunctionCallOutputPayload::from_content_items(vec![                    FunctionCallOutputContentItem::InputImage {                        image_url: "data:image/png;base64,custom".to_string(),                        detail: None,                    },                ]),                metadata: None,            },        ]    );    assert_eq!(prompt.input, original);    assert_eq!(        prompt.get_formatted_input_for_request(/*use_responses_lite*/ false),        original    );}#[test]fn serializes_text_verbosity_when_set() {    let input: Vec<ResponseItem> = vec![];    let tools: Vec<serde_json::Value> = vec![];    let req = ResponsesApiRequest {        model: "gpt-5.4".to_string(),        instructions: "i".to_string(),        input,        tools,        tool_choice: "auto".to_string(),        parallel_tool_calls: true,        reasoning: None,        store: false,        stream: true,        include: vec![],        prompt_cache_key: None,        service_tier: None,        text: Some(TextControls {            verbosity: Some(OpenAiVerbosity::Low),            format: None,        }),        client_metadata: None,    };    let v = serde_json::to_value(&req).expect("json");    assert_eq!(        v.get("text")            .and_then(|t| t.get("verbosity"))            .and_then(|s| s.as_str()),        Some("low")    );}#[test]fn serializes_text_schema_with_strict_format() {    let input: Vec<ResponseItem> = vec![];    let tools: Vec<serde_json::Value> = vec![];    let schema = serde_json::json!({        "type": "object",        "properties": {            "answer": {"type": "string"}        },        "required": ["answer"],    });    let text_controls = create_text_param_for_request(        /*verbosity*/ None,        &Some(schema.clone()),        /*output_schema_strict*/ true,    )    .expect("text controls");    let req = ResponsesApiRequest {        model: "gpt-5.4".to_string(),        instructions: "i".to_string(),        input,        tools,        tool_choice: "auto".to_string(),        parallel_tool_calls: true,        reasoning: None,        store: false,        stream: true,        include: vec![],        prompt_cache_key: None,        service_tier: None,        text: Some(text_controls),        client_metadata: None,    };    let v = serde_json::to_value(&req).expect("json");    let text = v.get("text").expect("text field");    assert!(text.get("verbosity").is_none());    let format = text.get("format").expect("format field");    assert_eq!(        format.get("name"),        Some(&serde_json::Value::String("codex_output_schema".into()))    );    assert_eq!(        format.get("type"),        Some(&serde_json::Value::String("json_schema".into()))    );    assert_eq!(format.get("strict"), Some(&serde_json::Value::Bool(true)));    assert_eq!(format.get("schema"), Some(&schema));}#[test]fn serializes_text_schema_with_non_strict_format() {    let schema = serde_json::json!({        "type": "object",        "properties": {            "answer": {"type": "string"},            "rationale": {"type": "string"}        },        "required": ["answer"],        "additionalProperties": false    });    let text_controls = create_text_param_for_request(        /*verbosity*/ None,        &Some(schema.clone()),        /*output_schema_strict*/ false,    )    .expect("text controls");    let format = text_controls.format.expect("format field");    assert!(!format.strict);    assert_eq!(format.schema, schema);}#[test]fn omits_text_when_not_set() {    let input: Vec<ResponseItem> = vec![];    let tools: Vec<serde_json::Value> = vec![];    let req = ResponsesApiRequest {        model: "gpt-5.4".to_string(),        instructions: "i".to_string(),        input,        tools,        tool_choice: "auto".to_string(),        parallel_tool_calls: true,        reasoning: None,        store: false,        stream: true,        include: vec![],        prompt_cache_key: None,        service_tier: None,        text: None,        client_metadata: None,    };    let v = serde_json::to_value(&req).expect("json");    assert!(v.get("text").is_none());}#[test]fn serializes_flex_service_tier_when_set() {    let req = ResponsesApiRequest {        model: "gpt-5.4".to_string(),        instructions: "i".to_string(),        input: vec![],        tools: vec![],        tool_choice: "auto".to_string(),        parallel_tool_calls: true,        reasoning: None,        store: false,        stream: true,        include: vec![],        prompt_cache_key: None,        service_tier: Some(ServiceTier::Flex.to_string()),        text: None,        client_metadata: None,    };    let v = serde_json::to_value(&req).expect("json");    assert_eq!(        v.get("service_tier").and_then(|tier| tier.as_str()),        Some("flex")    );}