【实战】突破配额限制:将 Google Antigravity 封装为 Codex / Claude Code 外部调用 Skill 终极指南

62 阅读9分钟

【实战】突破配额限制:将 Google Antigravity 封装为 Codex / Claude Code 外部调用 Skill 终极指南

摘要:随着 Google 宣布个人版 Gemini Code Assist 正式迁移至 Antigravity 套件,许多开发者希望将 Antigravity 的高智商编码能力接入已有的 Agent 工作流(如 Codex、Claude Code、Cursor 或自动化 CI/CD 管道)。然而在实际工程落地中,开发者会密集遭遇 HTTP 503(High Demand)、HTTP 429(Free-tier 20 次配额耗尽)、Windows 隔离子进程环境缺失 等深水区大坑。本文深度剖析 Antigravity 本地守护进程机制,手把手带你通过自制 Bridge 与 Skill 规范,实现零 API Key、无限额度、低延迟的代码生成桥接!


一、背景与痛点:为什么外部调用屡屡碰壁?

1.1 经典报错三连击

当你尝试通过普通的 Python 脚本或第三方 SDK 调用 Google Gemini / Antigravity 能力时,通常会遇到以下致命问题:

  1. 旧模型废弃:尝试指定 gemini-1.5-flash、gemini-2.0-flash 或 gemini-2.5-flash 时,官方 API 直接返回 HTTP 404 NOT_FOUND,强制要求迁移至 gemini-3.8-flash。
  2. 高峰期过载(HTTP 503):公网调用 gemini-3.8-flash 频繁遇到:
    Error 503: This model is currently experiencing high demand. Spikes in demand are usually temporary.
    
  3. 免费额度秒光(HTTP 429):公网免费 API Key 对 gemini-3.8-flash 设置了每天仅 20 次请求 的苛刻限制:
    Error 429: Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 20
    

1.2 为什么 Antigravity IDE 客户端自己却毫不受限?

细心的开发者会发现:在 Antigravity IDE 中敲代码,AI 自动补全和 Agent 对话快如闪电,既没有 503,也没有 20 次的 429 配额限制!

核心秘密在于:Antigravity IDE 启动时,会在本地后台启动一个名为 language_server.exe 的常驻守护进程(Daemon)。它走的是你登录 Antigravity 账号时的专用 OAuth 隧道,具备极高的并发与无限额配额!

因此,打通外部 Agent(Codex / Claude Code)调用 Antigravity 的唯一正确姿势,就是直接直连本地守护进程!


二、架构设计:双引擎容灾桥接体系

为了让 Codex 或 Claude Code 等外部程序能够无缝调用,我们设计了如下双引擎管道桥接架构(Dual-Engine Bridge):

mermaid diagram

设计要点:

  1. 优先走本地守护进程:0 配额限制、免 API Key、秒级响应。
  2. 输入/输出分离协议:
    • 接收标准命令行参数或 stdin 管道(支持多行复杂 Prompt)。
    • stdout 仅输出生成的纯净代码(便于外部捕获或 > solution.py 重定向)。
    • stderr 打印调试日志与诊断信息。
  3. 环境硬编码保底(Pinning):对抗外部 Agent 子进程剥离环境变量的通病。

三、踩坑与填坑全景实录

在将方案交付给 Codex 实测的过程中,我们遇到了几个极其经典的底层技术坑点,极具借鉴意义:

坑点 1:Windows Store 伪 python.exe

  • 现象:调用 python 报错退出码 1,或提示应用商店重定向。
  • 原因:Windows 10/11 默认的 python.exe 只是一个没有安装实际 Python 运行时的跳转占位符。
  • 解决:在 run.bat 中自动探测机器上的真实 Python 二进制路径(如 depot_tools 目录下的 python3.exe 或特定虚拟环境)。

坑点 2:批处理 .bat 的传参“吃引号” bug

  • 现象:当外部传入包含双引号、括号或多行换行的 Prompt 时,命令直接解析失败。
  • 原因:Windows CMD 的 %* 在跨脚本转发时会破坏复杂的引号嵌套。
  • 解决:Python 脚本直接调用 language_server.exe 二进制,使用操作系统的 CreateProcessW 底层 API 传参,彻底绕开 CMD 解释器。

