| |
| |
|
|
| use serde_json::{json, Value}; |
|
|
| |
| #[derive(Debug, Clone)] |
| pub struct RequestConfig { |
| |
| pub request_type: String, |
| |
| pub inject_google_search: bool, |
| |
| pub final_model: String, |
| |
| pub image_config: Option<Value>, |
| } |
|
|
| pub fn resolve_request_config( |
| original_model: &str, |
| mapped_model: &str, |
| tools: &Option<Vec<Value>>, |
| size: Option<&str>, |
| quality: Option<&str>, |
| image_size: Option<&str>, |
| body: Option<&Value>, |
| ) -> RequestConfig { |
| |
| if mapped_model.starts_with("gemini-3-pro-image") { |
| |
| |
| let (mut inferred_config, parsed_base_model) = |
| parse_image_config_with_params(original_model, size, quality, image_size); |
|
|
| |
| if let Some(body_val) = body { |
| if let Some(gen_config) = body_val.get("generationConfig") { |
| if let Some(body_image_config) = gen_config.get("imageConfig") { |
| tracing::info!( |
| "[Common-Utils] Found imageConfig in body, merging with inferred config from suffix/params" |
| ); |
| |
| if let Some(inferred_obj) = inferred_config.as_object_mut() { |
| if let Some(body_obj) = body_image_config.as_object() { |
| |
| for (key, value) in body_obj { |
| |
| |
| let is_size_downgrade = key == "imageSize" && |
| (value.as_str() == Some("1K") || value.is_null()) && |
| inferred_obj.contains_key("imageSize"); |
|
|
| if !is_size_downgrade { |
| inferred_obj.insert(key.clone(), value.clone()); |
| } else { |
| tracing::debug!("[Common-Utils] Shielding inferred imageSize from body downgrade"); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
|
|
| tracing::info!( |
| "[Common-Utils] Final Image Config for {}: {:?}", |
| parsed_base_model, inferred_config |
| ); |
|
|
| return RequestConfig { |
| request_type: "image_gen".to_string(), |
| inject_google_search: false, |
| final_model: parsed_base_model, |
| image_config: Some(inferred_config), |
| }; |
| } |
|
|
| |
| let has_networking_tool = detects_networking_tool(tools); |
| |
| let _has_non_networking = contains_non_networking_tool(tools); |
|
|
| |
| let is_online_suffix = original_model.ends_with("-online"); |
|
|
| |
| let _is_high_quality_model = mapped_model == "gemini-2.5-flash" |
| || mapped_model == "gemini-1.5-pro" |
| || mapped_model.starts_with("gemini-1.5-pro-") |
| || mapped_model.starts_with("gemini-2.5-flash-") |
| || mapped_model.starts_with("gemini-2.0-flash") |
| || mapped_model.starts_with("gemini-3-") |
| || mapped_model.starts_with("gemini-3.") |
| || mapped_model.contains("claude-3-5-sonnet") |
| || mapped_model.contains("claude-3-opus") |
| || mapped_model.contains("claude-sonnet") |
| || mapped_model.contains("claude-opus") |
| || mapped_model.contains("claude-4"); |
|
|
| |
| |
| |
| let enable_networking = is_online_suffix || has_networking_tool; |
|
|
| |
| |
| |
| let mut final_model = mapped_model.trim_end_matches("-online").to_string(); |
|
|
| |
| |
| |
| final_model = match final_model.as_str() { |
| "gemini-3-pro-image-preview" => "gemini-3-pro-image".to_string(), |
| "gemini-3-flash-preview" => "gemini-3-flash".to_string(), |
| _ => final_model, |
| }; |
|
|
| |
| |
| if enable_networking && !_is_high_quality_model { |
| tracing::info!( |
| "[Common-Utils] Downgrading {} to gemini-2.5-flash for web search (model not in search allowlist)", |
| final_model |
| ); |
| final_model = "gemini-2.5-flash".to_string(); |
| } |
|
|
| RequestConfig { |
| request_type: if enable_networking { |
| "web_search".to_string() |
| } else { |
| "agent".to_string() |
| }, |
| inject_google_search: enable_networking, |
| final_model, |
| image_config: None, |
| } |
| } |
|
|
| |
| #[allow(dead_code)] |
| pub fn parse_image_config(model_name: &str) -> (Value, String) { |
| parse_image_config_with_params(model_name, None, None, None) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| pub fn parse_image_config_with_params( |
| model_name: &str, |
| size: Option<&str>, |
| quality: Option<&str>, |
| image_size: Option<&str>, |
| ) -> (Value, String) { |
| let mut aspect_ratio = "1:1"; |
|
|
| |
| if let Some(s) = size { |
| aspect_ratio = calculate_aspect_ratio_from_size(s); |
| } else { |
| |
| if model_name.contains("-21x9") || model_name.contains("-21-9") { |
| aspect_ratio = "21:9"; |
| } else if model_name.contains("-16x9") || model_name.contains("-16-9") { |
| aspect_ratio = "16:9"; |
| } else if model_name.contains("-9x16") || model_name.contains("-9-16") { |
| aspect_ratio = "9:16"; |
| } else if model_name.contains("-4x3") || model_name.contains("-4-3") { |
| aspect_ratio = "4:3"; |
| } else if model_name.contains("-3x4") || model_name.contains("-3-4") { |
| aspect_ratio = "3:4"; |
| } else if model_name.contains("-3x2") || model_name.contains("-3-2") { |
| aspect_ratio = "3:2"; |
| } else if model_name.contains("-2x3") || model_name.contains("-2-3") { |
| aspect_ratio = "2:3"; |
| } else if model_name.contains("-5x4") || model_name.contains("-5-4") { |
| aspect_ratio = "5:4"; |
| } else if model_name.contains("-4x5") || model_name.contains("-4-5") { |
| aspect_ratio = "4:5"; |
| } else if model_name.contains("-1x1") || model_name.contains("-1-1") { |
| aspect_ratio = "1:1"; |
| } |
| } |
|
|
| let mut config = serde_json::Map::new(); |
| config.insert("aspectRatio".to_string(), json!(aspect_ratio)); |
|
|
| |
| if let Some(is) = image_size { |
| config.insert("imageSize".to_string(), json!(is.to_uppercase())); |
| } else { |
| |
| if let Some(q) = quality { |
| match q.to_lowercase().as_str() { |
| "hd" | "4k" => { |
| config.insert("imageSize".to_string(), json!("4K")); |
| } |
| "medium" | "2k" => { |
| config.insert("imageSize".to_string(), json!("2K")); |
| } |
| "standard" | "1k" => { |
| config.insert("imageSize".to_string(), json!("1K")); |
| } |
| _ => {} |
| } |
| } else { |
| |
| let is_hd = model_name.contains("-4k") || model_name.contains("-hd"); |
| let is_2k = model_name.contains("-2k"); |
|
|
| if is_hd { |
| config.insert("imageSize".to_string(), json!("4K")); |
| } else if is_2k { |
| config.insert("imageSize".to_string(), json!("2K")); |
| } |
| } |
| } |
|
|
| let clean_model_name = clean_image_model_name(model_name); |
|
|
| ( |
| serde_json::Value::Object(config), |
| clean_model_name, |
| ) |
| } |
|
|
| |
| |
| fn clean_image_model_name(model_name: &str) -> String { |
| let mut clean_name = model_name.to_lowercase(); |
| |
| |
| let suffixes = [ |
| "-4k", "-2k", "-1k", "-hd", "-standard", "-medium", |
| "-21x9", "-21-9", "-16x9", "-16-9", "-9x16", "-9-16", |
| "-4x3", "-4-3", "-3x4", "-3-4", "-3x2", "-3-2", |
| "-2x3", "-2-3", "-5x4", "-5-4", "-4x5", "-4-5", |
| "-1x1", "-1-1" |
| ]; |
|
|
| |
| let mut changed = true; |
| while changed { |
| changed = false; |
| for suffix in &suffixes { |
| if clean_name.ends_with(suffix) { |
| clean_name.truncate(clean_name.len() - suffix.len()); |
| changed = true; |
| } |
| } |
| } |
|
|
| clean_name |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| fn calculate_aspect_ratio_from_size(size: &str) -> &'static str { |
| |
| match size { |
| "21:9" => return "21:9", |
| "16:9" => return "16:9", |
| "9:16" => return "9:16", |
| "4:3" => return "4:3", |
| "3:4" => return "3:4", |
| "3:2" => return "3:2", |
| "2:3" => return "2:3", |
| "5:4" => return "5:4", |
| "4:5" => return "4:5", |
| "1:1" => return "1:1", |
| _ => {} |
| } |
|
|
| if let Some((w_str, h_str)) = size.split_once('x') { |
| if let (Ok(width), Ok(height)) = (w_str.parse::<f64>(), h_str.parse::<f64>()) { |
| if width > 0.0 && height > 0.0 { |
| let ratio = width / height; |
|
|
| |
| if (ratio - 21.0 / 9.0).abs() < 0.05 { |
| return "21:9"; |
| } |
| if (ratio - 16.0 / 9.0).abs() < 0.05 { |
| return "16:9"; |
| } |
| if (ratio - 4.0 / 3.0).abs() < 0.05 { |
| return "4:3"; |
| } |
| if (ratio - 3.0 / 4.0).abs() < 0.05 { |
| return "3:4"; |
| } |
| if (ratio - 9.0 / 16.0).abs() < 0.05 { |
| return "9:16"; |
| } |
| if (ratio - 3.0 / 2.0).abs() < 0.05 { |
| return "3:2"; |
| } |
| if (ratio - 2.0 / 3.0).abs() < 0.05 { |
| return "2:3"; |
| } |
| if (ratio - 5.0 / 4.0).abs() < 0.05 { |
| return "5:4"; |
| } |
| if (ratio - 4.0 / 5.0).abs() < 0.05 { |
| return "4:5"; |
| } |
| if (ratio - 1.0).abs() < 0.05 { |
| return "1:1"; |
| } |
| } |
| } |
| } |
|
|
| "1:1" |
| } |
|
|
| |
| pub fn inject_google_search_tool(body: &mut Value, mapped_model: Option<&str>) { |
| if let Some(obj) = body.as_object_mut() { |
| let tools_entry = obj.entry("tools").or_insert_with(|| json!([])); |
| if let Some(tools_arr) = tools_entry.as_array_mut() { |
| let has_functions = tools_arr.iter().any(|t| { |
| t.as_object() |
| .map_or(false, |o| o.contains_key("functionDeclarations")) |
| }); |
|
|
| |
| |
| |
| if has_functions { |
| tracing::debug!( |
| "Skipping googleSearch injection: functionDeclarations present (v1internal incompatible)" |
| ); |
| return; |
| } |
|
|
| |
| tools_arr.retain(|t| { |
| if let Some(o) = t.as_object() { |
| !(o.contains_key("googleSearch") || o.contains_key("googleSearchRetrieval")) |
| } else { |
| true |
| } |
| }); |
|
|
| |
| tools_arr.push(json!({ |
| "googleSearch": {} |
| })); |
| } |
| } |
| } |
|
|
| |
| pub fn deep_clean_undefined(value: &mut Value, depth: usize) { |
| if depth > 10 { |
| return; |
| } |
| match value { |
| Value::Object(map) => { |
| |
| map.retain(|_, v| { |
| if let Some(s) = v.as_str() { |
| s != "[undefined]" |
| } else { |
| true |
| } |
| }); |
| |
| for v in map.values_mut() { |
| deep_clean_undefined(v, depth + 1); |
| } |
| } |
| Value::Array(arr) => { |
| for v in arr.iter_mut() { |
| deep_clean_undefined(v, depth + 1); |
| } |
| } |
| _ => {} |
| } |
| } |
|
|
| |
| |
| pub fn detects_networking_tool(tools: &Option<Vec<Value>>) -> bool { |
| if let Some(list) = tools { |
| for tool in list { |
| |
| if let Some(n) = tool.get("name").and_then(|v| v.as_str()) { |
| if n == "web_search" |
| || n == "google_search" |
| || n == "web_search_20250305" |
| || n == "google_search_retrieval" |
| || n == "builtin_web_search" |
| { |
| return true; |
| } |
| } |
|
|
| if let Some(t) = tool.get("type").and_then(|v| v.as_str()) { |
| if t == "web_search_20250305" |
| || t == "google_search" |
| || t == "web_search" |
| || t == "google_search_retrieval" |
| || t == "builtin_web_search" |
| { |
| return true; |
| } |
| } |
|
|
| |
| if let Some(func) = tool.get("function") { |
| if let Some(n) = func.get("name").and_then(|v| v.as_str()) { |
| let keywords = [ |
| "web_search", |
| "google_search", |
| "web_search_20250305", |
| "google_search_retrieval", |
| "builtin_web_search", |
| ]; |
| if keywords.contains(&n) { |
| return true; |
| } |
| } |
| } |
|
|
| |
| if let Some(decls) = tool.get("functionDeclarations").and_then(|v| v.as_array()) { |
| for decl in decls { |
| if let Some(n) = decl.get("name").and_then(|v| v.as_str()) { |
| if n == "web_search" |
| || n == "google_search" |
| || n == "google_search_retrieval" |
| || n == "builtin_web_search" |
| { |
| return true; |
| } |
| } |
| } |
| } |
|
|
| |
| if tool.get("googleSearch").is_some() || tool.get("googleSearchRetrieval").is_some() { |
| return true; |
| } |
| } |
| } |
| false |
| } |
|
|
| |
| pub fn contains_non_networking_tool(tools: &Option<Vec<Value>>) -> bool { |
| if let Some(list) = tools { |
| for tool in list { |
| let mut is_networking = false; |
|
|
| |
| if let Some(n) = tool.get("name").and_then(|v| v.as_str()) { |
| let keywords = [ |
| "web_search", |
| "google_search", |
| "web_search_20250305", |
| "google_search_retrieval", |
| "builtin_web_search", |
| ]; |
| if keywords.contains(&n) { |
| is_networking = true; |
| } |
| } else if let Some(func) = tool.get("function") { |
| if let Some(n) = func.get("name").and_then(|v| v.as_str()) { |
| let keywords = [ |
| "web_search", |
| "google_search", |
| "web_search_20250305", |
| "google_search_retrieval", |
| "builtin_web_search", |
| ]; |
| if keywords.contains(&n) { |
| is_networking = true; |
| } |
| } |
| } else if tool.get("googleSearch").is_some() |
| || tool.get("googleSearchRetrieval").is_some() |
| { |
| is_networking = true; |
| } else if tool.get("functionDeclarations").is_some() { |
| |
| if let Some(decls) = tool.get("functionDeclarations").and_then(|v| v.as_array()) { |
| for decl in decls { |
| if let Some(n) = decl.get("name").and_then(|v| v.as_str()) { |
| let keywords = |
| ["web_search", "google_search", "google_search_retrieval", "builtin_web_search"]; |
| if !keywords.contains(&n) { |
| return true; |
| } |
| } |
| } |
| } |
| is_networking = true; |
| } |
|
|
| if !is_networking { |
| return true; |
| } |
| } |
| } |
| false |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use super::*; |
|
|
| #[test] |
| fn test_high_quality_model_auto_grounding() { |
| |
| let config = resolve_request_config("gpt-4o", "gemini-2.5-flash", &None, None, None, None, None); |
| assert_eq!(config.request_type, "agent"); |
| assert!(!config.inject_google_search); |
| } |
|
|
| #[test] |
| fn test_gemini_native_tool_detection() { |
| let tools = Some(vec![json!({ |
| "functionDeclarations": [ |
| { "name": "web_search", "parameters": {} } |
| ] |
| })]); |
| assert!(detects_networking_tool(&tools)); |
| } |
|
|
| #[test] |
| fn test_online_suffix_force_grounding() { |
| let config = |
| resolve_request_config("gemini-3-flash-online", "gemini-3-flash", &None, None, None, None, None); |
| assert_eq!(config.request_type, "web_search"); |
| assert!(config.inject_google_search); |
| assert_eq!(config.final_model, "gemini-3-flash"); |
| } |
|
|
| #[test] |
| fn test_default_no_grounding() { |
| let config = resolve_request_config("claude-sonnet", "gemini-3-flash", &None, None, None, None, None); |
| assert_eq!(config.request_type, "agent"); |
| assert!(!config.inject_google_search); |
| } |
|
|
| #[test] |
| fn test_image_model_excluded() { |
| let config = resolve_request_config( |
| "gemini-3-pro-image", |
| "gemini-3-pro-image", |
| &None, |
| None, |
| None, |
| None, |
| None, |
| ); |
| assert_eq!(config.request_type, "image_gen"); |
| assert!(!config.inject_google_search); |
| } |
|
|
| #[test] |
| fn test_image_2k_and_ultrawide_config() { |
| |
| let (config_2k, _) = parse_image_config("gemini-3-pro-image-2k"); |
| assert_eq!(config_2k["imageSize"], "2K"); |
|
|
| |
| let (config_21x9, _) = parse_image_config("gemini-3-pro-image-21x9"); |
| assert_eq!(config_21x9["aspectRatio"], "21:9"); |
|
|
| |
| let (config_combined, _) = parse_image_config("gemini-3-pro-image-2k-21x9"); |
| assert_eq!(config_combined["imageSize"], "2K"); |
| assert_eq!(config_combined["aspectRatio"], "21:9"); |
|
|
| |
| let (config_4k_wide, _) = parse_image_config("gemini-3-pro-image-4k-21x9"); |
| assert_eq!(config_4k_wide["imageSize"], "4K"); |
| assert_eq!(config_4k_wide["aspectRatio"], "21:9"); |
| } |
|
|
| #[test] |
| fn test_parse_image_config_with_openai_params() { |
| |
| let (config_hd, model_hd) = parse_image_config_with_params("gemini-3-pro-image", None, Some("hd"), None); |
| assert_eq!(config_hd["imageSize"], "4K"); |
| assert_eq!(config_hd["aspectRatio"], "1:1"); |
| assert_eq!(model_hd, "gemini-3-pro-image"); |
|
|
| let (config_medium, model_medium) = |
| parse_image_config_with_params("gemini-3-pro-image", None, Some("medium"), None); |
| assert_eq!(config_medium["imageSize"], "2K"); |
| assert_eq!(model_medium, "gemini-3-pro-image"); |
|
|
| let (config_standard, model_standard) = |
| parse_image_config_with_params("gemini-3-pro-image", None, Some("standard"), None); |
| assert_eq!(config_standard["imageSize"], "1K"); |
| assert_eq!(model_standard, "gemini-3-pro-image"); |
|
|
| |
| let (config_16_9, model_16_9) = |
| parse_image_config_with_params("gemini-3-pro-image", Some("1280x720"), None, None); |
| assert_eq!(config_16_9["aspectRatio"], "16:9"); |
| assert_eq!(model_16_9, "gemini-3-pro-image"); |
|
|
| let (config_9_16, model_9_16) = |
| parse_image_config_with_params("gemini-3-pro-image", Some("720x1280"), None, None); |
| assert_eq!(config_9_16["aspectRatio"], "9:16"); |
| assert_eq!(model_9_16, "gemini-3-pro-image"); |
|
|
| let (config_4_3, model_4_3) = |
| parse_image_config_with_params("gemini-3-pro-image", Some("800x600"), None, None); |
| assert_eq!(config_4_3["aspectRatio"], "4:3"); |
| assert_eq!(model_4_3, "gemini-3-pro-image"); |
|
|
| |
| let (config_combined, model_combined) = |
| parse_image_config_with_params("gemini-3-pro-image", Some("1920x1080"), Some("hd"), None); |
| assert_eq!(config_combined["aspectRatio"], "16:9"); |
| assert_eq!(config_combined["imageSize"], "4K"); |
| assert_eq!(model_combined, "gemini-3-pro-image"); |
|
|
| |
| let (config_compat, model_compat) = |
| parse_image_config_with_params("gemini-3-pro-image-16x9-4k", None, None, None); |
| assert_eq!(config_compat["aspectRatio"], "16:9"); |
| assert_eq!(config_compat["imageSize"], "4K"); |
| assert_eq!(model_compat, "gemini-3-pro-image"); |
|
|
| |
| let (config_override, model_override) = parse_image_config_with_params( |
| "gemini-3-pro-image-1x1-2k", |
| Some("1280x720"), |
| Some("hd"), |
| None, |
| ); |
| assert_eq!(config_override["aspectRatio"], "16:9"); |
| assert_eq!(config_override["imageSize"], "4K"); |
| assert_eq!(model_override, "gemini-3-pro-image"); |
| } |
|
|
| #[test] |
| fn test_clean_image_model_name() { |
| assert_eq!(clean_image_model_name("gemini-3.1-flash-image"), "gemini-3.1-flash-image"); |
| assert_eq!(clean_image_model_name("gemini-3.1-flash-image-4k"), "gemini-3.1-flash-image"); |
| assert_eq!(clean_image_model_name("gemini-3-pro-image-16x9"), "gemini-3-pro-image"); |
| assert_eq!(clean_image_model_name("gemini-3-pro-image-16x9-4k"), "gemini-3-pro-image"); |
| |
| assert_eq!(clean_image_model_name("gemini-3.1-flash-image-4k-16x9"), "gemini-3.1-flash-image"); |
| assert_eq!(clean_image_model_name("gemini-3.1-flash-image-16-9-hd"), "gemini-3.1-flash-image"); |
| assert_eq!(clean_image_model_name("gemini-3.1-flash-image-2k-9x16"), "gemini-3.1-flash-image"); |
| assert_eq!(clean_image_model_name("gemini-3.1-flash-image-1x1"), "gemini-3.1-flash-image"); |
| assert_eq!(clean_image_model_name("gemini-3.1-flash-image-standard"), "gemini-3.1-flash-image"); |
| assert_eq!(clean_image_model_name("gemini-3.1-flash-image-medium"), "gemini-3.1-flash-image"); |
| assert_eq!(clean_image_model_name("gemini-3.1-flash-image-21-9-4k"), "gemini-3.1-flash-image"); |
| } |
|
|
| #[test] |
| fn test_calculate_aspect_ratio_from_size() { |
| |
| assert_eq!(calculate_aspect_ratio_from_size("1280x720"), "16:9"); |
| assert_eq!(calculate_aspect_ratio_from_size("1920x1080"), "16:9"); |
| assert_eq!(calculate_aspect_ratio_from_size("720x1280"), "9:16"); |
| assert_eq!(calculate_aspect_ratio_from_size("1080x1920"), "9:16"); |
| assert_eq!(calculate_aspect_ratio_from_size("1024x1024"), "1:1"); |
| assert_eq!(calculate_aspect_ratio_from_size("800x600"), "4:3"); |
| assert_eq!(calculate_aspect_ratio_from_size("600x800"), "3:4"); |
| assert_eq!(calculate_aspect_ratio_from_size("2560x1080"), "21:9"); |
|
|
| |
| assert_eq!(calculate_aspect_ratio_from_size("1500x1000"), "3:2"); |
| assert_eq!(calculate_aspect_ratio_from_size("1000x1500"), "2:3"); |
| assert_eq!(calculate_aspect_ratio_from_size("1250x1000"), "5:4"); |
| assert_eq!(calculate_aspect_ratio_from_size("1000x1250"), "4:5"); |
|
|
| |
| assert_eq!(calculate_aspect_ratio_from_size("21:9"), "21:9"); |
| assert_eq!(calculate_aspect_ratio_from_size("16:9"), "16:9"); |
| assert_eq!(calculate_aspect_ratio_from_size("1:1"), "1:1"); |
|
|
| |
| assert_eq!(calculate_aspect_ratio_from_size("invalid"), "1:1"); |
| assert_eq!(calculate_aspect_ratio_from_size("1920x0"), "1:1"); |
| assert_eq!(calculate_aspect_ratio_from_size("0x1080"), "1:1"); |
| assert_eq!(calculate_aspect_ratio_from_size("abc x def"), "1:1"); |
| } |
|
|
| #[test] |
| fn test_image_config_merging_priority() { |
| |
| |
| let body = json!({ |
| "generationConfig": { |
| "imageConfig": { |
| "aspectRatio": "1:1", |
| "imageSize": "1K" |
| } |
| } |
| }); |
| let config = resolve_request_config( |
| "gemini-3-pro-image-4k", |
| "gemini-3-pro-image", |
| &None, |
| None, |
| None, |
| None, |
| Some(&body), |
| ); |
| let image_config = config.image_config.unwrap(); |
| assert_eq!(image_config["imageSize"], "4K", "Should shield inferred 4K from body downgrade"); |
| assert_eq!(image_config["aspectRatio"], "1:1", "Should take aspectRatio from body"); |
|
|
| |
| |
| let body_2 = json!({ |
| "generationConfig": { |
| "imageConfig": { |
| "aspectRatio": "1:1" |
| } |
| } |
| }); |
| let config_2 = resolve_request_config( |
| "gemini-3-pro-image-16x9", |
| "gemini-3-pro-image", |
| &None, |
| None, |
| None, |
| None, |
| Some(&body_2), |
| ); |
| let image_config_2 = config_2.image_config.unwrap(); |
| assert_eq!(image_config_2["aspectRatio"], "1:1", "Body should be allowed to override aspectRatio"); |
| } |
|
|
| #[test] |
| fn test_image_size_priority() { |
| |
| |
| let (config_1, _) = parse_image_config_with_params( |
| "gemini-3-pro-image", |
| None, |
| Some("standard"), |
| Some("4K"), |
| ); |
| assert_eq!(config_1["imageSize"], "4K"); |
|
|
| |
| |
| let (config_2, _) = parse_image_config_with_params( |
| "gemini-3-pro-image-4k", |
| None, |
| None, |
| Some("2K"), |
| ); |
| assert_eq!(config_2["imageSize"], "2K"); |
|
|
| |
| |
| let (config_3, _) = parse_image_config_with_params( |
| "gemini-3-pro-image", |
| Some("1920x1080"), |
| Some("standard"), |
| Some("4K"), |
| ); |
| assert_eq!(config_3["imageSize"], "4K"); |
| assert_eq!(config_3["aspectRatio"], "16:9"); |
| } |
| } |
|
|