mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
e304dce40e
- Replace config/config.example.json with V3 format (version: 3, api_keys array, channel_list) - Update config-versioning.md: version 2→3, ConfigV2→ConfigV3, CurrentVersion=3 - Update 7 project READMEs: api_key→api_keys, add version: 3 to quick-start examples - Update 12 security docs (ANTIGRAVITY_AUTH + credential_encryption): api_key→api_keys - Update provider-refactoring.md: api_key→api_keys in all config examples - Update security_configuration.md: api_key→api_keys in Before example - Update 3 channel docs: channels→channel_list in JSON examples
810 lines
20 KiB
Markdown
810 lines
20 KiB
Markdown
> 返回 [README](../project/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<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;
|
||
}
|
||
```
|
||
|
||
**项目 ID(API 调用必需):**
|
||
```typescript
|
||
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"; // 默认回退值
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 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<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,
|
||
},
|
||
}));
|
||
}
|
||
```
|
||
|
||
### 响应格式
|
||
|
||
```typescript
|
||
type FetchAvailableModelsResponse = {
|
||
models?: Record<string, {
|
||
displayName?: string;
|
||
quotaInfo?: {
|
||
remainingFraction?: number | string;
|
||
resetTime?: string; // ISO 8601 时间戳
|
||
isExhausted?: boolean;
|
||
};
|
||
}>;
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 用量追踪
|
||
|
||
### 获取用量数据
|
||
|
||
```typescript
|
||
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,
|
||
};
|
||
}
|
||
```
|
||
|
||
### 用量响应结构
|
||
|
||
```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<void>; // 浏览器打开器
|
||
oauth: {
|
||
createVpsAwareHandlers: Function;
|
||
};
|
||
};
|
||
```
|
||
|
||
### ProviderAuthResult
|
||
|
||
```typescript
|
||
type ProviderAuthResult = {
|
||
profiles: Array<{
|
||
profileId: string;
|
||
credential: AuthProfileCredential;
|
||
}>;
|
||
configPatch?: Partial<PicoClawConfig>;
|
||
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<string, unknown>): 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_keys": ["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_keys":["..."]}]'
|
||
```
|
||
|
||
---
|
||
|
||
## 参考资料
|
||
|
||
- **源文件:**
|
||
- `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`
|