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
22 KiB
Markdown
810 lines
22 KiB
Markdown
> Voltar ao [README](../project/README.pt-br.md)
|
|
|
|
# Guia de Autenticação e Integração do Antigravity
|
|
|
|
## Visão Geral
|
|
|
|
**Antigravity** (Google Cloud Code Assist) é um provedor de modelos de IA apoiado pelo Google que oferece acesso a modelos como Claude Opus 4.6 e Gemini através da infraestrutura de nuvem do Google. Este documento fornece um guia completo sobre como a autenticação funciona, como buscar modelos e como implementar um novo provedor no PicoClaw.
|
|
|
|
---
|
|
|
|
## Índice
|
|
|
|
1. [Fluxo de Autenticação](#fluxo-de-autenticação)
|
|
2. [Detalhes da Implementação OAuth](#detalhes-da-implementação-oauth)
|
|
3. [Gerenciamento de Tokens](#gerenciamento-de-tokens)
|
|
4. [Busca da Lista de Modelos](#busca-da-lista-de-modelos)
|
|
5. [Rastreamento de Uso](#rastreamento-de-uso)
|
|
6. [Estrutura do Plugin do Provedor](#estrutura-do-plugin-do-provedor)
|
|
7. [Requisitos de Integração](#requisitos-de-integração)
|
|
8. [Endpoints da API](#endpoints-da-api)
|
|
9. [Configuração](#configuração)
|
|
10. [Criando um Novo Provedor no PicoClaw](#criando-um-novo-provedor-no-picoclaw)
|
|
|
|
---
|
|
|
|
## Fluxo de Autenticação
|
|
|
|
### 1. OAuth 2.0 com PKCE
|
|
|
|
O Antigravity utiliza **OAuth 2.0 com PKCE (Proof Key for Code Exchange)** para autenticação segura:
|
|
|
|
```
|
|
┌─────────────┐ ┌─────────────────┐
|
|
│ 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. Etapas Detalhadas
|
|
|
|
#### Etapa 1: Gerar Parâmetros 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 };
|
|
}
|
|
```
|
|
|
|
#### Etapa 2: Construir a URL de Autorização
|
|
```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();
|
|
}
|
|
```
|
|
|
|
**Escopos Necessários:**
|
|
```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",
|
|
];
|
|
```
|
|
|
|
#### Etapa 3: Tratar o Callback OAuth
|
|
|
|
**Modo Automático (Desenvolvimento Local):**
|
|
- Iniciar um servidor HTTP local na porta 51121
|
|
- Aguardar o redirecionamento do Google
|
|
- Extrair o código de autorização dos parâmetros da query
|
|
|
|
**Modo Manual (Remoto/Sem Interface Gráfica):**
|
|
- Exibir a URL de autorização para o usuário
|
|
- O usuário completa a autenticação no navegador
|
|
- O usuário cola a URL de redirecionamento completa no terminal
|
|
- Analisar o código da URL colada
|
|
|
|
#### Etapa 4: Trocar o Código por Tokens
|
|
```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
|
|
};
|
|
}
|
|
```
|
|
|
|
#### Etapa 5: Buscar Dados Adicionais do Usuário
|
|
|
|
**E-mail do Usuário:**
|
|
```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 do Projeto (Necessário para chamadas de 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"; // Valor padrão de fallback
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Detalhes da Implementação OAuth
|
|
|
|
### Credenciais do Cliente
|
|
|
|
**Importante:** Estas são codificadas em base64 no código-fonte para sincronização com pi-ai:
|
|
|
|
```typescript
|
|
const decode = (s: string) => Buffer.from(s, "base64").toString();
|
|
|
|
const CLIENT_ID = decode(
|
|
"MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ=="
|
|
);
|
|
const CLIENT_SECRET = decode("R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY=");
|
|
```
|
|
|
|
### Modos do Fluxo OAuth
|
|
|
|
1. **Fluxo Automático** (máquinas locais com navegador):
|
|
- Abre o navegador automaticamente
|
|
- O servidor de callback local captura o redirecionamento
|
|
- Nenhuma interação do usuário necessária após a autenticação inicial
|
|
|
|
2. **Fluxo Manual** (remoto/sem interface/WSL2):
|
|
- URL exibida para copiar e colar manualmente
|
|
- O usuário completa a autenticação em um navegador externo
|
|
- O usuário cola a URL de redirecionamento completa de volta
|
|
|
|
```typescript
|
|
function shouldUseManualOAuthFlow(isRemote: boolean): boolean {
|
|
return isRemote || isWSL2Sync();
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Gerenciamento de Tokens
|
|
|
|
### Estrutura do Perfil de Autenticação
|
|
|
|
```typescript
|
|
type OAuthCredential = {
|
|
type: "oauth";
|
|
provider: "google-antigravity";
|
|
access: string; // Token de acesso
|
|
refresh: string; // Token de atualização
|
|
expires: number; // Timestamp de expiração (ms desde epoch)
|
|
email?: string; // E-mail do usuário
|
|
projectId?: string; // ID do projeto Google Cloud
|
|
};
|
|
```
|
|
|
|
### Atualização de Tokens
|
|
|
|
A credencial inclui um token de atualização que pode ser usado para obter novos tokens de acesso quando o atual expira. A expiração é definida com um buffer de 5 minutos para evitar condições de corrida.
|
|
|
|
---
|
|
|
|
## Busca da Lista de Modelos
|
|
|
|
### Buscar Modelos Disponíveis
|
|
|
|
```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();
|
|
|
|
// Retorna modelos com informações de cota
|
|
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,
|
|
},
|
|
}));
|
|
}
|
|
```
|
|
|
|
### Formato da Resposta
|
|
|
|
```typescript
|
|
type FetchAvailableModelsResponse = {
|
|
models?: Record<string, {
|
|
displayName?: string;
|
|
quotaInfo?: {
|
|
remainingFraction?: number | string;
|
|
resetTime?: string; // Timestamp ISO 8601
|
|
isExhausted?: boolean;
|
|
};
|
|
}>;
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Rastreamento de Uso
|
|
|
|
### Buscar Dados de Uso
|
|
|
|
```typescript
|
|
export async function fetchAntigravityUsage(
|
|
token: string,
|
|
timeoutMs: number
|
|
): Promise<ProviderUsageSnapshot> {
|
|
// 1. Buscar créditos e informações do plano
|
|
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",
|
|
},
|
|
}),
|
|
}
|
|
);
|
|
|
|
// Extrair informações de créditos
|
|
const { availablePromptCredits, planInfo, currentTier } = data;
|
|
|
|
// 2. Buscar cotas dos modelos
|
|
const modelsRes = await fetch(
|
|
`${BASE_URL}/v1internal:fetchAvailableModels`,
|
|
{
|
|
method: "POST",
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
body: JSON.stringify({ project: projectId }),
|
|
}
|
|
);
|
|
|
|
// Construir janelas de uso
|
|
return {
|
|
provider: "google-antigravity",
|
|
displayName: "Google Antigravity",
|
|
windows: [
|
|
{ label: "Credits", usedPercent: calculateUsedPercent(available, monthly) },
|
|
// Cotas individuais dos modelos...
|
|
],
|
|
plan: currentTier?.name || planType,
|
|
};
|
|
}
|
|
```
|
|
|
|
### Estrutura da Resposta de Uso
|
|
|
|
```typescript
|
|
type ProviderUsageSnapshot = {
|
|
provider: "google-antigravity";
|
|
displayName: string;
|
|
windows: UsageWindow[];
|
|
plan?: string;
|
|
error?: string;
|
|
};
|
|
|
|
type UsageWindow = {
|
|
label: string; // "Credits" ou ID do modelo
|
|
usedPercent: number; // 0-100
|
|
resetAt?: number; // Timestamp de quando a cota é redefinida
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Estrutura do Plugin do Provedor
|
|
|
|
### Definição do Plugin
|
|
|
|
```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) => {
|
|
// Implementação OAuth aqui
|
|
},
|
|
},
|
|
],
|
|
});
|
|
},
|
|
};
|
|
```
|
|
|
|
### ProviderAuthContext
|
|
|
|
```typescript
|
|
type ProviderAuthContext = {
|
|
config: PicoClawConfig;
|
|
agentDir?: string;
|
|
workspaceDir?: string;
|
|
prompter: WizardPrompter; // Prompts/notificações da UI
|
|
runtime: RuntimeEnv; // Logging, etc.
|
|
isRemote: boolean; // Se está executando remotamente
|
|
openUrl: (url: string) => Promise<void>; // Abridor de navegador
|
|
oauth: {
|
|
createVpsAwareHandlers: Function;
|
|
};
|
|
};
|
|
```
|
|
|
|
### ProviderAuthResult
|
|
|
|
```typescript
|
|
type ProviderAuthResult = {
|
|
profiles: Array<{
|
|
profileId: string;
|
|
credential: AuthProfileCredential;
|
|
}>;
|
|
configPatch?: Partial<PicoClawConfig>;
|
|
defaultModel?: string;
|
|
notes?: string[];
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Requisitos de Integração
|
|
|
|
### 1. Ambiente/Dependências Necessários
|
|
|
|
- Go ≥ 1.25
|
|
- Base de código do PicoClaw (`pkg/providers/` e `pkg/auth/`)
|
|
- Pacotes da biblioteca padrão `crypto` e `net/http`
|
|
|
|
### 2. Cabeçalhos Necessários para Chamadas de API
|
|
|
|
```typescript
|
|
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",
|
|
};
|
|
|
|
// Para chamadas loadCodeAssist, incluir também:
|
|
const CLIENT_METADATA = {
|
|
ideType: "ANTIGRAVITY", // ou "IDE_UNSPECIFIED"
|
|
platform: "PLATFORM_UNSPECIFIED",
|
|
pluginType: "GEMINI",
|
|
};
|
|
```
|
|
|
|
### 3. Sanitização de Schemas de Modelos
|
|
|
|
O Antigravity usa modelos compatíveis com Gemini, então os schemas de ferramentas devem ser sanitizados:
|
|
|
|
```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",
|
|
]);
|
|
|
|
// Limpar schema antes de enviar
|
|
function cleanToolSchemaForGemini(schema: Record<string, unknown>): unknown {
|
|
// Remover palavras-chave não suportadas
|
|
// Garantir que o nível superior tenha type: "object"
|
|
// Achatar uniões anyOf/oneOf
|
|
}
|
|
```
|
|
|
|
### 4. Tratamento de Blocos de Pensamento (Modelos Claude)
|
|
|
|
Para modelos Claude via Antigravity, os blocos de pensamento requerem tratamento especial:
|
|
|
|
```typescript
|
|
const ANTIGRAVITY_SIGNATURE_RE = /^[A-Za-z0-9+/]+={0,2}$/;
|
|
|
|
export function sanitizeAntigravityThinkingBlocks(
|
|
messages: AgentMessage[]
|
|
): AgentMessage[] {
|
|
// Validar assinaturas de pensamento
|
|
// Normalizar campos de assinatura
|
|
// Descartar blocos de pensamento não assinados
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Endpoints da API
|
|
|
|
### Endpoints de Autenticação
|
|
|
|
| Endpoint | Método | Finalidade |
|
|
|----------|--------|-----------|
|
|
| `https://accounts.google.com/o/oauth2/v2/auth` | GET | Autorização OAuth |
|
|
| `https://oauth2.googleapis.com/token` | POST | Troca de tokens |
|
|
| `https://www.googleapis.com/oauth2/v1/userinfo` | GET | Informações do usuário (e-mail) |
|
|
|
|
### Endpoints do Cloud Code Assist
|
|
|
|
| Endpoint | Método | Finalidade |
|
|
|----------|--------|-----------|
|
|
| `https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist` | POST | Carregar informações do projeto, créditos, plano |
|
|
| `https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels` | POST | Listar modelos disponíveis com cotas |
|
|
| `https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent?alt=sse` | POST | Endpoint de streaming de chat |
|
|
|
|
**Formato de Requisição da API (Chat):**
|
|
O endpoint `v1internal:streamGenerateContent` espera um envelope encapsulando a requisição Gemini padrão:
|
|
|
|
```json
|
|
{
|
|
"project": "your-project-id",
|
|
"model": "model-id",
|
|
"request": {
|
|
"contents": [...],
|
|
"systemInstruction": {...},
|
|
"generationConfig": {...},
|
|
"tools": [...]
|
|
},
|
|
"requestType": "agent",
|
|
"userAgent": "antigravity",
|
|
"requestId": "agent-timestamp-random"
|
|
}
|
|
```
|
|
|
|
**Formato de Resposta da API (SSE):**
|
|
Cada mensagem SSE (`data: {...}`) é encapsulada em um campo `response`:
|
|
|
|
```json
|
|
{
|
|
"response": {
|
|
"candidates": [...],
|
|
"usageMetadata": {...},
|
|
"modelVersion": "...",
|
|
"responseId": "..."
|
|
},
|
|
"traceId": "...",
|
|
"metadata": {}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Configuração
|
|
|
|
### Configuração do config.json
|
|
|
|
```json
|
|
{
|
|
"model_list": [
|
|
{
|
|
"model_name": "gemini-flash",
|
|
"model": "antigravity/gemini-3-flash",
|
|
"auth_method": "oauth"
|
|
}
|
|
],
|
|
"agents": {
|
|
"defaults": {
|
|
"model_name": "gemini-flash"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Armazenamento do Perfil de Autenticação
|
|
|
|
Os perfis de autenticação são armazenados em `~/.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"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Criando um Novo Provedor no PicoClaw
|
|
|
|
Os provedores do PicoClaw são implementados como pacotes Go em `pkg/providers/`. Para adicionar um novo provedor:
|
|
|
|
### Implementação Passo a Passo
|
|
|
|
#### 1. Criar o Arquivo do Provedor
|
|
|
|
Crie um novo arquivo Go em `pkg/providers/`:
|
|
|
|
```
|
|
pkg/providers/
|
|
└── your_provider.go
|
|
```
|
|
|
|
#### 2. Implementar a Interface Provider
|
|
|
|
Seu provedor deve implementar a interface `Provider` definida em `pkg/providers/types.go`:
|
|
|
|
```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 {
|
|
// Implementar conclusão de chat com streaming
|
|
}
|
|
```
|
|
|
|
#### 3. Registrar na Factory
|
|
|
|
Adicione seu provedor ao switch de protocolo em `pkg/providers/factory.go`:
|
|
|
|
```go
|
|
case "your-provider":
|
|
return NewYourProvider(sel.apiKey, sel.apiBase, sel.proxy), nil
|
|
```
|
|
|
|
#### 4. Adicionar Configuração Padrão (Opcional)
|
|
|
|
Adicione uma entrada padrão em `pkg/config/defaults.go`:
|
|
|
|
```go
|
|
{
|
|
ModelName: "your-model",
|
|
Model: "your-provider/model-name",
|
|
APIKey: "",
|
|
},
|
|
```
|
|
|
|
#### 5. Adicionar Suporte de Autenticação (Opcional)
|
|
|
|
Se seu provedor requer OAuth ou autenticação especial, adicione um caso em `cmd/picoclaw/internal/auth/helpers.go`:
|
|
|
|
```go
|
|
case "your-provider":
|
|
authLoginYourProvider()
|
|
```
|
|
|
|
#### 6. Configurar via `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"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Testando Sua Implementação
|
|
|
|
### Comandos CLI
|
|
|
|
```bash
|
|
# Autenticar com um provedor
|
|
picoclaw auth login --provider your-provider
|
|
|
|
# Listar modelos (para Antigravity)
|
|
picoclaw auth models
|
|
|
|
# Iniciar o gateway
|
|
picoclaw gateway
|
|
|
|
# Executar um agente com um modelo específico
|
|
picoclaw agent -m "Hello" --model your-model
|
|
```
|
|
|
|
### Variáveis de Ambiente para Testes
|
|
|
|
```bash
|
|
# Substituir o modelo padrão
|
|
export PICOCLAW_AGENTS_DEFAULTS_MODEL=your-model
|
|
|
|
# Substituir configurações do provedor
|
|
export PICOCLAW_MODEL_LIST='[{"model_name":"your-model","model":"your-provider/model-name","api_keys":["..."]}]'
|
|
```
|
|
|
|
---
|
|
|
|
## Referências
|
|
|
|
- **Arquivos Fonte:**
|
|
- `pkg/providers/antigravity_provider.go` - Implementação do provedor Antigravity
|
|
- `pkg/auth/oauth.go` - Implementação do fluxo OAuth
|
|
- `pkg/auth/store.go` - Armazenamento de credenciais de autenticação (`~/.picoclaw/auth.json`)
|
|
- `pkg/providers/factory.go` - Factory de provedores e roteamento de protocolo
|
|
- `pkg/providers/types.go` - Definições da interface do provedor
|
|
- `cmd/picoclaw/internal/auth/helpers.go` - Comandos CLI de autenticação
|
|
|
|
- **Documentação:**
|
|
- `docs/ANTIGRAVITY_USAGE.md` - Guia de uso do Antigravity
|
|
- `docs/migration/model-list-migration.md` - Guia de migração
|
|
|
|
---
|
|
|
|
## Observações
|
|
|
|
1. **Projeto Google Cloud:** O Antigravity requer que o Gemini for Google Cloud esteja habilitado no seu projeto Google Cloud
|
|
2. **Cotas:** Usa cotas do projeto Google Cloud (sem cobrança separada)
|
|
3. **Acesso a Modelos:** Os modelos disponíveis dependem da configuração do seu projeto Google Cloud
|
|
4. **Blocos de Pensamento:** Modelos Claude via Antigravity requerem tratamento especial de blocos de pensamento com assinaturas
|
|
5. **Sanitização de Schemas:** Os schemas de ferramentas devem ser sanitizados para remover palavras-chave JSON Schema não suportadas
|
|
|
|
---
|
|
|
|
---
|
|
|
|
## Tratamento de Erros Comuns
|
|
|
|
### 1. Limitação de Taxa (HTTP 429)
|
|
|
|
O Antigravity retorna um erro 429 quando as cotas do projeto/modelo estão esgotadas. A resposta de erro frequentemente contém um `quotaResetDelay` no campo `details`.
|
|
|
|
**Exemplo de Erro 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. Respostas Vazias (Modelos Restritos)
|
|
|
|
Alguns modelos podem aparecer na lista de modelos disponíveis, mas retornar uma resposta vazia (200 OK mas stream SSE vazio). Isso geralmente acontece com modelos em preview ou restritos que o projeto atual não tem permissão para usar.
|
|
|
|
**Tratamento:** Tratar respostas vazias como erros informando ao usuário que o modelo pode estar restrito ou inválido para seu projeto.
|
|
|
|
---
|
|
|
|
## Solução de Problemas
|
|
|
|
### "Token expired" (token expirado)
|
|
- Atualizar tokens OAuth: `picoclaw auth login --provider antigravity`
|
|
|
|
### "Gemini for Google Cloud is not enabled" (Gemini for Google Cloud não está habilitado)
|
|
- Habilitar a API no seu Google Cloud Console
|
|
|
|
### "Project not found" (projeto não encontrado)
|
|
- Verificar se seu projeto Google Cloud tem as APIs necessárias habilitadas
|
|
- Verificar se o ID do projeto foi obtido corretamente durante a autenticação
|
|
|
|
### Modelos não aparecem na lista
|
|
- Verificar se a autenticação OAuth foi concluída com sucesso
|
|
- Verificar o armazenamento do perfil de autenticação: `~/.picoclaw/auth.json`
|
|
- Executar novamente `picoclaw auth login --provider antigravity`
|