Revert "feat: add explicit license selection and status visibility (#6751)" (#7057)

This commit is contained in:
Tommaso Sciortino
2025-08-25 16:16:30 -07:00
committed by GitHub
parent d820c2335b
commit 925d747b9d
13 changed files with 40 additions and 382 deletions

View File

@@ -20,11 +20,10 @@ export async function createCodeAssistContentGenerator(
): Promise<ContentGenerator> {
if (
authType === AuthType.LOGIN_WITH_GOOGLE ||
authType === AuthType.LOGIN_WITH_GOOGLE_GCA ||
authType === AuthType.CLOUD_SHELL
) {
const authClient = await getOauthClient(authType, config);
const userData = await setupUser(authClient, authType);
const userData = await setupUser(authClient);
return new CodeAssistServer(
authClient,
userData.projectId,

View File

@@ -5,16 +5,11 @@
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
setupUser,
ProjectIdRequiredError,
ProjectAccessError,
} from './setup.js';
import { setupUser, ProjectIdRequiredError } from './setup.js';
import { CodeAssistServer } from '../code_assist/server.js';
import type { OAuth2Client } from 'google-auth-library';
import type { GeminiUserTier } from './types.js';
import { UserTierId } from './types.js';
import { AuthType } from '../core/contentGenerator.js';
vi.mock('../code_assist/server.js');
@@ -64,9 +59,8 @@ describe('setupUser for existing user', () => {
vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'test-project');
mockLoad.mockResolvedValue({
currentTier: mockPaidTier,
cloudaicompanionProject: 'test-project',
});
await setupUser({} as OAuth2Client, AuthType.LOGIN_WITH_GOOGLE_GCA);
await setupUser({} as OAuth2Client);
expect(CodeAssistServer).toHaveBeenCalledWith(
{},
'test-project',
@@ -82,10 +76,7 @@ describe('setupUser for existing user', () => {
cloudaicompanionProject: 'server-project',
currentTier: mockPaidTier,
});
const projectId = await setupUser(
{} as OAuth2Client,
AuthType.LOGIN_WITH_GOOGLE_GCA,
);
const projectId = await setupUser({} as OAuth2Client);
expect(CodeAssistServer).toHaveBeenCalledWith(
{},
'test-project',
@@ -106,9 +97,9 @@ describe('setupUser for existing user', () => {
throw new ProjectIdRequiredError();
});
await expect(
setupUser({} as OAuth2Client, AuthType.LOGIN_WITH_GOOGLE_GCA),
).rejects.toThrow(ProjectIdRequiredError);
await expect(setupUser({} as OAuth2Client)).rejects.toThrow(
ProjectIdRequiredError,
);
});
});
@@ -145,10 +136,7 @@ describe('setupUser for new user', () => {
mockLoad.mockResolvedValue({
allowedTiers: [mockPaidTier],
});
const userData = await setupUser(
{} as OAuth2Client,
AuthType.LOGIN_WITH_GOOGLE_GCA,
);
const userData = await setupUser({} as OAuth2Client);
expect(CodeAssistServer).toHaveBeenCalledWith(
{},
'test-project',
@@ -178,10 +166,7 @@ describe('setupUser for new user', () => {
mockLoad.mockResolvedValue({
allowedTiers: [mockFreeTier],
});
const userData = await setupUser(
{} as OAuth2Client,
AuthType.LOGIN_WITH_GOOGLE,
);
const userData = await setupUser({} as OAuth2Client);
expect(CodeAssistServer).toHaveBeenCalledWith(
{},
undefined,
@@ -205,7 +190,7 @@ describe('setupUser for new user', () => {
});
});
it('should throw ProjectAccessError when LOGIN_WITH_GOOGLE_GCA onboard response has no project ID', async () => {
it('should use GOOGLE_CLOUD_PROJECT when onboard response has no project ID', async () => {
vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'test-project');
mockLoad.mockResolvedValue({
allowedTiers: [mockPaidTier],
@@ -216,9 +201,11 @@ describe('setupUser for new user', () => {
cloudaicompanionProject: undefined,
},
});
await expect(
setupUser({} as OAuth2Client, AuthType.LOGIN_WITH_GOOGLE_GCA),
).rejects.toThrow(ProjectAccessError);
const userData = await setupUser({} as OAuth2Client);
expect(userData).toEqual({
projectId: 'test-project',
userTier: 'standard-tier',
});
});
it('should throw ProjectIdRequiredError when no project ID is available', async () => {
@@ -230,8 +217,8 @@ describe('setupUser for new user', () => {
done: true,
response: {},
});
await expect(
setupUser({} as OAuth2Client, AuthType.LOGIN_WITH_GOOGLE_GCA),
).rejects.toThrow(ProjectIdRequiredError);
await expect(setupUser({} as OAuth2Client)).rejects.toThrow(
ProjectIdRequiredError,
);
});
});

View File

@@ -13,7 +13,6 @@ import type {
import { UserTierId } from './types.js';
import { CodeAssistServer } from './server.js';
import type { OAuth2Client } from 'google-auth-library';
import { AuthType } from '../core/contentGenerator.js';
export class ProjectIdRequiredError extends Error {
constructor() {
@@ -23,41 +22,6 @@ export class ProjectIdRequiredError extends Error {
}
}
export class ProjectAccessError extends Error {
constructor(projectId: string, details?: string) {
super(
`Failed to access GCP project "${projectId}" for Gemini Code Assist.\n` +
`${details || ''}\n` +
`Please verify:\n` +
`1. The project ID is correct\n` +
`2. You have the necessary permissions for this project\n` +
`3. The Gemini for Cloud API is enabled for this project\n` +
`\n` +
`To use a different project:\n` +
` export GOOGLE_CLOUD_PROJECT=<your-project-id>\n` +
`\n` +
`To use Free Tier instead, run /auth and select "Login with Google - Free Tier"`,
);
}
}
export class LicenseMismatchError extends Error {
constructor(expected: string, actual: string) {
super(
`License type mismatch detected.\n` +
`You selected: ${expected}\n` +
`But the server returned: ${actual}\n` +
`\n` +
`This may indicate:\n` +
`1. The project doesn't have a valid GCA license\n` +
`2. You don't have access to the specified project\n` +
`3. The project configuration is incorrect\n` +
`\n` +
`Please verify your project settings or contact your administrator.`,
);
}
}
export interface UserData {
projectId: string;
userTier: UserTierId;
@@ -65,20 +29,11 @@ export interface UserData {
/**
*
* @param client OAuth2 client
* @param authType the authentication type being used
* @returns the user's actual project id and tier
* @param projectId the user's project id, if any
* @returns the user's actual project id
*/
export async function setupUser(
client: OAuth2Client,
authType: AuthType,
): Promise<UserData> {
// Only use GOOGLE_CLOUD_PROJECT for GCA login or Cloud Shell
const projectId =
authType === AuthType.LOGIN_WITH_GOOGLE_GCA ||
authType === AuthType.CLOUD_SHELL
? process.env['GOOGLE_CLOUD_PROJECT'] || undefined
: undefined;
export async function setupUser(client: OAuth2Client): Promise<UserData> {
const projectId = process.env['GOOGLE_CLOUD_PROJECT'] || undefined;
const caServer = new CodeAssistServer(client, projectId, {}, '', undefined);
const coreClientMetadata: ClientMetadata = {
ideType: 'IDE_UNSPECIFIED',
@@ -86,56 +41,22 @@ export async function setupUser(
pluginType: 'GEMINI',
};
let loadRes: LoadCodeAssistResponse;
try {
loadRes = await caServer.loadCodeAssist({
cloudaicompanionProject: projectId,
metadata: {
...coreClientMetadata,
duetProject: projectId,
},
});
} catch (error) {
// If GCA login failed with a project, throw a clear error
if (authType === AuthType.LOGIN_WITH_GOOGLE_GCA && projectId) {
throw new ProjectAccessError(
projectId,
error instanceof Error ? error.message : 'Authentication failed',
);
}
throw error;
}
const loadRes = await caServer.loadCodeAssist({
cloudaicompanionProject: projectId,
metadata: {
...coreClientMetadata,
duetProject: projectId,
},
});
if (loadRes.currentTier) {
// Check for license mismatch - GCA selected but Free Tier returned
if (
authType === AuthType.LOGIN_WITH_GOOGLE_GCA &&
loadRes.currentTier.id === UserTierId.FREE
) {
throw new LicenseMismatchError('Gemini Code Assist (GCA)', 'Free Tier');
}
if (!loadRes.cloudaicompanionProject) {
if (projectId) {
// GCA with project but no cloudaicompanionProject means project access issue
if (authType === AuthType.LOGIN_WITH_GOOGLE_GCA) {
throw new ProjectAccessError(
projectId,
'The project exists but is not configured for Gemini Code Assist',
);
}
return {
projectId,
userTier: loadRes.currentTier.id,
};
}
// For Free Tier login, don't require project ID
if (authType === AuthType.LOGIN_WITH_GOOGLE) {
return {
projectId: '',
userTier: loadRes.currentTier.id,
};
}
throw new ProjectIdRequiredError();
}
return {
@@ -146,16 +67,8 @@ export async function setupUser(
const tier = getOnboardTier(loadRes);
// Check for license mismatch during onboarding
if (
authType === AuthType.LOGIN_WITH_GOOGLE_GCA &&
tier.id === UserTierId.FREE
) {
throw new LicenseMismatchError('Gemini Code Assist (GCA)', 'Free Tier');
}
let onboardReq: OnboardUserRequest;
if (tier.id === UserTierId.FREE || authType === AuthType.LOGIN_WITH_GOOGLE) {
if (tier.id === UserTierId.FREE) {
// The free tier uses a managed google cloud project. Setting a project in the `onboardUser` request causes a `Precondition Failed` error.
onboardReq = {
tierId: tier.id,
@@ -182,42 +95,18 @@ export async function setupUser(
if (!lroRes.response?.cloudaicompanionProject?.id) {
if (projectId) {
// GCA with project but onboarding didn't return a project
if (authType === AuthType.LOGIN_WITH_GOOGLE_GCA) {
throw new ProjectAccessError(
projectId,
'Failed to onboard to Gemini Code Assist with this project',
);
}
return {
projectId,
userTier: tier.id,
};
}
// For Free Tier login, don't require project ID
if (authType === AuthType.LOGIN_WITH_GOOGLE) {
return {
projectId: '',
userTier: tier.id,
};
}
throw new ProjectIdRequiredError();
}
// Final validation: ensure GCA users don't get Free Tier
const finalUserData = {
return {
projectId: lroRes.response.cloudaicompanionProject.id,
userTier: tier.id,
};
if (
authType === AuthType.LOGIN_WITH_GOOGLE_GCA &&
finalUserData.userTier === UserTierId.FREE
) {
throw new LicenseMismatchError('Gemini Code Assist (GCA)', 'Free Tier');
}
return finalUserData;
}
function getOnboardTier(res: LoadCodeAssistResponse): GeminiUserTier {

View File

@@ -44,7 +44,6 @@ export interface ContentGenerator {
export enum AuthType {
LOGIN_WITH_GOOGLE = 'oauth-personal',
LOGIN_WITH_GOOGLE_GCA = 'oauth-gca',
USE_GEMINI = 'gemini-api-key',
USE_VERTEX_AI = 'vertex-ai',
CLOUD_SHELL = 'cloud-shell',
@@ -79,7 +78,6 @@ export function createContentGeneratorConfig(
// If we are using Google auth or we are in Cloud Shell, there is nothing else to validate for now
if (
authType === AuthType.LOGIN_WITH_GOOGLE ||
authType === AuthType.LOGIN_WITH_GOOGLE_GCA ||
authType === AuthType.CLOUD_SHELL
) {
return contentGeneratorConfig;
@@ -118,7 +116,6 @@ export async function createContentGenerator(
if (
config.authType === AuthType.LOGIN_WITH_GOOGLE ||
config.authType === AuthType.LOGIN_WITH_GOOGLE_GCA ||
config.authType === AuthType.CLOUD_SHELL
) {
const httpOptions = { headers: baseHeaders };