- Add OAuth handler with PKCE flow and local callback server - Automatic project discovery via loadCodeAssist/onboardUser endpoints - Store credentials with projectId for API calls - Encode token+projectId as JSON for provider to decode - Register as 'google-cloud-code-assist' OAuth provider
9.7 KiB
Google Cloud Code Assist Provider Implementation Plan
Overview
Add support for Gemini CLI / Antigravity authentication, which uses Google's Cloud Code Assist API (cloudcode-pa.googleapis.com) to access Gemini and Claude models through a unified gateway.
References
- Antigravity API Spec: https://github.com/NoeFabris/opencode-antigravity-auth/blob/main/docs/ANTIGRAVITY_API_SPEC.md
- Gemini CLI Auth: https://github.com/jenslys/opencode-gemini-auth
- Antigravity Auth: https://github.com/NoeFabris/opencode-antigravity-auth
Key Differences from Standard Google Provider
- Endpoint:
cloudcode-pa.googleapis.com/v1internal:streamGenerateContent?alt=sse - Auth: OAuth token (not API key)
- Request format: Wrapped in
{ project, model, request: {...} } - Response format: Wrapped in
{ response: {...} }(needs unwrapping) - Headers: Requires
User-Agent,X-Goog-Api-Client,Client-Metadata
OAuth Details
- Client ID:
681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com - Client Secret:
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 - Redirect URI:
http://localhost:8085/oauth2callback - Scopes:
cloud-platform,userinfo.email,userinfo.profile - Token URL:
https://oauth2.googleapis.com/token - Auth URL:
https://accounts.google.com/o/oauth2/v2/auth
Available Models
| Model ID | Type | Context | Output | Reasoning |
|---|---|---|---|---|
| gemini-3-pro-high | Gemini | 1M | 64K | Yes |
| gemini-3-pro-low | Gemini | 1M | 64K | Yes |
| gemini-3-flash | Gemini | 1M | 64K | No |
| claude-sonnet-4-5 | Claude | 200K | 64K | No |
| claude-sonnet-4-5-thinking | Claude | 200K | 64K | Yes |
| claude-opus-4-5-thinking | Claude | 200K | 64K | Yes |
| gpt-oss-120b-medium | GPT-OSS | 128K | 32K | No |
All models support: text, image, pdf input; text output; cost is $0 (uses Google account quota)
Implementation Steps
Phase 1: AI Provider (COMPLETED)
Steps 1-8 completed. The provider is implemented in:
packages/ai/src/providers/google-cloud-code-assist.tspackages/ai/src/providers/google-shared.ts- Models added to
packages/ai/scripts/generate-models.ts
Step 1: Update types.ts
File: packages/ai/src/types.ts
Add new API type:
export type Api = "openai-completions" | "openai-responses" | "anthropic-messages" | "google-generative-ai" | "google-cloud-code-assist";
Step 2: Create google-shared.ts
File: packages/ai/src/providers/google-shared.ts
Extract from google.ts:
convertMessages()- convert internal messages to Gemini Content[] formatconvertTools()- convert tools to Gemini function declarationsmapToolChoice()- map tool choice to Gemini enummapStopReason()- map Gemini finish reason to our stop reason- Shared types and imports
Make functions generic to work with both google-generative-ai and google-cloud-code-assist API types.
Step 3: Update google.ts
File: packages/ai/src/providers/google.ts
- Import shared functions from
google-shared.ts - Remove extracted functions
- Keep:
createClient(),buildParams(),streamGoogle()
Step 4: Create google-cloud-code-assist.ts
File: packages/ai/src/providers/google-cloud-code-assist.ts
Implement:
export interface GoogleCloudCodeAssistOptions extends StreamOptions {
toolChoice?: "auto" | "none" | "any";
thinking?: {
enabled: boolean;
budgetTokens?: number;
};
projectId?: string; // Google Cloud project ID
}
export const streamGoogleCloudCodeAssist: StreamFunction<"google-cloud-code-assist"> = (
model: Model<"google-cloud-code-assist">,
context: Context,
options?: GoogleCloudCodeAssistOptions,
): AssistantMessageEventStream => {
// Implementation
};
Key implementation details:
-
Build request body:
{ "project": "{projectId}", "model": "{modelId}", "request": { "contents": [...], "systemInstruction": { "parts": [{ "text": "..." }] }, "generationConfig": { ... }, "tools": [...] } } -
Headers:
Authorization: Bearer {accessToken} Content-Type: application/json Accept: text/event-stream User-Agent: google-api-nodejs-client/9.15.1 X-Goog-Api-Client: gl-node/22.17.0 Client-Metadata: ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI -
Endpoint:
https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent?alt=sse -
Parse SSE response:
- Each line:
data: {"response": {...}, "traceId": "..."} - Extract
responseobject, which has same structure as standard Gemini response - Handle thinking parts with
thought: trueandthoughtSignature
- Each line:
-
Use shared functions for message/tool conversion and stop reason mapping
Step 5: Update stream.ts
File: packages/ai/src/stream.ts
Add case for new provider:
import { streamGoogleCloudCodeAssist } from "./providers/google-cloud-code-assist.js";
// In the switch/case or if/else chain:
case "google-cloud-code-assist":
return streamGoogleCloudCodeAssist(model, context, {
...options,
// map reasoning to thinking config
});
Step 6: Update models.ts
File: packages/ai/src/models.ts
Add to xhighSupportedModels if applicable (check if any models support xhigh).
Step 7: Add models to generate-models.ts
File: packages/ai/scripts/generate-models.ts
Add hardcoded models:
const googleCloudCodeAssistModels: Model<"google-cloud-code-assist">[] = [
{
id: "gemini-3-pro-high",
provider: "google-cloud-code-assist",
api: "google-cloud-code-assist",
name: "Gemini 3 Pro High",
contextWindow: 1048576,
maxOutputTokens: 65535,
pricing: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
input: ["text", "image", "pdf"],
output: ["text"],
reasoning: true,
},
// ... other models
];
Step 8: Update index.ts exports
File: packages/ai/src/index.ts
Export new provider:
export { streamGoogleCloudCodeAssist, type GoogleCloudCodeAssistOptions } from "./providers/google-cloud-code-assist.js";
Phase 2: OAuth Flow in coding-agent
Step 9: Create google-cloud.ts OAuth handler
File: packages/coding-agent/src/core/oauth/google-cloud.ts
Implement:
import { createHash, randomBytes } from "crypto";
import { createServer } from "http";
import { type OAuthCredentials, saveOAuthCredentials } from "./storage.js";
const CLIENT_ID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com";
const CLIENT_SECRET = "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";
const REDIRECT_URI = "http://localhost:8085/oauth2callback";
const SCOPES = [
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
];
const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
const TOKEN_URL = "https://oauth2.googleapis.com/token";
export async function loginGoogleCloud(
onAuth: (info: { url: string; instructions?: string }) => void,
onProgress?: (message: string) => void,
): Promise<OAuthCredentials & { projectId?: string }> {
// 1. Generate PKCE
// 2. Start local server on port 8085
// 3. Build auth URL and call onAuth
// 4. Wait for callback with code
// 5. Exchange code for tokens
// 6. Discover/provision project via loadCodeAssist endpoint
// 7. Return credentials with projectId
}
export async function refreshGoogleCloudToken(refreshToken: string): Promise<OAuthCredentials> {
// Refresh token flow
}
// Project discovery
async function discoverProject(accessToken: string): Promise<string> {
// Call /v1internal:loadCodeAssist to get project ID
// Or /v1internal:onboardUser if needed
}
Step 10: Update oauth/index.ts
File: packages/coding-agent/src/core/oauth/index.ts
- Add
"google-cloud"toSupportedOAuthProvider - Add to
getOAuthProviders()list - Add case in
login()function - Add case in
refreshToken()function
Step 11: Update oauth/storage.ts
File: packages/coding-agent/src/core/oauth/storage.ts
Extend OAuthCredentials to include optional projectId:
export interface OAuthCredentials {
type: "oauth";
refresh: string;
access: string;
expires: number;
enterpriseUrl?: string;
projectId?: string; // For Google Cloud
}
Step 12: Update model-config.ts
File: packages/coding-agent/src/core/model-config.ts
Add logic to get API key for google-cloud-code-assist provider:
- Check for OAuth token via
getOAuthToken("google-cloud") - Return the access token as the "API key"
Phase 3: Testing
Manual Testing
- Run
piand use/loginto authenticate with Google - Select a google-cloud-code-assist model
- Send a message and verify streaming works
- Test tool calling
- Test thinking models
Verification Points
- OAuth flow completes successfully
- Token refresh works
- Streaming text works
- Thinking blocks are parsed correctly
- Tool calls work
- Tool results are sent correctly
- Error handling works (rate limits, auth errors)
Notes
systemInstruction Format
Must be object with parts, NOT plain string:
{
"systemInstruction": {
"parts": [{ "text": "You are a helpful assistant." }]
}
}
Tool Name Rules
- Must start with letter or underscore
- Allowed: a-zA-Z0-9, underscores, dots, colons, dashes
- Max 64 chars
- No slashes or spaces
Thinking Config
{
"generationConfig": {
"thinkingConfig": {
"thinkingBudget": 8000,
"includeThoughts": true
}
}
}
Response Unwrapping
SSE lines come as:
data: {"response": {...}, "traceId": "..."}
Need to extract response object which matches standard Gemini format.