mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
37 KiB
37 KiB
Antigravity Provider 插件文档
版本: 1.0.0 仓库:
aiclientproxy/antigravity-provider类型: OAuth Provider Plugin
一、概述
1.1 插件简介
Antigravity Provider 是 Lime 的 OAuth Provider 插件,用于对接 Google 内部 Gemini CLI 服务(Antigravity)。它支持 动态协议选择:根据模型类型自动选择输出协议(Claude 模型 → Anthropic,Gemini 模型 → Gemini)。
1.2 核心能力
| 能力 | 说明 |
|---|---|
| 动态协议 | claude-* → Anthropic SSE,gemini-* → Gemini 协议 |
| 多模型支持 | Gemini 3 Pro、Gemini 2.5 Flash、Claude Sonnet/Opus |
| Google OAuth | 使用 Google 账户认证 |
| 安全设置 | 自动附加 Safety Settings(关闭内容过滤) |
| 思维链 | 支持 reasoning_effort 配置 |
| 流式响应 | 真实端到端流式传输 |
1.3 支持的模型
| 用户模型名 | 内部 API 名称 | 底层模型 | 输出协议 |
|---|---|---|---|
gemini-3-pro-preview |
gemini-3-pro-high |
Gemini | Gemini |
gemini-3-pro-image-preview |
gemini-3-pro-image |
Gemini | Gemini |
gemini-3-flash-preview |
gemini-3-flash |
Gemini | Gemini |
gemini-2.5-flash |
gemini-2.5-flash |
Gemini | Gemini |
gemini-2.5-computer-use-preview-10-2025 |
rev19-uic3-1p |
Gemini | Gemini |
gemini-claude-sonnet-4-5 |
claude-sonnet-4-5 |
Claude | Anthropic |
gemini-claude-sonnet-4-5-thinking |
claude-sonnet-4-5-thinking |
Claude | Anthropic |
gemini-claude-opus-4-5-thinking |
claude-opus-4-5-thinking |
Claude | Anthropic |
二、插件架构
2.1 项目结构
antigravity-provider/
├── plugin/
│ ├── plugin.json # 插件元数据
│ └── config.json # 默认配置
│
├── src-tauri/src/ # 后端 Rust 代码
│ ├── lib.rs # 插件入口
│ ├── commands.rs # Tauri 命令
│ ├── provider.rs # AntigravityProvider 核心实现
│ ├── credentials.rs # 凭证管理
│ ├── oauth.rs # Google OAuth 流程
│ ├── token_refresh.rs # Token 刷新
│ ├── models.rs # 模型定义和别名映射
│ ├── safety_settings.rs # 安全设置
│ └── converter/ # 协议转换
│ ├── mod.rs
│ ├── openai_to_antigravity.rs # OpenAI → Antigravity
│ ├── anthropic_to_antigravity.rs # Anthropic → Antigravity
│ ├── antigravity_to_anthropic.rs # Antigravity → Anthropic SSE
│ └── antigravity_to_gemini.rs # Antigravity → Gemini
│
├── src/ # 前端 React UI
│ ├── index.tsx # 插件 UI 入口
│ ├── components/
│ │ ├── CredentialList.tsx # 凭证列表
│ │ ├── CredentialCard.tsx # 凭证卡片
│ │ ├── AntigravityForm.tsx # 凭证添加表单
│ │ ├── OAuthLogin.tsx # Google OAuth 登录
│ │ └── SettingsPanel.tsx # 插件设置
│ └── types/
│ └── index.ts # 类型定义
│
└── .github/
└── workflows/
└── release.yml # 自动构建发布
2.2 plugin.json
{
"name": "antigravity-provider",
"version": "1.0.0",
"description": "Antigravity (Google Gemini CLI) OAuth Provider - 支持 Gemini 和 Claude 模型",
"author": "Lime Team",
"homepage": "https://github.com/aiclientproxy/antigravity-provider",
"license": "MIT",
"plugin_type": "oauth_provider",
"entry": "antigravity-provider-cli",
"min_lime_version": "1.0.0",
"provider": {
"id": "antigravity",
"display_name": "Antigravity (Gemini CLI)",
"target_protocol": "dynamic",
"protocol_rules": {
"claude-*": "anthropic",
"gemini-*": "gemini"
},
"supported_models": [
"gemini-3-pro-*",
"gemini-2.5-*",
"gemini-claude-*"
],
"auth_types": ["oauth"],
"credential_schema": {
"type": "object",
"properties": {
"access_token": { "type": "string", "title": "Access Token" },
"refresh_token": { "type": "string", "title": "Refresh Token" },
"expiry_date": { "type": "integer", "title": "过期时间戳" },
"project_id": { "type": "string", "title": "Project ID" },
"email": { "type": "string", "title": "Google 邮箱" }
},
"required": ["access_token", "refresh_token"]
}
},
"binary": {
"binary_name": "antigravity-provider-cli",
"github_owner": "aiclientproxy",
"github_repo": "antigravity-provider",
"platform_binaries": {
"macos-arm64": "antigravity-provider-aarch64-apple-darwin",
"macos-x64": "antigravity-provider-x86_64-apple-darwin",
"linux-x64": "antigravity-provider-x86_64-unknown-linux-gnu",
"windows-x64": "antigravity-provider-x86_64-pc-windows-msvc.exe"
},
"checksum_file": "checksums.txt"
},
"ui": {
"surfaces": ["oauth_providers"],
"icon": "Sparkles",
"title": "Antigravity Provider",
"entry": "dist/index.js",
"styles": "dist/styles.css",
"default_width": 900,
"default_height": 700,
"permissions": [
"database:read",
"database:write",
"http:request",
"crypto:encrypt",
"shell:open"
]
}
}
2.3 config.json
{
"enabled": true,
"timeout_ms": 120000,
"settings": {
"api": {
"environment": "daily",
"base_url_daily": "https://daily-cloudcode-pa.sandbox.googleapis.com",
"base_url_autopush": "https://autopush-cloudcode-pa.sandbox.googleapis.com",
"api_version": "v1internal"
},
"safety_settings": {
"harassment": "OFF",
"hate_speech": "OFF",
"sexually_explicit": "OFF",
"dangerous_content": "OFF",
"civic_integrity": "BLOCK_NONE"
},
"token_refresh": {
"auto_refresh": true,
"refresh_skew_seconds": 3000,
"max_retry": 3
},
"reasoning": {
"default_effort": "medium",
"enable_thinking_models": true
}
}
}
三、后端实现
3.1 核心数据结构
凭证结构
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AntigravityCredentials {
pub access_token: Option<String>,
pub refresh_token: Option<String>,
pub token_type: Option<String>,
pub expiry_date: Option<i64>, // 毫秒时间戳
pub expire: Option<String>, // RFC3339 格式
pub scope: Option<String>,
pub last_refresh: Option<String>, // RFC3339 格式
pub cred_type: String, // 默认: "antigravity"
pub expires_in: Option<i64>, // 有效期(秒)
pub timestamp: Option<i64>, // 获取时间(毫秒)
pub enable: Option<bool>,
pub project_id: Option<String>,
pub email: Option<String>,
}
模型别名映射
/// 用户友好模型名 → 内部 API 模型名
pub fn map_model_name(model: &str) -> String {
let mappings = [
("gemini-2.5-computer-use-preview-10-2025", "rev19-uic3-1p"),
("gemini-3-pro-image-preview", "gemini-3-pro-image"),
("gemini-3-pro-preview", "gemini-3-pro-high"),
("gemini-3-flash-preview", "gemini-3-flash"),
("gemini-2.5-flash", "gemini-2.5-flash"),
("gemini-claude-sonnet-4-5", "claude-sonnet-4-5"),
("gemini-claude-sonnet-4-5-thinking", "claude-sonnet-4-5-thinking"),
("gemini-claude-opus-4-5-thinking", "claude-opus-4-5-thinking"),
];
for (from, to) in mappings {
if model.contains(from) {
return to.to_string();
}
}
model.to_string()
}
3.2 动态协议选择
/// 根据模型确定输出协议
pub fn determine_output_protocol(model: &str) -> OutputProtocol {
// Claude 模型 → Anthropic 协议
if model.contains("claude") {
return OutputProtocol::Anthropic;
}
// Gemini 模型 → Gemini 协议
OutputProtocol::Gemini
}
#[derive(Debug, Clone)]
pub enum OutputProtocol {
Anthropic, // Claude Code 使用
Gemini, // Gemini 客户端使用
}
3.3 OAuth 配置
/// OAuth 2.0 配置(Google)
/// 必须通过环境变量配置:
/// - ANTIGRAVITY_OAUTH_CLIENT_ID
/// - ANTIGRAVITY_OAUTH_CLIENT_SECRET
///
/// 获取方式:
/// 1. 使用 Antigravity CLI 的默认凭据
/// 2. 从 Google Cloud Console 创建 OAuth 2.0 客户端
fn oauth_client_id() -> String {
std::env::var("ANTIGRAVITY_OAUTH_CLIENT_ID")
.expect("ANTIGRAVITY_OAUTH_CLIENT_ID environment variable must be set")
}
fn oauth_client_secret() -> String {
std::env::var("ANTIGRAVITY_OAUTH_CLIENT_SECRET")
.expect("ANTIGRAVITY_OAUTH_CLIENT_SECRET environment variable must be set")
}
pub const OAUTH_SCOPES: &[&str] = &[
"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",
];
pub const OAUTH_AUTH_URL: &str = "https://accounts.google.com/o/oauth2/auth";
pub const OAUTH_TOKEN_URL: &str = "https://oauth2.googleapis.com/token";
3.4 Token 刷新
impl AntigravityProvider {
/// Token 刷新时间偏移(提前 50 分钟刷新)
const REFRESH_SKEW: i64 = 3000; // 秒
/// 检查 Token 是否需要刷新
pub fn needs_refresh(&self, credential: &AntigravityCredentials) -> bool {
let now = Utc::now().timestamp();
// 优先使用 expiry_date(毫秒时间戳)
if let Some(expiry_date) = credential.expiry_date {
let expiry_secs = expiry_date / 1000;
return now >= expiry_secs - Self::REFRESH_SKEW;
}
// 使用 expire(RFC3339 字符串)
if let Some(expire) = &credential.expire {
if let Ok(expiry) = DateTime::parse_from_rfc3339(expire) {
return now >= expiry.timestamp() - Self::REFRESH_SKEW;
}
}
// 使用 expires_in + timestamp
if let (Some(expires_in), Some(timestamp)) = (credential.expires_in, credential.timestamp) {
let expiry_secs = timestamp / 1000 + expires_in;
return now >= expiry_secs - Self::REFRESH_SKEW;
}
// 默认需要刷新
true
}
/// 刷新 Token
pub async fn refresh_token(&self, credential: &mut AntigravityCredentials) -> Result<()> {
let refresh_token = credential.refresh_token.as_ref()
.ok_or(Error::MissingRefreshToken)?;
let client_id = oauth_client_id();
let client_secret = oauth_client_secret();
let response = self.http_client
.post(OAUTH_TOKEN_URL)
.form(&[
("client_id", client_id.as_str()),
("client_secret", client_secret.as_str()),
("refresh_token", refresh_token.as_str()),
("grant_type", "refresh_token"),
])
.send()
.await?;
let token_response: TokenResponse = response.json().await?;
// 更新凭证
credential.access_token = Some(token_response.access_token);
credential.expiry_date = Some(Utc::now().timestamp_millis() + token_response.expires_in * 1000);
credential.last_refresh = Some(Utc::now().to_rfc3339());
Ok(())
}
}
3.5 安全设置
/// 默认安全设置(关闭内容过滤)
pub fn default_safety_settings() -> Vec<SafetySetting> {
vec![
SafetySetting {
category: "HARM_CATEGORY_HARASSMENT".to_string(),
threshold: "OFF".to_string(),
},
SafetySetting {
category: "HARM_CATEGORY_HATE_SPEECH".to_string(),
threshold: "OFF".to_string(),
},
SafetySetting {
category: "HARM_CATEGORY_SEXUALLY_EXPLICIT".to_string(),
threshold: "OFF".to_string(),
},
SafetySetting {
category: "HARM_CATEGORY_DANGEROUS_CONTENT".to_string(),
threshold: "OFF".to_string(),
},
SafetySetting {
category: "HARM_CATEGORY_CIVIC_INTEGRITY".to_string(),
threshold: "BLOCK_NONE".to_string(),
},
]
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SafetySetting {
pub category: String,
pub threshold: String,
}
3.6 协议转换
OpenAI → Antigravity
/// 将 OpenAI ChatCompletion 请求转换为 Antigravity 格式
pub fn convert_openai_to_antigravity(
request: &OpenAiRequest,
project_id: Option<&str>,
) -> AntigravityRequest {
// 1. 模型映射
let model = map_model_name(&request.model);
// 2. 消息转换
let contents = convert_messages(&request.messages);
// 3. 系统指令提取
let system_instruction = extract_system_instruction(&request.messages);
// 4. 工具转换
let tools = request.tools.as_ref().map(|t| convert_tools(t));
// 5. 生成配置
let generation_config = GenerationConfig {
max_output_tokens: request.max_tokens,
temperature: request.temperature,
top_p: request.top_p,
candidate_count: Some(1),
stop_sequences: request.stop.clone(),
// 思维链配置
thinking_config: request.reasoning_effort.as_ref().map(|effort| {
ThinkingConfig {
thinking_budget: match effort.as_str() {
"low" => 1024,
"medium" => 4096,
"high" => 16384,
_ => 4096,
},
}
}),
};
AntigravityRequest {
model,
contents,
system_instruction,
tools,
generation_config: Some(generation_config),
safety_settings: Some(default_safety_settings()),
}
}
/// 消息格式转换
fn convert_messages(messages: &[OpenAiMessage]) -> Vec<Content> {
messages
.iter()
.filter(|m| m.role != "system") // 系统消息单独处理
.map(|msg| {
let role = match msg.role.as_str() {
"user" => "user",
"assistant" => "model",
"tool" => "function",
_ => "user",
};
Content {
role: role.to_string(),
parts: convert_content_parts(&msg.content, &msg.tool_calls),
}
})
.collect()
}
Antigravity → Anthropic SSE(Claude 模型)
/// 将 Antigravity 响应转换为 Anthropic SSE 格式
pub struct AntigravityToAnthropicTranslator {
message_id: String,
model: String,
current_index: u32,
input_tokens: u32,
output_tokens: u32,
}
impl AntigravityToAnthropicTranslator {
pub fn translate_chunk(&mut self, chunk: &GeminiStreamChunk) -> Vec<AnthropicSseEvent> {
let mut events = Vec::new();
// 候选内容处理
if let Some(candidates) = &chunk.candidates {
for candidate in candidates {
if let Some(content) = &candidate.content {
for part in &content.parts {
// 文本内容
if let Some(text) = &part.text {
events.push(AnthropicSseEvent::ContentBlockDelta {
index: self.current_index,
delta: Delta::TextDelta { text: text.clone() },
});
}
// 思维内容(thinking models)
if let Some(thought) = &part.thought {
events.push(AnthropicSseEvent::ContentBlockDelta {
index: self.current_index,
delta: Delta::ThinkingDelta { thinking: thought.clone() },
});
}
// 工具调用
if let Some(function_call) = &part.function_call {
events.push(AnthropicSseEvent::ContentBlockStart {
index: self.current_index,
content_block: ContentBlock::ToolUse {
id: Uuid::new_v4().to_string(),
name: function_call.name.clone(),
},
});
}
}
}
// 完成原因
if let Some(finish_reason) = &candidate.finish_reason {
let stop_reason = match finish_reason.as_str() {
"STOP" => "end_turn",
"MAX_TOKENS" => "max_tokens",
"SAFETY" => "content_filter",
"TOOL_CALL" => "tool_use",
_ => "end_turn",
};
events.push(AnthropicSseEvent::MessageDelta {
delta: MessageDelta {
stop_reason: Some(stop_reason.to_string()),
},
usage: Usage {
input_tokens: self.input_tokens,
output_tokens: self.output_tokens,
},
});
}
}
}
// 使用统计
if let Some(usage) = &chunk.usage_metadata {
self.input_tokens = usage.prompt_token_count.unwrap_or(0);
self.output_tokens = usage.candidates_token_count.unwrap_or(0);
}
events
}
}
3.7 API 调用
impl AntigravityProvider {
/// API 基础 URL
fn get_base_url(&self) -> &str {
match self.config.environment.as_str() {
"autopush" => "https://autopush-cloudcode-pa.sandbox.googleapis.com",
_ => "https://daily-cloudcode-pa.sandbox.googleapis.com",
}
}
/// 流式 API 调用
pub async fn call_api_stream(
&self,
request: AntigravityRequest,
credential: &AntigravityCredentials,
) -> Result<impl Stream<Item = Result<GeminiStreamChunk>>> {
let url = format!(
"{}/v1internal/models/{}:streamGenerateContent?alt=sse",
self.get_base_url(),
request.model
);
let response = self.http_client
.post(&url)
.header("Authorization", format!("Bearer {}", credential.access_token.as_ref().unwrap()))
.header("Content-Type", "application/json")
.header("X-Goog-Api-Client", "genai-js/0.21.0")
.json(&request)
.send()
.await?;
// 解析 SSE 流
Ok(parse_sse_stream(response.bytes_stream()))
}
}
四、前端 UI 实现
4.1 插件入口
// src/index.tsx
import { LimePluginSDK } from '@lime/plugin-sdk';
import { CredentialList } from './components/CredentialList';
import { AntigravityForm } from './components/AntigravityForm';
import { SettingsPanel } from './components/SettingsPanel';
interface PluginProps {
sdk: LimePluginSDK;
pluginId: string;
}
export default function AntigravityProviderUI({ sdk, pluginId }: PluginProps) {
const [view, setView] = useState<'list' | 'add' | 'settings'>('list');
const [credentials, setCredentials] = useState<Credential[]>([]);
useEffect(() => {
loadCredentials();
}, []);
const loadCredentials = async () => {
const result = await sdk.database.query<Credential>(
'SELECT * FROM plugin_credentials WHERE plugin_id = ? ORDER BY created_at DESC',
[pluginId]
);
setCredentials(result);
};
return (
<div className="antigravity-provider-ui">
<Header>
<Title>Antigravity Provider</Title>
<Subtitle>支持 Gemini 3 Pro 和 Claude 模型</Subtitle>
<Actions>
<Button onClick={() => setView('add')}>添加凭证</Button>
<Button onClick={() => setView('settings')}>设置</Button>
</Actions>
</Header>
{/* 动态协议说明 */}
<InfoBanner>
<p>此 Provider 支持动态协议选择:</p>
<ul>
<li><strong>gemini-*</strong> 模型 → Gemini 协议输出</li>
<li><strong>claude-*</strong> 模型 → Anthropic 协议输出</li>
</ul>
</InfoBanner>
{view === 'list' && (
<CredentialList
credentials={credentials}
onRefresh={loadCredentials}
sdk={sdk}
/>
)}
{view === 'add' && (
<AntigravityForm
sdk={sdk}
onSuccess={() => {
loadCredentials();
setView('list');
}}
onCancel={() => setView('list')}
/>
)}
{view === 'settings' && (
<SettingsPanel
sdk={sdk}
pluginId={pluginId}
onClose={() => setView('list')}
/>
)}
</div>
);
}
4.2 凭证添加表单
// src/components/AntigravityForm.tsx
type AddMode = 'oauth' | 'file';
interface AntigravityFormProps {
sdk: LimePluginSDK;
onSuccess: () => void;
onCancel: () => void;
}
export function AntigravityForm({ sdk, onSuccess, onCancel }: AntigravityFormProps) {
const [mode, setMode] = useState<AddMode>('oauth');
const [loading, setLoading] = useState(false);
const [projectId, setProjectId] = useState('');
const [name, setName] = useState('');
const handleOAuthLogin = async () => {
setLoading(true);
try {
// 启动 OAuth 流程
const result = await sdk.http.request('/api/antigravity/oauth/start', {
method: 'POST',
body: JSON.stringify({
name: name || undefined,
skipProjectIdFetch: !projectId,
}),
});
// 打开授权 URL
await sdk.shell.open(result.authUrl);
// 等待回调
const credential = await sdk.http.request('/api/antigravity/oauth/callback/wait', {
method: 'POST',
timeout: 120000, // 2 分钟超时
});
sdk.notification.success('OAuth 登录成功');
onSuccess();
} catch (error) {
sdk.notification.error(`登录失败: ${error.message}`);
} finally {
setLoading(false);
}
};
const handleFileImport = async (filePath: string) => {
setLoading(true);
try {
await sdk.http.request('/api/antigravity/credentials/import', {
method: 'POST',
body: JSON.stringify({
credsFilePath: filePath,
projectId: projectId || undefined,
name: name || undefined,
}),
});
sdk.notification.success('凭证导入成功');
onSuccess();
} catch (error) {
sdk.notification.error(`导入失败: ${error.message}`);
} finally {
setLoading(false);
}
};
return (
<div className="antigravity-form">
{/* 模式选择 */}
<Tabs value={mode} onChange={setMode}>
<Tab value="oauth">Google OAuth 登录</Tab>
<Tab value="file">导入凭证文件</Tab>
</Tabs>
{/* 通用配置 */}
<FormField>
<Label>凭证名称(可选)</Label>
<Input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="我的 Antigravity 凭证"
/>
</FormField>
<FormField>
<Label>Project ID(可选)</Label>
<Input
value={projectId}
onChange={(e) => setProjectId(e.target.value)}
placeholder="自动获取或手动输入"
/>
<HelpText>留空将自动从 Google 账户获取</HelpText>
</FormField>
{mode === 'oauth' && (
<OAuthMode
loading={loading}
onLogin={handleOAuthLogin}
/>
)}
{mode === 'file' && (
<FileMode
loading={loading}
onImport={handleFileImport}
/>
)}
<FormActions>
<Button variant="secondary" onClick={onCancel}>取消</Button>
</FormActions>
</div>
);
}
4.3 OAuth 登录模式
// src/components/OAuthLogin.tsx
interface OAuthModeProps {
loading: boolean;
onLogin: () => void;
}
export function OAuthMode({ loading, onLogin }: OAuthModeProps) {
return (
<div className="oauth-mode">
<Alert type="info">
<p>点击下方按钮将打开浏览器进行 Google 账户授权。</p>
<p>授权完成后,凭证将自动添加。</p>
</Alert>
<div className="oauth-scopes">
<h4>请求的权限:</h4>
<ul>
<li>Cloud Platform 访问</li>
<li>用户邮箱和个人资料</li>
<li>Cloud Code 日志</li>
</ul>
</div>
<Button
onClick={onLogin}
loading={loading}
disabled={loading}
icon={<GoogleIcon />}
>
{loading ? '等待授权...' : '使用 Google 账户登录'}
</Button>
</div>
);
}
4.4 凭证卡片
// src/components/CredentialCard.tsx
interface CredentialCardProps {
credential: Credential;
onRefresh: () => void;
onDelete: () => void;
}
export function CredentialCard({ credential, onRefresh, onDelete }: CredentialCardProps) {
const data = JSON.parse(credential.credential_data) as AntigravityCredentials;
const isHealthy = credential.status === 'active';
return (
<Card className={`credential-card ${isHealthy ? 'healthy' : 'unhealthy'}`}>
<CardHeader>
<div className="status-indicator">
<StatusDot status={isHealthy ? 'green' : 'red'} />
<span>{isHealthy ? '健康' : '异常'}</span>
</div>
<Badge variant="primary">Google OAuth</Badge>
</CardHeader>
<CardBody>
<div className="info-row">
<label>名称</label>
<span>{credential.name || '未命名'}</span>
</div>
<div className="info-row">
<label>邮箱</label>
<span>{data.email || '-'}</span>
</div>
<div className="info-row">
<label>Project ID</label>
<span>{data.project_id || '自动'}</span>
</div>
<div className="info-row">
<label>过期时间</label>
<span>
{data.expiry_date
? formatDate(new Date(data.expiry_date))
: '-'
}
</span>
</div>
<div className="info-row">
<label>上次刷新</label>
<span>{data.last_refresh || '-'}</span>
</div>
{/* 支持的模型 */}
<div className="supported-models">
<label>支持的模型</label>
<div className="model-tags">
<Tag>Gemini 3 Pro</Tag>
<Tag>Gemini 2.5 Flash</Tag>
<Tag>Claude Sonnet 4.5</Tag>
<Tag>Claude Opus 4.5</Tag>
</div>
</div>
</CardBody>
<CardFooter>
<Button size="small" onClick={onRefresh}>刷新 Token</Button>
<Button size="small" variant="danger" onClick={onDelete}>删除</Button>
</CardFooter>
</Card>
);
}
4.5 设置面板
// src/components/SettingsPanel.tsx
interface SettingsPanelProps {
sdk: LimePluginSDK;
pluginId: string;
onClose: () => void;
}
export function SettingsPanel({ sdk, pluginId, onClose }: SettingsPanelProps) {
const [config, setConfig] = useState<PluginConfig | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadConfig();
}, []);
const loadConfig = async () => {
const result = await sdk.database.query<{ config: string }>(
'SELECT config FROM plugin_configs WHERE plugin_id = ?',
[pluginId]
);
if (result.length > 0) {
setConfig(JSON.parse(result[0].config));
}
setLoading(false);
};
const saveConfig = async () => {
await sdk.database.execute(
'UPDATE plugin_configs SET config = ? WHERE plugin_id = ?',
[JSON.stringify(config), pluginId]
);
sdk.notification.success('设置已保存');
};
if (loading) return <Loading />;
return (
<div className="settings-panel">
<h3>插件设置</h3>
{/* API 环境 */}
<FormField>
<Label>API 环境</Label>
<Select
value={config?.settings?.api?.environment || 'daily'}
onChange={(value) => setConfig({
...config,
settings: {
...config?.settings,
api: { ...config?.settings?.api, environment: value },
},
})}
>
<Option value="daily">Daily(推荐)</Option>
<Option value="autopush">Autopush</Option>
</Select>
</FormField>
{/* 思维链配置 */}
<FormField>
<Label>默认思维深度</Label>
<Select
value={config?.settings?.reasoning?.default_effort || 'medium'}
onChange={(value) => setConfig({
...config,
settings: {
...config?.settings,
reasoning: { ...config?.settings?.reasoning, default_effort: value },
},
})}
>
<Option value="low">低(1024 tokens)</Option>
<Option value="medium">中(4096 tokens)</Option>
<Option value="high">高(16384 tokens)</Option>
</Select>
<HelpText>影响 thinking 模型的思考预算</HelpText>
</FormField>
{/* 安全设置 */}
<FormField>
<Label>安全设置</Label>
<Checkbox
checked={config?.settings?.safety_settings?.harassment === 'OFF'}
onChange={(checked) => {
const value = checked ? 'OFF' : 'BLOCK_MEDIUM_AND_ABOVE';
setConfig({
...config,
settings: {
...config?.settings,
safety_settings: {
harassment: value,
hate_speech: value,
sexually_explicit: value,
dangerous_content: value,
civic_integrity: 'BLOCK_NONE',
},
},
});
}}
>
关闭所有内容过滤
</Checkbox>
</FormField>
<FormActions>
<Button variant="secondary" onClick={onClose}>取消</Button>
<Button onClick={saveConfig}>保存设置</Button>
</FormActions>
</div>
);
}
五、凭证文件格式
5.1 单凭证格式
路径: ~/.antigravity/oauth_creds.json
{
"access_token": "ya29.a0AfH6SMC...",
"refresh_token": "1//0gXXXXXXXXXXXX...",
"token_type": "Bearer",
"expiry_date": 1704369600000,
"scope": "https://www.googleapis.com/auth/cloud-platform ...",
"cred_type": "antigravity",
"project_id": "my-project-123",
"email": "user@example.com"
}
5.2 多凭证数组格式
路径: accounts.json(兼容 antigravity2api-nodejs)
[
{
"access_token": "ya29.a0AfH6SMC...",
"refresh_token": "1//0gXXXXXXXXXXXX...",
"expiry_date": 1704369600000,
"email": "user1@example.com",
"enable": true
},
{
"access_token": "ya29.b1BgH7TNE...",
"refresh_token": "1//0hYYYYYYYYYYYY...",
"expiry_date": 1704456000000,
"email": "user2@example.com",
"enable": true
}
]
六、API 调用流程
6.1 完整调用链路
┌─────────────────────────────────────────────────────────────────────────┐
│ Antigravity Provider 调用流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 客户端请求 (Anthropic/OpenAI 格式) │
│ │ │
│ ▼ │
│ 2. 模型识别 & 协议选择 │
│ ├── gemini-* → 输出 Gemini 协议 │
│ └── claude-* → 输出 Anthropic 协议 │
│ │ │
│ ▼ │
│ 3. 凭证获取 │
│ ├── 从凭证池选择健康凭证 │
│ ├── 检查 Token 是否过期 │
│ └── 必要时刷新 Token │
│ │ │
│ ▼ │
│ 4. 请求转换 │
│ ├── 模型名映射(用户名 → 内部 API 名) │
│ ├── 消息格式转换 │
│ ├── 附加 Safety Settings │
│ └── 配置思维链(reasoning_effort) │
│ │ │
│ ▼ │
│ 5. Antigravity API 调用 │
│ └── POST {base_url}/v1internal/models/{model}:streamGenerateContent │
│ │ │
│ ▼ │
│ 6. 响应转换(根据模型类型) │
│ ├── Gemini 模型 → Gemini SSE 格式 │
│ └── Claude 模型 → Anthropic SSE 格式 │
│ │ │
│ ▼ │
│ 7. 返回客户端 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
6.2 Antigravity API
端点: POST {base_url}/v1internal/models/{model}:streamGenerateContent?alt=sse
请求格式:
{
"model": "gemini-3-pro-high",
"contents": [
{
"role": "user",
"parts": [
{ "text": "Hello, how are you?" }
]
}
],
"systemInstruction": {
"parts": [
{ "text": "You are a helpful assistant." }
]
},
"generationConfig": {
"maxOutputTokens": 4096,
"temperature": 0.7,
"topP": 0.9,
"candidateCount": 1,
"thinkingConfig": {
"thinkingBudget": 4096
}
},
"safetySettings": [
{ "category": "HARM_CATEGORY_HARASSMENT", "threshold": "OFF" },
{ "category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "OFF" },
{ "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "OFF" },
{ "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "OFF" },
{ "category": "HARM_CATEGORY_CIVIC_INTEGRITY", "threshold": "BLOCK_NONE" }
]
}
响应格式: Gemini SSE Stream
七、错误处理
7.1 错误类型
| 错误码 | 说明 | 处理方式 |
|---|---|---|
| 401 | Token 过期 | 自动刷新 Token 重试 |
| 403 | 权限不足 / 账户无访问权限 | 标记凭证异常 |
| 429 | 请求限流 | 冷却期后重试 |
| 500 | 服务器错误 | 切换凭证重试 |
7.2 Token 刷新错误
pub enum TokenRefreshError {
MissingRefreshToken,
InvalidRefreshToken,
NetworkError(String),
AuthorizationRevoked,
}
八、开发指南
8.1 本地开发
# 克隆仓库
git clone https://github.com/aiclientproxy/antigravity-provider.git
cd antigravity-provider
# 安装依赖
pnpm install
cd src-tauri && cargo build
# 前端开发
pnpm dev
# 后端开发
cargo watch -x run
8.2 测试
# 单元测试
cargo test
# 协议转换测试
cargo test --test converter
# 前端测试
pnpm test
附录
A. 环境变量
| 变量 | 说明 | 默认值 |
|---|---|---|
ANTIGRAVITY_ENVIRONMENT |
API 环境 | daily |
ANTIGRAVITY_DEBUG |
调试模式 | false |
ANTIGRAVITY_TIMEOUT_MS |
请求超时 | 120000 |
ANTIGRAVITY_OAUTH_CLIENT_ID |
OAuth Client ID(必需) | 无,必须设置 |
ANTIGRAVITY_OAUTH_CLIENT_SECRET |
OAuth Client Secret(必需) | 无,必须设置 |
注意:OAuth 凭据可以从 Antigravity CLI 获取或从 Google Cloud Console 创建。