- 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.
When custom tools are set, don't forward ANY response from Google
to the LS. Instead, capture text and function calls directly into
MitmStore. The completions handler reads from MitmStore.
This eliminates the LS multi-turn loop (5 requests, 30+ seconds)
that occurred because the LS kept processing responses internally.
Tool calls now return in ~1.3s instead of timing out.
Check for MITM-captured function calls BEFORE emitting text in the
streaming handler. This prevents the dummy 'Tool call completed'
placeholder (sent to the LS) from leaking to OpenCode, which was
confusing it into infinite loops.
Also removes duplicate function call storage at end of response loop
since they're now stored immediately when detected.
Previously, captured function calls were only stored in MitmStore
after the response loop ended. The completions handler polls
take_any_function_calls() during streaming, creating a race condition
where the MitmStore was empty.
Now function calls are stored immediately when parse_streaming_chunk
detects them, in both the initial body and body chunk paths.
When the MITM detects a functionCall in Google's response AND custom
tools are active, send a forged clean text response to the LS instead
of the real one. This prevents the LS from seeing function calls for
tools it doesn't manage, eliminating the retry loop entirely.
The real function call data is captured in MitmStore and returned to
the client (OpenCode) through the completions handler.
Also removes the complex chunked-encoding response rewriting approach
in favor of this simpler forge-and-break strategy.
The function call stripping was only happening when no custom tools
were present. But even with custom tools injected, the LS history
contains functionCall/functionResponse parts for LS-internal tools
that we stripped, causing MALFORMED_FUNCTION_CALL. Now always strip
regardless of custom tools presence.
- 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
When MITM strips LS tools and injects custom tools:
- Google returns functionCall → captured in MitmStore
- Follow-up LS requests are blocked with fake SSE response
- Proxy consumes captured calls and clears the flag
- Result: 1 real Google API call instead of 5+ per tool call
Flow: Client → Proxy → LS → MITM(inject tool) → Google
Google returns functionCall → MITM captures it
LS tries follow-up → MITM blocks (fake response)
Proxy reads captured functionCall → returns to client
- Only set HTTPS_PROXY/HTTP_PROXY when iptables UID isolation is NOT
available. With iptables, double-proxying caused profile picture
fetches to fail with 'lookup http' DNS errors.
- Fix is_agent detection: handle JSON with spaces after colons
("requestType": "agent" vs "requestType":"agent")
- Suppress wrapper-not-installed warning in standalone mode
- Show 'iptables (standalone)' in banner instead of 'not installed'
- Spawn standalone LS as dedicated 'antigravity-ls' user via sudo
- UID-scoped iptables redirect (port 443 → MITM proxy) via mitm-redirect.sh
- Combined CA bundle (system CAs + MITM CA) for Go TLS trust
- Transparent TLS interception with chunked response detection
- Google SSE parser for streamGenerateContent usage extraction
- Timeouts on all MITM operations (TLS handshake, upstream, idle)
- Forward response data immediately (no buffering)
- Per-model token usage capture (input, output, thinking)
- Update docs and known issues to reflect resolved TLS blocker