mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 11:38:38 +00:00
fix(gemini-oauth): align OAuth project discovery metadata and endpoint fallbacks (#16684)
* fix(gemini-oauth): align loadCodeAssist metadata and endpoint fallback * test(gemini-oauth): cover endpoint fallback and env project fallback * fix(gemini-oauth): route timed fetches through ssrf guard * test(gemini-oauth): mock guarded fetch in oauth tests
This commit is contained in:
@@ -3,6 +3,19 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
|||||||
|
|
||||||
vi.mock("openclaw/plugin-sdk", () => ({
|
vi.mock("openclaw/plugin-sdk", () => ({
|
||||||
isWSL2Sync: () => false,
|
isWSL2Sync: () => false,
|
||||||
|
fetchWithSsrFGuard: async (params: {
|
||||||
|
url: string;
|
||||||
|
init?: RequestInit;
|
||||||
|
fetchImpl?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
||||||
|
}) => {
|
||||||
|
const fetchImpl = params.fetchImpl ?? globalThis.fetch;
|
||||||
|
const response = await fetchImpl(params.url, params.init);
|
||||||
|
return {
|
||||||
|
response,
|
||||||
|
finalUrl: params.url,
|
||||||
|
release: async () => {},
|
||||||
|
};
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Mock fs module before importing the module under test
|
// Mock fs module before importing the module under test
|
||||||
@@ -208,3 +221,204 @@ describe("extractGeminiCliCredentials", () => {
|
|||||||
expect(mockReadFileSync.mock.calls.length).toBe(readCount);
|
expect(mockReadFileSync.mock.calls.length).toBe(readCount);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("loginGeminiCliOAuth", () => {
|
||||||
|
const TOKEN_URL = "https://oauth2.googleapis.com/token";
|
||||||
|
const USERINFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json";
|
||||||
|
const LOAD_PROD = "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist";
|
||||||
|
const LOAD_DAILY = "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:loadCodeAssist";
|
||||||
|
const LOAD_AUTOPUSH =
|
||||||
|
"https://autopush-cloudcode-pa.sandbox.googleapis.com/v1internal:loadCodeAssist";
|
||||||
|
|
||||||
|
const ENV_KEYS = [
|
||||||
|
"OPENCLAW_GEMINI_OAUTH_CLIENT_ID",
|
||||||
|
"OPENCLAW_GEMINI_OAUTH_CLIENT_SECRET",
|
||||||
|
"GEMINI_CLI_OAUTH_CLIENT_ID",
|
||||||
|
"GEMINI_CLI_OAUTH_CLIENT_SECRET",
|
||||||
|
"GOOGLE_CLOUD_PROJECT",
|
||||||
|
"GOOGLE_CLOUD_PROJECT_ID",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
function getExpectedPlatform(): "WINDOWS" | "MACOS" | "LINUX" {
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
return "WINDOWS";
|
||||||
|
}
|
||||||
|
if (process.platform === "linux") {
|
||||||
|
return "LINUX";
|
||||||
|
}
|
||||||
|
return "MACOS";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRequestUrl(input: string | URL | Request): string {
|
||||||
|
return typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHeaderValue(headers: HeadersInit | undefined, name: string): string | undefined {
|
||||||
|
if (!headers) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (headers instanceof Headers) {
|
||||||
|
return headers.get(name) ?? undefined;
|
||||||
|
}
|
||||||
|
if (Array.isArray(headers)) {
|
||||||
|
return headers.find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
|
||||||
|
}
|
||||||
|
return (headers as Record<string, string>)[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
function responseJson(body: unknown, status = 200): Response {
|
||||||
|
return new Response(JSON.stringify(body), {
|
||||||
|
status,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let envSnapshot: Partial<Record<(typeof ENV_KEYS)[number], string>>;
|
||||||
|
beforeEach(() => {
|
||||||
|
envSnapshot = Object.fromEntries(ENV_KEYS.map((key) => [key, process.env[key]]));
|
||||||
|
process.env.OPENCLAW_GEMINI_OAUTH_CLIENT_ID = "test-client-id.apps.googleusercontent.com";
|
||||||
|
process.env.OPENCLAW_GEMINI_OAUTH_CLIENT_SECRET = "GOCSPX-test-client-secret";
|
||||||
|
delete process.env.GEMINI_CLI_OAUTH_CLIENT_ID;
|
||||||
|
delete process.env.GEMINI_CLI_OAUTH_CLIENT_SECRET;
|
||||||
|
delete process.env.GOOGLE_CLOUD_PROJECT;
|
||||||
|
delete process.env.GOOGLE_CLOUD_PROJECT_ID;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
for (const key of ENV_KEYS) {
|
||||||
|
const value = envSnapshot[key];
|
||||||
|
if (value === undefined) {
|
||||||
|
delete process.env[key];
|
||||||
|
} else {
|
||||||
|
process.env[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vi.unstubAllGlobals();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back across loadCodeAssist endpoints with aligned headers and metadata", async () => {
|
||||||
|
const requests: Array<{ url: string; init?: RequestInit }> = [];
|
||||||
|
const fetchMock = vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
|
||||||
|
const url = getRequestUrl(input);
|
||||||
|
requests.push({ url, init });
|
||||||
|
|
||||||
|
if (url === TOKEN_URL) {
|
||||||
|
return responseJson({
|
||||||
|
access_token: "access-token",
|
||||||
|
refresh_token: "refresh-token",
|
||||||
|
expires_in: 3600,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (url === USERINFO_URL) {
|
||||||
|
return responseJson({ email: "lobster@openclaw.ai" });
|
||||||
|
}
|
||||||
|
if (url === LOAD_PROD) {
|
||||||
|
return responseJson({ error: { message: "temporary failure" } }, 503);
|
||||||
|
}
|
||||||
|
if (url === LOAD_DAILY) {
|
||||||
|
return responseJson({
|
||||||
|
currentTier: { id: "standard-tier" },
|
||||||
|
cloudaicompanionProject: { id: "daily-project" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new Error(`Unexpected request: ${url}`);
|
||||||
|
});
|
||||||
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
|
||||||
|
let authUrl = "";
|
||||||
|
const { loginGeminiCliOAuth } = await import("./oauth.js");
|
||||||
|
const result = await loginGeminiCliOAuth({
|
||||||
|
isRemote: true,
|
||||||
|
openUrl: async () => {},
|
||||||
|
log: (msg) => {
|
||||||
|
const found = msg.match(/https:\/\/accounts\.google\.com\/o\/oauth2\/v2\/auth\?[^\s]+/);
|
||||||
|
if (found?.[0]) {
|
||||||
|
authUrl = found[0];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
note: async () => {},
|
||||||
|
prompt: async () => {
|
||||||
|
const state = new URL(authUrl).searchParams.get("state");
|
||||||
|
return `${"http://localhost:8085/oauth2callback"}?code=oauth-code&state=${state}`;
|
||||||
|
},
|
||||||
|
progress: { update: () => {}, stop: () => {} },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.projectId).toBe("daily-project");
|
||||||
|
const loadRequests = requests.filter((request) =>
|
||||||
|
request.url.includes("v1internal:loadCodeAssist"),
|
||||||
|
);
|
||||||
|
expect(loadRequests.map((request) => request.url)).toEqual([LOAD_PROD, LOAD_DAILY]);
|
||||||
|
|
||||||
|
const firstHeaders = loadRequests[0]?.init?.headers;
|
||||||
|
expect(getHeaderValue(firstHeaders, "X-Goog-Api-Client")).toBe(
|
||||||
|
`gl-node/${process.versions.node}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const clientMetadata = getHeaderValue(firstHeaders, "Client-Metadata");
|
||||||
|
expect(clientMetadata).toBeDefined();
|
||||||
|
expect(JSON.parse(clientMetadata as string)).toEqual({
|
||||||
|
ideType: "ANTIGRAVITY",
|
||||||
|
platform: getExpectedPlatform(),
|
||||||
|
pluginType: "GEMINI",
|
||||||
|
});
|
||||||
|
|
||||||
|
const body = JSON.parse(String(loadRequests[0]?.init?.body));
|
||||||
|
expect(body).toEqual({
|
||||||
|
metadata: {
|
||||||
|
ideType: "ANTIGRAVITY",
|
||||||
|
platform: getExpectedPlatform(),
|
||||||
|
pluginType: "GEMINI",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to GOOGLE_CLOUD_PROJECT when all loadCodeAssist endpoints fail", async () => {
|
||||||
|
process.env.GOOGLE_CLOUD_PROJECT = "env-project";
|
||||||
|
|
||||||
|
const requests: string[] = [];
|
||||||
|
const fetchMock = vi.fn(async (input: string | URL | Request) => {
|
||||||
|
const url = getRequestUrl(input);
|
||||||
|
requests.push(url);
|
||||||
|
|
||||||
|
if (url === TOKEN_URL) {
|
||||||
|
return responseJson({
|
||||||
|
access_token: "access-token",
|
||||||
|
refresh_token: "refresh-token",
|
||||||
|
expires_in: 3600,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (url === USERINFO_URL) {
|
||||||
|
return responseJson({ email: "lobster@openclaw.ai" });
|
||||||
|
}
|
||||||
|
if ([LOAD_PROD, LOAD_DAILY, LOAD_AUTOPUSH].includes(url)) {
|
||||||
|
return responseJson({ error: { message: "unavailable" } }, 503);
|
||||||
|
}
|
||||||
|
throw new Error(`Unexpected request: ${url}`);
|
||||||
|
});
|
||||||
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
|
||||||
|
let authUrl = "";
|
||||||
|
const { loginGeminiCliOAuth } = await import("./oauth.js");
|
||||||
|
const result = await loginGeminiCliOAuth({
|
||||||
|
isRemote: true,
|
||||||
|
openUrl: async () => {},
|
||||||
|
log: (msg) => {
|
||||||
|
const found = msg.match(/https:\/\/accounts\.google\.com\/o\/oauth2\/v2\/auth\?[^\s]+/);
|
||||||
|
if (found?.[0]) {
|
||||||
|
authUrl = found[0];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
note: async () => {},
|
||||||
|
prompt: async () => {
|
||||||
|
const state = new URL(authUrl).searchParams.get("state");
|
||||||
|
return `${"http://localhost:8085/oauth2callback"}?code=oauth-code&state=${state}`;
|
||||||
|
},
|
||||||
|
progress: { update: () => {}, stop: () => {} },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.projectId).toBe("env-project");
|
||||||
|
expect(requests.filter((url) => url.includes("v1internal:loadCodeAssist"))).toHaveLength(3);
|
||||||
|
expect(requests.some((url) => url.includes("v1internal:onboardUser"))).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|||||||
import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
||||||
import { createServer } from "node:http";
|
import { createServer } from "node:http";
|
||||||
import { delimiter, dirname, join } from "node:path";
|
import { delimiter, dirname, join } from "node:path";
|
||||||
import { isWSL2Sync } from "openclaw/plugin-sdk";
|
import { fetchWithSsrFGuard, isWSL2Sync } from "openclaw/plugin-sdk";
|
||||||
|
|
||||||
const CLIENT_ID_KEYS = ["OPENCLAW_GEMINI_OAUTH_CLIENT_ID", "GEMINI_CLI_OAUTH_CLIENT_ID"];
|
const CLIENT_ID_KEYS = ["OPENCLAW_GEMINI_OAUTH_CLIENT_ID", "GEMINI_CLI_OAUTH_CLIENT_ID"];
|
||||||
const CLIENT_SECRET_KEYS = [
|
const CLIENT_SECRET_KEYS = [
|
||||||
@@ -13,7 +13,15 @@ const REDIRECT_URI = "http://localhost:8085/oauth2callback";
|
|||||||
const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
||||||
const TOKEN_URL = "https://oauth2.googleapis.com/token";
|
const TOKEN_URL = "https://oauth2.googleapis.com/token";
|
||||||
const USERINFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json";
|
const USERINFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json";
|
||||||
const CODE_ASSIST_ENDPOINT = "https://cloudcode-pa.googleapis.com";
|
const CODE_ASSIST_ENDPOINT_PROD = "https://cloudcode-pa.googleapis.com";
|
||||||
|
const CODE_ASSIST_ENDPOINT_DAILY = "https://daily-cloudcode-pa.sandbox.googleapis.com";
|
||||||
|
const CODE_ASSIST_ENDPOINT_AUTOPUSH = "https://autopush-cloudcode-pa.sandbox.googleapis.com";
|
||||||
|
const LOAD_CODE_ASSIST_ENDPOINTS = [
|
||||||
|
CODE_ASSIST_ENDPOINT_PROD,
|
||||||
|
CODE_ASSIST_ENDPOINT_DAILY,
|
||||||
|
CODE_ASSIST_ENDPOINT_AUTOPUSH,
|
||||||
|
];
|
||||||
|
const DEFAULT_FETCH_TIMEOUT_MS = 10_000;
|
||||||
const SCOPES = [
|
const SCOPES = [
|
||||||
"https://www.googleapis.com/auth/cloud-platform",
|
"https://www.googleapis.com/auth/cloud-platform",
|
||||||
"https://www.googleapis.com/auth/userinfo.email",
|
"https://www.googleapis.com/auth/userinfo.email",
|
||||||
@@ -216,6 +224,38 @@ function generatePkce(): { verifier: string; challenge: string } {
|
|||||||
return { verifier, challenge };
|
return { verifier, challenge };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolvePlatform(): "WINDOWS" | "MACOS" | "LINUX" {
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
return "WINDOWS";
|
||||||
|
}
|
||||||
|
if (process.platform === "linux") {
|
||||||
|
return "LINUX";
|
||||||
|
}
|
||||||
|
return "MACOS";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchWithTimeout(
|
||||||
|
url: string,
|
||||||
|
init: RequestInit,
|
||||||
|
timeoutMs = DEFAULT_FETCH_TIMEOUT_MS,
|
||||||
|
): Promise<Response> {
|
||||||
|
const { response, release } = await fetchWithSsrFGuard({
|
||||||
|
url,
|
||||||
|
init,
|
||||||
|
timeoutMs,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const body = await response.arrayBuffer();
|
||||||
|
return new Response(body, {
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
headers: response.headers,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
await release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function buildAuthUrl(challenge: string, verifier: string): string {
|
function buildAuthUrl(challenge: string, verifier: string): string {
|
||||||
const { clientId } = resolveOAuthClientConfig();
|
const { clientId } = resolveOAuthClientConfig();
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
@@ -369,9 +409,13 @@ async function exchangeCodeForTokens(
|
|||||||
body.set("client_secret", clientSecret);
|
body.set("client_secret", clientSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(TOKEN_URL, {
|
const response = await fetchWithTimeout(TOKEN_URL, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
||||||
|
Accept: "*/*",
|
||||||
|
"User-Agent": "google-api-nodejs-client/9.15.1",
|
||||||
|
},
|
||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -405,7 +449,7 @@ async function exchangeCodeForTokens(
|
|||||||
|
|
||||||
async function getUserEmail(accessToken: string): Promise<string | undefined> {
|
async function getUserEmail(accessToken: string): Promise<string | undefined> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(USERINFO_URL, {
|
const response = await fetchWithTimeout(USERINFO_URL, {
|
||||||
headers: { Authorization: `Bearer ${accessToken}` },
|
headers: { Authorization: `Bearer ${accessToken}` },
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
@@ -420,20 +464,25 @@ async function getUserEmail(accessToken: string): Promise<string | undefined> {
|
|||||||
|
|
||||||
async function discoverProject(accessToken: string): Promise<string> {
|
async function discoverProject(accessToken: string): Promise<string> {
|
||||||
const envProject = process.env.GOOGLE_CLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT_ID;
|
const envProject = process.env.GOOGLE_CLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT_ID;
|
||||||
|
const platform = resolvePlatform();
|
||||||
|
const metadata = {
|
||||||
|
ideType: "ANTIGRAVITY",
|
||||||
|
platform,
|
||||||
|
pluginType: "GEMINI",
|
||||||
|
};
|
||||||
const headers = {
|
const headers = {
|
||||||
Authorization: `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"User-Agent": "google-api-nodejs-client/9.15.1",
|
"User-Agent": "google-api-nodejs-client/9.15.1",
|
||||||
"X-Goog-Api-Client": "gl-node/openclaw",
|
"X-Goog-Api-Client": `gl-node/${process.versions.node}`,
|
||||||
|
"Client-Metadata": JSON.stringify(metadata),
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadBody = {
|
const loadBody = {
|
||||||
cloudaicompanionProject: envProject,
|
...(envProject ? { cloudaicompanionProject: envProject } : {}),
|
||||||
metadata: {
|
metadata: {
|
||||||
ideType: "IDE_UNSPECIFIED",
|
...metadata,
|
||||||
platform: "PLATFORM_UNSPECIFIED",
|
...(envProject ? { duetProject: envProject } : {}),
|
||||||
pluginType: "GEMINI",
|
|
||||||
duetProject: envProject,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -442,29 +491,46 @@ async function discoverProject(accessToken: string): Promise<string> {
|
|||||||
cloudaicompanionProject?: string | { id?: string };
|
cloudaicompanionProject?: string | { id?: string };
|
||||||
allowedTiers?: Array<{ id?: string; isDefault?: boolean }>;
|
allowedTiers?: Array<{ id?: string; isDefault?: boolean }>;
|
||||||
} = {};
|
} = {};
|
||||||
|
let activeEndpoint = CODE_ASSIST_ENDPOINT_PROD;
|
||||||
|
let loadError: Error | undefined;
|
||||||
|
for (const endpoint of LOAD_CODE_ASSIST_ENDPOINTS) {
|
||||||
|
try {
|
||||||
|
const response = await fetchWithTimeout(`${endpoint}/v1internal:loadCodeAssist`, {
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(loadBody),
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
if (!response.ok) {
|
||||||
const response = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, {
|
const errorPayload = await response.json().catch(() => null);
|
||||||
method: "POST",
|
if (isVpcScAffected(errorPayload)) {
|
||||||
headers,
|
data = { currentTier: { id: TIER_STANDARD } };
|
||||||
body: JSON.stringify(loadBody),
|
activeEndpoint = endpoint;
|
||||||
});
|
loadError = undefined;
|
||||||
|
break;
|
||||||
if (!response.ok) {
|
}
|
||||||
const errorPayload = await response.json().catch(() => null);
|
loadError = new Error(`loadCodeAssist failed: ${response.status} ${response.statusText}`);
|
||||||
if (isVpcScAffected(errorPayload)) {
|
continue;
|
||||||
data = { currentTier: { id: TIER_STANDARD } };
|
|
||||||
} else {
|
|
||||||
throw new Error(`loadCodeAssist failed: ${response.status} ${response.statusText}`);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
data = (await response.json()) as typeof data;
|
data = (await response.json()) as typeof data;
|
||||||
|
activeEndpoint = endpoint;
|
||||||
|
loadError = undefined;
|
||||||
|
break;
|
||||||
|
} catch (err) {
|
||||||
|
loadError = err instanceof Error ? err : new Error("loadCodeAssist failed", { cause: err });
|
||||||
}
|
}
|
||||||
} catch (err) {
|
}
|
||||||
if (err instanceof Error) {
|
|
||||||
throw err;
|
const hasLoadCodeAssistData =
|
||||||
|
Boolean(data.currentTier) ||
|
||||||
|
Boolean(data.cloudaicompanionProject) ||
|
||||||
|
Boolean(data.allowedTiers?.length);
|
||||||
|
if (!hasLoadCodeAssistData && loadError) {
|
||||||
|
if (envProject) {
|
||||||
|
return envProject;
|
||||||
}
|
}
|
||||||
throw new Error("loadCodeAssist failed", { cause: err });
|
throw loadError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.currentTier) {
|
if (data.currentTier) {
|
||||||
@@ -494,9 +560,7 @@ async function discoverProject(accessToken: string): Promise<string> {
|
|||||||
const onboardBody: Record<string, unknown> = {
|
const onboardBody: Record<string, unknown> = {
|
||||||
tierId,
|
tierId,
|
||||||
metadata: {
|
metadata: {
|
||||||
ideType: "IDE_UNSPECIFIED",
|
...metadata,
|
||||||
platform: "PLATFORM_UNSPECIFIED",
|
|
||||||
pluginType: "GEMINI",
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (tierId !== TIER_FREE && envProject) {
|
if (tierId !== TIER_FREE && envProject) {
|
||||||
@@ -504,7 +568,7 @@ async function discoverProject(accessToken: string): Promise<string> {
|
|||||||
(onboardBody.metadata as Record<string, unknown>).duetProject = envProject;
|
(onboardBody.metadata as Record<string, unknown>).duetProject = envProject;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onboardResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, {
|
const onboardResponse = await fetchWithTimeout(`${activeEndpoint}/v1internal:onboardUser`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify(onboardBody),
|
body: JSON.stringify(onboardBody),
|
||||||
@@ -521,7 +585,7 @@ async function discoverProject(accessToken: string): Promise<string> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!lro.done && lro.name) {
|
if (!lro.done && lro.name) {
|
||||||
lro = await pollOperation(lro.name, headers);
|
lro = await pollOperation(activeEndpoint, lro.name, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectId = lro.response?.cloudaicompanionProject?.id;
|
const projectId = lro.response?.cloudaicompanionProject?.id;
|
||||||
@@ -567,12 +631,13 @@ function getDefaultTier(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function pollOperation(
|
async function pollOperation(
|
||||||
|
endpoint: string,
|
||||||
operationName: string,
|
operationName: string,
|
||||||
headers: Record<string, string>,
|
headers: Record<string, string>,
|
||||||
): Promise<{ done?: boolean; response?: { cloudaicompanionProject?: { id?: string } } }> {
|
): Promise<{ done?: boolean; response?: { cloudaicompanionProject?: { id?: string } } }> {
|
||||||
for (let attempt = 0; attempt < 24; attempt += 1) {
|
for (let attempt = 0; attempt < 24; attempt += 1) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||||
const response = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal/${operationName}`, {
|
const response = await fetchWithTimeout(`${endpoint}/v1internal/${operationName}`, {
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
Reference in New Issue
Block a user