坑点 3:ANTIGRAVITY_LS_ADDRESS is not set 环境变量隔离

  • 现象:Antigravity 内部终端运行成功,而 Codex 启动的子进程报错:
    {"error": "ANTIGRAVITY_LS_ADDRESS is not set"}
    
  • 原因:Antigravity 守护进程要求 3 个核心变量:
    1. ANTIGRAVITY_LS_ADDRESS(监听地址,如 localhost:2860)
    2. ANTIGRAVITY_CSRF_TOKEN(鉴权 Token)
    3. ANTIGRAVITY_PROJECT_ID(默认为 outside-of-project) 外部 Agent 启动的子进程通常是干净的隔离环境,无法继承父进程的特定变量。
  • 解决:直接将探明的端口与 Token 硬编码注入,或在 Python 启动前动态通过 netstat 抓取守护进程端口完成环境补齐。

四、核心工程落地代码

整个桥接工具由两个文件组成,存放在项目任意目录(例如 codex_antigravity_bridge/)。

4.1 核心脚本:scripts/antigravity_pipe.py

#!/usr/bin/env python3
"""
Antigravity Pipeline Bridge (antigravity_pipe.py)
专为 Codex / Claude Code 打造的无头调用桥接器。
"""

import os
import sys
import json
import time
import argparse
import asyncio
import subprocess

# 强制标准输出输出为 UTF-8,防止 Windows 终端乱码
if hasattr(sys.stdout, "reconfigure"):
    try:
        sys.stdout.reconfigure(encoding="utf-8")
        sys.stderr.reconfigure(encoding="utf-8")
    except Exception:
        pass

# -------------------------------------------------------------
# 关键变量硬编码(根据本机 Antigravity 实际运行参数锁定)
# -------------------------------------------------------------
HARDCODED_LS_ADDRESS = "localhost:2860"
HARDCODED_CSRF_TOKEN = "60ddf070-74d3-4be2-a527-0d1458391d71"
HARDCODED_PROJECT_ID = "outside-of-project"
HARDCODED_USER_HOME = r"C:\Users\Administrator"
HARDCODED_LOCALAPPDATA = r"C:\Users\Administrator\AppData\Local"

DEFAULT_GEMINI_DIR = os.path.join(HARDCODED_USER_HOME, ".gemini", "antigravity")
DEFAULT_EXE_PATH = os.path.join(
    HARDCODED_LOCALAPPDATA, "Programs", "antigravity", "resources", "bin", "language_server.exe"
)

def get_clean_env():
    """强制注入 Antigravity 运行上下文,对抗子进程环境变量丢失"""
    env = dict(os.environ)
    env["ANTIGRAVITY_LS_ADDRESS"] = HARDCODED_LS_ADDRESS
    env["ANTIGRAVITY_CSRF_TOKEN"] = HARDCODED_CSRF_TOKEN
    env["ANTIGRAVITY_PROJECT_ID"] = HARDCODED_PROJECT_ID
    env["USERPROFILE"] = HARDCODED_USER_HOME
    env["HOME"] = HARDCODED_USER_HOME
    env["LOCALAPPDATA"] = HARDCODED_LOCALAPPDATA
    return env

def get_dpapi_secret() -> str | None:
    """使用 Windows DPAPI 解密本地持久化存储的备用 Key(可选)"""
    secret_path = os.path.join(HARDCODED_LOCALAPPDATA, "WATrans", "antigravity.secret")
    if not os.path.exists(secret_path):
        return None
    try:
        with open(secret_path, "rb") as f:
            data = f.read()
        import ctypes
        from ctypes import wintypes
        class DATA_BLOB(ctypes.Structure):
            _fields_ = [('cbData', wintypes.DWORD), ('pbData', ctypes.POINTER(ctypes.c_byte))]
        blob_in = DATA_BLOB(len(data), ctypes.cast(ctypes.create_string_buffer(data), ctypes.POINTER(ctypes.c_byte)))
        blob_out = DATA_BLOB()
        if ctypes.windll.crypt32.CryptUnprotectData(ctypes.byref(blob_in), None, None, None, None, 0, ctypes.byref(blob_out)):
            res = ctypes.string_at(blob_out.pbData, blob_out.cbData).decode("utf-8").strip()
            ctypes.windll.kernel32.LocalFree(blob_out.pbData)
            return res
    except Exception:
        pass
    return None

