From 8a33b147f148e87bfc727d3502c1c6d59b5d9f34 Mon Sep 17 00:00:00 2001 From: hobokenchicken Date: Thu, 5 Mar 2026 16:45:18 +0000 Subject: [PATCH] fix(gemini): align thought_signature logic with Gemini API requirements - Extract thought_signature from the Part level during response parsing. - Provide thought_signature as a sibling to functionCall during request assembly. - This fully resolves the 'Unknown name thoughtSignature at function_call' error. --- src/providers/gemini.rs | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/providers/gemini.rs b/src/providers/gemini.rs index 98f12b39..611b3c75 100644 --- a/src/providers/gemini.rs +++ b/src/providers/gemini.rs @@ -57,6 +57,8 @@ struct GeminiPart { function_response: Option, #[serde(skip_serializing_if = "Option::is_none")] thought: Option, + #[serde(skip_serializing_if = "Option::is_none")] + thought_signature: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -70,8 +72,6 @@ struct GeminiInlineData { struct GeminiFunctionCall { name: String, args: Value, - #[serde(skip_serializing_if = "Option::is_none")] - thought_signature: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -231,6 +231,7 @@ impl GeminiProvider { function_call: None, function_response: None, thought: None, + thought_signature: None, }); } } @@ -270,6 +271,7 @@ impl GeminiProvider { response: response_value, }), thought: None, + thought_signature: None, }); } else if msg.role == "assistant" { // Assistant messages: handle text, thought (reasoning), and tool_calls @@ -282,6 +284,7 @@ impl GeminiProvider { function_call: None, function_response: None, thought: None, + thought_signature: None, }); } } @@ -296,6 +299,7 @@ impl GeminiProvider { function_call: None, function_response: None, thought: Some(reasoning.clone()), + thought_signature: None, }); } } @@ -318,10 +322,10 @@ impl GeminiProvider { function_call: Some(GeminiFunctionCall { name: tc.function.name.clone(), args, - thought_signature, }), function_response: None, thought: None, + thought_signature, }); } } @@ -337,6 +341,7 @@ impl GeminiProvider { function_call: None, function_response: None, thought: None, + thought_signature: None, }); } } @@ -355,6 +360,7 @@ impl GeminiProvider { function_call: None, function_response: None, thought: None, + thought_signature: None, }); } } @@ -390,6 +396,7 @@ impl GeminiProvider { function_call: None, function_response: None, thought: None, + thought_signature: None, }], }); } @@ -516,10 +523,11 @@ impl GeminiProvider { fn extract_tool_calls(parts: &[GeminiPart]) -> Option> { let calls: Vec = parts .iter() - .filter_map(|p| p.function_call.as_ref()) - .map(|fc| { - // CAPTURE: Use thought_signature as the ID if available - let id = fc.thought_signature.clone().unwrap_or_else(|| format!("call_{}", Uuid::new_v4().simple())); + .filter(|p| p.function_call.is_some()) + .map(|p| { + let fc = p.function_call.as_ref().unwrap(); + // CAPTURE: Use thought_signature from Part as the ID if available + let id = p.thought_signature.clone().unwrap_or_else(|| format!("call_{}", Uuid::new_v4().simple())); ToolCall { id, @@ -539,11 +547,12 @@ impl GeminiProvider { fn extract_tool_call_deltas(parts: &[GeminiPart]) -> Option> { let deltas: Vec = parts .iter() - .filter_map(|p| p.function_call.as_ref()) .enumerate() - .map(|(i, fc)| { - // CAPTURE: Use thought_signature as the ID if available - let id = fc.thought_signature.clone().unwrap_or_else(|| format!("call_{}", Uuid::new_v4().simple())); + .filter(|(_, p)| p.function_call.is_some()) + .map(|(i, p)| { + let fc = p.function_call.as_ref().unwrap(); + // CAPTURE: Use thought_signature from Part as the ID if available + let id = p.thought_signature.clone().unwrap_or_else(|| format!("call_{}", Uuid::new_v4().simple())); ToolCallDelta { index: i as u32,