feat: sync all endpoints with MITM LS bypass + real-time thinking streaming
- Responses API (streaming): MITM bypass path polls MitmStore directly when custom tools are active, skipping LS step polling entirely. Streams thinking text deltas in real-time as they arrive from the MITM. Handles function calls, text response, and thinking/reasoning events. - Responses API (sync): Same MITM bypass for non-streaming responses. Polls MitmStore for function calls or completed text before falling back to LS path. - Gemini endpoint: MITM bypass polls MitmStore directly for tool call responses, eliminating LS overhead. - MitmStore: Added captured_thinking_text field with set/peek/take methods for real-time thinking text capture from MITM SSE. - MITM proxy: Now captures both thinking_text and response_text from StreamingAccumulator into MitmStore when bypass mode is active.
This commit is contained in:
@@ -183,6 +183,75 @@ pub(crate) async fn handle_gemini(
|
||||
}
|
||||
}
|
||||
|
||||
let has_custom_tools = state.mitm_store.get_tools().await.is_some();
|
||||
|
||||
// Clear stale response
|
||||
state.mitm_store.clear_response_async().await;
|
||||
|
||||
// ── MITM bypass: when tools active, poll MitmStore directly ──
|
||||
if has_custom_tools {
|
||||
let start = std::time::Instant::now();
|
||||
while start.elapsed().as_secs() < body.timeout {
|
||||
// Check for function calls
|
||||
let captured = state.mitm_store.take_any_function_calls().await;
|
||||
if let Some(ref calls) = captured {
|
||||
if !calls.is_empty() {
|
||||
let parts: Vec<serde_json::Value> = calls
|
||||
.iter()
|
||||
.map(|fc| {
|
||||
serde_json::json!({
|
||||
"functionCall": {
|
||||
"name": fc.name,
|
||||
"args": fc.args,
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
return Json(serde_json::json!({
|
||||
"candidates": [{
|
||||
"content": {
|
||||
"parts": parts,
|
||||
"role": "model",
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
}],
|
||||
"modelVersion": model_name,
|
||||
}))
|
||||
.into_response();
|
||||
}
|
||||
}
|
||||
|
||||
// Check for completed text response
|
||||
if state.mitm_store.is_response_complete() {
|
||||
let text = state.mitm_store.take_response_text().await.unwrap_or_default();
|
||||
return Json(serde_json::json!({
|
||||
"candidates": [{
|
||||
"content": {
|
||||
"parts": [{"text": text}],
|
||||
"role": "model",
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
}],
|
||||
"modelVersion": model_name,
|
||||
}))
|
||||
.into_response();
|
||||
}
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
|
||||
}
|
||||
|
||||
// Timeout
|
||||
return Json(serde_json::json!({
|
||||
"error": {
|
||||
"message": "Request timed out",
|
||||
"type": "timeout_error",
|
||||
}
|
||||
}))
|
||||
.into_response();
|
||||
}
|
||||
|
||||
// ── Normal LS path (no custom tools) ──
|
||||
// Poll for response
|
||||
let poll_result = poll_for_response(&state, &cascade_id, body.timeout).await;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user