Improve Auth error messaging (#1358)
This commit is contained in:
parent
104f23da90
commit
0abd2a644e
|
@ -22,7 +22,7 @@ The Gemini CLI requires you to authenticate with Google's AI services. On initia
|
||||||
source ~/.bashrc
|
source ~/.bashrc
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Login with Google Work**
|
3. **Login with Google Workspace**
|
||||||
|
|
||||||
- Use this option to log in with the **Google Workspace Accounts**. This is a paid service for businesses and organizations that provides a suite of productivity tools, including a custom email domain (e.g. your-name@your-company.com), enhanced security features, and administrative controls. These accounts are often managed by an employer or school.
|
- Use this option to log in with the **Google Workspace Accounts**. This is a paid service for businesses and organizations that provides a suite of productivity tools, including a custom email domain (e.g. your-name@your-company.com), enhanced security features, and administrative controls. These accounts are often managed by an employer or school.
|
||||||
- Google Workspace Account must configure a Google Cloud Project Id to use. You can temporarily set the environment variable in your current shell session using the following command:
|
- Google Workspace Account must configure a Google Cloud Project Id to use. You can temporarily set the environment variable in your current shell session using the following command:
|
||||||
|
|
|
@ -35,7 +35,7 @@ export function AuthDialog({
|
||||||
},
|
},
|
||||||
{ label: 'Gemini API Key', value: AuthType.USE_GEMINI },
|
{ label: 'Gemini API Key', value: AuthType.USE_GEMINI },
|
||||||
{
|
{
|
||||||
label: 'Login with Google Work',
|
label: 'Login with Google Workspace',
|
||||||
value: AuthType.LOGIN_WITH_GOOGLE_ENTERPRISE,
|
value: AuthType.LOGIN_WITH_GOOGLE_ENTERPRISE,
|
||||||
},
|
},
|
||||||
{ label: 'Vertex AI', value: AuthType.USE_VERTEX_AI },
|
{ label: 'Vertex AI', value: AuthType.USE_VERTEX_AI },
|
||||||
|
|
|
@ -49,7 +49,7 @@ export const useAuthCommand = (
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
settings.merged.selectedAuthType ===
|
settings.merged.selectedAuthType ===
|
||||||
AuthType.LOGIN_WITH_GOOGLE_PERSONAL
|
AuthType.LOGIN_WITH_GOOGLE_PERSONAL
|
||||||
? `Failed to login. Ensure your Google account is not an enterprise account.
|
? `Failed to login. Ensure your Google account is not a Workspace account.
|
||||||
Message: ${getErrorMessage(e)}`
|
Message: ${getErrorMessage(e)}`
|
||||||
: `Failed to login. Message: ${getErrorMessage(e)}`;
|
: `Failed to login. Message: ${getErrorMessage(e)}`;
|
||||||
setAuthError(errorMessage);
|
setAuthError(errorMessage);
|
||||||
|
|
|
@ -22,7 +22,7 @@ import {
|
||||||
GitService,
|
GitService,
|
||||||
EditorType,
|
EditorType,
|
||||||
ThoughtSummary,
|
ThoughtSummary,
|
||||||
isAuthError,
|
UnauthorizedError,
|
||||||
UserPromptEvent,
|
UserPromptEvent,
|
||||||
} from '@gemini-cli/core';
|
} from '@gemini-cli/core';
|
||||||
import { type Part, type PartListUnion } from '@google/genai';
|
import { type Part, type PartListUnion } from '@google/genai';
|
||||||
|
@ -537,7 +537,7 @@ export const useGeminiStream = (
|
||||||
'GEMINI_DEBUG: Caught error in useGeminiStream.ts:',
|
'GEMINI_DEBUG: Caught error in useGeminiStream.ts:',
|
||||||
JSON.stringify(error),
|
JSON.stringify(error),
|
||||||
);
|
);
|
||||||
if (isAuthError(error)) {
|
if (error instanceof UnauthorizedError) {
|
||||||
onAuthError();
|
onAuthError();
|
||||||
} else if (!isNodeError(error) || error.name !== 'AbortError') {
|
} else if (!isNodeError(error) || error.name !== 'AbortError') {
|
||||||
addItem(
|
addItem(
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2025 Google LLC
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { GaxiosError } from 'gaxios';
|
|
||||||
|
|
||||||
export function isAuthError(error: unknown): boolean {
|
|
||||||
return (
|
|
||||||
error instanceof GaxiosError && error.response?.data?.error?.code === 401
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -292,8 +292,7 @@ export class GeminiClient {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const parsedJson = JSON.parse(text);
|
return JSON.parse(text);
|
||||||
return parsedJson;
|
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
await reportError(
|
await reportError(
|
||||||
parseError,
|
parseError,
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { getResponseText } from '../utils/generateContentResponseUtilities.js';
|
||||||
import { reportError } from '../utils/errorReporting.js';
|
import { reportError } from '../utils/errorReporting.js';
|
||||||
import { getErrorMessage } from '../utils/errors.js';
|
import { getErrorMessage } from '../utils/errors.js';
|
||||||
import { GeminiChat } from './geminiChat.js';
|
import { GeminiChat } from './geminiChat.js';
|
||||||
import { isAuthError } from '../code_assist/errors.js';
|
import { UnauthorizedError, toFriendlyError } from '../utils/errors.js';
|
||||||
|
|
||||||
// Define a structure for tools passed to the server
|
// Define a structure for tools passed to the server
|
||||||
export interface ServerTool {
|
export interface ServerTool {
|
||||||
|
@ -224,8 +224,9 @@ export class Turn {
|
||||||
value: { ...this.lastUsageMetadata, apiTimeMs: durationMs },
|
value: { ...this.lastUsageMetadata, apiTimeMs: durationMs },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
if (isAuthError(error)) {
|
const error = toFriendlyError(e);
|
||||||
|
if (error instanceof UnauthorizedError) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
if (signal.aborted) {
|
if (signal.aborted) {
|
||||||
|
|
|
@ -21,7 +21,6 @@ export * from './core/nonInteractiveToolExecutor.js';
|
||||||
|
|
||||||
export * from './code_assist/codeAssist.js';
|
export * from './code_assist/codeAssist.js';
|
||||||
export * from './code_assist/oauth2.js';
|
export * from './code_assist/oauth2.js';
|
||||||
export * from './code_assist/errors.js';
|
|
||||||
|
|
||||||
// Export utilities
|
// Export utilities
|
||||||
export * from './utils/paths.js';
|
export * from './utils/paths.js';
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { GaxiosError } from 'gaxios';
|
||||||
|
|
||||||
export function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
export function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
||||||
return error instanceof Error && 'code' in error;
|
return error instanceof Error && 'code' in error;
|
||||||
}
|
}
|
||||||
|
@ -11,12 +13,50 @@ export function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
||||||
export function getErrorMessage(error: unknown): string {
|
export function getErrorMessage(error: unknown): string {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
return error.message;
|
return error.message;
|
||||||
} else {
|
}
|
||||||
try {
|
try {
|
||||||
const errorMessage = String(error);
|
return String(error);
|
||||||
return errorMessage;
|
} catch {
|
||||||
} catch {
|
return 'Failed to get error details';
|
||||||
return 'Failed to get error details';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ForbiddenError extends Error {}
|
||||||
|
export class UnauthorizedError extends Error {}
|
||||||
|
export class BadRequestError extends Error {}
|
||||||
|
|
||||||
|
interface ResponseData {
|
||||||
|
error?: {
|
||||||
|
code?: number;
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toFriendlyError(error: unknown): unknown {
|
||||||
|
if (error instanceof GaxiosError) {
|
||||||
|
const data = parseResponseData(error);
|
||||||
|
if (data.error && data.error.message && data.error.code) {
|
||||||
|
switch (data.error.code) {
|
||||||
|
case 400:
|
||||||
|
return new BadRequestError(data.error.message);
|
||||||
|
case 401:
|
||||||
|
return new UnauthorizedError(data.error.message);
|
||||||
|
case 403:
|
||||||
|
// It's import to pass the message here since it might
|
||||||
|
// explain the cause like "the cloud project you're
|
||||||
|
// using doesn't have code assist enabled".
|
||||||
|
return new ForbiddenError(data.error.message);
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseResponseData(error: GaxiosError): ResponseData {
|
||||||
|
// Inexplicably, Gaxios sometimes doesn't JSONify the response data.
|
||||||
|
if (typeof error.response?.data === 'string') {
|
||||||
|
return JSON.parse(error.response?.data) as ResponseData;
|
||||||
|
}
|
||||||
|
return typeof error.response?.data as ResponseData;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue