docs/security/ANTIGRAVITY_AUTH.ja.md
README に戻る
Antigravity(Google Cloud Code Assist)は、Google が提供する AI モデルプロバイダーで、Google のクラウドインフラストラクチャを通じて Claude Opus 4.6 や Gemini などのモデルへのアクセスを提供します。本ドキュメントでは、認証の仕組み、モデルの取得方法、PicoClaw での新しいプロバイダーの実装方法について完全なガイドを提供します。
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──── │ │
└─────────────┘ └─────────────────┘
function generatePkce(): { verifier: string; challenge: string } {
const verifier = randomBytes(32).toString("hex");
const challenge = createHash("sha256").update(verifier).digest("base64url");
return { verifier, challenge };
}
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();
}
必要なスコープ:
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",
];
自動モード(ローカル開発):
手動モード(リモート/ヘッドレス):
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
};
}
ユーザーメール:
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 呼び出しに必須):
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"; // デフォルトのフォールバック
}
重要: これらは pi-ai との同期のためにソースコード内で base64 エンコードされています:
const decode = (s: string) => Buffer.from(s, "base64").toString();
const CLIENT_ID = decode(
"MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ=="
);
const CLIENT_SECRET = decode("R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY=");
自動フロー(ブラウザのあるローカルマシン):
手動フロー(リモート/ヘッドレス/WSL2):
function shouldUseManualOAuthFlow(isRemote: boolean): boolean {
return isRemote || isWSL2Sync();
}
type OAuthCredential = {
type: "oauth";
provider: "google-antigravity";
access: string; // アクセストークン
refresh: string; // リフレッシュトークン
expires: number; // 有効期限タイムスタンプ(エポックからのミリ秒)
email?: string; // ユーザーメール
projectId?: string; // Google Cloud プロジェクト ID
};
認証情報にはリフレッシュトークンが含まれており、現在のアクセストークンが期限切れになった際に新しいアクセストークンを取得するために使用できます。有効期限は競合状態を防ぐために 5 分のバッファを設けています。
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,
},
}));
}
type FetchAvailableModelsResponse = {
models?: Record<string, {
displayName?: string;
quotaInfo?: {
remainingFraction?: number | string;
resetTime?: string; // ISO 8601 タイムスタンプ
isExhausted?: boolean;
};
}>;
};
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,
};
}
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; // クォータがリセットされるタイムスタンプ
};
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 実装はここに記述
},
},
],
});
},
};
type ProviderAuthContext = {
config: PicoClawConfig;
agentDir?: string;
workspaceDir?: string;
prompter: WizardPrompter; // UI プロンプト/通知
runtime: RuntimeEnv; // ログなど
isRemote: boolean; // リモート実行かどうか
openUrl: (url: string) => Promise<void>; // ブラウザオープナー
oauth: {
createVpsAwareHandlers: Function;
};
};
type ProviderAuthResult = {
profiles: Array<{
profileId: string;
credential: AuthProfileCredential;
}>;
configPatch?: Partial<PicoClawConfig>;
defaultModel?: string;
notes?: string[];
};
pkg/providers/ および pkg/auth/)crypto および net/http 標準ライブラリパッケージ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",
};
Antigravity は Gemini 互換モデルを使用するため、ツールスキーマのサニタイズが必要です:
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",
]);
// 送信前にスキーマをクリーンアップ
function cleanToolSchemaForGemini(schema: Record<string, unknown>): unknown {
// サポートされていないキーワードを削除
// トップレベルに type: "object" があることを確認
// anyOf/oneOf ユニオンをフラット化
}
Antigravity の Claude モデルでは、思考ブロックに特別な処理が必要です:
const ANTIGRAVITY_SIGNATURE_RE = /^[A-Za-z0-9+/]+={0,2}$/;
export function sanitizeAntigravityThinkingBlocks(
messages: AgentMessage[]
): AgentMessage[] {
// 思考シグネチャを検証
// シグネチャフィールドを正規化
// 署名されていない思考ブロックを破棄
}
| エンドポイント | メソッド | 用途 |
|---|---|---|
https://accounts.google.com/o/oauth2/v2/auth | GET | OAuth 認可 |
https://oauth2.googleapis.com/token | POST | トークン交換 |
https://www.googleapis.com/oauth2/v1/userinfo | GET | ユーザー情報(メール) |
| エンドポイント | メソッド | 用途 |
|---|---|---|
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 リクエストをラップするエンベロープ形式を期待します:
{
"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 フィールドでラップされます:
{
"response": {
"candidates": [...],
"usageMetadata": {...},
"modelVersion": "...",
"responseId": "..."
},
"traceId": "...",
"metadata": {}
}
{
"model_list": [
{
"model_name": "gemini-flash",
"model": "antigravity/gemini-3-flash",
"auth_method": "oauth"
}
],
"agents": {
"defaults": {
"model_name": "gemini-flash"
}
}
}
認証プロファイルは ~/.picoclaw/auth.json に保存されます:
{
"credentials": {
"google-antigravity": {
"access_token": "ya29...",
"refresh_token": "1//...",
"expires_at": "2026-01-01T00:00:00Z",
"provider": "google-antigravity",
"auth_method": "oauth",
"email": "[email protected]",
"project_id": "my-project-id"
}
}
}
PicoClaw のプロバイダーは pkg/providers/ 配下の Go パッケージとして実装されます。新しいプロバイダーを追加するには:
pkg/providers/ に新しい Go ファイルを作成します:
pkg/providers/
└── your_provider.go
プロバイダーは 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 {
// ストリーミング付きチャット補完を実装
}
pkg/providers/factory.go のプロトコルスイッチにプロバイダーを追加します:
case "your-provider":
return NewYourProvider(sel.apiKey, sel.apiBase, sel.proxy), nil
pkg/config/defaults.go にデフォルトエントリを追加します:
{
ModelName: "your-model",
Model: "your-provider/model-name",
APIKey: "",
},
プロバイダーが OAuth や特別な認証を必要とする場合、cmd/picoclaw/internal/auth/helpers.go にケースを追加します:
case "your-provider":
authLoginYourProvider()
config.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"
}
]
}
# プロバイダーで認証
picoclaw auth login --provider your-provider
# モデルの一覧表示(Antigravity 用)
picoclaw auth models
# ゲートウェイの起動
picoclaw gateway
# 特定のモデルでエージェントを実行
picoclaw agent -m "Hello" --model your-model
# デフォルトモデルの上書き
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 - 移行ガイドプロジェクト/モデルのクォータが枯渇すると、Antigravity は 429 エラーを返します。エラーレスポンスには通常、details フィールドに quotaResetDelay が含まれます。
429 エラーの例:
{
"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"
}
}
]
}
}
一部のモデルは利用可能モデルリストに表示されますが、空のレスポンスを返す場合があります(200 OK だが SSE ストリームが空)。これは通常、現在のプロジェクトに使用権限がないプレビュー版または制限付きモデルで発生します。
対処法: 空のレスポンスをエラーとして扱い、そのモデルがプロジェクトに対して制限されているか無効である可能性があることをユーザーに通知します。
picoclaw auth login --provider antigravity~/.picoclaw/auth.jsonpicoclaw auth login --provider antigravity を再実行してください