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") );}