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

@@ -16,18 +16,6 @@ export const validateAuthMethod = (authMethod: string): string | null => {
return null; return null;
} }
if (authMethod === AuthType.LOGIN_WITH_GOOGLE_GCA) {
if (!process.env['GOOGLE_CLOUD_PROJECT']) {
return (
'[Error] GOOGLE_CLOUD_PROJECT is not set.\n' +
'Please set it using:\n' +
' export GOOGLE_CLOUD_PROJECT=<your-project-id>\n' +
'and try again.'
);
}
return null;
}
if (authMethod === AuthType.USE_GEMINI) { if (authMethod === AuthType.USE_GEMINI) {
if (!process.env['GEMINI_API_KEY']) { if (!process.env['GEMINI_API_KEY']) {
return 'GEMINI_API_KEY environment variable not found. Add that to your environment and try again (no reload needed if using .env)!'; return 'GEMINI_API_KEY environment variable not found. Add that to your environment and try again (no reload needed if using .env)!';

View File

@@ -6,12 +6,12 @@
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { aboutCommand } from './aboutCommand.js'; import { aboutCommand } from './aboutCommand.js';
import type { CommandContext } from './types.js'; import { type CommandContext } from './types.js';
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js'; import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
import * as versionUtils from '../../utils/version.js'; import * as versionUtils from '../../utils/version.js';
import { MessageType } from '../types.js'; import { MessageType } from '../types.js';
import type { IdeClient } from '@google/gemini-cli-core'; import type { IdeClient } from '../../../../core/src/ide/ide-client.js';
vi.mock('../../utils/version.js', () => ({ vi.mock('../../utils/version.js', () => ({
getCliVersion: vi.fn(), getCliVersion: vi.fn(),
@@ -29,11 +29,10 @@ describe('aboutCommand', () => {
getModel: vi.fn(), getModel: vi.fn(),
getIdeClient: vi.fn(), getIdeClient: vi.fn(),
getIdeMode: vi.fn().mockReturnValue(true), getIdeMode: vi.fn().mockReturnValue(true),
getGeminiClient: vi.fn(),
}, },
settings: { settings: {
merged: { merged: {
selectedAuthType: 'oauth-gca', selectedAuthType: 'test-auth',
}, },
}, },
}, },
@@ -53,11 +52,6 @@ describe('aboutCommand', () => {
vi.spyOn(mockContext.services.config!, 'getIdeClient').mockReturnValue({ vi.spyOn(mockContext.services.config!, 'getIdeClient').mockReturnValue({
getDetectedIdeDisplayName: vi.fn().mockReturnValue('test-ide'), getDetectedIdeDisplayName: vi.fn().mockReturnValue('test-ide'),
} as Partial<IdeClient> as IdeClient); } as Partial<IdeClient> as IdeClient);
vi.spyOn(mockContext.services.config!, 'getGeminiClient').mockReturnValue({
getUserTier: vi.fn().mockReturnValue(undefined),
} as unknown as ReturnType<
NonNullable<typeof mockContext.services.config>['getGeminiClient']
>);
}); });
afterEach(() => { afterEach(() => {
@@ -89,10 +83,9 @@ describe('aboutCommand', () => {
osVersion: 'test-os', osVersion: 'test-os',
sandboxEnv: 'no sandbox', sandboxEnv: 'no sandbox',
modelVersion: 'test-model', modelVersion: 'test-model',
selectedAuthType: 'oauth-gca', selectedAuthType: 'test-auth',
gcpProject: 'test-gcp-project', gcpProject: 'test-gcp-project',
ideClient: 'test-ide', ideClient: 'test-ide',
userTier: undefined,
}, },
expect.any(Number), expect.any(Number),
); );
@@ -132,14 +125,11 @@ describe('aboutCommand', () => {
}); });
it('should not show ide client when it is not detected', async () => { it('should not show ide client when it is not detected', async () => {
// Change to oauth type that doesn't use GCP project
mockContext.services.settings.merged.selectedAuthType = 'oauth';
vi.spyOn(mockContext.services.config!, 'getIdeClient').mockReturnValue({ vi.spyOn(mockContext.services.config!, 'getIdeClient').mockReturnValue({
getDetectedIdeDisplayName: vi.fn().mockReturnValue(undefined), getDetectedIdeDisplayName: vi.fn().mockReturnValue(undefined),
} as Partial<IdeClient> as IdeClient); } as Partial<IdeClient> as IdeClient);
process.env['SANDBOX'] = ''; process.env.SANDBOX = '';
if (!aboutCommand.action) { if (!aboutCommand.action) {
throw new Error('The about command must have an action.'); throw new Error('The about command must have an action.');
} }
@@ -153,8 +143,8 @@ describe('aboutCommand', () => {
osVersion: 'test-os', osVersion: 'test-os',
sandboxEnv: 'no sandbox', sandboxEnv: 'no sandbox',
modelVersion: 'test-model', modelVersion: 'test-model',
selectedAuthType: 'oauth', selectedAuthType: 'test-auth',
gcpProject: '', gcpProject: 'test-gcp-project',
ideClient: '', ideClient: '',
}), }),
expect.any(Number), expect.any(Number),

View File

@@ -28,18 +28,11 @@ export const aboutCommand: SlashCommand = {
const cliVersion = await getCliVersion(); const cliVersion = await getCliVersion();
const selectedAuthType = const selectedAuthType =
context.services.settings.merged.selectedAuthType || ''; context.services.settings.merged.selectedAuthType || '';
// Only show GCP Project for auth types that actually use it const gcpProject = process.env['GOOGLE_CLOUD_PROJECT'] || '';
const gcpProject =
selectedAuthType === 'oauth-gca' ||
selectedAuthType === 'vertex-ai' ||
selectedAuthType === 'cloud-shell'
? process.env['GOOGLE_CLOUD_PROJECT'] || ''
: '';
const ideClient = const ideClient =
(context.services.config?.getIdeMode() && (context.services.config?.getIdeMode() &&
context.services.config?.getIdeClient()?.getDetectedIdeDisplayName()) || context.services.config?.getIdeClient()?.getDetectedIdeDisplayName()) ||
''; '';
const userTier = context.services.config?.getGeminiClient()?.getUserTier();
const aboutItem: Omit<HistoryItemAbout, 'id'> = { const aboutItem: Omit<HistoryItemAbout, 'id'> = {
type: MessageType.ABOUT, type: MessageType.ABOUT,
@@ -50,7 +43,6 @@ export const aboutCommand: SlashCommand = {
selectedAuthType, selectedAuthType,
gcpProject, gcpProject,
ideClient, ideClient,
userTier,
}; };
context.ui.addItem(aboutItem, Date.now()); context.ui.addItem(aboutItem, Date.now());

View File

@@ -8,8 +8,6 @@ import type React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { Colors } from '../colors.js';
import { GIT_COMMIT_INFO } from '../../generated/git-commit.js'; import { GIT_COMMIT_INFO } from '../../generated/git-commit.js';
import type { UserTierId } from '@google/gemini-cli-core';
import { getLicenseDisplay } from '../../utils/license.js';
interface AboutBoxProps { interface AboutBoxProps {
cliVersion: string; cliVersion: string;
@@ -19,7 +17,6 @@ interface AboutBoxProps {
selectedAuthType: string; selectedAuthType: string;
gcpProject: string; gcpProject: string;
ideClient: string; ideClient: string;
userTier?: UserTierId;
} }
export const AboutBox: React.FC<AboutBoxProps> = ({ export const AboutBox: React.FC<AboutBoxProps> = ({
@@ -30,7 +27,6 @@ export const AboutBox: React.FC<AboutBoxProps> = ({
selectedAuthType, selectedAuthType,
gcpProject, gcpProject,
ideClient, ideClient,
userTier,
}) => ( }) => (
<Box <Box
borderStyle="round" borderStyle="round"
@@ -109,16 +105,6 @@ export const AboutBox: React.FC<AboutBoxProps> = ({
</Text> </Text>
</Box> </Box>
</Box> </Box>
<Box flexDirection="row">
<Box width="35%">
<Text bold color={Colors.LightBlue}>
License
</Text>
</Box>
<Box>
<Text>{getLicenseDisplay(selectedAuthType, userTier)}</Text>
</Box>
</Box>
{gcpProject && ( {gcpProject && (
<Box flexDirection="row"> <Box flexDirection="row">
<Box width="35%"> <Box width="35%">

View File

@@ -64,14 +64,9 @@ export function AuthDialog({
}); });
const items = [ const items = [
{ {
label: 'Login with Google - Free Tier', label: 'Login with Google',
value: AuthType.LOGIN_WITH_GOOGLE, value: AuthType.LOGIN_WITH_GOOGLE,
}, },
{
label:
'Login with Google - Gemini Code Assist (Requires GOOGLE_CLOUD_PROJECT)',
value: AuthType.LOGIN_WITH_GOOGLE_GCA,
},
...(process.env['CLOUD_SHELL'] === 'true' ...(process.env['CLOUD_SHELL'] === 'true'
? [ ? [
{ {

View File

@@ -74,7 +74,6 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
selectedAuthType={item.selectedAuthType} selectedAuthType={item.selectedAuthType}
gcpProject={item.gcpProject} gcpProject={item.gcpProject}
ideClient={item.ideClient} ideClient={item.ideClient}
userTier={item.userTier}
/> />
)} )}
{item.type === 'help' && commands && <Help commands={commands} />} {item.type === 'help' && commands && <Help commands={commands} />}

View File

@@ -7,7 +7,6 @@
import type { import type {
ToolCallConfirmationDetails, ToolCallConfirmationDetails,
ToolResultDisplay, ToolResultDisplay,
UserTierId,
} from '@google/gemini-cli-core'; } from '@google/gemini-cli-core';
// Only defining the state enum needed by the UI // Only defining the state enum needed by the UI
@@ -97,7 +96,6 @@ export type HistoryItemAbout = HistoryItemBase & {
selectedAuthType: string; selectedAuthType: string;
gcpProject: string; gcpProject: string;
ideClient: string; ideClient: string;
userTier?: UserTierId;
}; };
export type HistoryItemHelp = HistoryItemBase & { export type HistoryItemHelp = HistoryItemBase & {

View File

@@ -1,119 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect } from 'vitest';
import { getLicenseDisplay } from './license.js';
import { AuthType, UserTierId } from '@google/gemini-cli-core';
describe('getLicenseDisplay', () => {
describe('Free Tier (Login with Google)', () => {
it('should return Free Tier for LOGIN_WITH_GOOGLE', () => {
expect(getLicenseDisplay(AuthType.LOGIN_WITH_GOOGLE)).toBe(
'Free Tier (Login with Google)',
);
});
it('should ignore userTier for LOGIN_WITH_GOOGLE', () => {
expect(
getLicenseDisplay(AuthType.LOGIN_WITH_GOOGLE, UserTierId.STANDARD),
).toBe('Free Tier (Login with Google)');
expect(
getLicenseDisplay(AuthType.LOGIN_WITH_GOOGLE, UserTierId.LEGACY),
).toBe('Free Tier (Login with Google)');
});
});
describe('Gemini Code Assist (Google Workspace)', () => {
it('should return GCA Standard for LOGIN_WITH_GOOGLE_GCA with STANDARD tier', () => {
expect(
getLicenseDisplay(AuthType.LOGIN_WITH_GOOGLE_GCA, UserTierId.STANDARD),
).toBe('Gemini Code Assist Standard (Google Workspace)');
});
it('should return GCA Enterprise for LOGIN_WITH_GOOGLE_GCA with LEGACY tier', () => {
expect(
getLicenseDisplay(AuthType.LOGIN_WITH_GOOGLE_GCA, UserTierId.LEGACY),
).toBe('Gemini Code Assist Enterprise (Google Workspace)');
});
it('should return generic GCA for LOGIN_WITH_GOOGLE_GCA without tier', () => {
expect(getLicenseDisplay(AuthType.LOGIN_WITH_GOOGLE_GCA)).toBe(
'Gemini Code Assist (Google Workspace)',
);
});
it('should return generic GCA for LOGIN_WITH_GOOGLE_GCA with unknown tier', () => {
expect(
getLicenseDisplay(
AuthType.LOGIN_WITH_GOOGLE_GCA,
'unknown-tier' as UserTierId,
),
).toBe('Gemini Code Assist (Google Workspace)');
});
it('should return generic GCA for LOGIN_WITH_GOOGLE_GCA with FREE tier', () => {
expect(
getLicenseDisplay(AuthType.LOGIN_WITH_GOOGLE_GCA, UserTierId.FREE),
).toBe('Gemini Code Assist (Google Workspace)');
});
});
describe('Gemini API Key', () => {
it('should return Gemini API Key for USE_GEMINI', () => {
expect(getLicenseDisplay(AuthType.USE_GEMINI)).toBe('Gemini API Key');
});
it('should ignore userTier for USE_GEMINI', () => {
expect(getLicenseDisplay(AuthType.USE_GEMINI, UserTierId.STANDARD)).toBe(
'Gemini API Key',
);
});
});
describe('Vertex AI', () => {
it('should return Vertex AI for USE_VERTEX_AI', () => {
expect(getLicenseDisplay(AuthType.USE_VERTEX_AI)).toBe('Vertex AI');
});
it('should ignore userTier for USE_VERTEX_AI', () => {
expect(getLicenseDisplay(AuthType.USE_VERTEX_AI, UserTierId.LEGACY)).toBe(
'Vertex AI',
);
});
});
describe('Cloud Shell', () => {
it('should return Cloud Shell for CLOUD_SHELL', () => {
expect(getLicenseDisplay(AuthType.CLOUD_SHELL)).toBe('Cloud Shell');
});
it('should ignore userTier for CLOUD_SHELL', () => {
expect(getLicenseDisplay(AuthType.CLOUD_SHELL, UserTierId.STANDARD)).toBe(
'Cloud Shell',
);
});
});
describe('Unknown auth types', () => {
it('should return the auth type as-is for unknown values', () => {
expect(getLicenseDisplay('custom-auth-type')).toBe('custom-auth-type');
expect(getLicenseDisplay('oauth')).toBe('oauth');
expect(getLicenseDisplay('unknown-auth')).toBe('unknown-auth');
});
it('should handle undefined gracefully', () => {
expect(getLicenseDisplay(undefined as unknown as string)).toBe(undefined);
});
it('should handle null gracefully', () => {
expect(getLicenseDisplay(null as unknown as string)).toBe(null);
});
it('should handle empty string', () => {
expect(getLicenseDisplay('')).toBe('');
});
});
});

View File

@@ -1,43 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { AuthType, UserTierId } from '@google/gemini-cli-core';
/**
* Get human-readable license display text based on auth type and user tier.
* @param selectedAuthType - The authentication type selected by the user
* @param userTier - Optional user tier information from the server
* @returns Human-readable license information
*/
export function getLicenseDisplay(
selectedAuthType: string,
userTier?: UserTierId,
): string {
switch (selectedAuthType) {
case AuthType.LOGIN_WITH_GOOGLE:
return 'Free Tier (Login with Google)';
case AuthType.LOGIN_WITH_GOOGLE_GCA:
if (userTier === UserTierId.STANDARD) {
return 'Gemini Code Assist Standard (Google Workspace)';
} else if (userTier === UserTierId.LEGACY) {
return 'Gemini Code Assist Enterprise (Google Workspace)';
}
return 'Gemini Code Assist (Google Workspace)';
case AuthType.USE_GEMINI:
return 'Gemini API Key';
case AuthType.USE_VERTEX_AI:
return 'Vertex AI';
case AuthType.CLOUD_SHELL:
return 'Cloud Shell';
default:
return selectedAuthType;
}
}

View File

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

View File

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

View File

@@ -13,7 +13,6 @@ import type {
import { UserTierId } from './types.js'; import { UserTierId } from './types.js';
import { CodeAssistServer } from './server.js'; import { CodeAssistServer } from './server.js';
import type { OAuth2Client } from 'google-auth-library'; import type { OAuth2Client } from 'google-auth-library';
import { AuthType } from '../core/contentGenerator.js';
export class ProjectIdRequiredError extends Error { export class ProjectIdRequiredError extends Error {
constructor() { 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 { export interface UserData {
projectId: string; projectId: string;
userTier: UserTierId; userTier: UserTierId;
@@ -65,20 +29,11 @@ export interface UserData {
/** /**
* *
* @param client OAuth2 client * @param projectId the user's project id, if any
* @param authType the authentication type being used * @returns the user's actual project id
* @returns the user's actual project id and tier
*/ */
export async function setupUser( export async function setupUser(client: OAuth2Client): Promise<UserData> {
client: OAuth2Client, const projectId = process.env['GOOGLE_CLOUD_PROJECT'] || undefined;
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;
const caServer = new CodeAssistServer(client, projectId, {}, '', undefined); const caServer = new CodeAssistServer(client, projectId, {}, '', undefined);
const coreClientMetadata: ClientMetadata = { const coreClientMetadata: ClientMetadata = {
ideType: 'IDE_UNSPECIFIED', ideType: 'IDE_UNSPECIFIED',
@@ -86,56 +41,22 @@ export async function setupUser(
pluginType: 'GEMINI', pluginType: 'GEMINI',
}; };
let loadRes: LoadCodeAssistResponse; const loadRes = await caServer.loadCodeAssist({
try {
loadRes = await caServer.loadCodeAssist({
cloudaicompanionProject: projectId, cloudaicompanionProject: projectId,
metadata: { metadata: {
...coreClientMetadata, ...coreClientMetadata,
duetProject: projectId, 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;
}
if (loadRes.currentTier) { 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 (!loadRes.cloudaicompanionProject) {
if (projectId) { 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 { return {
projectId, projectId,
userTier: loadRes.currentTier.id, 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(); throw new ProjectIdRequiredError();
} }
return { return {
@@ -146,16 +67,8 @@ export async function setupUser(
const tier = getOnboardTier(loadRes); 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; 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. // The free tier uses a managed google cloud project. Setting a project in the `onboardUser` request causes a `Precondition Failed` error.
onboardReq = { onboardReq = {
tierId: tier.id, tierId: tier.id,
@@ -182,42 +95,18 @@ export async function setupUser(
if (!lroRes.response?.cloudaicompanionProject?.id) { if (!lroRes.response?.cloudaicompanionProject?.id) {
if (projectId) { 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 { return {
projectId, projectId,
userTier: tier.id, 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(); throw new ProjectIdRequiredError();
} }
// Final validation: ensure GCA users don't get Free Tier return {
const finalUserData = {
projectId: lroRes.response.cloudaicompanionProject.id, projectId: lroRes.response.cloudaicompanionProject.id,
userTier: tier.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 { function getOnboardTier(res: LoadCodeAssistResponse): GeminiUserTier {

View File

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