PicoClaw Antigravity 认证与接入全指南:OAuth 2.0 + PKCE 登录、模型管理与自定义 Provider 实现
【免费下载链接】picoclawTiny, Fast, and Deployable anywhere — automate the mundane, unleash your creativity项目地址: https://gitcode.com/gh_mirrors/pi/picoclaw
本指南以仓库文档 ANTIGRAVITY_AUTH.pt-br.md 为主体,结合 pkg/auth、pkg/providers/oauth/antigravity_provider.go 等源码,系统讲解 PicoClaw 中 Google Antigravity(Cloud Code Assist)的认证机制、令牌生命周期、模型与用量接口,以及如何从零扩展一个全新的模型 Provider。读完本文,你将能够独立完成 Antigravity 的 OAuth 登录配置、模型列表查询与排障,并掌握 PicoClaw 插件化 Provider 的完整实现路径。
1. 概述:Antigravity 在 PicoClaw 中的角色
Antigravity(Google Cloud Code Assist)是 Google 支持的 AI 模型服务,通过 Google 云基础设施提供 Claude Opus 4.6、Gemini 系列等模型的访问能力。在 PicoClaw 中,Antigravity 被实现为一个完整的模型 Provider:它以OAuth 2.0 + PKCE完成用户认证,通过cloudcode-pa.googleapis.com的v1internal系列接口获取项目信息、模型列表、配额与流式生成能力。
从仓库源码结构看,Antigravity 的实现横跨两个核心包:
- pkg/auth:OAuth 流程、PKCE 生成、令牌刷新与凭据存储(
auth.json); - pkg/providers/oauth/antigravity_provider.go:Provider 的
Chat流式调用、SSE 解析、模型列表拉取与错误处理。
下文将沿着"认证 → 令牌 → 模型 → 用量 → 接入 → 扩展"的链路逐层展开。
2. 认证流程:OAuth 2.0 with PKCE
2.1 整体时序
Antigravity 使用OAuth 2.0 授权码模式 + 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──── │ │ └─────────────┘ └─────────────────┘具体而言,PKCE 用于防止授权码被拦截后重放:客户端先生成随机的code_verifier,将它的 SHA-256 摘要code_challenge附在授权 URL 上;换取令牌时必须提交原始的code_verifier,服务器据此校验发起者身份。由于 Google OAuth 使用机密客户端(confidential client)模式,client_secret也会在令牌交换阶段一并提交。
2.2 步骤一:生成 PKCE 参数
function generatePkce(): { verifier: string; challenge: string } { const verifier = randomBytes(32).toString("hex"); const challenge = createHash("sha256").update(verifier).digest("base64url"); return { verifier, challenge }; }在 PicoClaw 的 Go 实现中,对应逻辑位于 pkg/auth/pkce.go:它使用crypto/rand生成 64 字节随机数并以 base64url 编码为 verifier,再对 verifier 做 SHA-256 后同样以 base64url 编码为 challenge:
buf := make([]byte, 64) rand.Read(buf) verifier := base64.RawURLEncoding.EncodeToString(buf) hash := sha256.Sum256([]byte(verifier)) challenge := base64.RawURLEncoding.EncodeToString(hash[:])2.3 步骤二:构造授权 URL
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(); }需要申请的OAuth 作用域(Scopes):
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", ];仓库中 pkg/auth/oauth.go 的GoogleAntigravityOAuthConfig()定义了完全一致的作用域列表,并固定使用本地回调端口51121。源码还特别针对 Google 追加了两个参数(buildAuthorizeURL):access_type=offline(获取可用于刷新令牌的 refresh_token)与prompt=consent(强制每次授权弹窗,确保返回 refresh token)。同时生成随机的state参数(GenerateState()生成 32 字节 hex 串)用于 CSRF 防护——回调时若state不匹配会直接返回 "state mismatch" 错误。
2.4 步骤三:处理 OAuth 回调
PicoClaw 支持两种回调模式(对应 LoginBrowserWithOptions 的并行等待逻辑):
- 自动模式(本地开发机):在本地端口 51121 启动 HTTP 回调服务器,浏览器完成授权后 Google 将用户重定向到
http://localhost:<port>/auth/callback,回调处理器校验state并从 query 中提取code(参考 oauthCallbackHandler)。若端口被占用,源码会自动尝试其他端口并以实际端口重建 redirect_uri。 - 手动模式(远程/无图形界面/WSL2):终端打印授权 URL,用户在其他设备浏览器完成登录后,把浏览器地址栏中的完整重定向 URL(或仅
code参数)粘贴回终端。源码会尝试解析 URL 并从?code=中提取授权码,粘贴裸 code 也能直接工作。
function shouldUseManualOAuthFlow(isRemote: boolean): boolean { return isRemote || isWSL2Sync(); }在命令行中,无浏览器环境可通过--no-browser强制走手动模式。此外整个登录等待有5 分钟超时,超时自动失败。
2.5 步骤四:用授权码交换令牌
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 分钟 buffer,用于避免令牌在临界时刻过期导致的竞态。PicoClaw 的 Go 版本ExchangeCodeForTokens位于 pkg/auth/oauth.go,会根据令牌端点域名自动判定 provider 为google-antigravity,并在响应缺少refresh_token时保留旧值。
2.6 步骤五:拉取用户附加信息
用户邮箱:
async function fetchUserEmail(accessToken: string): Promise<string | undefined> { 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; }PicoClaw 命令行在 cmd/picoclaw/internal/auth/helpers.go 中通过https://www.googleapis.com/oauth2/v2/userinfo获取邮箱并写入凭据的Email字段;获取失败仅打印警告,不阻断登录。
项目 ID(API 调用必需):
async function fetchProjectId(accessToken: string): Promise<string> { 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"; // Valor padrão de fallback }该逻辑在 Go 侧对应 FetchAntigravityProjectID,它解析响应 JSON 的cloudaicompanionProject字段。若拉取失败,createAntigravityTokenSource 会回退到与 OpenCode 一致的默认项目 IDrising-fact-p41fc。
3. OAuth 实现细节与双流程模式
3.1 客户端凭据
注意:以下客户端凭据以 base64 编码内置于源码中,用于与 pi-ai / OpenCode 生态保持同步:
const decode = (s: string) => Buffer.from(s, "base64").toString(); const CLIENT_ID = decode( "MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==" ); const CLIENT_SECRET = decode("R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY=");Go 实现见 GoogleAntigravityOAuthConfig,通过decodeBase64解出同样的凭据,并配置Issuer: https://accounts.google.com/o/oauth2/v2、TokenURL: https://oauth2.googleapis.com/token、回调端口51121。
3.2 两种 OAuth 流程模式
- 自动流程(本地带浏览器):自动唤起默认浏览器(OpenBrowser 按 darwin/linux/windows 分别调用
open、xdg-open、cmd /c start),本地回调服务器捕获重定向,首次授权后无需额外交互; - 手动流程(远程 / 无图形界面 / WSL2):打印 URL 供手动复制,用户在外置浏览器完成认证后,将完整重定向 URL 粘贴回终端。
function shouldUseManualOAuthFlow(isRemote: boolean): boolean { return isRemote || isWSL2Sync(); }两种模式共用同一套"浏览器回调 + 终端粘贴"双通道等待机制(select同时监听回调通道与 stdin),任一通道先完成即胜出,显著提升无头服务器的接入体验。
4. 令牌管理:凭据结构、刷新与持久化
4.1 认证凭据结构
type OAuthCredential = { type: "oauth"; provider: "google-antigravity"; access: string; // 访问令牌 refresh: string; // 刷新令牌 expires: number; // 过期时间戳(epoch 毫秒) email?: string; // 用户邮箱 projectId?: string; // Google Cloud 项目 ID };Go 侧对应的结构体是 pkg/auth/store.go 的AuthCredential,JSON 字段为access_token、refresh_token、account_id、expires_at(time.Time)、provider、auth_method、email、project_id。
4.2 刷新机制与 5 分钟缓冲
凭据包含 refresh_token,可在 access_token 过期时换取新令牌。过期时间在生成时已扣除 5 分钟缓冲,避免竞态;Go 侧再叠加两道防线(store.go):
func (c *AuthCredential) IsExpired() bool { ... } // 已过期 func (c *AuthCredential) NeedsRefresh() bool { return time.Now().Add(5 * time.Minute).After(c.ExpiresAt) // 剩余不足 5 分钟即视为待刷新 }实际刷新发生在两个位置:CLI 的auth models命令在查询前主动刷新(helpers.go),以及 Provider 每次 Chat 调用时通过 createAntigravityTokenSource 惰性刷新——NeedsRefresh()为真且存在 refresh_token 时,调用auth.RefreshAccessToken并用新令牌覆盖写入存储。刷新实现见 RefreshAccessToken,会保留刷新响应中缺失的 Email、ProjectID 等字段。
4.3 凭据存储:~/.picoclaw/auth.json
认证配置保存在~/.picoclaw/auth.json(路径由 config.GetHome 推导,见 authFilePath):
{ "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" } } }存储层(pkg/auth/store.go)具备两项关键健壮性设计:
- 别名归一化:
canonicalProvider将antigravity归一化为google-antigravity,LoadStore还会合并同一 provider 下的重复条目、优先保留未过期且使用规范名的凭据(normalizeStore); - 原子写入:
SaveStore使用 pkg/fileutil 的WriteFileAtomic以0600权限写入,并对闪存介质显式 sync,避免掉电损坏凭据文件。
5. 模型列表拉取
5.1 接口调用
const BASE_URL = "https://cloudcode-pa.googleapis.com"; async function fetchAvailableModels( accessToken: string, projectId: string ): Promise<Model[]> { 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, }, })); }5.2 响应格式
type FetchAvailableModelsResponse = { models?: Record<string, { displayName?: string; quotaInfo?: { remainingFraction?: number | string; resetTime?: string; // ISO 8601 时间戳 isExhausted?: boolean; }; }>; };Go 侧实现为 FetchAntigravityModels,请求体为{"project": projectID},将models映射解析为AntigravityModelInfo{ID, DisplayName, IsExhausted}。源码还做了一件贴心的事:兜底补齐——无论接口返回与否,都会确保gemini-3-flash与gemini-3-flash-preview出现在列表中,避免默认模型不可用时列表为空。
CLI 侧,picoclaw auth models命令(authModelsCmd)负责在终端以✓/✗ (quota exhausted)标记每个模型的可用状态,方便用户一眼判断哪些模型配额已耗尽。
6. 用量追踪
6.1 拉取用量数据
export async function fetchAntigravityUsage( token: string, timeoutMs: number ): Promise<ProviderUsageSnapshot> { // 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, }; }6.2 用量响应结构
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; // 配额重置时间戳 };用量数据汇总自两个端点:loadCodeAssist提供整体积分(availablePromptCredits)、套餐(planInfo)与当前档位(currentTier),fetchAvailableModels提供各模型独立配额。二者合并后可同时呈现"总积分消耗"与"单模型配额"两级视角。
7. Provider 插件结构与扩展接口
7.1 插件定义
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 }, }, ], }); }, };7.2 ProviderAuthContext
type ProviderAuthContext = { config: PicoClawConfig; agentDir?: string; workspaceDir?: string; prompter: WizardPrompter; // UI 提示/通知 runtime: RuntimeEnv; // 日志等 isRemote: boolean; // 是否远程执行 openUrl: (url: string) => Promise<void>; // 浏览器打开器 oauth: { createVpsAwareHandlers: Function; }; };7.3 ProviderAuthResult
type ProviderAuthResult = { profiles: Array<{ profileId: string; credential: AuthProfileCredential; }>; configPatch?: Partial<PicoClawConfig>; defaultModel?: string; notes?: string[]; };在 Go 仓库中,上述 TS 插件的对应物是 pkg/providers/factory_provider.go 中的协议分发分支:
case "antigravity": return finalizeProviderFromConfig(NewAntigravityProvider(), modelID, cfg)NewAntigravityProvider(antigravity_provider.go)不依赖 API Key,而是注入一个tokenSource闭包:每次调用从auth.json读取凭据、按需刷新、必要时回退默认 project ID,再返回(accessToken, projectID)。底层 HTTP 客户端超时设为 120 秒,适配长流式响应。
8. 集成要求
8.1 环境 / 依赖
- Go ≥ 1.25;
- PicoClaw 代码库(
pkg/providers/与pkg/auth/); - 标准库
crypto与net/http包(PKCE、签名、HTTP 调用所需)。
8.2 必需的 API 请求头
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", };Go 侧实际发送的请求头(antigravity_provider.go)与此完全对齐,并额外增加了Accept: text/event-stream(SSE 必需)以及带版本号的User-Agent: antigravity/1.15.8 linux/amd64。
8.3 模型 Schema 清理(Google/Gemini 兼容)
Antigravity 底层走 Gemini 兼容模型,因此工具 Schema 必须先做清洗,剔除 Gemini 不支持的 JSON Schema 关键字:
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<string, unknown>): unknown { // 移除不支持的关键字 // 确保顶层有 type: "object" // 展平 anyOf/oneOf 联合类型 }PicoClaw 的 Go 实现位于 pkg/providers/common/google_schema.go:SanitizeSchemaForGoogle将 JSON Schema 收敛到 Google/Gemini 函数声明接受的保守子集——解析本地$ref、折叠anyOf/oneOf/allOf组合关键字、剥离高级关键字,并递归限制最大深度 64(maxGeminiSchemaDepth);SanitizeSchemaForGemini作为兼容别名保留。顶层只要含properties就强制补type: "object",确保函数声明格式合法。该清洗可通过配置项ToolSchemaTransform(取值为""关闭 /"simple"开启)启用,见 pkg/providers/tool_schema_transform.go 与 factory_provider.go,并有对应的 tool_schema_transform_test.go 单测覆盖。
8.4 思考块(Thought Blocks)处理(Claude 模型)
经 Antigravity 访问 Claude 模型时,思考块需要特殊处理:
const ANTIGRAVITY_SIGNATURE_RE = /^[A-Za-z0-9+/]+={0,2}$/; export function sanitizeAntigravityThinkingBlocks( messages: AgentMessage[] ): AgentMessage[] { // 校验思考签名 // 规范化签名字段 // 丢弃未签名的思考块 }从源码结构看,antigravity_provider.go 为消息 part 定义了ThoughtSignature与ThoughtSignatureSnake(snake_case 兼容字段)两个签名槽位,发送时两者同时写入;解析响应时用 extractPartThoughtSignature 优先取驼峰、回退蛇形字段。带thought: true标记的 part 会被单独归入ReasoningContent,不混入正文;工具调用的签名则透传给后续轮次,保证多轮工具调用链的签名一致性。
9. API 端点汇总
9.1 认证端点
| 端点 | 方法 | 用途 |
|---|---|---|
https://accounts.google.com/o/oauth2/v2/auth | GET | OAuth 授权 |
https://oauth2.googleapis.com/token | POST | 令牌交换/刷新 |
https://www.googleapis.com/oauth2/v1/userinfo | GET | 用户信息(邮箱) |
9.2 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 | 聊天流式生成 |
9.3 Chat API 请求格式
v1internal:streamGenerateContent期望一个封装标准 Gemini 请求的 envelope:
{ "project": "your-project-id", "model": "model-id", "request": { "contents": [...], "systemInstruction": {...}, "generationConfig": {...}, "tools": [...] }, "requestType": "agent", "userAgent": "antigravity", "requestId": "agent-timestamp-random" }Go 侧的 envelope 构造见 antigravity_provider.go:requestId形如agent-<UnixMilli>-<9位随机串>,requestType固定为"agent";内部request由 buildRequest 将系统消息映射为systemInstruction、用户/助手消息映射为contents(角色user/model)、工具调用映射为functionCall/functionResponsepart,max_tokens与temperature则透传至generationConfig。
9.4 SSE 响应格式
每条 SSE 消息(data: {...})都封装在response字段中:
{ "response": { "candidates": [...], "usageMetadata": {...}, "modelVersion": "...", "responseId": "..." }, "traceId": "...", "metadata": {} }Go 的 SSE 解析器 parseSSEResponse 逐行扫描data:前缀(遇[DONE]终止),解析response.candidates[].content.parts:普通文本拼入Content,thought: true拼入ReasoningContent,functionCall转为ToolCall(并携带签名),usageMetadata折算为 token 用量。finishReason会被归一化:MAX_TOKENS→length,有工具调用 →tool_calls,其余 →stop。若最终文本为空且无工具调用,则判定为受限模型的空响应错误——这正是下文错误处理一节要展开的场景。
10. 配置
10.1 config.json 配置
{ "model_list": [ { "model_name": "gemini-flash", "model": "antigravity/gemini-3-flash", "auth_method": "oauth" } ], "agents": { "defaults": { "model_name": "gemini-flash" } } }配置仓库中的默认模型入口见 pkg/config/defaults.go,model字段的 provider 前缀(antigravity/或google-antigravity/)会被 factory_provider.go 路由到NewAntigravityProvider()。实际调用时 Chat 还会再做一次前缀剥离与默认模型兜底:模型为空或为antigravity/google-antigravity时,回落到gemini-3-flash。
10.2 认证凭据存储位置
认证凭据统一保存在~/.picoclaw/auth.json,结构如第 4.3 节所示。该文件与config.json分离,令牌绝不写入模型配置,降低误提交密钥的风险。
11. 在 PicoClaw 中创建全新 Provider
PicoClaw 的 Provider 以 Go 包形式位于pkg/providers/。接入新 Provider 的完整步骤:
11.1 创建 Provider 文件
pkg/providers/ └── your_provider.go11.2 实现 Provider 接口
实现定义于pkg/providers/types.go的Provider接口:
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 { // 实现带流式的对话补全 }11.3 在 Factory 中注册
在pkg/providers/factory.go的协议 switch 中注册(实际分发逻辑在 factory_provider.go,Antigravity 分支见第 7.3 节):
case "your-provider": return NewYourProvider(sel.apiKey, sel.apiBase, sel.proxy), nil11.4 添加默认配置(可选)
在pkg/config/defaults.go增加默认条目:
{ ModelName: "your-model", Model: "your-provider/model-name", APIKey: "", },11.5 添加认证支持(可选)
若 Provider 需要 OAuth 或特殊认证,在cmd/picoclaw/internal/auth/helpers.go增加分支(Antigravity 分支见 authLoginGoogleAntigravity,登录成功后会拉取邮箱与项目 ID、把默认模型切换为gemini-flash并自动改写config.json的model_list):
case "your-provider": authLoginYourProvider()11.6 通过 config.json 配置
{ "model_list": [ { "model_name": "your-model", "model": "your-provider/model-name", "api_keys": ["your-api-key"], "api_base": "https://api.your-provider.com/v1" } ] }12. 验证你的实现
12.1 CLI 命令
# 使用某 Provider 登录认证 picoclaw auth login --provider your-provider # 列出模型(针对 Antigravity) picoclaw auth models # 启动网关 picoclaw gateway # 使用指定模型运行 Agent picoclaw agent -m "Hello" --model your-modelAntigravity 相关命令的完整集合:
picoclaw auth login --provider google-antigravity # 或别名 antigravity picoclaw auth login --provider google-antigravity --no-browser # 无头服务器手动流程 picoclaw auth models # 列出带配额状态的模型 picoclaw auth status # 查看各 Provider 登录状态/过期时间/用量 picoclaw auth logout --provider google-antigravity # 登出并清理凭据其中--no-browser与auth models均对应源码 helpers.go 中的实现;auth status会逐一显示每个凭据的Method、Status(active / needs refresh / expired)、Email、Project 与过期时间。
12.2 测试用环境变量
# 覆盖默认模型 export PICOCLAW_AGENTS_DEFAULTS_MODEL=your-model # 覆盖 Provider 配置 export PICOCLAW_MODEL_LIST='[{"model_name":"your-model","model":"your-provider/model-name","api_keys":["..."]}]'13. 常见错误处理
13.1 限流(HTTP 429)
当项目/模型配额耗尽时,Antigravity 返回 429 错误,错误响应的details中通常携带quotaResetDelay:
{ "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" } } ] } }Go 侧的 parseAntigravityError 会遍历details提取quotaResetDelay,拼入错误信息返回,方便用户在日志中直接看到"还需等待多久",而非只有抽象的 429。
13.2 空响应(受限模型)
部分模型会出现在可用列表里,但实际返回 200 OK + 空 SSE 流——通常是该模型处于预览/受限状态,当前项目无权限使用。
处理方式:将空响应视为错误,提示用户该模型可能对其项目受限或无效。Go 实现见 Chat:Content == ""且无工具调用时报错antigravity: model returned an empty response (this model might be invalid or restricted)。
14. 排障清单
| 现象 | 处理方式 |
|---|---|
| "Token expired"(令牌过期) | 重新登录刷新令牌:picoclaw auth login --provider antigravity |
| "Gemini for Google Cloud is not enabled" | 在 Google Cloud Console 中启用对应 API |
| "Project not found"(项目不存在) | 确认 Google Cloud 项目已启用所需 API;确认登录时正确获取了项目 ID |
| 模型不出现在列表 | 确认 OAuth 登录成功;检查凭据存储~/.picoclaw/auth.json;重新执行picoclaw auth login --provider antigravity |
补充两条源码级排查建议:凭据若长期未刷新,可先执行picoclaw auth status查看NeedsRefresh状态;若project_id为空,auth models会明确报错"no project id stored. Try logging in again",此时重新登录即可自动补齐。
15. 注意事项总结
- Google Cloud 项目:Antigravity 要求在你的 Google Cloud 项目中启用Gemini for Google Cloud;
- 配额:使用 Google Cloud 项目配额(无单独计费);
- 模型访问:可用模型取决于 Google Cloud 项目的配置;
- 思考块:经 Antigravity 访问 Claude 模型时,思考块需要携带签名并做专门处理;
- Schema 清洗:工具 Schema 必须剔除 Gemini 不支持的 JSON Schema 关键字后再发送。
16. 参考资源
源码文件(以仓库当前实现为准):
- pkg/providers/oauth/antigravity_provider.go — Antigravity Provider 实现(原文档引用的
pkg/providers/antigravity_provider.go在当前仓库中已移入oauth子目录) - pkg/auth/oauth.go — OAuth 流程实现(授权 URL、回调、令牌交换与刷新)
- pkg/auth/pkce.go — PKCE 参数生成
- pkg/auth/store.go — 认证凭据存储(
~/.picoclaw/auth.json) - pkg/providers/factory_provider.go — Provider Factory 与协议路由
- pkg/providers/types.go — Provider 接口定义
- pkg/providers/common/google_schema.go — Google/Gemini Schema 清洗
- cmd/picoclaw/internal/auth/helpers.go — CLI 认证命令(login/logout/status/models)
文档:
- docs/guides/ANTIGRAVITY_USAGE.md — Antigravity 使用指南(原文档引用的
docs/ANTIGRAVITY_USAGE.md在当前仓库中位于docs/guides/下,另有.zh.md中文版) - docs/migration/model-list-migration.md — 模型列表迁移指南
- docs/security/ANTIGRAVITY_AUTH.zh.md — 本文的简体中文姊妹篇(若存在)
【免费下载链接】picoclawTiny, Fast, and Deployable anywhere — automate the mundane, unleash your creativity项目地址: https://gitcode.com/gh_mirrors/pi/picoclaw
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考