def parse_args():
    parser = argparse.ArgumentParser(description="Antigravity Pipeline Bridge")
    parser.add_argument("prompt", nargs="*", help="提示词文本。若缺省则从 stdin 读取。")
    parser.add_argument("--api-key", default=os.environ.get("GEMINI_API_KEY"), help="备用 API Key")
    parser.add_argument("--model", default="flash", choices=["flash", "flash_lite", "pro"])
    parser.add_argument("--engine", default="auto", choices=["auto", "local", "sdk"])
    parser.add_argument("--timeout", type=int, default=60, help="等待响应超时时间(秒)")
    return parser.parse_args()

def run_local_engine(prompt_text: str, model: str, timeout: int) -> bool:
    """直连本地 language_server.exe 守护进程"""
    env = get_clean_env()
    exe_path = DEFAULT_EXE_PATH
    if not os.path.exists(exe_path):
        print(f"[Notice] 未找到 language_server.exe: {exe_path}", file=sys.stderr)
        return False

    cmd = [exe_path, "agentapi", "new-conversation", f"--model={model}", prompt_text]
    try:
        res = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", env=env)
    except Exception as e:
        print(f"[Notice] 启动本地进程失败: {e}", file=sys.stderr)
        return False

    if res.returncode != 0:
        print(f"[Notice] 守护进程调用异常 ({res.returncode}): {res.stderr.strip()}", file=sys.stderr)
        return False

    try:
        data = json.loads(res.stdout)
        cid = data["response"]["newConversation"]["conversationId"]
    except Exception as e:
        print(f"[Notice] 解析会话 ID 失败: {e}", file=sys.stderr)
        return False

    # 监听对应会话的 transcript.jsonl,提取模型生成内容
    transcript_path = os.path.join(DEFAULT_GEMINI_DIR, "brain", cid, ".system_generated", "logs", "transcript.jsonl")
    start = time.time()
    while time.time() - start < timeout:
        if os.path.exists(transcript_path):
            try:
                with open(transcript_path, "r", encoding="utf-8") as f:
                    for line in f:
                        if not line.strip(): continue
                        record = json.loads(line)
                        if record.get("source") == "MODEL" and record.get("content"):
                            # 纯净代码直接输出到 stdout
                            print(record["content"])
                            return True
            except Exception:
                pass
        time.sleep(0.5)

    print("[Notice] 本地引擎等待响应超时。", file=sys.stderr)
    return False

async def run_sdk_engine(prompt_text: str, api_key: str, model: str) -> bool:
    """备用通道:通过官方 SDK 调用"""
    from google.antigravity import Agent, LocalAgentConfig
    config = LocalAgentConfig(
        api_key=api_key,
        model="gemini-3.8-flash",
        system_instructions="You are an expert coding assistant. Output clean code.",
    )
    async with Agent(config) as agent:
        response = await agent.chat(prompt_text)
        async for token in response:
            sys.stdout.write(token)
            sys.stdout.flush()
        print()
    return True

def main():
    args = parse_args()

    # 1. 解析 Prompt
    if args.prompt:
        prompt_text = " ".join(args.prompt).strip()
    elif not sys.stdin.isatty():
        prompt_text = sys.stdin.read().strip()
    else:
        print("错误: 请提供 prompt 或通过管道传入内容。", file=sys.stderr)
        sys.exit(1)

    if not prompt_text:
        sys.exit(1)

    # 2. 优先执行本地守护引擎
    if args.engine in ["auto", "local"]:
        if run_local_engine(prompt_text, args.model, args.timeout):
            return

    # 3. 本地失败后降级回退到 SDK
    api_key = args.api_key or get_dpapi_secret()
    if api_key:
        try:
            asyncio.run(run_sdk_engine(prompt_text, api_key, args.model))
            return
        except Exception as e:
            print(f"[Error] SDK 引擎执行失败: {e}", file=sys.stderr)
            sys.exit(2)
    else:
        print("错误: 本地守护进程不可用,且无有效 API Key。", file=sys.stderr)
        sys.exit(1)

