import { fetchGeminiUsage } from "../../src/infra/provider-usage.fetch.js"; import { buildOauthProviderAuthResult } from "../../src/plugin-sdk/provider-auth-result.js"; import type { OpenClawPluginApi, ProviderAuthContext, ProviderFetchUsageSnapshotContext, } from "../../src/plugins/types.js"; import { loginGeminiCliOAuth } from "./oauth.js"; import { isModernGoogleModel, resolveGoogle31ForwardCompatModel } from "./provider-models.js"; const PROVIDER_ID = "google-gemini-cli"; const PROVIDER_LABEL = "Gemini CLI OAuth"; const DEFAULT_MODEL = "google-gemini-cli/gemini-3.1-pro-preview"; const ENV_VARS = [ "OPENCLAW_GEMINI_OAUTH_CLIENT_ID", "OPENCLAW_GEMINI_OAUTH_CLIENT_SECRET", "GEMINI_CLI_OAUTH_CLIENT_ID", "GEMINI_CLI_OAUTH_CLIENT_SECRET", ]; function parseGoogleUsageToken(apiKey: string): string { try { const parsed = JSON.parse(apiKey) as { token?: unknown }; if (typeof parsed?.token === "string") { return parsed.token; } } catch { // ignore } return apiKey; } function formatGoogleOauthApiKey(cred: { type?: string; access?: string; projectId?: string; }): string { if (cred.type !== "oauth" || typeof cred.access !== "string" || !cred.access.trim()) { return ""; } return JSON.stringify({ token: cred.access, projectId: cred.projectId, }); } async function fetchGeminiCliUsage(ctx: ProviderFetchUsageSnapshotContext) { return await fetchGeminiUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn, PROVIDER_ID); } export function registerGoogleGeminiCliProvider(api: OpenClawPluginApi) { api.registerProvider({ id: PROVIDER_ID, label: PROVIDER_LABEL, docsPath: "/providers/models", aliases: ["gemini-cli"], envVars: ENV_VARS, auth: [ { id: "oauth", label: "Google OAuth", hint: "PKCE + localhost callback", kind: "oauth", run: async (ctx: ProviderAuthContext) => { await ctx.prompter.note( [ "This is an unofficial integration and is not endorsed by Google.", "Some users have reported account restrictions or suspensions after using third-party Gemini CLI and Antigravity OAuth clients.", "Proceed only if you understand and accept this risk.", ].join("\n"), "Google Gemini CLI caution", ); const proceed = await ctx.prompter.confirm({ message: "Continue with Google Gemini CLI OAuth?", initialValue: false, }); if (!proceed) { await ctx.prompter.note("Skipped Google Gemini CLI OAuth setup.", "Setup skipped"); return { profiles: [] }; } const spin = ctx.prompter.progress("Starting Gemini CLI OAuth…"); try { const result = await loginGeminiCliOAuth({ isRemote: ctx.isRemote, openUrl: ctx.openUrl, log: (msg) => ctx.runtime.log(msg), note: ctx.prompter.note, prompt: async (message) => String(await ctx.prompter.text({ message })), progress: spin, }); spin.stop("Gemini CLI OAuth complete"); return buildOauthProviderAuthResult({ providerId: PROVIDER_ID, defaultModel: DEFAULT_MODEL, access: result.access, refresh: result.refresh, expires: result.expires, email: result.email, credentialExtra: { projectId: result.projectId }, notes: ["If requests fail, set GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID."], }); } catch (err) { spin.stop("Gemini CLI OAuth failed"); await ctx.prompter.note( "Trouble with OAuth? Ensure your Google account has Gemini CLI access.", "OAuth help", ); throw err; } }, }, ], wizard: { setup: { choiceId: "google-gemini-cli", choiceLabel: "Gemini CLI OAuth", choiceHint: "Google OAuth with project-aware token payload", methodId: "oauth", }, }, resolveDynamicModel: (ctx) => resolveGoogle31ForwardCompatModel({ providerId: PROVIDER_ID, ctx }), isModernModelRef: ({ modelId }) => isModernGoogleModel(modelId), formatApiKey: (cred) => formatGoogleOauthApiKey(cred), resolveUsageAuth: async (ctx) => { const auth = await ctx.resolveOAuthToken(); if (!auth) { return null; } return { ...auth, token: parseGoogleUsageToken(auth.token), }; }, fetchUsageSnapshot: async (ctx) => await fetchGeminiCliUsage(ctx), }); }