Auth First Run (#1207)

Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
Co-authored-by: N. Taylor Mullen <ntaylormullen@google.com>
This commit is contained in:
matt korwel
2025-06-19 16:52:22 -07:00
committed by GitHub
parent c48fcaa8c3
commit 04518b52c0
37 changed files with 636 additions and 349 deletions

View File

@@ -103,6 +103,7 @@ describe('useSlashCommandProcessor', () => {
let mockSetShowHelp: ReturnType<typeof vi.fn>;
let mockOnDebugMessage: ReturnType<typeof vi.fn>;
let mockOpenThemeDialog: ReturnType<typeof vi.fn>;
let mockOpenAuthDialog: ReturnType<typeof vi.fn>;
let mockOpenEditorDialog: ReturnType<typeof vi.fn>;
let mockPerformMemoryRefresh: ReturnType<typeof vi.fn>;
let mockSetQuittingMessages: ReturnType<typeof vi.fn>;
@@ -120,6 +121,7 @@ describe('useSlashCommandProcessor', () => {
mockSetShowHelp = vi.fn();
mockOnDebugMessage = vi.fn();
mockOpenThemeDialog = vi.fn();
mockOpenAuthDialog = vi.fn();
mockOpenEditorDialog = vi.fn();
mockPerformMemoryRefresh = vi.fn().mockResolvedValue(undefined);
mockSetQuittingMessages = vi.fn();
@@ -171,6 +173,7 @@ describe('useSlashCommandProcessor', () => {
mockSetShowHelp,
mockOnDebugMessage,
mockOpenThemeDialog,
mockOpenAuthDialog,
mockOpenEditorDialog,
mockPerformMemoryRefresh,
mockCorgiMode,

View File

@@ -68,6 +68,7 @@ export const useSlashCommandProcessor = (
setShowHelp: React.Dispatch<React.SetStateAction<boolean>>,
onDebugMessage: (message: string) => void,
openThemeDialog: () => void,
openAuthDialog: () => void,
openEditorDialog: () => void,
performMemoryRefresh: () => Promise<void>,
toggleCorgiMode: () => void,
@@ -197,6 +198,13 @@ export const useSlashCommandProcessor = (
openThemeDialog();
},
},
{
name: 'auth',
description: 'change the auth method',
action: (_mainCommand, _subCommand, _args) => {
openAuthDialog();
},
},
{
name: 'editor',
description: 'set external editor preference',
@@ -907,6 +915,7 @@ Add any other context about the problem here.
setShowHelp,
refreshStatic,
openThemeDialog,
openAuthDialog,
openEditorDialog,
clearItems,
performMemoryRefresh,

View File

@@ -0,0 +1,57 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { useState, useCallback, useEffect } from 'react';
import { LoadedSettings, SettingScope } from '../../config/settings.js';
import { AuthType, Config, clearCachedCredentialFile } from '@gemini-cli/core';
async function performAuthFlow(authMethod: AuthType, config: Config) {
await config.refreshAuth(authMethod);
console.log(`Authenticated via "${authMethod}".`);
}
export const useAuthCommand = (
settings: LoadedSettings,
setAuthError: (error: string | null) => void,
config: Config,
) => {
const [isAuthDialogOpen, setIsAuthDialogOpen] = useState(
settings.merged.selectedAuthType === undefined,
);
useEffect(() => {
if (!isAuthDialogOpen) {
performAuthFlow(settings.merged.selectedAuthType as AuthType, config);
}
}, [isAuthDialogOpen, settings, config]);
const openAuthDialog = useCallback(() => {
setIsAuthDialogOpen(true);
}, []);
const handleAuthSelect = useCallback(
async (authMethod: string | undefined, scope: SettingScope) => {
if (authMethod) {
await clearCachedCredentialFile();
settings.setValue(scope, 'selectedAuthType', authMethod);
}
setIsAuthDialogOpen(false);
setAuthError(null);
},
[settings, setAuthError],
);
const handleAuthHighlight = useCallback((_authMethod: string | undefined) => {
// For now, we don't do anything on highlight.
}, []);
return {
isAuthDialogOpen,
openAuthDialog,
handleAuthSelect,
handleAuthHighlight,
};
};

View File

@@ -359,6 +359,7 @@ describe('useGeminiStream', () => {
props.handleSlashCommand,
props.shellModeActive,
() => 'vscode' as EditorType,
() => {},
),
{
initialProps: {

View File

@@ -22,6 +22,7 @@ import {
GitService,
EditorType,
ThoughtSummary,
isAuthError,
} from '@gemini-cli/core';
import { type Part, type PartListUnion } from '@google/genai';
import {
@@ -87,6 +88,7 @@ export const useGeminiStream = (
>,
shellModeActive: boolean,
getPreferredEditor: () => EditorType | undefined,
onAuthError: () => void,
) => {
const [initError, setInitError] = useState<string | null>(null);
const abortControllerRef = useRef<AbortController | null>(null);
@@ -496,7 +498,9 @@ export const useGeminiStream = (
setPendingHistoryItem(null);
}
} catch (error: unknown) {
if (!isNodeError(error) || error.name !== 'AbortError') {
if (isAuthError(error)) {
onAuthError();
} else if (!isNodeError(error) || error.name !== 'AbortError') {
addItem(
{
type: MessageType.ERROR,
@@ -522,6 +526,7 @@ export const useGeminiStream = (
setInitError,
geminiClient,
startNewTurn,
onAuthError,
],
);