Improve error messages on failed onboarding (#17357)

This commit is contained in:
Gaurav
2026-01-26 06:31:19 -08:00
committed by GitHub
parent cb772a5b7f
commit 5fe328c56a
17 changed files with 458 additions and 56 deletions
+79 -9
View File
@@ -10,9 +10,12 @@ import type {
LoadCodeAssistResponse,
OnboardUserRequest,
} from './types.js';
import { UserTierId } from './types.js';
import { UserTierId, IneligibleTierReasonCode } from './types.js';
import { CodeAssistServer } from './server.js';
import type { AuthClient } from 'google-auth-library';
import type { ValidationHandler } from '../fallback/types.js';
import { ChangeAuthRequestedError } from '../utils/errors.js';
import { ValidationRequiredError } from '../utils/googleQuotaErrors.js';
export class ProjectIdRequiredError extends Error {
constructor() {
@@ -22,6 +25,16 @@ export class ProjectIdRequiredError extends Error {
}
}
/**
* Error thrown when user cancels the validation process.
* This is a non-recoverable error that should result in auth failure.
*/
export class ValidationCancelledError extends Error {
constructor() {
super('User cancelled account validation');
}
}
export interface UserData {
projectId: string;
userTier: UserTierId;
@@ -33,7 +46,10 @@ export interface UserData {
* @param projectId the user's project id, if any
* @returns the user's actual project id
*/
export async function setupUser(client: AuthClient): Promise<UserData> {
export async function setupUser(
client: AuthClient,
validationHandler?: ValidationHandler,
): Promise<UserData> {
const projectId =
process.env['GOOGLE_CLOUD_PROJECT'] ||
process.env['GOOGLE_CLOUD_PROJECT_ID'] ||
@@ -52,13 +68,36 @@ export async function setupUser(client: AuthClient): Promise<UserData> {
pluginType: 'GEMINI',
};
const loadRes = await caServer.loadCodeAssist({
cloudaicompanionProject: projectId,
metadata: {
...coreClientMetadata,
duetProject: projectId,
},
});
let loadRes: LoadCodeAssistResponse;
while (true) {
loadRes = await caServer.loadCodeAssist({
cloudaicompanionProject: projectId,
metadata: {
...coreClientMetadata,
duetProject: projectId,
},
});
try {
validateLoadCodeAssistResponse(loadRes);
break;
} catch (e) {
if (e instanceof ValidationRequiredError && validationHandler) {
const intent = await validationHandler(
e.validationLink,
e.validationDescription,
);
if (intent === 'verify') {
continue;
}
if (intent === 'change_auth') {
throw new ChangeAuthRequestedError();
}
throw new ValidationCancelledError();
}
throw e;
}
}
if (loadRes.currentTier) {
if (!loadRes.cloudaicompanionProject) {
@@ -139,3 +178,34 @@ function getOnboardTier(res: LoadCodeAssistResponse): GeminiUserTier {
userDefinedCloudaicompanionProject: true,
};
}
function validateLoadCodeAssistResponse(res: LoadCodeAssistResponse): void {
if (!res) {
throw new Error('LoadCodeAssist returned empty response');
}
if (
!res.currentTier &&
res.ineligibleTiers &&
res.ineligibleTiers.length > 0
) {
// Check for VALIDATION_REQUIRED first - this is a recoverable state
const validationTier = res.ineligibleTiers.find(
(t) =>
t.validationUrl &&
t.reasonCode === IneligibleTierReasonCode.VALIDATION_REQUIRED,
);
const validationUrl = validationTier?.validationUrl;
if (validationTier && validationUrl) {
throw new ValidationRequiredError(
`Account validation required: ${validationTier.reasonMessage}`,
undefined,
validationUrl,
validationTier.reasonMessage,
);
}
// For other ineligibility reasons, throw a generic error
const reasons = res.ineligibleTiers.map((t) => t.reasonMessage).join(', ');
throw new Error(reasons);
}
}