diff --git a/crates/buzz-agent/src/agent.rs b/crates/buzz-agent/src/agent.rs index 730e87b2e..4dc679040 100644 --- a/crates/buzz-agent/src/agent.rs +++ b/crates/buzz-agent/src/agent.rs @@ -679,6 +679,7 @@ pub(crate) fn push_hook_outputs_as_tool_results( provider_id: provider_id.clone(), name: tool_name, arguments: serde_json::json!({}), + extra_content: None, }], }); history.push(HistoryItem::ToolResult(ToolResult { diff --git a/crates/buzz-agent/src/llm.rs b/crates/buzz-agent/src/llm.rs index d3e3e73d2..2ae5f786c 100644 --- a/crates/buzz-agent/src/llm.rs +++ b/crates/buzz-agent/src/llm.rs @@ -532,11 +532,18 @@ fn openai_body( let calls: Vec = tool_calls .iter() .map(|c| { - json!({ + let mut call = json!({ "id": c.provider_id, "type": "function", "function": { "name": c.name, "arguments": serde_json::to_string(&c.arguments) - .unwrap_or_else(|_| "{}".into()) } }) + .unwrap_or_else(|_| "{}".into()) } }); + // Echo back the provider's opaque `extra_content` + // (Gemini 3.x requires the `thought_signature` here + // or it rejects the follow-up turn with HTTP 400). + if let Some(extra) = &c.extra_content { + call["extra_content"] = extra.clone(); + } + call }) .collect(); msg.insert("tool_calls".into(), Value::Array(calls)); @@ -962,11 +969,12 @@ fn parse_openai(v: Value) -> Result { let raw = f.get("arguments").and_then(Value::as_str).unwrap_or("{}"); let args: Value = serde_json::from_str(raw) .map_err(|e| AgentError::Llm(format!("tool_call.arguments not valid JSON: {e}")))?; - tool_calls.push(make_tool_call( - str_field(tc, "id"), - str_field(f, "name"), - args, - )?); + let mut call = make_tool_call(str_field(tc, "id"), str_field(f, "name"), args)?; + // Preserve the opaque `extra_content` (Gemini 3.x `thought_signature`) + // so it can be echoed back on the next turn. Absent for providers + // that don't emit it. + call.extra_content = tc.get("extra_content").cloned(); + tool_calls.push(call); } } let input_tokens = openai_chat_input_tokens(&v); @@ -998,6 +1006,7 @@ fn make_tool_call(id: String, name: String, args: Value) -> Result, } #[derive(Debug, Clone)]