docs: update standalone LS, MITM, and panel stream investigation
- Add panel-stream-investigation.md documenting dead end - Update KNOWN_ISSUES: move polling and panel stream to resolved - Update GEMINI.md with standalone LS section and new MITM setup - Fix standalone-ls-todo to reflect default mode
This commit is contained in:
93
GEMINI.md
93
GEMINI.md
@@ -8,11 +8,17 @@ OpenAI-compatible proxy that intercepts and relays requests to Google's Antigrav
|
||||
# Build
|
||||
cargo build --release
|
||||
|
||||
# Run (language server must be running)
|
||||
# First-time setup (creates user + iptables for MITM)
|
||||
sudo ./scripts/mitm-redirect.sh install
|
||||
|
||||
# Run (spawns standalone LS automatically)
|
||||
RUST_LOG=info ./target/release/antigravity-proxy
|
||||
|
||||
# Custom port
|
||||
RUST_LOG=info ./target/release/antigravity-proxy --port 9000
|
||||
|
||||
# Attach to existing LS instead of spawning standalone
|
||||
RUST_LOG=info ./target/release/antigravity-proxy --no-standalone
|
||||
```
|
||||
|
||||
Default port: **8741**
|
||||
@@ -115,62 +121,75 @@ Version strings (Antigravity, Chrome, Electron, Client) are **auto-detected** at
|
||||
|
||||
Falls back to hardcoded values if the app isn't installed. No manual updates needed when Antigravity updates.
|
||||
|
||||
## Standalone LS
|
||||
|
||||
By default, the proxy spawns its own Language Server instance for full isolation:
|
||||
|
||||
1. Discovers the main LS config (`extension_server_port`, `csrf_token`) from the running Antigravity app
|
||||
2. Spawns a standalone LS binary on a random port
|
||||
3. Builds init metadata protobuf (model config, `detect_and_use_proxy=ENABLED`)
|
||||
4. If MITM is active, spawns as `antigravity-ls` user for UID-scoped traffic interception
|
||||
5. Kills the child on proxy shutdown
|
||||
|
||||
Disable with `--no-standalone` to attach to the real LS instead.
|
||||
|
||||
**Module:** `src/standalone.rs`
|
||||
|
||||
## Stealth Features
|
||||
|
||||
- **TLS fingerprint**: BoringSSL with Chrome JA3/JA4 + H2 fingerprint via `wreq` (version auto-detected)
|
||||
- **Protobuf**: Hand-rolled encoder producing byte-exact match to real webview traffic
|
||||
- **Warmup**: Mimics real webview startup RPC calls
|
||||
- **Heartbeat**: Periodic keep-alive matching real webview lifecycle
|
||||
- **Jitter**: Randomized polling intervals to avoid automation fingerprint
|
||||
- **Session reuse**: Cascades are reused for multi-turn, matching real webview behavior
|
||||
- **MITM proxy**: TLS-intercepting proxy for real token usage capture (opt-in)
|
||||
- **Reactive streaming**: `StreamCascadeReactiveUpdates` for real-time state diffs (polling fallback)
|
||||
- **Jitter**: Randomized intervals to avoid automation fingerprint
|
||||
- **Session reuse**: Cascades reused for multi-turn, matching real webview behavior
|
||||
- **MITM proxy**: TLS-intercepting proxy for real token usage capture
|
||||
|
||||
## MITM Proxy
|
||||
|
||||
Built-in MITM proxy intercepts LS ↔ Google/Anthropic traffic to capture **real** token usage (input, output, cache read, cache creation). Disabled with `--no-mitm`.
|
||||
Built-in MITM proxy intercepts LS ↔ Google API traffic to capture **real** token usage (input, output, thinking tokens). Enabled by default with the standalone LS. Disable with `--no-mitm`.
|
||||
|
||||
### How It Works
|
||||
|
||||
```
|
||||
Client → Proxy (8741) → Standalone LS (as antigravity-ls user)
|
||||
↓ (port 443 traffic)
|
||||
iptables REDIRECT (UID-scoped)
|
||||
↓
|
||||
MITM Proxy (8742)
|
||||
↓ (TLS decrypt + parse SSE)
|
||||
Google API (daily-cloudcode-pa.googleapis.com)
|
||||
```
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
# 1. Start proxy (generates CA cert automatically)
|
||||
# One-time setup (creates user + iptables rule)
|
||||
sudo ./scripts/mitm-redirect.sh install
|
||||
|
||||
# Run proxy (standalone LS + MITM are both on by default)
|
||||
RUST_LOG=info ./target/release/antigravity-proxy
|
||||
|
||||
# 2. Patch extension to enable proxy detection (required!)
|
||||
# The LS has a protobuf field `detect_and_use_proxy` that defaults to UNSPECIFIED,
|
||||
# which means it ignores HTTPS_PROXY for LLM API calls. This patch sets it to ENABLED (1).
|
||||
# Must be re-applied after every Antigravity update.
|
||||
sudo sed -i -E 's/detectAndUseProxy=[^,;)]+/detectAndUseProxy=1/g' \
|
||||
/usr/share/antigravity/resources/app/extensions/antigravity/dist/extension.js
|
||||
|
||||
# 3. Install wrapper (patches LS binary to route through MITM)
|
||||
./scripts/mitm-wrapper.sh install
|
||||
|
||||
# 4. Restart Antigravity — done!
|
||||
|
||||
# Check status
|
||||
./scripts/mitm-wrapper.sh status
|
||||
|
||||
# Uninstall
|
||||
./scripts/mitm-wrapper.sh uninstall
|
||||
```
|
||||
|
||||
### Extension Patch Details
|
||||
|
||||
The LS uses `daily-cloudcode-pa.googleapis.com/v1internal:streamGenerateContent?alt=sse` for LLM API calls (regular HTTPS+SSE, NOT gRPC). The LS binary checks a protobuf field (`detect_and_use_proxy`, field 34 on init metadata) to decide whether to honor `HTTPS_PROXY`. The extension defaults this to `UNSPECIFIED` (ignore proxy). The sed patch above changes it to `ENABLED` (value `1`), allowing the MITM wrapper's env vars to take effect.
|
||||
|
||||
**Verify patch:** `grep -o 'detectAndUseProxy=[^;]*' /usr/share/antigravity/resources/app/extensions/antigravity/dist/extension.js` should show `detectAndUseProxy=1`.
|
||||
|
||||
**Model IDs** (for standalone LS testing): See `docs/ls-binary-analysis.md` for the full proto enum mapping.
|
||||
|
||||
### Usage Stats
|
||||
|
||||
```bash
|
||||
# Check intercepted usage
|
||||
curl -s http://localhost:8741/v1/usage | jq .
|
||||
|
||||
# Cleanup
|
||||
sudo ./scripts/mitm-redirect.sh uninstall
|
||||
```
|
||||
|
||||
Returns aggregate token counts from all intercepted API calls.
|
||||
### Details
|
||||
|
||||
- **UID-scoped iptables**: Only the standalone LS's traffic is intercepted (no side effects)
|
||||
- **Combined CA bundle**: System CAs + MITM CA → `/tmp/antigravity-mitm-combined-ca.pem`
|
||||
- **Google SSE parsing**: Extracts `promptTokenCount`, `candidatesTokenCount`, `thoughtsTokenCount`
|
||||
- **Init metadata**: Protobuf field 34 `detect_and_use_proxy` set to ENABLED (1)
|
||||
- See `docs/mitm-interception-status.md` for full technical details
|
||||
- See `docs/ls-binary-analysis.md` for proto enum mappings and model IDs
|
||||
|
||||
### CLI Flags
|
||||
|
||||
- `--no-mitm`: Disable MITM proxy entirely
|
||||
- `--no-standalone`: Attach to existing LS instead of spawning standalone
|
||||
- `--mitm-port <PORT>`: Override MITM proxy port (default: auto-assign)
|
||||
- `--port <PORT>`: Override proxy listen port (default: 8741)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Known Issues & Future Work
|
||||
|
||||
All critical blockers have been resolved. MITM interception is fully working
|
||||
in standalone mode with UID-scoped iptables redirection.
|
||||
All critical blockers have been resolved. Standalone LS with MITM interception
|
||||
is fully working. Reactive streaming is implemented with polling fallback.
|
||||
|
||||
---
|
||||
|
||||
@@ -11,8 +11,8 @@ in standalone mode with UID-scoped iptables redirection.
|
||||
|
||||
**Status: SOLVED (2026-02-14)**
|
||||
|
||||
Previously the #1 blocker. The standalone LS (`--standalone` flag) now routes
|
||||
all LLM API traffic through the MITM proxy with full decryption.
|
||||
Previously the #1 blocker. The standalone LS (`--standalone` flag, now default)
|
||||
routes all LLM API traffic through the MITM proxy with full decryption.
|
||||
|
||||
**Solution:**
|
||||
|
||||
@@ -28,6 +28,23 @@ all LLM API traffic through the MITM proxy with full decryption.
|
||||
|
||||
**Verified:** `/v1/usage` returns per-model token usage from intercepted traffic.
|
||||
|
||||
### ~~Polling-Based Cascade Updates~~
|
||||
|
||||
**Status: SOLVED (2026-02-14)**
|
||||
|
||||
`StreamCascadeReactiveUpdates` is now used for real-time cascade state
|
||||
notifications. Falls back to timer-based polling if the streaming RPC is
|
||||
unavailable. Reactive diffs also carry progressive response text and thinking
|
||||
content (see `docs/panel-stream-investigation.md`).
|
||||
|
||||
### ~~StreamCascadePanelReactiveUpdates — Dead End~~
|
||||
|
||||
**Status: INVESTIGATED & CLOSED (2026-02-14)**
|
||||
|
||||
`CascadePanelState` only contains `plan_status` and `user_settings` — not
|
||||
thinking text. The panel reactive component uses a workspace-scoped ID, not
|
||||
cascade IDs. See `docs/panel-stream-investigation.md`.
|
||||
|
||||
---
|
||||
|
||||
## 🟡 Medium (Architecture / Future Work)
|
||||
@@ -56,29 +73,17 @@ prompts, modifying model selection).
|
||||
|
||||
---
|
||||
|
||||
### 3. Polling-Based Cascade Updates vs Streaming RPC
|
||||
|
||||
**File:** `src/api/polling.rs`
|
||||
|
||||
We poll `GetCascadeTrajectorySteps` on a timer. The LS has a
|
||||
`StreamCascadeReactiveUpdates` streaming gRPC method that pushes updates
|
||||
in real-time. Polling works but adds latency.
|
||||
|
||||
**Status:** Functional but suboptimal.
|
||||
|
||||
---
|
||||
|
||||
## 🟢 Low
|
||||
|
||||
### 4. MITM Integration Tests
|
||||
### 3. MITM Integration Tests
|
||||
|
||||
Unit tests cover protobuf decoding and intercept parsing (18 tests pass).
|
||||
Integration tests for the full MITM pipeline (TLS interception, response
|
||||
parsing, usage recording) would be valuable now that interception works.
|
||||
|
||||
### 5. MITM for Main Antigravity Session
|
||||
### 4. MITM for Main Antigravity Session
|
||||
|
||||
The current MITM only works for the standalone LS (`--standalone` mode).
|
||||
The current MITM only works for the standalone LS (default mode).
|
||||
Intercepting the main Antigravity session's LS is harder because:
|
||||
|
||||
- The main LS is managed by the Antigravity app, not by us
|
||||
@@ -86,4 +91,11 @@ Intercepting the main Antigravity session's LS is harder because:
|
||||
- The `mitm-wrapper.sh` approach sets env vars but the LLM client ignores
|
||||
`HTTPS_PROXY` unless `detect_and_use_proxy` is ENABLED via init metadata
|
||||
|
||||
**Workaround:** Use `--standalone` mode for all proxy traffic.
|
||||
**Workaround:** Use standalone mode (default) for all proxy traffic.
|
||||
|
||||
### 5. Progressive Thinking Streaming
|
||||
|
||||
For extended-thinking models (Opus), thinking text may arrive progressively
|
||||
across multiple reactive diffs. Currently thinking is captured atomically via
|
||||
polling. Progressive streaming would require parsing reactive diff field numbers
|
||||
to extract incremental thinking deltas. See `docs/panel-stream-investigation.md`.
|
||||
|
||||
93
docs/panel-stream-investigation.md
Normal file
93
docs/panel-stream-investigation.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Panel Stream Investigation — Dead End
|
||||
|
||||
## Summary
|
||||
|
||||
Investigated `StreamCascadePanelReactiveUpdates` RPC as a potential source for
|
||||
progressive thinking text. **Result: dead end.** The panel state only contains
|
||||
UI metadata (`plan_status`, `user_settings`), not thinking content.
|
||||
|
||||
## What We Tried
|
||||
|
||||
### 1. Subscribe with Cascade ID
|
||||
|
||||
Attempted to subscribe to `StreamCascadePanelReactiveUpdates` using the cascade
|
||||
ID as the reactive component identifier:
|
||||
|
||||
```json
|
||||
{ "protocolVersion": 1, "id": "<cascade-id>" }
|
||||
```
|
||||
|
||||
**Result:** `"reactive component <cascade-id> not found"`
|
||||
|
||||
### 2. Retry with Delays
|
||||
|
||||
Added retry logic (3 attempts, 500ms/1s/1.5s delays) to handle the possibility
|
||||
that the panel state is created asynchronously after cascade start.
|
||||
|
||||
**Result:** Same error on all attempts. The panel state uses a different
|
||||
identifier than the cascade ID.
|
||||
|
||||
### 3. InitializeCascadePanelState Analysis
|
||||
|
||||
Examined the RPC that creates panel state:
|
||||
|
||||
```js
|
||||
await this.client.initializeCascadePanelState({ metadata: e, userStatus: t });
|
||||
```
|
||||
|
||||
Takes workspace metadata + user status, not cascade ID. Panel state is
|
||||
workspace-scoped, not cascade-scoped.
|
||||
|
||||
## CascadePanelState Proto Definition
|
||||
|
||||
```
|
||||
exa.cortex_pb.CascadePanelState:
|
||||
field 1: plan_status (PlanStatus)
|
||||
field 2: user_settings (UserSettings)
|
||||
```
|
||||
|
||||
Only 2 fields — neither contains thinking text.
|
||||
|
||||
## Where Thinking Text Actually Lives
|
||||
|
||||
Thinking text flows through **`StreamCascadeReactiveUpdates`** (the cascade
|
||||
reactive diffs that we already subscribe to):
|
||||
|
||||
```
|
||||
CascadeState (jetski_cortex_pb)
|
||||
└─ field 2: trajectory (gemini_coder.Trajectory)
|
||||
└─ field 2: steps[] (gemini_coder.Step)
|
||||
└─ field 20: planner_response (CortexStepPlannerResponse)
|
||||
├─ field 1: response (string — streams progressively)
|
||||
├─ field 3: thinking (string — raw thinking text)
|
||||
├─ field 8: modified_response (string)
|
||||
└─ field 11: thinking_duration (Duration)
|
||||
```
|
||||
|
||||
### Observed Behavior (gemini-3-flash)
|
||||
|
||||
- Thinking text arrives as a **single atomic diff** (341 chars, one shot)
|
||||
- Response text streams progressively across many diffs (26 → 1796 chars)
|
||||
- Total diffs per request: ~20
|
||||
|
||||
### Current Proxy Approach
|
||||
|
||||
The proxy already captures thinking text correctly through polling
|
||||
`GetCascadeTrajectory` + `extract_thinking_content()`. No reactive diff
|
||||
parsing needed for current functionality.
|
||||
|
||||
### Future: Progressive Thinking for Extended-Thinking Models
|
||||
|
||||
For Opus models with extended thinking, the thinking text _might_ arrive
|
||||
progressively across multiple reactive diffs. If needed:
|
||||
|
||||
1. Parse reactive diff JSON for field 3 changes within field 20
|
||||
2. Diff the thinking text between updates for incremental deltas
|
||||
3. Emit `response.reasoning_summary_text.delta` events as thinking grows
|
||||
|
||||
## Cleanup
|
||||
|
||||
- Removed `stream_cascade_panel_updates()` from `backend.rs`
|
||||
- Removed panel stream subscription + retry code from `responses.rs`
|
||||
- `StreamCascadeReactiveUpdates` (cascade diffs) is still used for
|
||||
real-time notification of state changes (with polling as fallback)
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Status: ✅ FULLY IMPLEMENTED (incl. MITM interception)
|
||||
|
||||
The standalone LS is fully working via `--standalone` flag on the proxy.
|
||||
The standalone LS is the default mode. Disable with `--no-standalone`.
|
||||
All cascade types (sync, streaming, multi-turn) and all endpoints work.
|
||||
MITM interception captures real token usage from Google's API.
|
||||
|
||||
@@ -38,7 +38,7 @@ When `scripts/mitm-redirect.sh install` has been run:
|
||||
sudo ./scripts/mitm-redirect.sh install
|
||||
|
||||
# Run
|
||||
RUST_LOG=info ./target/release/antigravity-proxy --standalone
|
||||
RUST_LOG=info ./target/release/antigravity-proxy
|
||||
|
||||
# Check intercepted usage
|
||||
curl -s http://localhost:8741/v1/usage | jq .
|
||||
|
||||
Reference in New Issue
Block a user