← Back to Workflow
Internal

open_browser_cdp9000.py

Python script that launches the Antigravity IDE's built-in visible browser by connecting to the IDE's CDP debug port on localhost:9000, clicking the Chrome icon in the workbench, and then waiting for the browser's own CDP port (9222) to become active. Falls back to a Connect-RPC HTTP/1.1 call if the debug port method fails.

import asyncio
from playwright.async_api import async_playwright
import subprocess
import time
import sys
import urllib.request
import ssl
import json
import re

def get_csrf_token_and_port():
    try:
        # Note: double quotes inside PowerShell must be escaped carefully
        cmd = 'powershell -Command "Get-CimInstance Win32_Process -Filter \\"name LIKE \'%language_server_windows_x64%\'\\" | Select-Object ProcessId, CommandLine | ConvertTo-Json"'
        output = subprocess.check_output(cmd, shell=True, text=True, encoding='utf-8')
        processes = json.loads(output)
        proc_list = processes if isinstance(processes, list) else [processes]
        
        # Get connections
        port_cmd = 'powershell -Command "Get-NetTCPConnection -State Listen | Where-Object { $_.LocalAddress -eq \'127.0.0.1\' -or $_.LocalAddress -eq \'::1\' } | Select-Object LocalPort, OwningProcess | ConvertTo-Json"'
        port_output = subprocess.check_output(port_cmd, shell=True, text=True, encoding='utf-8')
        connections = json.loads(port_output)
        conn_list = connections if isinstance(connections, list) else [connections]
        
        for proc in proc_list:
            if not proc or "ProcessId" not in proc:
                continue
            pid = proc["ProcessId"]
            cmdline = proc.get("CommandLine", "")
            
            # Find ports owned by this pid
            ports = [c["LocalPort"] for c in conn_list if c and c.get("OwningProcess") == pid]
            
            csrf_match = re.search(r'--csrf_token\s+([a-fA-F0-9\-]+)', cmdline)
            if csrf_match and ports:
                # Find port that isn't the extension server (which is 40139 or 31713)
                target_port = None
                for p in ports:
                    if p not in [40139, 31713, 9953]:
                        target_port = p
                        break
                if target_port:
                    return csrf_match.group(1), target_port
        return None, None
    except Exception as e:
        print(f"Error discovering process: {e}", file=sys.stderr)
        return None, None

def launch_browser_via_http1(target_url="about:blank"):
    csrf_token, port = get_csrf_token_and_port()
    if not csrf_token or not port:
        print("Could not find active IDE language server port and CSRF token.", file=sys.stderr)
        return False
        
    print(f"Discovered active language server on port {port}", file=sys.stderr)
    
    # Try both http and https protocols
    for proto in ["http", "https"]:
        url = f"{proto}://127.0.0.1:{port}/exa.language_server_pb.LanguageServerService/SmartOpenBrowser"
        
        headers = {
            "Content-Type": "application/json",
            "connect-protocol-version": "1",
            "x-codeium-csrf-token": csrf_token
        }
        
        payload = {
            "url": target_url
        }
        
        data = json.dumps(payload).encode('utf-8')
        req = urllib.request.Request(url, data=data, headers=headers, method="POST")
        
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
        
        try:
            print(f"Sending Connect-RPC JSON launch command to {url}...", file=sys.stderr)
            with urllib.request.urlopen(req, context=ctx) as response:
                if response.status == 200:
                    print("Programmatic Connect-RPC launch command succeeded!", file=sys.stderr)
                    return True
        except Exception as e:
            print(f"Connection failed over {proto}: {e}", file=sys.stderr)
            
    return False

async def launch_browser(target_url="about:blank"):
    # Method 4: Connect to Antigravity IDE debug port on port 9000
    try:
        async with async_playwright() as p:
            print("Attempting Method 4: Connecting to Antigravity IDE debug port on http://localhost:9000...", file=sys.stderr)
            browser = await p.chromium.connect_over_cdp("http://localhost:9000")
            try:
                context = browser.contexts[0]
                pages = context.pages
                
                workbench_page = None
                for page in pages:
                    if "workbench.html" in page.url:
                        workbench_page = page
                        break
                
                if not workbench_page:
                    print("Could not find IDE workbench page.", file=sys.stderr)
                    return False

                print("Found IDE workbench. Searching for the Chrome icon button...", file=sys.stderr)
                button = workbench_page.locator("a.codicon-chrome")
                
                if await button.count() == 0:
                    print("Could not find the Chrome icon button in the DOM.", file=sys.stderr)
                    return False
                    
                print("Found the button. Clicking it now...", file=sys.stderr)
                await button.click()
                print("Button clicked successfully!", file=sys.stderr)
                return True
            finally:
                await browser.close()
    except Exception as e:
        print(f"Method 4 failed: {e}. Falling back to Connect-RPC HTTP/1.1 method...", file=sys.stderr)
        
        # Method 1 Fallback: Call SmartOpenBrowser via Connect-RPC
        return launch_browser_via_http1(target_url)

def wait_for_cdp_port(timeout=10):
    print("Waiting for browser CDP port 9222 to become active...", file=sys.stderr)
    for _ in range(timeout * 2):
        try:
            # Query if port 9222 is active
            cmd = 'powershell -Command "Get-NetTCPConnection -State Listen | Where-Object { $_.LocalPort -eq 9222 }"'
            subprocess.check_output(cmd, shell=True)
            print("Browser CDP port 9222 is active and listening!", file=sys.stderr)
            return True
        except subprocess.CalledProcessError:
            time.sleep(0.5)
    print("Timeout waiting for port 9222.", file=sys.stderr)
    return False

if __name__ == "__main__":
    if asyncio.run(launch_browser()):
        wait_for_cdp_port()

This is used in: