* feat: match Go TLS fingerprint for MITM upstream connections
Replace rustls with boring2 (BoringSSL) for all MITM→Google upstream
connections, configured with Go crypto/tls exact defaults:
- Cipher suites: TLS_AES_128_GCM_SHA256 + 14 others in Go order
- Curves: X25519, P-256, P-384
- Signature algorithms: ECDSA+SHA256, RSA-PSS+SHA256, etc.
- HTTP/2 SETTINGS: 4MB stream window, 1GB connection window, 10MB
header list, no adaptive windowing
Local TLS (LS→MITM) still uses rustls for CA cert presentation.
boring2/tokio-boring2 were already compiled as transitive deps from
wreq — no new build time added.
* chore: fmt + update README TLS description
All three setup scripts (Linux, macOS, Windows) now verify that the
Antigravity app is installed and the LS binary exists before proceeding.
Fails early with a clear error message and suggestions instead of a
cryptic runtime crash.
Closes#5
When LD_PRELOAD DNS redirect is active, setting HTTPS_PROXY causes Go's
net/http to send HTTP CONNECT requests through the MITM proxy. However,
the MITM proxy expects direct TLS connections for SNI-based interception,
not CONNECT tunneling. This mismatch causes all non-gRPC calls (OAuth
token refresh, fetchUserInfo, etc.) to fail with EOF/timeout errors.
Changes:
- Only set HTTPS_PROXY/HTTP_PROXY as fallback when DNS redirect SO is
not available
- Add GODEBUG=netdns=cgo to force Go's cgo (libc) DNS resolver, since
the pure-Go resolver bypasses LD_PRELOAD getaddrinfo() hooks entirely
Fixes#4
- User-Agent now matches actual OS (macOS/Windows/Linux)
- grep -oP replaced with grep -oE for macOS BSD compat
- Port-killer gated with cfg(unix)/cfg(windows)
- zg binary: macOS uses launchctl, Windows uses schtasks
- Data dir mismatch fixed in mitm-redirect.sh
- Windows setup-windows.ps1 ProjectDir fixed
- README: token path, prerequisites updated
- setup-linux.sh: pre-flight dependency checks
- OAuth token auto-read from Antigravity state.vscdb
- Version bump to 1.0.1
Introduces src/platform.rs with OS detection and env var overrides.
All hardcoded Linux paths replaced with Platform::detect() across
8 source files. Key changes:
- New Platform struct with 11 fields (all overridable via env vars)
- /proc/ access gated to Linux (#[cfg(target_os = "linux")])
- pgrep/pkill patterns broadened for cross-platform LS discovery
- sec-ch-ua-platform header now dynamic per OS
- Token, traces, config, CA cert paths use platform module
- LD_PRELOAD DNS redirect gated to Linux only
- Setup scripts for Linux (systemd) and macOS (launchd)
- find_ls_binary_path has cross-platform stubs
All 46 tests pass, cargo check clean.
- Delete handle_gemini handler (identical to handle_gemini_v1beta)
- Remove /v1/gemini route from router
- Update root handler service name to zerogravity
- Clean all doc references
Replace /v1/gemini with proper Gemini API paths:
- POST /v1beta/models/{model}:generateContent (sync)
- POST /v1beta/models/{model}:streamGenerateContent (streaming)
Model is extracted from URL path. Uses axum wildcard
catch-all since colons in path segments are not supported.
Gemini endpoint now accepts responseMimeType and responseSchema
fields, injected into Google's generationConfig via MITM. Supports
both snake_case and camelCase aliases.
logs command was using journalctl -f (follow) which blocks forever.
Split into three commands:
- logs [N]: show last N lines and exit (default 30)
- logs-follow [N]: tail + follow (old behavior)
- logs-all: full dump
Root cause: errors from Google were being swallowed, replaced with
placeholders like 'Google API returned HTTP 400' or '[Timeout waiting
for response]', or silently converted to fake 'incomplete' responses.
Changes across all endpoints (/v1/chat/completions, /v1/responses,
/v1/gemini, /v1/search):
Error message fidelity:
- UpstreamError message now includes Google's status prefix: [STATUS] msg
- Falls back to raw body if JSON parsing fails (protobuf, HTML, etc.)
- ErrorDetail gains optional code and param fields
Timeout handling:
- poll_for_response returns UpstreamError(504, DEADLINE_EXCEEDED) on timeout
instead of '[Timeout waiting for AI response]' placeholder text
- Streaming timeouts emit proper error events, not fake content
- Sync bypass timeouts return 504 Gateway Timeout, not 200 incomplete
Missing error checks added:
- responses.rs sync bypass: added upstream_error check in polling loop
- gemini.rs sync bypass: added upstream_error check in polling loop
- gemini.rs streaming: added upstream_error check in polling loop
(was completely missing — errors only handled in sync path)
DRY helpers:
- upstream_error_message(): shared exact message extraction
- upstream_error_type(): shared Google→OpenAI error type mapping
- All streaming handlers use these instead of inline formatting
Root cause: proxy.rs eagerly pushed tool rounds via push_tool_round_calls
when intercepting Google's functionCall response. These stale rounds leaked
into LS follow-up requests, producing malformed history that Google timed
out on (60s 'no upstream response').
Changes:
- Remove push_tool_round_calls from proxy.rs response interception
- proxy.rs: use get_tool_rounds (non-destructive) instead of take_tool_rounds
so accumulated rounds persist across multiple LS requests per cascade
- responses.rs/gemini.rs: build rounds via take+push+set pattern — each
handler accumulates its own rounds from get_last_function_calls + results
- completions.rs: unchanged (set_tool_rounds replaces from messages)
- clear_tools: also clears tool_rounds to prevent stale data between sessions
- store.rs: add get_tool_rounds (non-destructive clone) method
- 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
- Add ToolRound struct to pair function calls with results per-round
- Replace single-match history rewrite (broke after first round) with
multi-round loop that rewrites ALL placeholder model turns
- Fix tool result name fallback: use positional index instead of always
picking the first call
- Set is_complete for any finishReason (FUNCTION_CALL, MAX_TOKENS, etc.)
not just STOP — prevents response_complete flag from never being set
- Legacy fallback: responses.rs path (single-round via last_calls +
pending_results) still works when tool_rounds is empty
- Add tests: multi-round rewrite, single-round legacy, no-op, and
FUNCTION_CALL/MAX_TOKENS finishReason handling
- store.rs: record_function_call now falls back to active_cascade_id
(matching record_usage behavior) instead of blind _latest fallback
- store.rs: add cascade-aware take_function_calls(cascade_id) method
with priority: exact match → active cascade → _latest → any key
- completions.rs: extract tool_calls from assistant messages and tool
results from tool messages, storing them for MITM injection. This was
the ROOT CAUSE — the completions handler stored tool definitions but
never extracted tool results, so modify_request couldn't rewrite the
LS conversation history with proper functionCall/functionResponse
- responses.rs: use cascade-aware take_function_calls for consistency
Without this, request_in_flight stayed true after tool call streaming,
blocking all subsequent turns until the next completions handler
happened to clear it first.