feat: full tool call support (OpenAI + Gemini endpoints)

- store.rs: Add tool context storage (active tools, tool config, pending
  tool results, call_id mapping, last function calls for history rewrite)
- types.rs: Add tools/tool_choice fields to ResponsesRequest, add
  build_function_call_output helper for OpenAI function_call output items
- modify.rs: Replace hardcoded get_weather with dynamic ToolContext
  injection. Add openai_tools_to_gemini and openai_tool_choice_to_gemini
  converters. Add conversation history rewriting for tool result turns
  (replaces fake 'Tool call completed' model turn with real functionCall,
  injects functionResponse before last user turn)
- proxy.rs: Build ToolContext from MitmStore before calling modify_request.
  Save last_function_calls for history rewriting on subsequent turns
- responses.rs: Store client tools in MitmStore before LS call. Detect
  function_call_output in input array for tool result submission. Return
  captured functionCalls as OpenAI function_call output items with
  generated call_ids and stringified arguments
- gemini.rs: New Gemini-native endpoint (POST /v1/gemini) with zero
  format translation. Accepts functionDeclarations directly, returns
  functionCall in Gemini format directly
- mod.rs: Wire /v1/gemini route, bump version to 3.3.0
This commit is contained in:
Nikketryhard
2026-02-14 22:56:44 -06:00
parent 8455aa674f
commit 786987116b
8 changed files with 989 additions and 51 deletions

View File

@@ -556,7 +556,24 @@ async fn handle_http_over_tls(
|| body_str.contains("\"requestType\": \"agent\"");
if is_agent {
if let Some(modified_body) = super::modify::modify_request(&raw_body) {
// Build ToolContext from store
let tools = store.get_tools().await;
let tool_config = store.get_tool_config().await;
let pending_results = store.take_tool_results().await;
let last_calls = store.get_last_function_calls().await;
let tool_ctx = if tools.is_some() || !pending_results.is_empty() {
Some(super::modify::ToolContext {
tools,
tool_config,
pending_results,
last_calls,
})
} else {
None
};
if let Some(modified_body) = super::modify::modify_request(&raw_body, tool_ctx.as_ref()) {
// Rebuild request_buf: original headers + rechunked modified body
let new_chunked = super::modify::rechunk(&modified_body);
let mut new_buf = request_buf[..headers_end].to_vec();
@@ -766,6 +783,10 @@ async fn handle_http_over_tls(
for fc in &streaming_acc.function_calls {
store.record_function_call(cascade_hint.as_deref(), fc.clone()).await;
}
// Also save for history rewriting on tool result turns
if !streaming_acc.function_calls.is_empty() {
store.set_last_function_calls(streaming_acc.function_calls.clone()).await;
}
let usage = streaming_acc.into_usage();
store.record_usage(cascade_hint.as_deref(), usage).await;
}