Use IdeClient directly instead of config.ideClient (#7627)

This commit is contained in:
Tommaso Sciortino
2025-09-04 09:32:09 -07:00
committed by GitHub
parent 45d494a8d8
commit cb43bb9ca4
24 changed files with 288 additions and 217 deletions

View File

@@ -260,7 +260,7 @@ export class Config {
private readonly folderTrustFeature: boolean;
private readonly folderTrust: boolean;
private ideMode: boolean;
private ideClient!: IdeClient;
private inFallbackMode = false;
private readonly maxSessionTurns: number;
private readonly listExtensions: boolean;
@@ -383,7 +383,12 @@ export class Config {
throw Error('Config was already initialized');
}
this.initialized = true;
this.ideClient = await IdeClient.getInstance();
if (this.getIdeMode()) {
await (await IdeClient.getInstance()).connect();
logIdeConnection(this, new IdeConnectionEvent(IdeConnectionType.START));
}
// Initialize centralized FileDiscoveryService
this.getFileService();
if (this.getCheckpointingEnabled()) {
@@ -762,20 +767,6 @@ export class Config {
this.ideMode = value;
}
async setIdeModeAndSyncConnection(value: boolean): Promise<void> {
this.ideMode = value;
if (value) {
await this.ideClient.connect();
logIdeConnection(this, new IdeConnectionEvent(IdeConnectionType.SESSION));
} else {
await this.ideClient.disconnect();
}
}
getIdeClient(): IdeClient {
return this.ideClient;
}
/**
* Get the current FileSystemService
*/

View File

@@ -65,7 +65,7 @@ function getRealPath(path: string): string {
* Manages the connection to and interaction with the IDE server.
*/
export class IdeClient {
private static instance: IdeClient;
private static instancePromise: Promise<IdeClient> | null = null;
private client: Client | undefined = undefined;
private state: IDEConnectionState = {
status: IDEConnectionStatus.Disconnected,
@@ -81,19 +81,21 @@ export class IdeClient {
private constructor() {}
static async getInstance(): Promise<IdeClient> {
if (!IdeClient.instance) {
const client = new IdeClient();
client.ideProcessInfo = await getIdeProcessInfo();
client.currentIde = detectIde(client.ideProcessInfo);
if (client.currentIde) {
client.currentIdeDisplayName = getIdeInfo(
client.currentIde,
).displayName;
}
IdeClient.instance = client;
static getInstance(): Promise<IdeClient> {
if (!IdeClient.instancePromise) {
IdeClient.instancePromise = (async () => {
const client = new IdeClient();
client.ideProcessInfo = await getIdeProcessInfo();
client.currentIde = detectIde(client.ideProcessInfo);
if (client.currentIde) {
client.currentIdeDisplayName = getIdeInfo(
client.currentIde,
).displayName;
}
return client;
})();
}
return IdeClient.instance;
return IdeClient.instancePromise;
}
addStatusChangeListener(listener: (state: IDEConnectionState) => void) {

View File

@@ -10,7 +10,17 @@ const mockEnsureCorrectEdit = vi.hoisted(() => vi.fn());
const mockGenerateJson = vi.hoisted(() => vi.fn());
const mockOpenDiff = vi.hoisted(() => vi.fn());
import { IDEConnectionStatus } from '../ide/ide-client.js';
import { IdeClient, IDEConnectionStatus } from '../ide/ide-client.js';
vi.mock('../ide/ide-client.js', () => ({
IdeClient: {
getInstance: vi.fn(),
},
IDEConnectionStatus: {
Connected: 'connected',
Disconnected: 'disconnected',
},
}));
vi.mock('../utils/editCorrector.js', () => ({
ensureCorrectEdit: mockEnsureCorrectEdit,
@@ -70,7 +80,6 @@ describe('EditTool', () => {
setApprovalMode: vi.fn(),
getWorkspaceContext: () => createMockWorkspaceContext(rootDir),
getFileSystemService: () => new StandardFileSystemService(),
getIdeClient: () => undefined,
getIdeMode: () => false,
// getGeminiConfig: () => ({ apiKey: 'test-api-key' }), // This was not a real Config method
// Add other properties/methods of Config if EditTool uses them
@@ -878,8 +887,8 @@ describe('EditTool', () => {
status: IDEConnectionStatus.Connected,
}),
};
vi.mocked(IdeClient.getInstance).mockResolvedValue(ideClient);
(mockConfig as any).getIdeMode = () => true;
(mockConfig as any).getIdeClient = () => ideClient;
});
it('should call ideClient.openDiff and update params on confirmation', async () => {

View File

@@ -33,7 +33,7 @@ import type {
ModifiableDeclarativeTool,
ModifyContext,
} from './modifiable-tool.js';
import { IDEConnectionStatus } from '../ide/ide-client.js';
import { IdeClient, IDEConnectionStatus } from '../ide/ide-client.js';
export function applyReplacement(
currentContent: string | null,
@@ -267,7 +267,7 @@ class EditToolInvocation implements ToolInvocation<EditToolParams, ToolResult> {
'Proposed',
DEFAULT_DIFF_OPTIONS,
);
const ideClient = this.config.getIdeClient();
const ideClient = await IdeClient.getInstance();
const ideConfirmation =
this.config.getIdeMode() &&
ideClient?.getConnectionStatus().status === IDEConnectionStatus.Connected

View File

@@ -10,7 +10,17 @@ const mockFixLLMEditWithInstruction = vi.hoisted(() => vi.fn());
const mockGenerateJson = vi.hoisted(() => vi.fn());
const mockOpenDiff = vi.hoisted(() => vi.fn());
import { IDEConnectionStatus } from '../ide/ide-client.js';
import { IdeClient, IDEConnectionStatus } from '../ide/ide-client.js';
vi.mock('../ide/ide-client.js', () => ({
IdeClient: {
getInstance: vi.fn(),
},
IDEConnectionStatus: {
Connected: 'connected',
Disconnected: 'disconnected',
},
}));
vi.mock('../utils/llm-edit-fixer.js', () => ({
FixLLMEditWithInstruction: mockFixLLMEditWithInstruction,
@@ -75,7 +85,6 @@ describe('SmartEditTool', () => {
setApprovalMode: vi.fn(),
getWorkspaceContext: () => createMockWorkspaceContext(rootDir),
getFileSystemService: () => new StandardFileSystemService(),
getIdeClient: () => undefined,
getIdeMode: () => false,
getApiKey: () => 'test-api-key',
getModel: () => 'test-model',
@@ -449,8 +458,8 @@ describe('SmartEditTool', () => {
status: IDEConnectionStatus.Connected,
}),
};
vi.mocked(IdeClient.getInstance).mockResolvedValue(ideClient);
(mockConfig as any).getIdeMode = () => true;
(mockConfig as any).getIdeClient = () => ideClient;
});
it('should call ideClient.openDiff and update params on confirmation', async () => {

View File

@@ -28,7 +28,7 @@ import {
type ModifiableDeclarativeTool,
type ModifyContext,
} from './modifiable-tool.js';
import { IDEConnectionStatus } from '../ide/ide-client.js';
import { IdeClient, IDEConnectionStatus } from '../ide/ide-client.js';
import { FixLLMEditWithInstruction } from '../utils/llm-edit-fixer.js';
export function applyReplacement(
@@ -526,7 +526,7 @@ class EditToolInvocation implements ToolInvocation<EditToolParams, ToolResult> {
'Proposed',
DEFAULT_DIFF_OPTIONS,
);
const ideClient = this.config.getIdeClient();
const ideClient = await IdeClient.getInstance();
const ideConfirmation =
this.config.getIdeMode() &&
ideClient?.getConnectionStatus().status === IDEConnectionStatus.Connected

View File

@@ -39,7 +39,11 @@ const rootDir = path.resolve(os.tmpdir(), 'gemini-cli-test-root');
// --- MOCKS ---
vi.mock('../core/client.js');
vi.mock('../utils/editCorrector.js');
vi.mock('../ide/ide-client.js', () => ({
IdeClient: {
getInstance: vi.fn(),
},
}));
let mockGeminiClientInstance: Mocked<GeminiClient>;
const mockEnsureCorrectEdit = vi.fn<typeof ensureCorrectEdit>();
const mockEnsureCorrectFileContent = vi.fn<typeof ensureCorrectFileContent>();
@@ -58,7 +62,6 @@ const mockConfigInternal = {
setApprovalMode: vi.fn(),
getGeminiClient: vi.fn(), // Initialize as a plain mock function
getFileSystemService: () => fsService,
getIdeClient: vi.fn(),
getIdeMode: vi.fn(() => false),
getWorkspaceContext: () => createMockWorkspaceContext(rootDir),
getApiKey: () => 'test-key',
@@ -120,14 +123,6 @@ describe('WriteFileTool', () => {
mockConfigInternal.getGeminiClient.mockReturnValue(
mockGeminiClientInstance,
);
mockConfigInternal.getIdeClient.mockReturnValue({
openDiff: vi.fn(),
closeDiff: vi.fn(),
getIdeContext: vi.fn(),
subscribeToIdeContext: vi.fn(),
isCodeTrackerEnabled: vi.fn(),
getTrackedCode: vi.fn(),
});
tool = new WriteFileTool(mockConfig);

View File

@@ -35,7 +35,7 @@ import type {
ModifiableDeclarativeTool,
ModifyContext,
} from './modifiable-tool.js';
import { IDEConnectionStatus } from '../ide/ide-client.js';
import { IdeClient, IDEConnectionStatus } from '../ide/ide-client.js';
import { logFileOperation } from '../telemetry/loggers.js';
import { FileOperationEvent } from '../telemetry/types.js';
import { FileOperation } from '../telemetry/metrics.js';
@@ -193,7 +193,7 @@ class WriteFileToolInvocation extends BaseToolInvocation<
DEFAULT_DIFF_OPTIONS,
);
const ideClient = this.config.getIdeClient();
const ideClient = await IdeClient.getInstance();
const ideConfirmation =
this.config.getIdeMode() &&
ideClient.getConnectionStatus().status === IDEConnectionStatus.Connected