# Model Switching

## Status: IMPLEMENTED + TESTED -- Needs CLI wiring

## The Problem

The Gemini web interface uses BotGuard tokens (inner[3] in the StreamGenerate
request body, ~1300 chars starting with "!") to authorize model routing.
Without this token, the server ignores the model header and defaults to Flash.

- Confirmed: no existing Python library has solved this (tested gemini-webapi v1.19.2)
- The BotGuard token is generated by obfuscated JavaScript (window.botguard)
- The token changes per request and cannot be replicated outside the browser

## The Solution: BrowserTransport (IMPLEMENTED)

Use headless Chrome as the execution engine for model-sensitive requests.
The browser generates BotGuard tokens automatically.

**Implementation:** `src/gemini_web_mcp_cli/core/cdp.py` -> `BrowserTransport` class

### How It Works

1. Launch headless Chrome (or reuse existing instance) via CDP
2. Navigate to gemini.google.com/app
3. Click model picker: `[data-test-id="bard-mode-menu-button"]`
4. Select model: `[data-test-id="bard-mode-option-pro"]` (or thinking/fast)
5. Type prompt via `document.execCommand('insertText', false, prompt)`
6. Click Send: `button[aria-label="Send message"]`
7. Poll DOM for response stability (3 seconds unchanged = done)
8. Return response text

### Tested Results (Feb 15, 2026)

```
Pro:  response="PRO_WORKS"  model button="Pro"   ✓
Fast: response="FAST_WORKS" model button="Fast"  ✓
```

No Playwright dependency -- pure CDP via websocket-client (already in project).

## Key Selectors

```
Model picker button:  [data-test-id="bard-mode-menu-button"]
Fast option:          [data-test-id="bard-mode-option-fast"]
Thinking option:      [data-test-id="bard-mode-option-thinking"]
Pro option:           [data-test-id="bard-mode-option-pro"]
Send button:          button[aria-label="Send message"]
Input field:          div[contenteditable="true"] or textarea
```

## Architecture: Dual-Mode Transport

```
gemcli chat "hello"            --> RPCTransport (direct HTTP, Flash)
gemcli chat "hello" -m pro     --> BrowserTransport (headless Chrome, any model)
```

- **RPCTransport** (core/rpc.py): Direct HTTP. Fast, no browser. Flash only.
- **BrowserTransport** (core/cdp.py): Headless Chrome via CDP. All models.

## What's Left To Do (Next Session)

1. **Wire BrowserTransport into ChatService** (services/chat.py):
   - When model is "pro" or "thinking", use BrowserTransport
   - When model is "fast" or default, use RPCTransport (current behavior)

2. **Wire into CLI** (cli.py):
   - `/model pro` in interactive chat should switch to BrowserTransport
   - `gemcli chat -m pro "prompt"` should auto-use BrowserTransport
   - Show "[Pro/Browser]" tag in response to indicate browser-driven

3. **Chrome lifecycle management**:
   - Keep headless Chrome running between requests (don't restart each time)
   - Reuse the same BrowserTransport instance in interactive chat
   - Clean shutdown on exit

4. **Headless mode testing**:
   - Tested with visible Chrome (headless=False) -- works
   - Need to verify headless=True works with BotGuard (should, since
     --headless=new runs full browser engine)

## Research Journey

1. Captured model hashes from web UI model picker (Fast/Thinking/Pro)
2. Sent model header in direct HTTP requests -> server returned Flash
3. Added all web UI request body fields -> still Flash
4. Identified BotGuard token at inner[3] as the missing piece
5. Confirmed gemini-webapi has same problem (model switching broken)
6. Found social-browser-agents project with working Playwright approach
7. Implemented BrowserTransport using pure CDP (no Playwright)
8. Tested end-to-end: Pro and Fast confirmed working via CDP
