2025-12-12 22:55:06 +09:00
|
|
|
/**
|
|
|
|
|
* Google Antigravity Auth Plugin for OpenCode
|
|
|
|
|
*
|
|
|
|
|
* Provides OAuth authentication for Google models via Antigravity API.
|
|
|
|
|
* This plugin integrates with OpenCode's auth system to enable:
|
|
|
|
|
* - OAuth 2.0 with PKCE flow for Google authentication
|
|
|
|
|
* - Automatic token refresh
|
|
|
|
|
* - Request/response transformation for Antigravity API
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
* ```json
|
|
|
|
|
* // opencode.json
|
|
|
|
|
* {
|
|
|
|
|
* "plugin": ["oh-my-opencode"],
|
|
|
|
|
* "provider": {
|
|
|
|
|
* "google": {
|
|
|
|
|
* "options": {
|
|
|
|
|
* "clientId": "custom-client-id",
|
|
|
|
|
* "clientSecret": "custom-client-secret"
|
|
|
|
|
* }
|
|
|
|
|
* }
|
|
|
|
|
* }
|
|
|
|
|
* }
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import type { Auth, Provider } from "@opencode-ai/sdk"
|
|
|
|
|
import type { AuthHook, AuthOuathResult, PluginInput } from "@opencode-ai/plugin"
|
|
|
|
|
|
|
|
|
|
import { ANTIGRAVITY_CLIENT_ID, ANTIGRAVITY_CLIENT_SECRET } from "./constants"
|
|
|
|
|
import {
|
|
|
|
|
buildAuthURL,
|
|
|
|
|
exchangeCode,
|
|
|
|
|
startCallbackServer,
|
|
|
|
|
fetchUserInfo,
|
|
|
|
|
} from "./oauth"
|
|
|
|
|
import { createAntigravityFetch } from "./fetch"
|
|
|
|
|
import { fetchProjectContext } from "./project"
|
2026-01-08 22:37:38 +09:00
|
|
|
import { formatTokenForStorage, parseStoredToken } from "./token"
|
|
|
|
|
import { AccountManager } from "./accounts"
|
|
|
|
|
import { loadAccounts } from "./storage"
|
|
|
|
|
import { promptAddAnotherAccount, promptAccountTier } from "./cli"
|
|
|
|
|
import { openBrowserURL } from "./browser"
|
|
|
|
|
import type { AccountTier, AntigravityRefreshParts } from "./types"
|
2025-12-12 22:55:06 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Provider ID for Google models
|
|
|
|
|
* Antigravity is an auth method for Google, not a separate provider
|
|
|
|
|
*/
|
|
|
|
|
const GOOGLE_PROVIDER_ID = "google"
|
|
|
|
|
|
2026-01-08 22:37:38 +09:00
|
|
|
/**
|
|
|
|
|
* Maximum number of Google accounts that can be added
|
|
|
|
|
*/
|
|
|
|
|
const MAX_ACCOUNTS = 10
|
|
|
|
|
|
2025-12-12 22:55:06 +09:00
|
|
|
/**
|
|
|
|
|
* Type guard to check if auth is OAuth type
|
|
|
|
|
*/
|
|
|
|
|
function isOAuthAuth(
|
|
|
|
|
auth: Auth
|
|
|
|
|
): auth is { type: "oauth"; access: string; refresh: string; expires: number } {
|
|
|
|
|
return auth.type === "oauth"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates the Google Antigravity OAuth plugin for OpenCode.
|
|
|
|
|
*
|
|
|
|
|
* This factory function creates an auth plugin that:
|
|
|
|
|
* 1. Provides OAuth flow for Google authentication
|
|
|
|
|
* 2. Creates a custom fetch interceptor for Antigravity API
|
|
|
|
|
* 3. Handles token management and refresh
|
|
|
|
|
*
|
|
|
|
|
* @param input - Plugin input containing the OpenCode client
|
|
|
|
|
* @returns Hooks object with auth configuration
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
* ```typescript
|
|
|
|
|
* // Used by OpenCode automatically when plugin is loaded
|
|
|
|
|
* const hooks = await createGoogleAntigravityAuthPlugin({ client, ... })
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
|
|
|
|
export async function createGoogleAntigravityAuthPlugin({
|
|
|
|
|
client,
|
|
|
|
|
}: PluginInput): Promise<{ auth: AuthHook }> {
|
2025-12-12 23:24:20 +09:00
|
|
|
// Cache for custom credentials from provider.options
|
|
|
|
|
// These are populated by loader() and used by authorize()
|
|
|
|
|
// Falls back to defaults if loader hasn't been called yet
|
|
|
|
|
let cachedClientId: string = ANTIGRAVITY_CLIENT_ID
|
|
|
|
|
let cachedClientSecret: string = ANTIGRAVITY_CLIENT_SECRET
|
|
|
|
|
|
2025-12-12 22:55:06 +09:00
|
|
|
const authHook: AuthHook = {
|
|
|
|
|
/**
|
|
|
|
|
* Provider identifier - must be "google" as Antigravity is
|
|
|
|
|
* an auth method for Google models, not a separate provider
|
|
|
|
|
*/
|
|
|
|
|
provider: GOOGLE_PROVIDER_ID,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Loader function called when auth is needed.
|
|
|
|
|
* Reads credentials from provider.options and creates custom fetch.
|
|
|
|
|
*
|
|
|
|
|
* @param auth - Function to retrieve current auth state
|
|
|
|
|
* @param provider - Provider configuration including options
|
|
|
|
|
* @returns Object with custom fetch function
|
|
|
|
|
*/
|
|
|
|
|
loader: async (
|
|
|
|
|
auth: () => Promise<Auth>,
|
|
|
|
|
provider: Provider
|
|
|
|
|
): Promise<Record<string, unknown>> => {
|
|
|
|
|
const currentAuth = await auth()
|
2025-12-13 00:30:17 +09:00
|
|
|
|
|
|
|
|
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
|
|
|
console.log("[antigravity-plugin] loader called")
|
|
|
|
|
console.log("[antigravity-plugin] auth type:", currentAuth?.type)
|
|
|
|
|
console.log("[antigravity-plugin] auth keys:", Object.keys(currentAuth || {}))
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-12 22:55:06 +09:00
|
|
|
if (!isOAuthAuth(currentAuth)) {
|
2025-12-13 00:30:17 +09:00
|
|
|
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
|
|
|
console.log("[antigravity-plugin] NOT OAuth auth, returning empty")
|
|
|
|
|
}
|
2025-12-12 22:55:06 +09:00
|
|
|
return {}
|
|
|
|
|
}
|
2025-12-13 00:30:17 +09:00
|
|
|
|
|
|
|
|
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
|
|
|
console.log("[antigravity-plugin] OAuth auth detected, creating custom fetch")
|
|
|
|
|
}
|
2025-12-12 22:55:06 +09:00
|
|
|
|
2026-01-08 22:37:38 +09:00
|
|
|
let accountManager: AccountManager | null = null
|
|
|
|
|
try {
|
|
|
|
|
const storedAccounts = await loadAccounts()
|
|
|
|
|
if (storedAccounts) {
|
|
|
|
|
accountManager = new AccountManager(currentAuth, storedAccounts)
|
|
|
|
|
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
|
|
|
console.log(`[antigravity-plugin] Loaded ${accountManager.getAccountCount()} accounts from storage`)
|
|
|
|
|
}
|
|
|
|
|
} else if (currentAuth.refresh.includes("|||")) {
|
|
|
|
|
const tokens = currentAuth.refresh.split("|||")
|
|
|
|
|
const firstToken = tokens[0]!
|
|
|
|
|
accountManager = new AccountManager(
|
|
|
|
|
{ refresh: firstToken, access: currentAuth.access || "", expires: currentAuth.expires || 0 },
|
|
|
|
|
null
|
|
|
|
|
)
|
|
|
|
|
for (let i = 1; i < tokens.length; i++) {
|
|
|
|
|
const parts = parseStoredToken(tokens[i]!)
|
|
|
|
|
accountManager.addAccount(parts)
|
|
|
|
|
}
|
|
|
|
|
await accountManager.save()
|
|
|
|
|
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
|
|
|
console.log("[antigravity-plugin] Migrated multi-account auth to storage")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
|
|
|
console.error(
|
|
|
|
|
`[antigravity-plugin] Failed to load accounts: ${
|
|
|
|
|
error instanceof Error ? error.message : "Unknown error"
|
|
|
|
|
}`
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-12 23:24:20 +09:00
|
|
|
cachedClientId =
|
2025-12-12 22:55:06 +09:00
|
|
|
(provider.options?.clientId as string) || ANTIGRAVITY_CLIENT_ID
|
2025-12-12 23:24:20 +09:00
|
|
|
cachedClientSecret =
|
2025-12-12 22:55:06 +09:00
|
|
|
(provider.options?.clientSecret as string) || ANTIGRAVITY_CLIENT_SECRET
|
|
|
|
|
|
|
|
|
|
// Log if using custom credentials (for debugging)
|
|
|
|
|
if (
|
|
|
|
|
process.env.ANTIGRAVITY_DEBUG === "1" &&
|
2025-12-12 23:24:20 +09:00
|
|
|
(cachedClientId !== ANTIGRAVITY_CLIENT_ID ||
|
|
|
|
|
cachedClientSecret !== ANTIGRAVITY_CLIENT_SECRET)
|
2025-12-12 22:55:06 +09:00
|
|
|
) {
|
|
|
|
|
console.log(
|
|
|
|
|
"[antigravity-plugin] Using custom credentials from provider.options"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create adapter for client.auth.set that matches fetch.ts AuthClient interface
|
|
|
|
|
const authClient = {
|
|
|
|
|
set: async (
|
|
|
|
|
providerId: string,
|
|
|
|
|
authData: { access?: string; refresh?: string; expires?: number }
|
|
|
|
|
) => {
|
|
|
|
|
await client.auth.set({
|
|
|
|
|
body: {
|
|
|
|
|
type: "oauth",
|
|
|
|
|
access: authData.access || "",
|
|
|
|
|
refresh: authData.refresh || "",
|
|
|
|
|
expires: authData.expires || 0,
|
|
|
|
|
},
|
|
|
|
|
path: { id: providerId },
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create auth getter that returns compatible format for fetch.ts
|
|
|
|
|
const getAuth = async (): Promise<{
|
|
|
|
|
access?: string
|
|
|
|
|
refresh?: string
|
|
|
|
|
expires?: number
|
|
|
|
|
}> => {
|
|
|
|
|
const authState = await auth()
|
|
|
|
|
if (isOAuthAuth(authState)) {
|
|
|
|
|
return {
|
|
|
|
|
access: authState.access,
|
|
|
|
|
refresh: authState.refresh,
|
|
|
|
|
expires: authState.expires,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const antigravityFetch = createAntigravityFetch(
|
|
|
|
|
getAuth,
|
|
|
|
|
authClient,
|
2025-12-12 23:24:20 +09:00
|
|
|
GOOGLE_PROVIDER_ID,
|
|
|
|
|
cachedClientId,
|
|
|
|
|
cachedClientSecret
|
2025-12-12 22:55:06 +09:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
fetch: antigravityFetch,
|
2025-12-13 00:30:17 +09:00
|
|
|
apiKey: "antigravity-oauth",
|
2026-01-08 22:37:38 +09:00
|
|
|
accountManager,
|
2025-12-12 22:55:06 +09:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Authentication methods available for this provider.
|
|
|
|
|
* Only OAuth is supported - no prompts for credentials.
|
|
|
|
|
*/
|
|
|
|
|
methods: [
|
|
|
|
|
{
|
|
|
|
|
type: "oauth",
|
|
|
|
|
label: "OAuth with Google (Antigravity)",
|
|
|
|
|
// NO prompts - credentials come from provider.options or defaults
|
|
|
|
|
// OAuth flow starts immediately when user selects this method
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Starts the OAuth authorization flow.
|
|
|
|
|
* Opens browser for Google OAuth and waits for callback.
|
2026-01-08 22:37:38 +09:00
|
|
|
* Supports multi-account flow with prompts for additional accounts.
|
2025-12-12 22:55:06 +09:00
|
|
|
*
|
|
|
|
|
* @returns Authorization result with URL and callback
|
|
|
|
|
*/
|
|
|
|
|
authorize: async (): Promise<AuthOuathResult> => {
|
2025-12-13 00:30:17 +09:00
|
|
|
const serverHandle = startCallbackServer()
|
2026-01-09 02:24:43 +09:00
|
|
|
const { url, state: expectedState } = await buildAuthURL(undefined, cachedClientId, serverHandle.port)
|
2025-12-12 22:55:06 +09:00
|
|
|
|
2026-01-08 22:37:38 +09:00
|
|
|
const browserOpened = await openBrowserURL(url)
|
|
|
|
|
|
2025-12-12 22:55:06 +09:00
|
|
|
return {
|
|
|
|
|
url,
|
2026-01-08 22:37:38 +09:00
|
|
|
instructions: browserOpened
|
|
|
|
|
? "Opening browser for sign-in. We'll automatically detect when you're done."
|
|
|
|
|
: "Please open the URL above in your browser to sign in.",
|
2025-12-12 22:55:06 +09:00
|
|
|
method: "auto",
|
|
|
|
|
|
|
|
|
|
callback: async () => {
|
|
|
|
|
try {
|
2025-12-13 00:30:17 +09:00
|
|
|
const result = await serverHandle.waitForCallback()
|
2025-12-12 22:55:06 +09:00
|
|
|
|
|
|
|
|
if (result.error) {
|
2025-12-12 23:24:20 +09:00
|
|
|
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
2025-12-13 00:30:17 +09:00
|
|
|
console.error(`[antigravity-plugin] OAuth error: ${result.error}`)
|
2025-12-12 23:24:20 +09:00
|
|
|
}
|
2025-12-12 22:55:06 +09:00
|
|
|
return { type: "failed" as const }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!result.code) {
|
2025-12-12 23:24:20 +09:00
|
|
|
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
2025-12-13 00:30:17 +09:00
|
|
|
console.error("[antigravity-plugin] No authorization code received")
|
2025-12-12 23:24:20 +09:00
|
|
|
}
|
2025-12-12 22:55:06 +09:00
|
|
|
return { type: "failed" as const }
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 02:24:43 +09:00
|
|
|
if (result.state !== expectedState) {
|
2025-12-12 23:24:20 +09:00
|
|
|
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
2026-01-09 02:24:43 +09:00
|
|
|
console.error("[antigravity-plugin] State mismatch - possible CSRF attack")
|
2025-12-12 23:24:20 +09:00
|
|
|
}
|
2025-12-12 22:55:06 +09:00
|
|
|
return { type: "failed" as const }
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 02:24:43 +09:00
|
|
|
const redirectUri = `http://localhost:${serverHandle.port}/oauth-callback`
|
|
|
|
|
const tokens = await exchangeCode(result.code, redirectUri, cachedClientId, cachedClientSecret)
|
2025-12-12 22:55:06 +09:00
|
|
|
|
2026-01-08 22:37:38 +09:00
|
|
|
if (!tokens.refresh_token) {
|
|
|
|
|
serverHandle.close()
|
|
|
|
|
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
|
|
|
console.error("[antigravity-plugin] OAuth response missing refresh_token")
|
|
|
|
|
}
|
|
|
|
|
return { type: "failed" as const }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let email: string | undefined
|
2025-12-12 22:55:06 +09:00
|
|
|
try {
|
|
|
|
|
const userInfo = await fetchUserInfo(tokens.access_token)
|
2026-01-08 22:37:38 +09:00
|
|
|
email = userInfo.email
|
2025-12-12 22:55:06 +09:00
|
|
|
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
2026-01-08 22:37:38 +09:00
|
|
|
console.log(`[antigravity-plugin] Authenticated as: ${email}`)
|
2025-12-12 22:55:06 +09:00
|
|
|
}
|
|
|
|
|
} catch {
|
2025-12-13 00:30:17 +09:00
|
|
|
// User info is optional
|
2025-12-12 22:55:06 +09:00
|
|
|
}
|
|
|
|
|
|
2025-12-13 00:30:17 +09:00
|
|
|
const projectContext = await fetchProjectContext(tokens.access_token)
|
2026-01-08 22:37:38 +09:00
|
|
|
const projectId = projectContext.cloudaicompanionProject || ""
|
|
|
|
|
const tier = await promptAccountTier()
|
|
|
|
|
|
|
|
|
|
const expires = Date.now() + tokens.expires_in * 1000
|
|
|
|
|
const accounts: Array<{
|
|
|
|
|
parts: AntigravityRefreshParts
|
|
|
|
|
access: string
|
|
|
|
|
expires: number
|
|
|
|
|
email?: string
|
|
|
|
|
tier: AccountTier
|
|
|
|
|
projectId: string
|
|
|
|
|
}> = [{
|
|
|
|
|
parts: {
|
|
|
|
|
refreshToken: tokens.refresh_token,
|
|
|
|
|
projectId,
|
|
|
|
|
managedProjectId: projectContext.managedProjectId,
|
|
|
|
|
},
|
|
|
|
|
access: tokens.access_token,
|
|
|
|
|
expires,
|
|
|
|
|
email,
|
|
|
|
|
tier,
|
|
|
|
|
projectId,
|
|
|
|
|
}]
|
|
|
|
|
|
|
|
|
|
await client.tui.showToast({
|
|
|
|
|
body: {
|
|
|
|
|
message: `Account 1 authenticated${email ? ` (${email})` : ""}`,
|
|
|
|
|
variant: "success",
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
while (accounts.length < MAX_ACCOUNTS) {
|
|
|
|
|
const addAnother = await promptAddAnotherAccount(accounts.length)
|
|
|
|
|
if (!addAnother) break
|
|
|
|
|
|
|
|
|
|
const additionalServerHandle = startCallbackServer()
|
2026-01-09 02:24:43 +09:00
|
|
|
const { url: additionalUrl, state: expectedAdditionalState } = await buildAuthURL(
|
2026-01-08 22:37:38 +09:00
|
|
|
undefined,
|
|
|
|
|
cachedClientId,
|
|
|
|
|
additionalServerHandle.port
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const additionalBrowserOpened = await openBrowserURL(additionalUrl)
|
|
|
|
|
if (!additionalBrowserOpened) {
|
|
|
|
|
await client.tui.showToast({
|
|
|
|
|
body: {
|
|
|
|
|
message: `Please open in browser: ${additionalUrl}`,
|
|
|
|
|
variant: "warning",
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const additionalResult = await additionalServerHandle.waitForCallback()
|
|
|
|
|
|
|
|
|
|
if (additionalResult.error || !additionalResult.code) {
|
|
|
|
|
additionalServerHandle.close()
|
|
|
|
|
await client.tui.showToast({
|
|
|
|
|
body: {
|
|
|
|
|
message: "Skipping this account...",
|
|
|
|
|
variant: "warning",
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
continue
|
|
|
|
|
}
|
2025-12-12 22:55:06 +09:00
|
|
|
|
2026-01-09 02:24:43 +09:00
|
|
|
if (additionalResult.state !== expectedAdditionalState) {
|
2026-01-08 22:37:38 +09:00
|
|
|
additionalServerHandle.close()
|
|
|
|
|
await client.tui.showToast({
|
|
|
|
|
body: {
|
2026-01-09 02:24:43 +09:00
|
|
|
message: "State mismatch, skipping...",
|
2026-01-08 22:37:38 +09:00
|
|
|
variant: "warning",
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 02:24:43 +09:00
|
|
|
const additionalRedirectUri = `http://localhost:${additionalServerHandle.port}/oauth-callback`
|
2026-01-08 22:37:38 +09:00
|
|
|
const additionalTokens = await exchangeCode(
|
|
|
|
|
additionalResult.code,
|
2026-01-09 02:24:43 +09:00
|
|
|
additionalRedirectUri,
|
2026-01-08 22:37:38 +09:00
|
|
|
cachedClientId,
|
2026-01-09 02:24:43 +09:00
|
|
|
cachedClientSecret
|
2026-01-08 22:37:38 +09:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (!additionalTokens.refresh_token) {
|
|
|
|
|
additionalServerHandle.close()
|
|
|
|
|
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
|
|
|
console.error("[antigravity-plugin] Additional account OAuth response missing refresh_token")
|
|
|
|
|
}
|
|
|
|
|
await client.tui.showToast({
|
|
|
|
|
body: {
|
|
|
|
|
message: "Account missing refresh token, skipping...",
|
|
|
|
|
variant: "warning",
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let additionalEmail: string | undefined
|
|
|
|
|
try {
|
|
|
|
|
const additionalUserInfo = await fetchUserInfo(additionalTokens.access_token)
|
|
|
|
|
additionalEmail = additionalUserInfo.email
|
|
|
|
|
} catch {
|
|
|
|
|
// User info is optional
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const additionalProjectContext = await fetchProjectContext(additionalTokens.access_token)
|
|
|
|
|
const additionalProjectId = additionalProjectContext.cloudaicompanionProject || ""
|
|
|
|
|
const additionalTier = await promptAccountTier()
|
|
|
|
|
|
|
|
|
|
const additionalExpires = Date.now() + additionalTokens.expires_in * 1000
|
|
|
|
|
|
|
|
|
|
accounts.push({
|
|
|
|
|
parts: {
|
|
|
|
|
refreshToken: additionalTokens.refresh_token,
|
|
|
|
|
projectId: additionalProjectId,
|
|
|
|
|
managedProjectId: additionalProjectContext.managedProjectId,
|
|
|
|
|
},
|
|
|
|
|
access: additionalTokens.access_token,
|
|
|
|
|
expires: additionalExpires,
|
|
|
|
|
email: additionalEmail,
|
|
|
|
|
tier: additionalTier,
|
|
|
|
|
projectId: additionalProjectId,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
additionalServerHandle.close()
|
|
|
|
|
|
|
|
|
|
await client.tui.showToast({
|
|
|
|
|
body: {
|
|
|
|
|
message: `Account ${accounts.length} authenticated${additionalEmail ? ` (${additionalEmail})` : ""}`,
|
|
|
|
|
variant: "success",
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
} catch (error) {
|
|
|
|
|
additionalServerHandle.close()
|
|
|
|
|
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
|
|
|
console.error(
|
|
|
|
|
`[antigravity-plugin] Additional account OAuth failed: ${
|
|
|
|
|
error instanceof Error ? error.message : "Unknown error"
|
|
|
|
|
}`
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
await client.tui.showToast({
|
|
|
|
|
body: {
|
|
|
|
|
message: "Failed to authenticate additional account, skipping...",
|
|
|
|
|
variant: "warning",
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const firstAccount = accounts[0]!
|
|
|
|
|
try {
|
|
|
|
|
const accountManager = new AccountManager(
|
|
|
|
|
{
|
|
|
|
|
refresh: formatTokenForStorage(
|
|
|
|
|
firstAccount.parts.refreshToken,
|
|
|
|
|
firstAccount.projectId,
|
|
|
|
|
firstAccount.parts.managedProjectId
|
|
|
|
|
),
|
|
|
|
|
access: firstAccount.access,
|
|
|
|
|
expires: firstAccount.expires,
|
|
|
|
|
},
|
|
|
|
|
null
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for (let i = 1; i < accounts.length; i++) {
|
|
|
|
|
const acc = accounts[i]!
|
|
|
|
|
accountManager.addAccount(
|
|
|
|
|
acc.parts,
|
|
|
|
|
acc.access,
|
|
|
|
|
acc.expires,
|
|
|
|
|
acc.email,
|
|
|
|
|
acc.tier
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const currentAccount = accountManager.getCurrentAccount()
|
|
|
|
|
if (currentAccount) {
|
|
|
|
|
currentAccount.email = firstAccount.email
|
|
|
|
|
currentAccount.tier = firstAccount.tier
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await accountManager.save()
|
|
|
|
|
|
|
|
|
|
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
|
|
|
console.log(`[antigravity-plugin] Saved ${accounts.length} accounts to storage`)
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
|
|
|
console.error(
|
|
|
|
|
`[antigravity-plugin] Failed to save accounts: ${
|
|
|
|
|
error instanceof Error ? error.message : "Unknown error"
|
|
|
|
|
}`
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const allRefreshTokens = accounts
|
|
|
|
|
.map((acc) => formatTokenForStorage(
|
|
|
|
|
acc.parts.refreshToken,
|
|
|
|
|
acc.projectId,
|
|
|
|
|
acc.parts.managedProjectId
|
|
|
|
|
))
|
|
|
|
|
.join("|||")
|
2025-12-12 22:55:06 +09:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
type: "success" as const,
|
2026-01-08 22:37:38 +09:00
|
|
|
access: firstAccount.access,
|
|
|
|
|
refresh: allRefreshTokens,
|
|
|
|
|
expires: firstAccount.expires,
|
2025-12-12 22:55:06 +09:00
|
|
|
}
|
|
|
|
|
} catch (error) {
|
2025-12-13 00:30:17 +09:00
|
|
|
serverHandle.close()
|
2025-12-12 23:24:20 +09:00
|
|
|
if (process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
|
|
|
console.error(
|
|
|
|
|
`[antigravity-plugin] OAuth flow failed: ${
|
|
|
|
|
error instanceof Error ? error.message : "Unknown error"
|
|
|
|
|
}`
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-12-12 22:55:06 +09:00
|
|
|
return { type: "failed" as const }
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
auth: authHook,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Default export for OpenCode plugin system
|
|
|
|
|
*/
|
|
|
|
|
export default createGoogleAntigravityAuthPlugin
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Named export for explicit imports
|
|
|
|
|
*/
|
|
|
|
|
export const GoogleAntigravityAuthPlugin = createGoogleAntigravityAuthPlugin
|