diff --git a/src/providers/gemini.rs b/src/providers/gemini.rs index 5d3a8118..54e48c94 100644 --- a/src/providers/gemini.rs +++ b/src/providers/gemini.rs @@ -216,7 +216,7 @@ impl GeminiProvider { let role = match msg.role.as_str() { "assistant" => "model".to_string(), - "tool" => "user".to_string(), // Tool results are technically from the user side in Gemini + "tool" => "user".to_string(), // Tool results are user-side in Gemini _ => "user".to_string(), }; @@ -233,7 +233,6 @@ impl GeminiProvider { }) .unwrap_or_default(); - // Gemini function response MUST have a name. Fallback to tool_call_id if name is missing. let name = msg.name.clone().or_else(|| msg.tool_call_id.clone()).unwrap_or_else(|| "unknown_function".to_string()); let response_value = serde_json::from_str::(&text_content) .unwrap_or_else(|_| serde_json::json!({ "result": text_content })); @@ -250,7 +249,6 @@ impl GeminiProvider { } else if msg.role == "assistant" && msg.tool_calls.is_some() { // Assistant messages with tool_calls if let Some(tool_calls) = &msg.tool_calls { - // Include text content if present for p in &msg.content { if let ContentPart::Text { text } = p { if !text.trim().is_empty() { @@ -316,7 +314,8 @@ impl GeminiProvider { continue; } - // Merge with previous message if role matches + // STRATEGY: Strictly enforce alternating roles. + // If current message has the same role as the last one, merge their parts. if let Some(last_content) = contents.last_mut() { if last_content.role.as_ref() == Some(&role) { last_content.parts.extend(parts); @@ -331,7 +330,6 @@ impl GeminiProvider { } // Gemini requires the first message to be from "user". - // If it starts with "model", we prepend a placeholder user message. if let Some(first) = contents.first() { if first.role.as_deref() == Some("model") { contents.insert(0, GeminiContent { @@ -346,6 +344,12 @@ impl GeminiProvider { } } + // Final check: ensure we don't have empty contents after filtering. + // If the last message was merged or filtered, we might have an empty array. + if contents.is_empty() && system_parts.is_empty() { + return Err(AppError::ProviderError("No valid content parts after filtering".to_string())); + } + let system_instruction = if !system_parts.is_empty() { Some(GeminiContent { parts: system_parts,