Merge f4c35815c5 into 09be5d45d5
This commit is contained in:
commit
2e8c2bdb15
@ -435,3 +435,6 @@ const antigravityPlugin = {
|
||||
};
|
||||
|
||||
export default antigravityPlugin;
|
||||
|
||||
// Re-export refresh function
|
||||
export { refreshGoogleAntigravityCredentials } from "./refresh.js";
|
||||
|
||||
61
extensions/google-antigravity-auth/refresh.ts
Normal file
61
extensions/google-antigravity-auth/refresh.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
const GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
||||
|
||||
// OAuth constants - decoded from pi-ai's base64 encoded values to stay in sync
|
||||
const decode = (s: string) => Buffer.from(s, "base64").toString();
|
||||
const CLIENT_ID = decode(
|
||||
"MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==",
|
||||
);
|
||||
const CLIENT_SECRET = decode("R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY=");
|
||||
|
||||
export type GoogleAntigravityCredentials = {
|
||||
access: string;
|
||||
refresh: string;
|
||||
expires: number;
|
||||
};
|
||||
|
||||
export async function refreshGoogleAntigravityCredentials(
|
||||
credentials: GoogleAntigravityCredentials,
|
||||
): Promise<GoogleAntigravityCredentials> {
|
||||
if (!credentials.refresh?.trim()) {
|
||||
throw new Error("Google Antigravity OAuth refresh token missing; re-authenticate.");
|
||||
}
|
||||
|
||||
const response = await fetch(GOOGLE_TOKEN_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: credentials.refresh,
|
||||
client_id: CLIENT_ID,
|
||||
client_secret: CLIENT_SECRET,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
if (response.status === 400 || response.status === 401) {
|
||||
throw new Error(
|
||||
`Google Antigravity OAuth refresh token expired or invalid. Re-authenticate with \`clawdbot models auth login --provider google-antigravity\`.`,
|
||||
);
|
||||
}
|
||||
throw new Error(`Google Antigravity OAuth refresh failed: ${text || response.statusText}`);
|
||||
}
|
||||
|
||||
const payload = await response.json();
|
||||
if (!payload.access_token || !payload.expires_in) {
|
||||
throw new Error("Google Antigravity OAuth refresh response missing access token.");
|
||||
}
|
||||
|
||||
return {
|
||||
...credentials,
|
||||
access: payload.access_token,
|
||||
refresh: payload.refresh_token || credentials.refresh,
|
||||
expires: Date.now() + payload.expires_in * 1000,
|
||||
};
|
||||
}
|
||||
@ -508,6 +508,9 @@ async function pollOperation(
|
||||
throw new Error("Operation polling timeout");
|
||||
}
|
||||
|
||||
// Re-export refresh function
|
||||
export { refreshGoogleGeminiCliCredentials } from "./refresh.js";
|
||||
|
||||
export async function loginGeminiCliOAuth(ctx: GeminiCliOAuthContext): Promise<GeminiCliOAuthCredentials> {
|
||||
const needsManual = shouldUseManualOAuthFlow(ctx.isRemote);
|
||||
await ctx.note(
|
||||
|
||||
125
extensions/google-gemini-cli-auth/refresh.ts
Normal file
125
extensions/google-gemini-cli-auth/refresh.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
||||
import { delimiter, dirname, join } from "node:path";
|
||||
|
||||
const GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
||||
|
||||
const CLIENT_ID_KEYS = ["CLAWDBOT_GEMINI_OAUTH_CLIENT_ID", "GEMINI_CLI_OAUTH_CLIENT_ID"];
|
||||
const CLIENT_SECRET_KEYS = [
|
||||
"CLAWDBOT_GEMINI_OAUTH_CLIENT_SECRET",
|
||||
"GEMINI_CLI_OAUTH_CLIENT_SECRET",
|
||||
];
|
||||
|
||||
export type GeminiCliOAuthCredentials = {
|
||||
access: string;
|
||||
refresh: string;
|
||||
expires: number;
|
||||
email?: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
function resolveEnv(keys: string[]): string | undefined {
|
||||
for (const key of keys) {
|
||||
const value = process.env[key]?.trim();
|
||||
if (value) return value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function findInPath(name: string): string | null {
|
||||
const exts = process.platform === "win32" ? [".cmd", ".bat", ".exe", ""] : [""];
|
||||
const paths = process.env.PATH?.split(delimiter) || [];
|
||||
for (const dir of paths) {
|
||||
for (const ext of exts) {
|
||||
const full = join(dir, name + ext);
|
||||
if (existsSync(full)) return full;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveCredentials(): { clientId: string; clientSecret: string | undefined } {
|
||||
const envClientId = resolveEnv(CLIENT_ID_KEYS);
|
||||
const envClientSecret = resolveEnv(CLIENT_SECRET_KEYS);
|
||||
if (envClientId) {
|
||||
return { clientId: envClientId, clientSecret: envClientSecret };
|
||||
}
|
||||
|
||||
// Try to extract from Gemini CLI
|
||||
try {
|
||||
const geminiPath = findInPath("gemini");
|
||||
if (geminiPath) {
|
||||
const resolvedPath = realpathSync(geminiPath);
|
||||
const geminiCliDir = dirname(dirname(resolvedPath));
|
||||
const searchPaths = [
|
||||
join(geminiCliDir, "node_modules", "@google", "gemini-cli-core", "dist", "src", "code_assist", "oauth2.js"),
|
||||
join(geminiCliDir, "node_modules", "@google", "gemini-cli-core", "dist", "code_assist", "oauth2.js"),
|
||||
];
|
||||
for (const p of searchPaths) {
|
||||
if (existsSync(p)) {
|
||||
const content = readFileSync(p, "utf8");
|
||||
const idMatch = content.match(/(\d+-[a-z0-9]+\.apps\.googleusercontent\.com)/);
|
||||
const secretMatch = content.match(/(GOCSPX-[A-Za-z0-9_-]+)/);
|
||||
if (idMatch && secretMatch) {
|
||||
return { clientId: idMatch[1], clientSecret: secretMatch[1] };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Extraction failed
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"Gemini CLI OAuth credentials not found. Set GEMINI_CLI_OAUTH_CLIENT_ID and GEMINI_CLI_OAUTH_CLIENT_SECRET, or ensure gemini-cli is installed.",
|
||||
);
|
||||
}
|
||||
|
||||
export async function refreshGoogleGeminiCliCredentials(
|
||||
credentials: GeminiCliOAuthCredentials,
|
||||
): Promise<GeminiCliOAuthCredentials> {
|
||||
if (!credentials.refresh?.trim()) {
|
||||
throw new Error("Google Gemini CLI OAuth refresh token missing; re-authenticate.");
|
||||
}
|
||||
|
||||
const { clientId, clientSecret } = resolveCredentials();
|
||||
|
||||
const body = new URLSearchParams({
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: credentials.refresh,
|
||||
client_id: clientId,
|
||||
});
|
||||
if (clientSecret) {
|
||||
body.set("client_secret", clientSecret);
|
||||
}
|
||||
|
||||
const response = await fetch(GOOGLE_TOKEN_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
if (response.status === 400 || response.status === 401) {
|
||||
throw new Error(
|
||||
`Google Gemini CLI OAuth refresh token expired or invalid. Re-authenticate with \`clawdbot models auth login --provider google-gemini-cli\`.`,
|
||||
);
|
||||
}
|
||||
throw new Error(`Google Gemini CLI OAuth refresh failed: ${text || response.statusText}`);
|
||||
}
|
||||
|
||||
const payload = await response.json();
|
||||
if (!payload.access_token || !payload.expires_in) {
|
||||
throw new Error("Google Gemini CLI OAuth refresh response missing access token.");
|
||||
}
|
||||
|
||||
return {
|
||||
...credentials,
|
||||
access: payload.access_token,
|
||||
refresh: payload.refresh_token || credentials.refresh,
|
||||
expires: Date.now() + payload.expires_in * 1000,
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user