Skip to content

[FEAT]: Add checking of rate limits from a running antigravity instance #6

@theblazehen

Description

@theblazehen

We can't easily check the upstream rate limits ourselves, however we can with a running Antigravity instance (Or at least the language server?)

This would allow us to potentially add something to /status showing the rate limit status.

Here's an example script showing rate limit status if you have a running Antigravity instance, based on https://github.com/Zendevve/antigravity-usage

#!/usr/bin/env ts-node
/**
 * Standalone CLI Quota Checker
 * Queries a running AntiGravity language server for quota information
 * No GUI required - works with headless AntiGravity instances
 */

import { exec } from 'child_process';
import { promisify } from 'util';
import * as https from 'https';
import * as os from 'os';

const execAsync = promisify(exec);

interface ProcessInfo {
  pid: number;
  extensionPort: number | null;
  csrfToken: string;
}

function getProcessName(): string {
  const platform = os.platform();
  if (platform === 'linux') return 'language_server_linux_x64';
  if (platform === 'darwin') return 'language_server_macos_arm';
  if (platform === 'win32') return 'language_server_windows_x64.exe';
  throw new Error(`Unsupported platform: ${platform}`);
}

function getProcessListCommand(processName: string): string {
  const platform = os.platform();
  if (platform === 'linux' || platform === 'darwin') {
    return `pgrep -fa ${processName}`;
  }
  if (platform === 'win32') {
    return `wmic process where "name='${processName}'" get ProcessId,CommandLine /format:list`;
  }
  throw new Error(`Unsupported platform: ${platform}`);
}

function parseProcessInfo(output: string): ProcessInfo | null {
  const platform = os.platform();

  if (platform === 'linux' || platform === 'darwin') {
    // Format: "PID /path/to/language_server --api_server_port 12345 --csrf_token abc123..."
    const lines = output.trim().split('\n');
    for (const line of lines) {
      const match = line.match(/^(\d+)\s+.*--csrf_token\s+([a-f0-9-]+)/i);
      if (match) {
        const pid = parseInt(match[1], 10);
        const csrfToken = match[2];
        const portMatch = line.match(/--api_server_port\s+(\d+)/);
        const extensionPort = portMatch ? parseInt(portMatch[1], 10) : null;
        return { pid, extensionPort, csrfToken };
      }
    }
  } else if (platform === 'win32') {
    // Windows format from wmic
    const pidMatch = output.match(/ProcessId=(\d+)/);
    const csrfMatch = output.match(/--csrf_token\s+([a-f0-9-]+)/i);
    const portMatch = output.match(/--api_server_port\s+(\d+)/);

    if (pidMatch && csrfMatch) {
      return {
        pid: parseInt(pidMatch[1], 10),
        extensionPort: portMatch ? parseInt(portMatch[1], 10) : null,
        csrfToken: csrfMatch[1]
      };
    }
  }

  return null;
}

function getPortListCommand(pid: number): string {
  const platform = os.platform();
  if (platform === 'linux') {
    return `ss -tlnp 2>/dev/null | grep "pid=${pid},"`;
  }
  if (platform === 'darwin') {
    return `lsof -nP -iTCP -sTCP:LISTEN -a -p ${pid}`;
  }
  if (platform === 'win32') {
    return `netstat -ano | findstr ${pid}`;
  }
  throw new Error(`Unsupported platform: ${platform}`);
}

function parseListeningPorts(output: string): number[] {
  const platform = os.platform();
  const ports: number[] = [];

  if (platform === 'linux') {
    // ss format: "LISTEN 0 128 127.0.0.1:12345 0.0.0.0:*"
    const regex = /127\.0\.0\.1:(\d+)/g;
    let match;
    while ((match = regex.exec(output)) !== null) {
      ports.push(parseInt(match[1], 10));
    }
  } else if (platform === 'darwin') {
    // lsof format: "language 12345 user 3u IPv4 0x... 0t0 TCP 127.0.0.1:12345 (LISTEN)"
    const regex = /127\.0\.0\.1:(\d+)/g;
    let match;
    while ((match = regex.exec(output)) !== null) {
      ports.push(parseInt(match[1], 10));
    }
  } else if (platform === 'win32') {
    // netstat format: "TCP 127.0.0.1:12345 0.0.0.0:0 LISTENING 12345"
    const regex = /127\.0\.0\.1:(\d+).*LISTENING/g;
    let match;
    while ((match = regex.exec(output)) !== null) {
      ports.push(parseInt(match[1], 10));
    }
  }

  return [...new Set(ports)];
}

interface QuotaData {
  model: string;
  remainingPercent: number;
  usedPercent: number;
  resetTime?: string;
  isExhausted: boolean;
}

