Disallow unnecessary awaits. (#15172)

This commit is contained in:
Christian Gunderman
2025-12-16 21:28:18 -08:00
committed by GitHub
parent 3e9a0a7628
commit 7f2d33458a
38 changed files with 129 additions and 106 deletions
+1
View File
@@ -165,6 +165,7 @@ export default tseslint.config(
'prefer-const': ['error', { destructuring: 'all' }], 'prefer-const': ['error', { destructuring: 'all' }],
radix: 'error', radix: 'error',
'default-case': 'error', 'default-case': 'error',
'@typescript-eslint/await-thenable': ['error'],
'@typescript-eslint/no-floating-promises': ['error'], '@typescript-eslint/no-floating-promises': ['error'],
'@typescript-eslint/no-unnecessary-type-assertion': ['error'], '@typescript-eslint/no-unnecessary-type-assertion': ['error'],
}, },
+1 -1
View File
@@ -123,7 +123,7 @@ export class Task {
// process. This is not scoped to the individual task but reflects the global connection // process. This is not scoped to the individual task but reflects the global connection
// state managed within the @gemini-cli/core module. // state managed within the @gemini-cli/core module.
async getMetadata(): Promise<TaskMetadata> { async getMetadata(): Promise<TaskMetadata> {
const toolRegistry = await this.config.getToolRegistry(); const toolRegistry = this.config.getToolRegistry();
const mcpServers = this.config.getMcpClientManager()?.getMcpServers() || {}; const mcpServers = this.config.getMcpClientManager()?.getMcpServers() || {};
const serverStatuses = getAllMCPServerStatuses(); const serverStatuses = getAllMCPServerStatuses();
const servers = Object.keys(mcpServers).map((serverName) => ({ const servers = Object.keys(mcpServers).map((serverName) => ({
@@ -228,7 +228,9 @@ describe('extensions disable command', () => {
_: [], _: [],
$0: '', $0: '',
}; };
await (command.handler as unknown as (args: TestArgv) => void)(argv); await (command.handler as unknown as (args: TestArgv) => Promise<void>)(
argv,
);
expect(mockExtensionManager).toHaveBeenCalledWith( expect(mockExtensionManager).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
workspaceDir: '/test/dir', workspaceDir: '/test/dir',
@@ -205,7 +205,9 @@ describe('extensions enable command', () => {
_: [], _: [],
$0: '', $0: '',
}; };
await (command.handler as unknown as (args: TestArgv) => void)(argv); await (command.handler as unknown as (args: TestArgv) => Promise<void>)(
argv,
);
expect( expect(
mockExtensionManager.prototype.enableExtension, mockExtensionManager.prototype.enableExtension,
@@ -180,7 +180,9 @@ describe('extensions link command', () => {
_: [], _: [],
$0: '', $0: '',
}; };
await (command.handler as unknown as (args: TestArgv) => void)(argv); await (command.handler as unknown as (args: TestArgv) => Promise<void>)(
argv,
);
expect( expect(
mockExtensionManager.prototype.installOrUpdateExtension, mockExtensionManager.prototype.installOrUpdateExtension,
@@ -49,7 +49,7 @@ const setCommand: CommandModule<object, SetArgs> = {
if (!extension || !extensionManager) { if (!extension || !extensionManager) {
return; return;
} }
const extensionConfig = extensionManager.loadExtensionConfig( const extensionConfig = await extensionManager.loadExtensionConfig(
extension.path, extension.path,
); );
if (!extensionConfig) { if (!extensionConfig) {
@@ -89,7 +89,7 @@ const listCommand: CommandModule<object, ListArgs> = {
if (!extension || !extensionManager) { if (!extension || !extensionManager) {
return; return;
} }
const extensionConfig = extensionManager.loadExtensionConfig( const extensionConfig = await extensionManager.loadExtensionConfig(
extension.path, extension.path,
); );
if ( if (
@@ -287,7 +287,9 @@ describe('extensions uninstall command', () => {
[key: string]: unknown; [key: string]: unknown;
} }
const argv: TestArgv = { names: ['my-extension'], _: [], $0: '' }; const argv: TestArgv = { names: ['my-extension'], _: [], $0: '' };
await (command.handler as unknown as (args: TestArgv) => void)(argv); await (command.handler as unknown as (args: TestArgv) => Promise<void>)(
argv,
);
expect(mockUninstallExtension).toHaveBeenCalledWith( expect(mockUninstallExtension).toHaveBeenCalledWith(
'my-extension', 'my-extension',
@@ -41,7 +41,7 @@ async function validateExtension(args: ValidateArgs) {
}); });
const absoluteInputPath = path.resolve(args.path); const absoluteInputPath = path.resolve(args.path);
const extensionConfig: ExtensionConfig = const extensionConfig: ExtensionConfig =
extensionManager.loadExtensionConfig(absoluteInputPath); await extensionManager.loadExtensionConfig(absoluteInputPath);
const warnings: string[] = []; const warnings: string[] = [];
const errors: string[] = []; const errors: string[] = [];
+9 -1
View File
@@ -4,7 +4,15 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import { describe, it, expect, vi, type Mock, type MockInstance } from 'vitest'; import {
describe,
it,
expect,
vi,
beforeEach,
type Mock,
type MockInstance,
} from 'vitest';
import yargs, { type Argv } from 'yargs'; import yargs, { type Argv } from 'yargs';
import { addCommand } from './add.js'; import { addCommand } from './add.js';
import { loadSettings, SettingScope } from '../../config/settings.js'; import { loadSettings, SettingScope } from '../../config/settings.js';
+5 -5
View File
@@ -220,7 +220,7 @@ export class ExtensionManager extends ExtensionLoader {
} }
try { try {
newExtensionConfig = this.loadExtensionConfig(localSourcePath); newExtensionConfig = await this.loadExtensionConfig(localSourcePath);
if (isUpdate && installMetadata.autoUpdate) { if (isUpdate && installMetadata.autoUpdate) {
const oldSettings = new Set( const oldSettings = new Set(
@@ -364,7 +364,7 @@ export class ExtensionManager extends ExtensionLoader {
// to get the name and version for logging. // to get the name and version for logging.
if (!newExtensionConfig && localSourcePath) { if (!newExtensionConfig && localSourcePath) {
try { try {
newExtensionConfig = this.loadExtensionConfig(localSourcePath); newExtensionConfig = await this.loadExtensionConfig(localSourcePath);
} catch { } catch {
// Ignore error, this is just for logging. // Ignore error, this is just for logging.
} }
@@ -491,7 +491,7 @@ export class ExtensionManager extends ExtensionLoader {
} }
try { try {
let config = this.loadExtensionConfig(effectiveExtensionPath); let config = await this.loadExtensionConfig(effectiveExtensionPath);
if ( if (
this.getExtensions().find((extension) => extension.name === config.name) this.getExtensions().find((extension) => extension.name === config.name)
) { ) {
@@ -571,13 +571,13 @@ export class ExtensionManager extends ExtensionLoader {
return this.maybeStopExtension(extension); return this.maybeStopExtension(extension);
} }
loadExtensionConfig(extensionDir: string): ExtensionConfig { async loadExtensionConfig(extensionDir: string): Promise<ExtensionConfig> {
const configFilePath = path.join(extensionDir, EXTENSIONS_CONFIG_FILENAME); const configFilePath = path.join(extensionDir, EXTENSIONS_CONFIG_FILENAME);
if (!fs.existsSync(configFilePath)) { if (!fs.existsSync(configFilePath)) {
throw new Error(`Configuration file not found at ${configFilePath}`); throw new Error(`Configuration file not found at ${configFilePath}`);
} }
try { try {
const configContent = fs.readFileSync(configFilePath, 'utf-8'); const configContent = await fs.promises.readFile(configFilePath, 'utf-8');
const rawConfig = JSON.parse(configContent) as ExtensionConfig; const rawConfig = JSON.parse(configContent) as ExtensionConfig;
if (!rawConfig.name || !rawConfig.version) { if (!rawConfig.name || !rawConfig.version) {
throw new Error( throw new Error(
+10 -8
View File
@@ -1424,9 +1424,10 @@ This extension will run the following MCP servers:
], ],
}); });
const previousExtensionConfig = extensionManager.loadExtensionConfig( const previousExtensionConfig =
path.join(userExtensionsDir, 'my-local-extension'), await extensionManager.loadExtensionConfig(
); path.join(userExtensionsDir, 'my-local-extension'),
);
mockPromptForSettings.mockResolvedValueOnce('new-setting-value'); mockPromptForSettings.mockResolvedValueOnce('new-setting-value');
// 3. Call installOrUpdateExtension to perform the update. // 3. Call installOrUpdateExtension to perform the update.
@@ -1481,9 +1482,10 @@ This extension will run the following MCP servers:
], ],
}); });
const previousExtensionConfig = extensionManager.loadExtensionConfig( const previousExtensionConfig =
path.join(userExtensionsDir, 'my-auto-update-ext'), await extensionManager.loadExtensionConfig(
); path.join(userExtensionsDir, 'my-auto-update-ext'),
);
// 3. Attempt to update and assert it fails // 3. Attempt to update and assert it fails
await expect( await expect(
@@ -1967,7 +1969,7 @@ This extension will run the following MCP servers:
expect(activeExtensions).toHaveLength(0); expect(activeExtensions).toHaveLength(0);
await extensionManager.enableExtension('ext1', SettingScope.User); await extensionManager.enableExtension('ext1', SettingScope.User);
activeExtensions = await getActiveExtensions(); activeExtensions = getActiveExtensions();
expect(activeExtensions).toHaveLength(1); expect(activeExtensions).toHaveLength(1);
expect(activeExtensions[0].name).toBe('ext1'); expect(activeExtensions[0].name).toBe('ext1');
}); });
@@ -1984,7 +1986,7 @@ This extension will run the following MCP servers:
expect(activeExtensions).toHaveLength(0); expect(activeExtensions).toHaveLength(0);
await extensionManager.enableExtension('ext1', SettingScope.Workspace); await extensionManager.enableExtension('ext1', SettingScope.Workspace);
activeExtensions = await getActiveExtensions(); activeExtensions = getActiveExtensions();
expect(activeExtensions).toHaveLength(1); expect(activeExtensions).toHaveLength(1);
expect(activeExtensions[0].name).toBe('ext1'); expect(activeExtensions[0].name).toBe('ext1');
}); });
@@ -221,9 +221,11 @@ describe('github.ts', () => {
}); });
it('should return NOT_UPDATABLE for non-git/non-release extensions', async () => { it('should return NOT_UPDATABLE for non-git/non-release extensions', async () => {
vi.mocked(mockExtensionManager.loadExtensionConfig).mockReturnValue({ vi.mocked(mockExtensionManager.loadExtensionConfig).mockReturnValue(
version: '1.0.0', Promise.resolve({
} as unknown as ExtensionConfig); version: '1.0.0',
} as unknown as ExtensionConfig),
);
const linkExt = { const linkExt = {
installMetadata: { type: 'link' }, installMetadata: { type: 'link' },
+1 -1
View File
@@ -175,7 +175,7 @@ export async function checkForExtensionUpdate(
if (installMetadata?.type === 'local') { if (installMetadata?.type === 'local') {
let latestConfig: ExtensionConfig | undefined; let latestConfig: ExtensionConfig | undefined;
try { try {
latestConfig = extensionManager.loadExtensionConfig( latestConfig = await extensionManager.loadExtensionConfig(
installMetadata.source, installMetadata.source,
); );
} catch (e) { } catch (e) {
@@ -145,10 +145,12 @@ describe('Extension Update Logic', () => {
}); });
it('should successfully update extension and set state to UPDATED_NEEDS_RESTART by default', async () => { it('should successfully update extension and set state to UPDATED_NEEDS_RESTART by default', async () => {
vi.mocked(mockExtensionManager.loadExtensionConfig).mockReturnValue({ vi.mocked(mockExtensionManager.loadExtensionConfig).mockReturnValue(
name: 'test-extension', Promise.resolve({
version: '1.0.0', name: 'test-extension',
}); version: '1.0.0',
}),
);
vi.mocked( vi.mocked(
mockExtensionManager.installOrUpdateExtension, mockExtensionManager.installOrUpdateExtension,
).mockResolvedValue({ ).mockResolvedValue({
@@ -183,10 +185,12 @@ describe('Extension Update Logic', () => {
}); });
it('should set state to UPDATED if enableExtensionReloading is true', async () => { it('should set state to UPDATED if enableExtensionReloading is true', async () => {
vi.mocked(mockExtensionManager.loadExtensionConfig).mockReturnValue({ vi.mocked(mockExtensionManager.loadExtensionConfig).mockReturnValue(
name: 'test-extension', Promise.resolve({
version: '1.0.0', name: 'test-extension',
}); version: '1.0.0',
}),
);
vi.mocked( vi.mocked(
mockExtensionManager.installOrUpdateExtension, mockExtensionManager.installOrUpdateExtension,
).mockResolvedValue({ ).mockResolvedValue({
@@ -212,10 +216,12 @@ describe('Extension Update Logic', () => {
}); });
it('should rollback and set state to ERROR if installation fails', async () => { it('should rollback and set state to ERROR if installation fails', async () => {
vi.mocked(mockExtensionManager.loadExtensionConfig).mockReturnValue({ vi.mocked(mockExtensionManager.loadExtensionConfig).mockReturnValue(
name: 'test-extension', Promise.resolve({
version: '1.0.0', name: 'test-extension',
}); version: '1.0.0',
}),
);
vi.mocked( vi.mocked(
mockExtensionManager.installOrUpdateExtension, mockExtensionManager.installOrUpdateExtension,
).mockRejectedValue(new Error('Install failed')); ).mockRejectedValue(new Error('Install failed'));
@@ -257,10 +263,12 @@ describe('Extension Update Logic', () => {
['ext3', { status: ExtensionUpdateState.UPDATE_AVAILABLE }], ['ext3', { status: ExtensionUpdateState.UPDATE_AVAILABLE }],
]); ]);
vi.mocked(mockExtensionManager.loadExtensionConfig).mockReturnValue({ vi.mocked(mockExtensionManager.loadExtensionConfig).mockReturnValue(
name: 'ext', Promise.resolve({
version: '1.0.0', name: 'ext',
}); version: '1.0.0',
}),
);
vi.mocked( vi.mocked(
mockExtensionManager.installOrUpdateExtension, mockExtensionManager.installOrUpdateExtension,
).mockResolvedValue({ ...mockExtension, version: '1.1.0' }); ).mockResolvedValue({ ...mockExtension, version: '1.1.0' });
+1 -1
View File
@@ -59,7 +59,7 @@ export async function updateExtension(
const tempDir = await ExtensionStorage.createTmpDir(); const tempDir = await ExtensionStorage.createTmpDir();
try { try {
const previousExtensionConfig = extensionManager.loadExtensionConfig( const previousExtensionConfig = await extensionManager.loadExtensionConfig(
extension.path, extension.path,
); );
let updatedExtension: GeminiCLIExtension; let updatedExtension: GeminiCLIExtension;
@@ -48,7 +48,7 @@ describe('chatCommand', () => {
beforeEach(() => { beforeEach(() => {
mockGetHistory = vi.fn().mockReturnValue([]); mockGetHistory = vi.fn().mockReturnValue([]);
mockGetChat = vi.fn().mockResolvedValue({ mockGetChat = vi.fn().mockReturnValue({
getHistory: mockGetHistory, getHistory: mockGetHistory,
}); });
mockSaveCheckpoint = vi.fn().mockResolvedValue(undefined); mockSaveCheckpoint = vi.fn().mockResolvedValue(undefined);
+2 -2
View File
@@ -121,7 +121,7 @@ const saveCommand: SlashCommand = {
} }
} }
const chat = await config?.getGeminiClient()?.getChat(); const chat = config?.getGeminiClient()?.getChat();
if (!chat) { if (!chat) {
return { return {
type: 'message', type: 'message',
@@ -332,7 +332,7 @@ const shareCommand: SlashCommand = {
}; };
} }
const chat = await context.services.config?.getGeminiClient()?.getChat(); const chat = context.services.config?.getGeminiClient()?.getChat();
if (!chat) { if (!chat) {
return { return {
type: 'message', type: 'message',
+1 -1
View File
@@ -15,7 +15,7 @@ export const copyCommand: SlashCommand = {
kind: CommandKind.BUILT_IN, kind: CommandKind.BUILT_IN,
autoExecute: true, autoExecute: true,
action: async (context, _args): Promise<SlashCommandActionReturn | void> => { action: async (context, _args): Promise<SlashCommandActionReturn | void> => {
const chat = await context.services.config?.getGeminiClient()?.getChat(); const chat = context.services.config?.getGeminiClient()?.getChat();
const history = chat?.getHistory(); const history = chat?.getHistory();
// Get the last message from the AI (model role) // Get the last message from the AI (model role)
@@ -85,7 +85,7 @@ describe('mcpCommand', () => {
}), }),
getMcpServers: vi.fn().mockReturnValue({}), getMcpServers: vi.fn().mockReturnValue({}),
getBlockedMcpServers: vi.fn().mockReturnValue([]), getBlockedMcpServers: vi.fn().mockReturnValue([]),
getPromptRegistry: vi.fn().mockResolvedValue({ getPromptRegistry: vi.fn().mockReturnValue({
getAllPrompts: vi.fn().mockReturnValue([]), getAllPrompts: vi.fn().mockReturnValue([]),
getPromptsByServer: vi.fn().mockReturnValue([]), getPromptsByServer: vi.fn().mockReturnValue([]),
}), }),
+1 -1
View File
@@ -216,7 +216,7 @@ const listAction = async (
const allTools = toolRegistry.getAllTools(); const allTools = toolRegistry.getAllTools();
const mcpTools = allTools.filter((tool) => tool instanceof DiscoveredMCPTool); const mcpTools = allTools.filter((tool) => tool instanceof DiscoveredMCPTool);
const promptRegistry = await config.getPromptRegistry(); const promptRegistry = config.getPromptRegistry();
const mcpPrompts = promptRegistry const mcpPrompts = promptRegistry
.getAllPrompts() .getAllPrompts()
.filter( .filter(
@@ -85,7 +85,7 @@ export const memoryCommand: SlashCommand = {
); );
try { try {
const config = await context.services.config; const config = context.services.config;
if (config) { if (config) {
const { memoryContent, fileCount } = const { memoryContent, fileCount } =
await refreshServerHierarchicalMemory(config); await refreshServerHierarchicalMemory(config);
@@ -116,7 +116,7 @@ async function restoreAction(
} else if (action.type === 'load_history' && loadHistory) { } else if (action.type === 'load_history' && loadHistory) {
loadHistory(action.history); loadHistory(action.history);
if (action.clientHistory) { if (action.clientHistory) {
await config?.getGeminiClient()?.setHistory(action.clientHistory); config?.getGeminiClient()?.setHistory(action.clientHistory);
} }
} }
} }
@@ -89,7 +89,7 @@ describe('MultiFolderTrustDialog', () => {
const keypressCallback = mockedUseKeypress.mock.calls[0][0]; const keypressCallback = mockedUseKeypress.mock.calls[0][0];
await act(async () => { await act(async () => {
await keypressCallback({ keypressCallback({
name: 'escape', name: 'escape',
ctrl: false, ctrl: false,
meta: false, meta: false,
@@ -117,7 +117,7 @@ describe('MultiFolderTrustDialog', () => {
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0]; const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
await act(async () => { await act(async () => {
await onSelect(MultiFolderTrustChoice.NO); onSelect(MultiFolderTrustChoice.NO);
}); });
expect(mockFinishAddingDirectories).toHaveBeenCalledWith( expect(mockFinishAddingDirectories).toHaveBeenCalledWith(
@@ -145,7 +145,7 @@ describe('MultiFolderTrustDialog', () => {
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0]; const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
await act(async () => { await act(async () => {
await onSelect(MultiFolderTrustChoice.YES); onSelect(MultiFolderTrustChoice.YES);
}); });
expect(mockAddDirectory).toHaveBeenCalledWith('/path/to/folder1'); expect(mockAddDirectory).toHaveBeenCalledWith('/path/to/folder1');
@@ -166,7 +166,7 @@ describe('MultiFolderTrustDialog', () => {
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0]; const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
await act(async () => { await act(async () => {
await onSelect(MultiFolderTrustChoice.YES_AND_REMEMBER); onSelect(MultiFolderTrustChoice.YES_AND_REMEMBER);
}); });
expect(mockAddDirectory).toHaveBeenCalledWith('/path/to/folder1'); expect(mockAddDirectory).toHaveBeenCalledWith('/path/to/folder1');
@@ -192,7 +192,7 @@ describe('MultiFolderTrustDialog', () => {
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0]; const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
await act(async () => { await act(async () => {
await onSelect(MultiFolderTrustChoice.NO); onSelect(MultiFolderTrustChoice.NO);
}); });
expect(lastFrame()).toContain('Applying trust settings...'); expect(lastFrame()).toContain('Applying trust settings...');
@@ -210,7 +210,7 @@ describe('MultiFolderTrustDialog', () => {
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0]; const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
await act(async () => { await act(async () => {
await onSelect(MultiFolderTrustChoice.YES); onSelect(MultiFolderTrustChoice.YES);
}); });
expect(mockAddItem).toHaveBeenCalledWith( expect(mockAddItem).toHaveBeenCalledWith(
@@ -243,7 +243,7 @@ describe('MultiFolderTrustDialog', () => {
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0]; const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
await act(async () => { await act(async () => {
await onSelect(MultiFolderTrustChoice.YES); onSelect(MultiFolderTrustChoice.YES);
}); });
expect(mockAddDirectory).toHaveBeenCalledWith('/path/to/good'); expect(mockAddDirectory).toHaveBeenCalledWith('/path/to/good');
@@ -55,7 +55,7 @@ export const VimModeProvider = ({
if (newValue) { if (newValue) {
setVimMode('NORMAL'); setVimMode('NORMAL');
} }
await settings.setValue(SettingScope.User, 'general.vimMode', newValue); settings.setValue(SettingScope.User, 'general.vimMode', newValue);
return newValue; return newValue;
}, [vimEnabled, settings]); }, [vimEnabled, settings]);
@@ -1120,7 +1120,7 @@ describe('useSlashCommandProcessor', () => {
// We should not see a change until we fire an event. // We should not see a change until we fire an event.
await waitFor(() => expect(result.current.slashCommands).toEqual([])); await waitFor(() => expect(result.current.slashCommands).toEqual([]));
await act(() => { act(() => {
appEvents.emit('extensionsStarting'); appEvents.emit('extensionsStarting');
}); });
await waitFor(() => await waitFor(() =>
@@ -140,9 +140,7 @@ describe('useFolderTrust', () => {
}); });
await act(async () => { await act(async () => {
await result.current.handleFolderTrustSelect( result.current.handleFolderTrustSelect(FolderTrustChoice.TRUST_FOLDER);
FolderTrustChoice.TRUST_FOLDER,
);
}); });
await waitFor(() => { await waitFor(() => {
@@ -136,7 +136,7 @@ describe('useQuotaAndFallback', () => {
mockGoogleApiError, mockGoogleApiError,
1000 * 60 * 5, 1000 * 60 * 5,
); // 5 minutes ); // 5 minutes
await act(() => { act(() => {
promise = handler('gemini-pro', 'gemini-flash', error); promise = handler('gemini-pro', 'gemini-flash', error);
}); });
@@ -155,7 +155,7 @@ describe('useQuotaAndFallback', () => {
expect(mockHistoryManager.addItem).not.toHaveBeenCalled(); expect(mockHistoryManager.addItem).not.toHaveBeenCalled();
// Simulate the user choosing to continue with the fallback model // Simulate the user choosing to continue with the fallback model
await act(() => { act(() => {
result.current.handleProQuotaChoice('retry_always'); result.current.handleProQuotaChoice('retry_always');
}); });
@@ -182,7 +182,7 @@ describe('useQuotaAndFallback', () => {
.calls[0][0] as FallbackModelHandler; .calls[0][0] as FallbackModelHandler;
let promise1: Promise<FallbackIntent | null>; let promise1: Promise<FallbackIntent | null>;
await act(() => { act(() => {
promise1 = handler( promise1 = handler(
'gemini-pro', 'gemini-pro',
'gemini-flash', 'gemini-flash',
@@ -206,7 +206,7 @@ describe('useQuotaAndFallback', () => {
expect(result2!).toBe('stop'); expect(result2!).toBe('stop');
expect(result.current.proQuotaRequest).toBe(firstRequest); expect(result.current.proQuotaRequest).toBe(firstRequest);
await act(() => { act(() => {
result.current.handleProQuotaChoice('retry_always'); result.current.handleProQuotaChoice('retry_always');
}); });
@@ -247,7 +247,7 @@ describe('useQuotaAndFallback', () => {
.calls[0][0] as FallbackModelHandler; .calls[0][0] as FallbackModelHandler;
let promise: Promise<FallbackIntent | null>; let promise: Promise<FallbackIntent | null>;
await act(() => { act(() => {
promise = handler('model-A', 'model-B', error); promise = handler('model-A', 'model-B', error);
}); });
@@ -265,7 +265,7 @@ describe('useQuotaAndFallback', () => {
); );
// Simulate the user choosing to continue with the fallback model // Simulate the user choosing to continue with the fallback model
await act(() => { act(() => {
result.current.handleProQuotaChoice('retry_always'); result.current.handleProQuotaChoice('retry_always');
}); });
@@ -303,7 +303,7 @@ describe('useQuotaAndFallback', () => {
let promise: Promise<FallbackIntent | null>; let promise: Promise<FallbackIntent | null>;
const error = new ModelNotFoundError('model not found', 404); const error = new ModelNotFoundError('model not found', 404);
await act(() => { act(() => {
promise = handler('gemini-3-pro-preview', 'gemini-2.5-pro', error); promise = handler('gemini-3-pro-preview', 'gemini-2.5-pro', error);
}); });
@@ -322,7 +322,7 @@ To disable Gemini 3, disable "Preview features" in /settings.`,
); );
// Simulate the user choosing to switch // Simulate the user choosing to switch
await act(() => { act(() => {
result.current.handleProQuotaChoice('retry_always'); result.current.handleProQuotaChoice('retry_always');
}); });
@@ -364,7 +364,7 @@ To disable Gemini 3, disable "Preview features" in /settings.`,
const handler = setFallbackHandlerSpy.mock const handler = setFallbackHandlerSpy.mock
.calls[0][0] as FallbackModelHandler; .calls[0][0] as FallbackModelHandler;
let promise: Promise<FallbackIntent | null>; let promise: Promise<FallbackIntent | null>;
await act(() => { act(() => {
promise = handler( promise = handler(
'gemini-pro', 'gemini-pro',
'gemini-flash', 'gemini-flash',
@@ -372,7 +372,7 @@ To disable Gemini 3, disable "Preview features" in /settings.`,
); );
}); });
await act(() => { act(() => {
result.current.handleProQuotaChoice('retry_later'); result.current.handleProQuotaChoice('retry_later');
}); });
@@ -395,7 +395,7 @@ To disable Gemini 3, disable "Preview features" in /settings.`,
.calls[0][0] as FallbackModelHandler; .calls[0][0] as FallbackModelHandler;
let promise: Promise<FallbackIntent | null>; let promise: Promise<FallbackIntent | null>;
await act(() => { act(() => {
promise = handler( promise = handler(
'gemini-pro', 'gemini-pro',
'gemini-flash', 'gemini-flash',
@@ -403,7 +403,7 @@ To disable Gemini 3, disable "Preview features" in /settings.`,
); );
}); });
await act(() => { act(() => {
result.current.handleProQuotaChoice('retry_always'); result.current.handleProQuotaChoice('retry_always');
}); });
@@ -431,7 +431,7 @@ To disable Gemini 3, disable "Preview features" in /settings.`,
const handler = setFallbackHandlerSpy.mock const handler = setFallbackHandlerSpy.mock
.calls[0][0] as FallbackModelHandler; .calls[0][0] as FallbackModelHandler;
let promise: Promise<FallbackIntent | null>; let promise: Promise<FallbackIntent | null>;
await act(() => { act(() => {
promise = handler( promise = handler(
PREVIEW_GEMINI_MODEL, PREVIEW_GEMINI_MODEL,
DEFAULT_GEMINI_MODEL, DEFAULT_GEMINI_MODEL,
@@ -439,7 +439,7 @@ To disable Gemini 3, disable "Preview features" in /settings.`,
); );
}); });
await act(() => { act(() => {
result.current.handleProQuotaChoice('retry_always'); result.current.handleProQuotaChoice('retry_always');
}); });
@@ -466,7 +466,7 @@ To disable Gemini 3, disable "Preview features" in /settings.`,
const handler = setFallbackHandlerSpy.mock const handler = setFallbackHandlerSpy.mock
.calls[0][0] as FallbackModelHandler; .calls[0][0] as FallbackModelHandler;
let promise: Promise<FallbackIntent | null>; let promise: Promise<FallbackIntent | null>;
await act(() => { act(() => {
promise = handler( promise = handler(
PREVIEW_GEMINI_MODEL, PREVIEW_GEMINI_MODEL,
DEFAULT_GEMINI_FLASH_MODEL, DEFAULT_GEMINI_FLASH_MODEL,
@@ -474,7 +474,7 @@ To disable Gemini 3, disable "Preview features" in /settings.`,
); );
}); });
await act(() => { act(() => {
result.current.handleProQuotaChoice('retry_always'); result.current.handleProQuotaChoice('retry_always');
}); });
+1 -3
View File
@@ -270,9 +270,7 @@ describe('AgentExecutor', () => {
); );
parentToolRegistry.registerTool(MOCK_TOOL_NOT_ALLOWED); parentToolRegistry.registerTool(MOCK_TOOL_NOT_ALLOWED);
vi.spyOn(mockConfig, 'getToolRegistry').mockResolvedValue( vi.spyOn(mockConfig, 'getToolRegistry').mockReturnValue(parentToolRegistry);
parentToolRegistry,
);
mockedGetDirectoryContextString.mockResolvedValue( mockedGetDirectoryContextString.mockResolvedValue(
'Mocked Environment Context', 'Mocked Environment Context',
+1 -1
View File
@@ -106,7 +106,7 @@ export class AgentExecutor<TOutput extends z.ZodTypeAny> {
): Promise<AgentExecutor<TOutput>> { ): Promise<AgentExecutor<TOutput>> {
// Create an isolated tool registry for this agent instance. // Create an isolated tool registry for this agent instance.
const agentToolRegistry = new ToolRegistry(runtimeContext); const agentToolRegistry = new ToolRegistry(runtimeContext);
const parentToolRegistry = await runtimeContext.getToolRegistry(); const parentToolRegistry = runtimeContext.getToolRegistry();
if (definition.toolConfig) { if (definition.toolConfig) {
for (const toolRef of definition.toolConfig.tools) { for (const toolRef of definition.toolConfig.tools) {
+9 -9
View File
@@ -189,7 +189,7 @@ describe('oauth2', () => {
end: vi.fn(), end: vi.fn(),
} as unknown as http.ServerResponse; } as unknown as http.ServerResponse;
await requestCallback(mockReq, mockRes); requestCallback(mockReq, mockRes);
const client = await clientPromise; const client = await clientPromise;
expect(client).toBe(mockOAuth2Client); expect(client).toBe(mockOAuth2Client);
@@ -203,7 +203,9 @@ describe('oauth2', () => {
// Manually trigger the 'tokens' event listener // Manually trigger the 'tokens' event listener
if (tokensListener) { if (tokensListener) {
await tokensListener(mockTokens); await (
tokensListener as unknown as (tokens: Credentials) => Promise<void>
)(mockTokens);
} }
// Verify Google Account was cached // Verify Google Account was cached
@@ -575,9 +577,7 @@ describe('oauth2', () => {
const mockExternalAccountClient = { const mockExternalAccountClient = {
getAccessToken: vi.fn().mockResolvedValue({ token: 'byoid-token' }), getAccessToken: vi.fn().mockResolvedValue({ token: 'byoid-token' }),
}; };
const mockFromJSON = vi const mockFromJSON = vi.fn().mockReturnValue(mockExternalAccountClient);
.fn()
.mockResolvedValue(mockExternalAccountClient);
const mockGoogleAuthInstance = { const mockGoogleAuthInstance = {
fromJSON: mockFromJSON, fromJSON: mockFromJSON,
}; };
@@ -834,7 +834,7 @@ describe('oauth2', () => {
} as unknown as http.ServerResponse; } as unknown as http.ServerResponse;
await expect(async () => { await expect(async () => {
await requestCallback(mockReq, mockRes); requestCallback(mockReq, mockRes);
await clientPromise; await clientPromise;
}).rejects.toThrow( }).rejects.toThrow(
'Google OAuth error: access_denied. User denied access', 'Google OAuth error: access_denied. User denied access',
@@ -891,7 +891,7 @@ describe('oauth2', () => {
} as unknown as http.ServerResponse; } as unknown as http.ServerResponse;
await expect(async () => { await expect(async () => {
await requestCallback(mockReq, mockRes); requestCallback(mockReq, mockRes);
await clientPromise; await clientPromise;
}).rejects.toThrow( }).rejects.toThrow(
'Google OAuth error: server_error. No additional details provided', 'Google OAuth error: server_error. No additional details provided',
@@ -954,7 +954,7 @@ describe('oauth2', () => {
} as unknown as http.ServerResponse; } as unknown as http.ServerResponse;
await expect(async () => { await expect(async () => {
await requestCallback(mockReq, mockRes); requestCallback(mockReq, mockRes);
await clientPromise; await clientPromise;
}).rejects.toThrow( }).rejects.toThrow(
'Failed to exchange authorization code for tokens: Token exchange failed', 'Failed to exchange authorization code for tokens: Token exchange failed',
@@ -1038,7 +1038,7 @@ describe('oauth2', () => {
end: vi.fn(), end: vi.fn(),
} as unknown as http.ServerResponse; } as unknown as http.ServerResponse;
await requestCallback(mockReq, mockRes); requestCallback(mockReq, mockRes);
const client = await clientPromise; const client = await clientPromise;
// Authentication should succeed even if fetchAndCacheUserInfo fails // Authentication should succeed even if fetchAndCacheUserInfo fails
+1 -1
View File
@@ -120,7 +120,7 @@ async function initOauthClient(
const auth = new GoogleAuth({ const auth = new GoogleAuth({
scopes: OAUTH_SCOPE, scopes: OAUTH_SCOPE,
}); });
const byoidClient = await auth.fromJSON({ const byoidClient = auth.fromJSON({
...credentials, ...credentials,
refresh_token: credentials.refresh_token ?? undefined, refresh_token: credentials.refresh_token ?? undefined,
}); });
+3 -3
View File
@@ -304,12 +304,12 @@ describe('Gemini Client (client.ts)', () => {
it('should create a new chat session, clearing the old history', async () => { it('should create a new chat session, clearing the old history', async () => {
// 1. Get the initial chat instance and add some history. // 1. Get the initial chat instance and add some history.
const initialChat = client.getChat(); const initialChat = client.getChat();
const initialHistory = await client.getHistory(); const initialHistory = client.getHistory();
await client.addHistory({ await client.addHistory({
role: 'user', role: 'user',
parts: [{ text: 'some old message' }], parts: [{ text: 'some old message' }],
}); });
const historyWithOldMessage = await client.getHistory(); const historyWithOldMessage = client.getHistory();
expect(historyWithOldMessage.length).toBeGreaterThan( expect(historyWithOldMessage.length).toBeGreaterThan(
initialHistory.length, initialHistory.length,
); );
@@ -319,7 +319,7 @@ describe('Gemini Client (client.ts)', () => {
// 3. Get the new chat instance and its history. // 3. Get the new chat instance and its history.
const newChat = client.getChat(); const newChat = client.getChat();
const newHistory = await client.getHistory(); const newHistory = client.getHistory();
// 4. Assert that the chat instance is new and the history is reset. // 4. Assert that the chat instance is new and the history is reset.
expect(newChat).not.toBe(initialChat); expect(newChat).not.toBe(initialChat);
+1 -1
View File
@@ -540,7 +540,7 @@ export class GeminiClient {
if (this.currentSequenceModel) { if (this.currentSequenceModel) {
modelToUse = this.currentSequenceModel; modelToUse = this.currentSequenceModel;
} else { } else {
const router = await this.config.getModelRouterService(); const router = this.config.getModelRouterService();
const decision = await router.route(routingContext); const decision = await router.route(routingContext);
modelToUse = decision.model; modelToUse = decision.model;
} }
+2 -2
View File
@@ -300,7 +300,7 @@ export async function initializeTelemetry(
}); });
try { try {
await sdk.start(); sdk.start();
if (config.getDebugMode()) { if (config.getDebugMode()) {
debugLogger.log('OpenTelemetry SDK started successfully.'); debugLogger.log('OpenTelemetry SDK started successfully.');
} }
@@ -355,7 +355,7 @@ export async function shutdownTelemetry(
return; return;
} }
try { try {
await ClearcutLogger.getInstance()?.shutdown(); ClearcutLogger.getInstance()?.shutdown();
await sdk.shutdown(); await sdk.shutdown();
if (config.getDebugMode() && fromProcessExit) { if (config.getDebugMode() && fromProcessExit) {
debugLogger.log('OpenTelemetry SDK shut down successfully.'); debugLogger.log('OpenTelemetry SDK shut down successfully.');
@@ -149,7 +149,7 @@ describe('checkpoint utils', () => {
] as ToolCallRequestInfo[]; ] as ToolCallRequestInfo[];
(mockGitService.createFileSnapshot as Mock).mockResolvedValue('hash123'); (mockGitService.createFileSnapshot as Mock).mockResolvedValue('hash123');
(mockGeminiClient.getHistory as Mock).mockResolvedValue([ (mockGeminiClient.getHistory as Mock).mockReturnValue([
{ role: 'user', parts: [] }, { role: 'user', parts: [] },
]); ]);
+1 -1
View File
@@ -125,7 +125,7 @@ export async function processRestorableToolCalls<HistoryType>(
continue; continue;
} }
const clientHistory = await geminiClient.getHistory(); const clientHistory = geminiClient.getHistory();
const checkpointData: ToolCallData<HistoryType> = { const checkpointData: ToolCallData<HistoryType> = {
history, history,
clientHistory, clientHistory,
@@ -237,7 +237,7 @@ describe('editCorrector', () => {
mockGeminiClientInstance = new GeminiClient( mockGeminiClientInstance = new GeminiClient(
mockConfigInstance, mockConfigInstance,
) as Mocked<GeminiClient>; ) as Mocked<GeminiClient>;
mockGeminiClientInstance.getHistory = vi.fn().mockResolvedValue([]); mockGeminiClientInstance.getHistory = vi.fn().mockReturnValue([]);
mockBaseLlmClientInstance = { mockBaseLlmClientInstance = {
generateJson: mockGenerateJson, generateJson: mockGenerateJson,
config: { config: {
@@ -606,9 +606,7 @@ describe('editCorrector', () => {
], ],
}, },
]; ];
(mockGeminiClientInstance.getHistory as Mock).mockResolvedValue( (mockGeminiClientInstance.getHistory as Mock).mockReturnValue(history);
history,
);
const result = await ensureCorrectEdit( const result = await ensureCorrectEdit(
filePath, filePath,
+1 -1
View File
@@ -90,7 +90,7 @@ async function findLastEditTimestamp(
filePath: string, filePath: string,
client: GeminiClient, client: GeminiClient,
): Promise<number> { ): Promise<number> {
const history = (await client.getHistory()) ?? []; const history = client.getHistory() ?? [];
// Tools that may reference the file path in their FunctionResponse `output`. // Tools that may reference the file path in their FunctionResponse `output`.
const toolsInResp = new Set([ const toolsInResp = new Set([