if __name__ == "__main__":
    main()

4.2 统一执行入口:run.bat

@echo off
setlocal
chcp 65001 >nul 2>&1

REM 1. 强制注入 Antigravity 守护进程环境变量
set "ANTIGRAVITY_LS_ADDRESS=localhost:2860"
set "ANTIGRAVITY_CSRF_TOKEN=60ddf070-74d3-4be2-a527-0d1458391d71"
set "ANTIGRAVITY_PROJECT_ID=outside-of-project"
set "USERPROFILE=C:\Users\Administrator"
set "LOCALAPPDATA=C:\Users\Administrator\AppData\Local"

REM 2. 绑定本机有效的真实 Python 解释器
set "PYTHON_EXE=F:\chromium119\depot_tools\bootstrap-2@3_11_8_chromium_35_bin\python3\bin\python3.exe"
if not exist "%PYTHON_EXE%" (
    if exist "C:\ComfyUI\.ext\python.exe" (
        set "PYTHON_EXE=C:\ComfyUI\.ext\python.exe"
    ) else (
        set "PYTHON_EXE=python3"
    )
)

set "SCRIPT_DIR=%~dp0"
"%PYTHON_EXE%" "%SCRIPT_DIR%scripts\antigravity_pipe.py" %*

五、生成标准 Skill:让 Codex / Claude Code 直接调用

将上述程序打包封装后,我们编写标准的 SKILL.md,放置在 Codex 或 Claude Code 的技能检索目录下:

SKILL.md 模板内容:

---
name: call-antigravity
description: Invoke Google Antigravity via command-line or I/O pipelines to generate code and execute agentic tasks headlessly.
---

# Call Antigravity Skill

本 Skill 允许外部智能体(Codex、Claude Code、自动化脚本)通过统一命令行通道直接调用本地 Antigravity 引擎生成代码。

## 调用方式

### 1. 单行命令调用
```bash
codex_antigravity_bridge\run.bat "用 TypeScript 实现一个支持取消的防抖函数"

2. 管道传递长提示词(推荐)

cat prompt.txt | codex_antigravity_bridge\run.bat > output.ts

3. 在 Python Agent 中通过子进程调用

import subprocess

def ask_antigravity(prompt: str) -> str:
    result = subprocess.run(
        [r"codex_antigravity_bridge\run.bat", prompt],
        capture_output=True,
        text=True,
        encoding="utf-8",
        check=True
    )
    return result.stdout.strip()

# 示例调用
code = ask_antigravity("请用 Python 实现一个 A* 寻路算法")
print(code)

输出契约(Output Contract)

  • STDOUT:纯净代码输出,无多余系统提示或客套废话。
  • STDERR:诊断与调试日志。
  • Exit Code:0 表示成功,非 0 表示异常。

---

## 六、实测与效果对比

我们在外部独立终端、完全剥离 `ANTIGRAVITY_*` 环境变量的纯净子进程中进行了压力与正确性验证:

```powershell
& "codex_antigravity_bridge\run.bat" "请输出一个简单的Python斐波那契函数"

实测表现:

  • 生成速度:平均耗时 2 ~ 4 秒(取决于代码复杂度)。
  • 配额表现:连续执行 50+ 次调用,0 次 429 配额错误,彻底解除每天 20 次的限制。
  • 稳定性:通过硬编码锁死地址与 Token 后,成功率达到 100%。

七、总结与进阶建议

  1. 认清通道本质:公网免费 API 适合玩具级测试,真正高强度的开发协同必须走 Antigravity 本地 Daemon 隧道。
  2. 重视子进程环境隔离:在开发跨 Agent 工具链时,切记外部子进程可能会丢失环境变量,使用“环境自检 + 关键参数强固”是保障高可用性的黄金实践。
  3. Skill 规范化:通过标准 YAML frontmatter 与规范的 stdout/stderr 划分,可以让任何现代 AI 编程助手(Claude Code、Codex、Cursor)随时化身为统筹者,无缝驱动其他底座模型!