- 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
23 KiB
Retour au README
Guide d'authentification et d'intégration Antigravity
Aperçu
Antigravity (Google Cloud Code Assist) est un fournisseur de modèles IA soutenu par Google qui offre l'accès à des modèles tels que Claude Opus 4.6 et Gemini via l'infrastructure cloud de Google. Ce document fournit un guide complet sur le fonctionnement de l'authentification, la récupération des modèles et l'implémentation d'un nouveau fournisseur dans PicoClaw.
Table des matières
- Flux d'authentification
- Détails de l'implémentation OAuth
- Gestion des jetons
- Récupération de la liste des modèles
- Suivi de l'utilisation
- Structure du plugin fournisseur
- Exigences d'intégration
- Points de terminaison API
- Configuration
- Créer un nouveau fournisseur dans PicoClaw
Flux d'authentification
1. OAuth 2.0 avec PKCE
Antigravity utilise OAuth 2.0 avec PKCE (Proof Key for Code Exchange) pour une authentification sécurisée :
┌─────────────┐ ┌─────────────────┐
│ 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. Étapes détaillées
Étape 1 : Générer les paramètres PKCE
function generatePkce(): { verifier: string; challenge: string } {
const verifier = randomBytes(32).toString("hex");
const challenge = createHash("sha256").update(verifier).digest("base64url");
return { verifier, challenge };
}
Étape 2 : Construire l'URL d'autorisation
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();
}
Portées requises :
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",
];
Étape 3 : Gérer le callback OAuth
Mode automatique (développement local) :
- Démarrer un serveur HTTP local sur le port 51121
- Attendre la redirection de Google
- Extraire le code d'autorisation des paramètres de requête
Mode manuel (distant/sans interface graphique) :
- Afficher l'URL d'autorisation à l'utilisateur
- L'utilisateur complète l'authentification dans son navigateur
- L'utilisateur colle l'URL de redirection complète dans le terminal
- Analyser le code depuis l'URL collée
Étape 4 : Échanger le code contre des jetons
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
};
}
Étape 5 : Récupérer les données utilisateur supplémentaires
E-mail de l'utilisateur :
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 du projet (requis pour les appels 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"; // Valeur par défaut
}
Détails de l'implémentation OAuth
Identifiants client
Important : Ceux-ci sont encodés en base64 dans le code source pour la synchronisation avec pi-ai :
const decode = (s: string) => Buffer.from(s, "base64").toString();
const CLIENT_ID = decode(
"MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ=="
);
const CLIENT_SECRET = decode("R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY=");
Modes de flux OAuth
-
Flux automatique (machines locales avec navigateur) :
- Ouvre le navigateur automatiquement
- Le serveur de callback local capture la redirection
- Aucune interaction utilisateur requise après l'authentification initiale
-
Flux manuel (distant/sans interface/WSL2) :
- URL affichée pour copier-coller manuellement
- L'utilisateur complète l'authentification dans un navigateur externe
- L'utilisateur colle l'URL de redirection complète
function shouldUseManualOAuthFlow(isRemote: boolean): boolean {
return isRemote || isWSL2Sync();
}
Gestion des jetons
Structure du profil d'authentification
type OAuthCredential = {
type: "oauth";
provider: "google-antigravity";
access: string; // Jeton d'accès
refresh: string; // Jeton de rafraîchissement
expires: number; // Horodatage d'expiration (ms depuis epoch)
email?: string; // E-mail de l'utilisateur
projectId?: string; // ID du projet Google Cloud
};
Rafraîchissement des jetons
Les identifiants incluent un jeton de rafraîchissement qui peut être utilisé pour obtenir de nouveaux jetons d'accès lorsque le jeton actuel expire. L'expiration est définie avec un tampon de 5 minutes pour éviter les conditions de concurrence.
Récupération de la liste des modèles
Récupérer les modèles disponibles
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();
// Retourne les modèles avec les informations de quota
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,
},
}));
}
Format de réponse
type FetchAvailableModelsResponse = {
models?: Record<string, {
displayName?: string;
quotaInfo?: {
remainingFraction?: number | string;
resetTime?: string; // Horodatage ISO 8601
isExhausted?: boolean;
};
}>;
};
Suivi de l'utilisation
Récupérer les données d'utilisation
export async function fetchAntigravityUsage(
token: string,
timeoutMs: number
): Promise<ProviderUsageSnapshot> {
// 1. Récupérer les crédits et les informations du plan
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",
},
}),
}
);
// Extraire les informations de crédits
const { availablePromptCredits, planInfo, currentTier } = data;
// 2. Récupérer les quotas des modèles
const modelsRes = await fetch(
`${BASE_URL}/v1internal:fetchAvailableModels`,
{
method: "POST",
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({ project: projectId }),
}
);
// Construire les fenêtres d'utilisation
return {
provider: "google-antigravity",
displayName: "Google Antigravity",
windows: [
{ label: "Credits", usedPercent: calculateUsedPercent(available, monthly) },
// Quotas individuels des modèles...
],
plan: currentTier?.name || planType,
};
}
Structure de la réponse d'utilisation
type ProviderUsageSnapshot = {
provider: "google-antigravity";
displayName: string;
windows: UsageWindow[];
plan?: string;
error?: string;
};
type UsageWindow = {
label: string; // "Credits" ou ID du modèle
usedPercent: number; // 0-100
resetAt?: number; // Horodatage de réinitialisation du quota
};
Structure du plugin fournisseur
Définition du plugin
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) => {
// Implémentation OAuth ici
},
},
],
});
},
};
ProviderAuthContext
type ProviderAuthContext = {
config: PicoClawConfig;
agentDir?: string;
workspaceDir?: string;
prompter: WizardPrompter; // Invites/notifications UI
runtime: RuntimeEnv; // Journalisation, etc.
isRemote: boolean; // Exécution à distance ou non
openUrl: (url: string) => Promise<void>; // Ouverture du navigateur
oauth: {
createVpsAwareHandlers: Function;
};
};
ProviderAuthResult
type ProviderAuthResult = {
profiles: Array<{
profileId: string;
credential: AuthProfileCredential;
}>;
configPatch?: Partial<PicoClawConfig>;
defaultModel?: string;
notes?: string[];
};
Exigences d'intégration
1. Environnement/dépendances requis
- Go ≥ 1.25
- Base de code PicoClaw (
pkg/providers/etpkg/auth/) - Packages de la bibliothèque standard
cryptoetnet/http
2. En-têtes requis pour les appels API
const REQUIRED_HEADERS = {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
"User-Agent": "antigravity", // ou "google-api-nodejs-client/9.15.1"
"X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
};
// Pour les appels loadCodeAssist, inclure également :
const CLIENT_METADATA = {
ideType: "ANTIGRAVITY", // ou "IDE_UNSPECIFIED"
platform: "PLATFORM_UNSPECIFIED",
pluginType: "GEMINI",
};
3. Assainissement des schémas de modèles
Antigravity utilise des modèles compatibles Gemini, les schémas d'outils doivent donc être assainis :
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",
]);
// Nettoyer le schéma avant l'envoi
function cleanToolSchemaForGemini(schema: Record<string, unknown>): unknown {
// Supprimer les mots-clés non supportés
// S'assurer que le niveau supérieur a type: "object"
// Aplatir les unions anyOf/oneOf
}
4. Gestion des blocs de réflexion (modèles Claude)
Pour les modèles Claude via Antigravity, les blocs de réflexion nécessitent un traitement spécial :
const ANTIGRAVITY_SIGNATURE_RE = /^[A-Za-z0-9+/]+={0,2}$/;
export function sanitizeAntigravityThinkingBlocks(
messages: AgentMessage[]
): AgentMessage[] {
// Valider les signatures de réflexion
// Normaliser les champs de signature
// Rejeter les blocs de réflexion non signés
}
Points de terminaison API
Points de terminaison d'authentification
| Point de terminaison | Méthode | Objectif |
|---|---|---|
https://accounts.google.com/o/oauth2/v2/auth |
GET | Autorisation OAuth |
https://oauth2.googleapis.com/token |
POST | Échange de jetons |
https://www.googleapis.com/oauth2/v1/userinfo |
GET | Informations utilisateur (e-mail) |
Points de terminaison Cloud Code Assist
| Point de terminaison | Méthode | Objectif |
|---|---|---|
https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist |
POST | Charger les infos du projet, crédits, plan |
https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels |
POST | Lister les modèles disponibles avec quotas |
https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent?alt=sse |
POST | Point de terminaison de streaming de chat |
Format de requête API (chat) :
Le point de terminaison v1internal:streamGenerateContent attend une enveloppe encapsulant la requête Gemini standard :
{
"project": "your-project-id",
"model": "model-id",
"request": {
"contents": [...],
"systemInstruction": {...},
"generationConfig": {...},
"tools": [...]
},
"requestType": "agent",
"userAgent": "antigravity",
"requestId": "agent-timestamp-random"
}
Format de réponse API (SSE) :
Chaque message SSE (data: {...}) est encapsulé dans un champ response :
{
"response": {
"candidates": [...],
"usageMetadata": {...},
"modelVersion": "...",
"responseId": "..."
},
"traceId": "...",
"metadata": {}
}
Configuration
Configuration config.json
{
"model_list": [
{
"model_name": "gemini-flash",
"model": "antigravity/gemini-3-flash",
"auth_method": "oauth"
}
],
"agents": {
"defaults": {
"model_name": "gemini-flash"
}
}
}
Stockage du profil d'authentification
Les profils d'authentification sont stockés dans ~/.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": "user@example.com",
"project_id": "my-project-id"
}
}
}
Créer un nouveau fournisseur dans PicoClaw
Les fournisseurs PicoClaw sont implémentés en tant que packages Go sous pkg/providers/. Pour ajouter un nouveau fournisseur :
Implémentation étape par étape
1. Créer le fichier du fournisseur
Créez un nouveau fichier Go dans pkg/providers/ :
pkg/providers/
└── your_provider.go
2. Implémenter l'interface Provider
Votre fournisseur doit implémenter l'interface Provider définie dans pkg/providers/types.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 {
// Implémenter la complétion de chat avec streaming
}
3. Enregistrer dans la factory
Ajoutez votre fournisseur au switch de protocole dans pkg/providers/factory.go :
case "your-provider":
return NewYourProvider(sel.apiKey, sel.apiBase, sel.proxy), nil
4. Ajouter la configuration par défaut (optionnel)
Ajoutez une entrée par défaut dans pkg/config/defaults.go :
{
ModelName: "your-model",
Model: "your-provider/model-name",
APIKey: "",
},
5. Ajouter le support d'authentification (optionnel)
Si votre fournisseur nécessite OAuth ou une authentification spéciale, ajoutez un cas dans cmd/picoclaw/internal/auth/helpers.go :
case "your-provider":
authLoginYourProvider()
6. Configurer via 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"
}
]
}
Tester votre implémentation
Commandes CLI
# S'authentifier avec un fournisseur
picoclaw auth login --provider your-provider
# Lister les modèles (pour Antigravity)
picoclaw auth models
# Démarrer la passerelle
picoclaw gateway
# Exécuter un agent avec un modèle spécifique
picoclaw agent -m "Hello" --model your-model
Variables d'environnement pour les tests
# Remplacer le modèle par défaut
export PICOCLAW_AGENTS_DEFAULTS_MODEL=your-model
# Remplacer les paramètres du fournisseur
export PICOCLAW_MODEL_LIST='[{"model_name":"your-model","model":"your-provider/model-name","api_keys":["..."]}]'
Références
-
Fichiers source :
pkg/providers/antigravity_provider.go- Implémentation du fournisseur Antigravitypkg/auth/oauth.go- Implémentation du flux OAuthpkg/auth/store.go- Stockage des identifiants d'authentification (~/.picoclaw/auth.json)pkg/providers/factory.go- Factory des fournisseurs et routage de protocolepkg/providers/types.go- Définitions de l'interface fournisseurcmd/picoclaw/internal/auth/helpers.go- Commandes CLI d'authentification
-
Documentation :
docs/ANTIGRAVITY_USAGE.md- Guide d'utilisation d'Antigravitydocs/migration/model-list-migration.md- Guide de migration
Notes
- Projet Google Cloud : Antigravity nécessite que Gemini for Google Cloud soit activé sur votre projet Google Cloud
- Quotas : Utilise les quotas du projet Google Cloud (pas de facturation séparée)
- Accès aux modèles : Les modèles disponibles dépendent de la configuration de votre projet Google Cloud
- Blocs de réflexion : Les modèles Claude via Antigravity nécessitent un traitement spécial des blocs de réflexion avec signatures
- Assainissement des schémas : Les schémas d'outils doivent être assainis pour supprimer les mots-clés JSON Schema non supportés
Gestion des erreurs courantes
1. Limitation de débit (HTTP 429)
Antigravity retourne une erreur 429 lorsque les quotas du projet/modèle sont épuisés. La réponse d'erreur contient souvent un quotaResetDelay dans le champ details.
Exemple d'erreur 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"
}
}
]
}
}
2. Réponses vides (modèles restreints)
Certains modèles peuvent apparaître dans la liste des modèles disponibles mais retourner une réponse vide (200 OK mais flux SSE vide). Cela se produit généralement pour les modèles en préversion ou restreints que le projet actuel n'a pas la permission d'utiliser.
Traitement : Traiter les réponses vides comme des erreurs informant l'utilisateur que le modèle pourrait être restreint ou invalide pour son projet.
Dépannage
"Token expired" (jeton expiré)
- Rafraîchir les jetons OAuth :
picoclaw auth login --provider antigravity
"Gemini for Google Cloud is not enabled" (Gemini for Google Cloud n'est pas activé)
- Activer l'API dans votre Google Cloud Console
"Project not found" (projet non trouvé)
- Vérifier que votre projet Google Cloud a les API nécessaires activées
- Vérifier que l'ID du projet est correctement récupéré lors de l'authentification
Les modèles n'apparaissent pas dans la liste
- Vérifier que l'authentification OAuth s'est terminée avec succès
- Vérifier le stockage du profil d'authentification :
~/.picoclaw/auth.json - Relancer
picoclaw auth login --provider antigravity