async function main() {
  try {
    const processName = getProcessName();
    const { stdout } = await execAsync(getProcessListCommand(processName), { timeout: 15000 });
    const processInfo = parseProcessInfo(stdout);

    if (!processInfo) {
      console.error('❌ AntiGravity language server not found. Make sure AntiGravity is running.\n');
      process.exit(1);
    }

    const { pid, extensionPort, csrfToken } = processInfo;
    const { stdout: portOutput } = await execAsync(getPortListCommand(pid), { timeout: 5000 });
    const ports = parseListeningPorts(portOutput);

    if (ports.length === 0) {
      console.error('❌ No listening ports found.\n');
      process.exit(1);
    }

    const portsToTest = extensionPort && ports.includes(extensionPort)
      ? [extensionPort, ...ports.filter(p => p !== extensionPort)]
      : ports;

    let quotaData: QuotaData[] | null = null;
    let lastError: Error | null = null;

    for (const port of portsToTest) {
      try {
        quotaData = await fetchQuotaData(port, csrfToken);
        break;
      } catch (error: any) {
        lastError = error;
      }
    }

    if (!quotaData) {
      throw lastError || new Error('Failed to fetch quota data from any port');
    }

    displayQuotaData(quotaData);

  } catch (error: any) {
    console.error(`❌ ${error.message}\n`);
    process.exit(1);
  }
}

async function fetchQuotaData(port: number, csrfToken: string): Promise<QuotaData[]> {
  return new Promise((resolve, reject) => {
    const requestBody = JSON.stringify({
      metadata: {
        ideName: 'antigravity',
        extensionName: 'antigravity',
        locale: 'en'
      }
    });

    const options: https.RequestOptions = {
      hostname: '127.0.0.1',
      port: port,
      path: '/exa.language_server_pb.LanguageServerService/GetUserStatus',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(requestBody),
        'Connect-Protocol-Version': '1',
        'X-Codeium-Csrf-Token': csrfToken
      },
      rejectUnauthorized: false,
      timeout: 5000
    };

    const req = https.request(options, (res) => {
      let data = '';
      res.on('data', (chunk) => { data += chunk; });
      res.on('end', () => {
        if (res.statusCode !== 200) {
          reject(new Error(`HTTP ${res.statusCode}: ${data.substring(0, 100)}`));
          return;
        }
        try {
          const parsed = JSON.parse(data);
          const quotas = parseQuotaResponse(parsed);
          resolve(quotas);
        } catch (e) {
          reject(new Error(`JSON parse error: ${data.substring(0, 100)}`));
        }
      });
    });

    req.on('error', (error) => reject(error));
    req.on('timeout', () => {
      req.destroy();
      reject(new Error('Request timeout'));
    });

    req.write(requestBody);
    req.end();
  });
}

function parseQuotaResponse(data: any): QuotaData[] {
  const userStatus = data.userStatus || data;
  const cascadeData = userStatus.cascadeModelConfigData;
  const rawModels = cascadeData?.clientModelConfigs || [];

  const quotas: QuotaData[] = [];

  for (const model of rawModels) {
    if (!model.quotaInfo) {
      continue;
    }

    const label = model.label || 'Unknown Model';
    const remainingFraction = model.quotaInfo.remainingFraction ?? 1;
    const remainingPercent = Math.round(remainingFraction * 100);
    const usedPercent = 100 - remainingPercent;
    const isExhausted = remainingFraction === 0;

    let resetTime: string | undefined;
    if (model.quotaInfo.resetTime) {
      const resetDate = new Date(model.quotaInfo.resetTime);
      const diff = resetDate.getTime() - Date.now();
      resetTime = formatTime(diff);
    }

    quotas.push({
      model: label,
      remainingPercent,
      usedPercent,
      resetTime,
      isExhausted
    });
  }

  return quotas;
}

function formatTime(ms: number): string {
  if (ms <= 0) return 'Ready';
  const mins = Math.ceil(ms / 60000);
  return mins < 60 ? `${mins}m` : `${Math.floor(mins / 60)}h ${mins % 60}m`;
}

function displayQuotaData(quotas: QuotaData[]) {
  if (quotas.length === 0) {
    console.log('⚠️  No quota data available.\n');
    return;
  }

  console.log('\n─── Quota Status ───\n');

  for (const quota of quotas) {
    const status = quota.isExhausted ? '🔴 EXHAUSTED' :
                   quota.remainingPercent < 15 ? '🔴 CRITICAL' :
                   quota.remainingPercent < 25 ? '🟡 LOW' :
                   '🟢 HEALTHY';

    console.log(`${status}  ${quota.model}`);
    console.log(`   ${quota.remainingPercent}% remaining | ${quota.usedPercent}% used${quota.resetTime ? ' | Resets: ' + quota.resetTime : ''}`);
    console.log('');
  }
}

if (require.main === module) {
  main().catch(console.error);
}

Upstream doesn't currently support a nice status display, but I believe there is a PR for it. I'll try do a preliminary PR tomorrow

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions