fix: extend multi-round tool history to responses and gemini endpoints
- proxy.rs: push_tool_round_calls alongside set_last_function_calls when Google responds with functionCall — accumulates rounds - responses.rs: attach_tool_round_results to pair tool results with the correct round instead of flat add_tool_result - gemini.rs: same attach_tool_round_results integration - store.rs: add push_tool_round_calls and attach_tool_round_results methods for cross-request round accumulation - Legacy add_tool_result kept for backward compat alongside new path
This commit is contained in:
@@ -209,22 +209,34 @@ pub(crate) async fn handle_gemini(
|
|||||||
|
|
||||||
// Handle tool results (Gemini format: functionResponse)
|
// Handle tool results (Gemini format: functionResponse)
|
||||||
if let Some(ref results) = body.tool_results {
|
if let Some(ref results) = body.tool_results {
|
||||||
|
let mut pending: Vec<PendingToolResult> = Vec::new();
|
||||||
for r in results {
|
for r in results {
|
||||||
if let Some(fr) = r.get("functionResponse") {
|
if let Some(fr) = r.get("functionResponse") {
|
||||||
let name = fr["name"].as_str().unwrap_or("unknown").to_string();
|
let name = fr["name"].as_str().unwrap_or("unknown").to_string();
|
||||||
let response = fr.get("response").cloned().unwrap_or(serde_json::json!({}));
|
let response = fr.get("response").cloned().unwrap_or(serde_json::json!({}));
|
||||||
|
// Legacy compat
|
||||||
state
|
state
|
||||||
.mitm_store
|
.mitm_store
|
||||||
.add_tool_result(PendingToolResult {
|
.add_tool_result(PendingToolResult {
|
||||||
name,
|
name: name.clone(),
|
||||||
result: response,
|
result: response.clone(),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
pending.push(PendingToolResult {
|
||||||
|
name,
|
||||||
|
result: response,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !pending.is_empty() {
|
||||||
|
state
|
||||||
|
.mitm_store
|
||||||
|
.attach_tool_round_results(pending)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
info!(
|
info!(
|
||||||
count = results.len(),
|
count = results.len(),
|
||||||
"Stored Gemini-native tool results for MITM injection"
|
"Stored Gemini-native tool results for MITM injection (attached to tool round)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -242,6 +242,7 @@ pub(crate) async fn handle_responses(
|
|||||||
// Handle tool result submission (function_call_output in input)
|
// Handle tool result submission (function_call_output in input)
|
||||||
let is_tool_result_turn = !tool_results.is_empty();
|
let is_tool_result_turn = !tool_results.is_empty();
|
||||||
if is_tool_result_turn {
|
if is_tool_result_turn {
|
||||||
|
let mut pending: Vec<PendingToolResult> = Vec::new();
|
||||||
for tr in &tool_results {
|
for tr in &tool_results {
|
||||||
// Look up function name from call_id
|
// Look up function name from call_id
|
||||||
let name = state
|
let name = state
|
||||||
@@ -254,17 +255,28 @@ pub(crate) async fn handle_responses(
|
|||||||
let result_value = serde_json::from_str::<serde_json::Value>(&tr.output)
|
let result_value = serde_json::from_str::<serde_json::Value>(&tr.output)
|
||||||
.unwrap_or_else(|_| serde_json::json!({"result": tr.output}));
|
.unwrap_or_else(|_| serde_json::json!({"result": tr.output}));
|
||||||
|
|
||||||
|
// Also store as pending (legacy compat)
|
||||||
state
|
state
|
||||||
.mitm_store
|
.mitm_store
|
||||||
.add_tool_result(PendingToolResult {
|
.add_tool_result(PendingToolResult {
|
||||||
name,
|
name: name.clone(),
|
||||||
result: result_value,
|
result: result_value.clone(),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
pending.push(PendingToolResult {
|
||||||
|
name,
|
||||||
|
result: result_value,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
// Attach results to the latest open ToolRound (pushed by proxy.rs)
|
||||||
|
state
|
||||||
|
.mitm_store
|
||||||
|
.attach_tool_round_results(pending)
|
||||||
|
.await;
|
||||||
info!(
|
info!(
|
||||||
count = tool_results.len(),
|
count = tool_results.len(),
|
||||||
"Stored tool results for MITM injection"
|
"Stored tool results for MITM injection (attached to tool round)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -826,6 +826,7 @@ async fn handle_http_over_tls(
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
store.set_last_function_calls(calls.clone()).await;
|
store.set_last_function_calls(calls.clone()).await;
|
||||||
|
store.push_tool_round_calls(calls.clone()).await;
|
||||||
info!(
|
info!(
|
||||||
"MITM: stored {} function call(s) from initial body",
|
"MITM: stored {} function call(s) from initial body",
|
||||||
calls.len()
|
calls.len()
|
||||||
@@ -902,6 +903,7 @@ async fn handle_http_over_tls(
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
store.set_last_function_calls(calls.clone()).await;
|
store.set_last_function_calls(calls.clone()).await;
|
||||||
|
store.push_tool_round_calls(calls.clone()).await;
|
||||||
info!(
|
info!(
|
||||||
"MITM: stored {} function call(s) from body chunk",
|
"MITM: stored {} function call(s) from body chunk",
|
||||||
calls.len()
|
calls.len()
|
||||||
|
|||||||
@@ -537,6 +537,34 @@ impl MitmStore {
|
|||||||
std::mem::take(&mut *self.tool_rounds.write().await)
|
std::mem::take(&mut *self.tool_rounds.write().await)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Push a new tool round from Google's response (calls only, results empty).
|
||||||
|
/// Called by proxy.rs when the MITM intercepts functionCall parts.
|
||||||
|
pub async fn push_tool_round_calls(&self, calls: Vec<CapturedFunctionCall>) {
|
||||||
|
if !calls.is_empty() {
|
||||||
|
self.tool_rounds.write().await.push(ToolRound {
|
||||||
|
calls,
|
||||||
|
results: Vec::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attach tool results to the latest incomplete tool round (one with empty results).
|
||||||
|
/// Called by responses.rs/gemini.rs when the client sends tool results.
|
||||||
|
/// If there's no open round, creates a legacy round with no calls.
|
||||||
|
pub async fn attach_tool_round_results(&self, results: Vec<PendingToolResult>) {
|
||||||
|
let mut rounds = self.tool_rounds.write().await;
|
||||||
|
// Find the last round that has no results yet
|
||||||
|
if let Some(round) = rounds.iter_mut().rev().find(|r| r.results.is_empty()) {
|
||||||
|
round.results = results;
|
||||||
|
} else {
|
||||||
|
// No open round — probably a race or legacy path, create standalone
|
||||||
|
rounds.push(ToolRound {
|
||||||
|
calls: Vec::new(),
|
||||||
|
results,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Direct response capture (bypass LS) ──────────────────────────────
|
// ── Direct response capture (bypass LS) ──────────────────────────────
|
||||||
|
|
||||||
/// Set (replace) the captured response text.
|
/// Set (replace) the captured response text.
|
||||||
|
|||||||
Reference in New Issue
Block a user