> 返回 [README](../../README.zh.md) # Antigravity 认证与集成指南 ## 概述 **Antigravity**(Google Cloud Code Assist)是由 Google 支持的 AI 模型提供商,通过 Google 的云基础设施提供对 Claude Opus 4.6 和 Gemini 等模型的访问。本文档提供了关于认证工作原理、如何获取模型以及如何在 PicoClaw 中实现新提供商的完整指南。 --- ## 目录 1. [认证流程](#认证流程) 2. [OAuth 实现细节](#oauth-实现细节) 3. [令牌管理](#令牌管理) 4. [模型列表获取](#模型列表获取) 5. [用量追踪](#用量追踪) 6. [提供商插件结构](#提供商插件结构) 7. [集成要求](#集成要求) 8. [API 端点](#api-端点) 9. [配置](#配置) 10. [在 PicoClaw 中创建新提供商](#在-picoclaw-中创建新提供商) --- ## 认证流程 ### 1. 带 PKCE 的 OAuth 2.0 Antigravity 使用 **OAuth 2.0 with PKCE(Proof Key for Code Exchange)** 进行安全认证: ``` ┌─────────────┐ ┌─────────────────┐ │ Client │ ───(1) Generate PKCE Pair────────> │ │ │ │ ───(2) Open Auth URL─────────────> │ Google OAuth │ │ │ │ Server │ │ │ <──(3) Redirect with Code───────── │ │ │ │ └─────────────────┘ │ │ ───(4) Exchange Code for Tokens──> │ Token URL │ │ │ │ │ │ │ <──(5) Access + Refresh Tokens──── │ │ └─────────────┘ └─────────────────┘ ``` ### 2. 详细步骤 #### 步骤 1:生成 PKCE 参数 ```typescript function generatePkce(): { verifier: string; challenge: string } { const verifier = randomBytes(32).toString("hex"); const challenge = createHash("sha256").update(verifier).digest("base64url"); return { verifier, challenge }; } ``` #### 步骤 2:构建授权 URL ```typescript const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"; const REDIRECT_URI = "http://localhost:51121/oauth-callback"; function buildAuthUrl(params: { challenge: string; state: string }): string { const url = new URL(AUTH_URL); url.searchParams.set("client_id", CLIENT_ID); url.searchParams.set("response_type", "code"); url.searchParams.set("redirect_uri", REDIRECT_URI); url.searchParams.set("scope", SCOPES.join(" ")); url.searchParams.set("code_challenge", params.challenge); url.searchParams.set("code_challenge_method", "S256"); url.searchParams.set("state", params.state); url.searchParams.set("access_type", "offline"); url.searchParams.set("prompt", "consent"); return url.toString(); } ``` **所需权限范围:** ```typescript const SCOPES = [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/cclog", "https://www.googleapis.com/auth/experimentsandconfigs", ]; ``` #### 步骤 3:处理 OAuth 回调 **自动模式(本地开发):** - 在端口 51121 上启动本地 HTTP 服务器 - 等待来自 Google 的重定向 - 从查询参数中提取授权码 **手动模式(远程/无头环境):** - 向用户显示授权 URL - 用户在浏览器中完成认证 - 用户将完整的重定向 URL 粘贴回终端 - 从粘贴的 URL 中解析授权码 #### 步骤 4:用授权码交换令牌 ```typescript const TOKEN_URL = "https://oauth2.googleapis.com/token"; async function exchangeCode(params: { code: string; verifier: string; }): Promise<{ access: string; refresh: string; expires: number }> { const response = await fetch(TOKEN_URL, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ client_id: CLIENT_ID, client_secret: CLIENT_SECRET, code: params.code, grant_type: "authorization_code", redirect_uri: REDIRECT_URI, code_verifier: params.verifier, }), }); const data = await response.json(); return { access: data.access_token, refresh: data.refresh_token, expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000, // 5 min buffer }; } ``` #### 步骤 5:获取额外的用户数据 **用户邮箱:** ```typescript async function fetchUserEmail(accessToken: string): Promise { const response = await fetch( "https://www.googleapis.com/oauth2/v1/userinfo?alt=json", { headers: { Authorization: `Bearer ${accessToken}` } } ); const data = await response.json(); return data.email; } ``` **项目 ID(API 调用必需):** ```typescript async function fetchProjectId(accessToken: string): Promise { const headers = { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", "User-Agent": "google-api-nodejs-client/9.15.1", "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1", "Client-Metadata": JSON.stringify({ ideType: "IDE_UNSPECIFIED", platform: "PLATFORM_UNSPECIFIED", pluginType: "GEMINI", }), }; const response = await fetch( "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist", { method: "POST", headers, body: JSON.stringify({ metadata: { ideType: "IDE_UNSPECIFIED", platform: "PLATFORM_UNSPECIFIED", pluginType: "GEMINI", }, }), } ); const data = await response.json(); return data.cloudaicompanionProject || "rising-fact-p41fc"; // 默认回退值 } ``` --- ## OAuth 实现细节 ### 客户端凭据 **重要:** 这些凭据在源代码中以 base64 编码存储,用于与 pi-ai 同步: ```typescript const decode = (s: string) => Buffer.from(s, "base64").toString(); const CLIENT_ID = decode( "MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==" ); const CLIENT_SECRET = decode("R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY="); ``` ### OAuth 流程模式 1. **自动流程**(有浏览器的本地机器): - 自动打开浏览器 - 本地回调服务器捕获重定向 - 初始认证后无需用户交互 2. **手动流程**(远程/无头/WSL2 环境): - 显示 URL 供手动复制粘贴 - 用户在外部浏览器中完成认证 - 用户将完整的重定向 URL 粘贴回来 ```typescript function shouldUseManualOAuthFlow(isRemote: boolean): boolean { return isRemote || isWSL2Sync(); } ``` --- ## 令牌管理 ### 认证配置文件结构 ```typescript type OAuthCredential = { type: "oauth"; provider: "google-antigravity"; access: string; // 访问令牌 refresh: string; // 刷新令牌 expires: number; // 过期时间戳(毫秒,自 epoch 起) email?: string; // 用户邮箱 projectId?: string; // Google Cloud 项目 ID }; ``` ### 令牌刷新 凭据包含一个刷新令牌,可在当前访问令牌过期时用于获取新的访问令牌。过期时间设置了 5 分钟的缓冲区以防止竞态条件。 --- ## 模型列表获取 ### 获取可用模型 ```typescript const BASE_URL = "https://cloudcode-pa.googleapis.com"; async function fetchAvailableModels( accessToken: string, projectId: string ): Promise { const headers = { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", "User-Agent": "antigravity", "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1", }; const response = await fetch( `${BASE_URL}/v1internal:fetchAvailableModels`, { method: "POST", headers, body: JSON.stringify({ project: projectId }), } ); const data = await response.json(); // 返回带有配额信息的模型 return Object.entries(data.models).map(([modelId, modelInfo]) => ({ id: modelId, displayName: modelInfo.displayName, quotaInfo: { remainingFraction: modelInfo.quotaInfo?.remainingFraction, resetTime: modelInfo.quotaInfo?.resetTime, isExhausted: modelInfo.quotaInfo?.isExhausted, }, })); } ``` ### 响应格式 ```typescript type FetchAvailableModelsResponse = { models?: Record; }; ``` --- ## 用量追踪 ### 获取用量数据 ```typescript export async function fetchAntigravityUsage( token: string, timeoutMs: number ): Promise { // 1. 获取额度和计划信息 const loadCodeAssistRes = await fetch( `${BASE_URL}/v1internal:loadCodeAssist`, { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ metadata: { ideType: "ANTIGRAVITY", platform: "PLATFORM_UNSPECIFIED", pluginType: "GEMINI", }, }), } ); // 提取额度信息 const { availablePromptCredits, planInfo, currentTier } = data; // 2. 获取模型配额 const modelsRes = await fetch( `${BASE_URL}/v1internal:fetchAvailableModels`, { method: "POST", headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify({ project: projectId }), } ); // 构建用量窗口 return { provider: "google-antigravity", displayName: "Google Antigravity", windows: [ { label: "Credits", usedPercent: calculateUsedPercent(available, monthly) }, // 各模型配额... ], plan: currentTier?.name || planType, }; } ``` ### 用量响应结构 ```typescript type ProviderUsageSnapshot = { provider: "google-antigravity"; displayName: string; windows: UsageWindow[]; plan?: string; error?: string; }; type UsageWindow = { label: string; // "Credits" 或模型 ID usedPercent: number; // 0-100 resetAt?: number; // 配额重置的时间戳 }; ``` --- ## 提供商插件结构 ### 插件定义 ```typescript const antigravityPlugin = { id: "google-antigravity-auth", name: "Google Antigravity Auth", description: "OAuth flow for Google Antigravity (Cloud Code Assist)", configSchema: emptyPluginConfigSchema(), register(api: PicoClawPluginApi) { api.registerProvider({ id: "google-antigravity", label: "Google Antigravity", docsPath: "/providers/models", aliases: ["antigravity"], auth: [ { id: "oauth", label: "Google OAuth", hint: "PKCE + localhost callback", kind: "oauth", run: async (ctx: ProviderAuthContext) => { // OAuth 实现在此处 }, }, ], }); }, }; ``` ### ProviderAuthContext ```typescript type ProviderAuthContext = { config: PicoClawConfig; agentDir?: string; workspaceDir?: string; prompter: WizardPrompter; // UI 提示/通知 runtime: RuntimeEnv; // 日志等 isRemote: boolean; // 是否在远程运行 openUrl: (url: string) => Promise; // 浏览器打开器 oauth: { createVpsAwareHandlers: Function; }; }; ``` ### ProviderAuthResult ```typescript type ProviderAuthResult = { profiles: Array<{ profileId: string; credential: AuthProfileCredential; }>; configPatch?: Partial; defaultModel?: string; notes?: string[]; }; ``` --- ## 集成要求 ### 1. 所需环境/依赖 - Go ≥ 1.25 - PicoClaw 代码库(`pkg/providers/` 和 `pkg/auth/`) - `crypto` 和 `net/http` 标准库包 ### 2. API 调用所需的请求头 ```typescript const REQUIRED_HEADERS = { "Authorization": `Bearer ${accessToken}`, "Content-Type": "application/json", "User-Agent": "antigravity", // 或 "google-api-nodejs-client/9.15.1" "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1", }; // 对于 loadCodeAssist 调用,还需包含: const CLIENT_METADATA = { ideType: "ANTIGRAVITY", // 或 "IDE_UNSPECIFIED" platform: "PLATFORM_UNSPECIFIED", pluginType: "GEMINI", }; ``` ### 3. 模型 Schema 清理 Antigravity 使用兼容 Gemini 的模型,因此工具 schema 必须进行清理: ```typescript const GOOGLE_SCHEMA_UNSUPPORTED_KEYWORDS = new Set([ "patternProperties", "additionalProperties", "$schema", "$id", "$ref", "$defs", "definitions", "examples", "minLength", "maxLength", "minimum", "maximum", "multipleOf", "pattern", "format", "minItems", "maxItems", "uniqueItems", "minProperties", "maxProperties", ]); // 发送前清理 schema function cleanToolSchemaForGemini(schema: Record): unknown { // 移除不支持的关键字 // 确保顶层有 type: "object" // 展平 anyOf/oneOf 联合类型 } ``` ### 4. 思维块处理(Claude 模型) 对于 Antigravity 的 Claude 模型,思维块需要特殊处理: ```typescript const ANTIGRAVITY_SIGNATURE_RE = /^[A-Za-z0-9+/]+={0,2}$/; export function sanitizeAntigravityThinkingBlocks( messages: AgentMessage[] ): AgentMessage[] { // 验证思维签名 // 规范化签名字段 // 丢弃未签名的思维块 } ``` --- ## API 端点 ### 认证端点 | 端点 | 方法 | 用途 | |------|------|------| | `https://accounts.google.com/o/oauth2/v2/auth` | GET | OAuth 授权 | | `https://oauth2.googleapis.com/token` | POST | 令牌交换 | | `https://www.googleapis.com/oauth2/v1/userinfo` | GET | 用户信息(邮箱) | ### Cloud Code Assist 端点 | 端点 | 方法 | 用途 | |------|------|------| | `https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist` | POST | 加载项目信息、额度、计划 | | `https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels` | POST | 列出可用模型及配额 | | `https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent?alt=sse` | POST | 聊天流式端点 | **API 请求格式(聊天):** `v1internal:streamGenerateContent` 端点期望一个包装标准 Gemini 请求的信封格式: ```json { "project": "your-project-id", "model": "model-id", "request": { "contents": [...], "systemInstruction": {...}, "generationConfig": {...}, "tools": [...] }, "requestType": "agent", "userAgent": "antigravity", "requestId": "agent-timestamp-random" } ``` **API 响应格式(SSE):** 每条 SSE 消息(`data: {...}`)被包装在 `response` 字段中: ```json { "response": { "candidates": [...], "usageMetadata": {...}, "modelVersion": "...", "responseId": "..." }, "traceId": "...", "metadata": {} } ``` --- ## 配置 ### config.json 配置 ```json { "model_list": [ { "model_name": "gemini-flash", "model": "antigravity/gemini-3-flash", "auth_method": "oauth" } ], "agents": { "defaults": { "model_name": "gemini-flash" } } } ``` ### 认证配置文件存储 认证配置文件存储在 `~/.picoclaw/auth.json` 中: ```json { "credentials": { "google-antigravity": { "access_token": "ya29...", "refresh_token": "1//...", "expires_at": "2026-01-01T00:00:00Z", "provider": "google-antigravity", "auth_method": "oauth", "email": "user@example.com", "project_id": "my-project-id" } } } ``` --- ## 在 PicoClaw 中创建新提供商 PicoClaw 提供商以 Go 包的形式实现,位于 `pkg/providers/` 下。要添加新提供商: ### 分步实现 #### 1. 创建提供商文件 在 `pkg/providers/` 中创建新的 Go 文件: ``` pkg/providers/ └── your_provider.go ``` #### 2. 实现 Provider 接口 你的提供商必须实现 `pkg/providers/types.go` 中定义的 `Provider` 接口: ```go package providers type YourProvider struct { apiKey string apiBase string } func NewYourProvider(apiKey, apiBase, proxy string) *YourProvider { if apiBase == "" { apiBase = "https://api.your-provider.com/v1" } return &YourProvider{apiKey: apiKey, apiBase: apiBase} } func (p *YourProvider) Chat(ctx context.Context, messages []Message, tools []Tool, cb StreamCallback) error { // 实现带流式传输的聊天补全 } ``` #### 3. 在工厂中注册 将你的提供商添加到 `pkg/providers/factory.go` 中的协议分支: ```go case "your-provider": return NewYourProvider(sel.apiKey, sel.apiBase, sel.proxy), nil ``` #### 4. 添加默认配置(可选) 在 `pkg/config/defaults.go` 中添加默认条目: ```go { ModelName: "your-model", Model: "your-provider/model-name", APIKey: "", }, ``` #### 5. 添加认证支持(可选) 如果你的提供商需要 OAuth 或特殊认证,在 `cmd/picoclaw/internal/auth/helpers.go` 中添加分支: ```go case "your-provider": authLoginYourProvider() ``` #### 6. 通过 `config.json` 配置 ```json { "model_list": [ { "model_name": "your-model", "model": "your-provider/model-name", "api_key": "your-api-key", "api_base": "https://api.your-provider.com/v1" } ] } ``` --- ## 测试你的实现 ### CLI 命令 ```bash # 使用提供商进行认证 picoclaw auth login --provider your-provider # 列出模型(用于 Antigravity) picoclaw auth models # 启动网关 picoclaw gateway # 使用指定模型运行代理 picoclaw agent -m "Hello" --model your-model ``` ### 测试用环境变量 ```bash # 覆盖默认模型 export PICOCLAW_AGENTS_DEFAULTS_MODEL=your-model # 覆盖提供商设置 export PICOCLAW_MODEL_LIST='[{"model_name":"your-model","model":"your-provider/model-name","api_key":"..."}]' ``` --- ## 参考资料 - **源文件:** - `pkg/providers/antigravity_provider.go` - Antigravity 提供商实现 - `pkg/auth/oauth.go` - OAuth 流程实现 - `pkg/auth/store.go` - 认证凭据存储(`~/.picoclaw/auth.json`) - `pkg/providers/factory.go` - 提供商工厂和协议路由 - `pkg/providers/types.go` - 提供商接口定义 - `cmd/picoclaw/internal/auth/helpers.go` - 认证 CLI 命令 - **文档:** - `docs/ANTIGRAVITY_USAGE.md` - Antigravity 使用指南 - `docs/migration/model-list-migration.md` - 迁移指南 --- ## 注意事项 1. **Google Cloud 项目:** Antigravity 要求在你的 Google Cloud 项目上启用 Gemini for Google Cloud 2. **配额:** 使用 Google Cloud 项目配额(非独立计费) 3. **模型访问:** 可用模型取决于你的 Google Cloud 项目配置 4. **思维块:** 通过 Antigravity 使用的 Claude 模型需要对带签名的思维块进行特殊处理 5. **Schema 清理:** 工具 schema 必须清理以移除不支持的 JSON Schema 关键字 --- --- ## 常见错误处理 ### 1. 速率限制(HTTP 429) 当项目/模型配额耗尽时,Antigravity 会返回 429 错误。错误响应通常在 `details` 字段中包含 `quotaResetDelay`。 **429 错误示例:** ```json { "error": { "code": 429, "message": "You have exhausted your capacity on this model. Your quota will reset after 4h30m28s.", "status": "RESOURCE_EXHAUSTED", "details": [ { "@type": "type.googleapis.com/google.rpc.ErrorInfo", "metadata": { "quotaResetDelay": "4h30m28.060903746s" } } ] } } ``` ### 2. 空响应(受限模型) 某些模型可能出现在可用模型列表中,但返回空响应(200 OK 但 SSE 流为空)。这通常发生在当前项目没有权限使用的预览版或受限模型上。 **处理方式:** 将空响应视为错误,通知用户该模型可能对其项目受限或无效。 --- ## 故障排除 ### "Token expired"(令牌已过期) - 刷新 OAuth 令牌:`picoclaw auth login --provider antigravity` ### "Gemini for Google Cloud is not enabled"(Gemini for Google Cloud 未启用) - 在 Google Cloud Console 中启用该 API ### "Project not found"(项目未找到) - 确保你的 Google Cloud 项目已启用必要的 API - 检查认证过程中项目 ID 是否正确获取 ### 模型未出现在列表中 - 验证 OAuth 认证是否成功完成 - 检查认证配置文件存储:`~/.picoclaw/auth.json` - 重新运行 `picoclaw auth login --provider antigravity`