mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-23 19:44:30 -07:00
feat(ide): Check for IDE diffing capabilities before opening diffs (#8266)
This commit is contained in:
@@ -83,6 +83,7 @@ describe('IdeClient', () => {
|
||||
close: vi.fn(),
|
||||
setNotificationHandler: vi.fn(),
|
||||
callTool: vi.fn(),
|
||||
request: vi.fn(),
|
||||
} as unknown as Mocked<Client>;
|
||||
mockHttpTransport = {
|
||||
close: vi.fn(),
|
||||
@@ -534,4 +535,93 @@ describe('IdeClient', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isDiffingEnabled', () => {
|
||||
it('should return false if not connected', async () => {
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
expect(ideClient.isDiffingEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if tool discovery fails', async () => {
|
||||
const config = { port: '8080' };
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config));
|
||||
(
|
||||
vi.mocked(fs.promises.readdir) as Mock<
|
||||
(path: fs.PathLike) => Promise<string[]>
|
||||
>
|
||||
).mockResolvedValue([]);
|
||||
mockClient.request.mockRejectedValue(new Error('Method not found'));
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
await ideClient.connect();
|
||||
|
||||
expect(ideClient.getConnectionStatus().status).toBe(
|
||||
IDEConnectionStatus.Connected,
|
||||
);
|
||||
expect(ideClient.isDiffingEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if diffing tools are not available', async () => {
|
||||
const config = { port: '8080' };
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config));
|
||||
(
|
||||
vi.mocked(fs.promises.readdir) as Mock<
|
||||
(path: fs.PathLike) => Promise<string[]>
|
||||
>
|
||||
).mockResolvedValue([]);
|
||||
mockClient.request.mockResolvedValue({
|
||||
tools: [{ name: 'someOtherTool' }],
|
||||
});
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
await ideClient.connect();
|
||||
|
||||
expect(ideClient.getConnectionStatus().status).toBe(
|
||||
IDEConnectionStatus.Connected,
|
||||
);
|
||||
expect(ideClient.isDiffingEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if only openDiff tool is available', async () => {
|
||||
const config = { port: '8080' };
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config));
|
||||
(
|
||||
vi.mocked(fs.promises.readdir) as Mock<
|
||||
(path: fs.PathLike) => Promise<string[]>
|
||||
>
|
||||
).mockResolvedValue([]);
|
||||
mockClient.request.mockResolvedValue({
|
||||
tools: [{ name: 'openDiff' }],
|
||||
});
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
await ideClient.connect();
|
||||
|
||||
expect(ideClient.getConnectionStatus().status).toBe(
|
||||
IDEConnectionStatus.Connected,
|
||||
);
|
||||
expect(ideClient.isDiffingEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true if connected and diffing tools are available', async () => {
|
||||
const config = { port: '8080' };
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config));
|
||||
(
|
||||
vi.mocked(fs.promises.readdir) as Mock<
|
||||
(path: fs.PathLike) => Promise<string[]>
|
||||
>
|
||||
).mockResolvedValue([]);
|
||||
mockClient.request.mockResolvedValue({
|
||||
tools: [{ name: 'openDiff' }, { name: 'closeDiff' }],
|
||||
});
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
await ideClient.connect();
|
||||
|
||||
expect(ideClient.getConnectionStatus().status).toBe(
|
||||
IDEConnectionStatus.Connected,
|
||||
);
|
||||
expect(ideClient.isDiffingEnabled()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,6 +22,7 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { EnvHttpProxyAgent } from 'undici';
|
||||
import { ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
||||
|
||||
const logger = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -78,6 +79,7 @@ export class IdeClient {
|
||||
private diffResponses = new Map<string, (result: DiffUpdateResult) => void>();
|
||||
private statusListeners = new Set<(state: IDEConnectionState) => void>();
|
||||
private trustChangeListeners = new Set<(isTrusted: boolean) => void>();
|
||||
private availableTools: string[] = [];
|
||||
/**
|
||||
* A mutex to ensure that only one diff view is open in the IDE at a time.
|
||||
* This prevents race conditions and UI issues in IDEs like VSCode that
|
||||
@@ -334,6 +336,53 @@ export class IdeClient {
|
||||
return this.currentIdeDisplayName;
|
||||
}
|
||||
|
||||
isDiffingEnabled(): boolean {
|
||||
return (
|
||||
!!this.client &&
|
||||
this.state.status === IDEConnectionStatus.Connected &&
|
||||
this.availableTools.includes('openDiff') &&
|
||||
this.availableTools.includes('closeDiff')
|
||||
);
|
||||
}
|
||||
|
||||
private async discoverTools(): Promise<void> {
|
||||
if (!this.client) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
logger.debug('Discovering tools from IDE...');
|
||||
const response = await this.client.request(
|
||||
{ method: 'tools/list', params: {} },
|
||||
ListToolsResultSchema,
|
||||
);
|
||||
|
||||
// Map the array of tool objects to an array of tool names (strings)
|
||||
this.availableTools = response.tools.map((tool) => tool.name);
|
||||
|
||||
if (this.availableTools.length > 0) {
|
||||
logger.debug(
|
||||
`Discovered ${this.availableTools.length} tools from IDE: ${this.availableTools.join(', ')}`,
|
||||
);
|
||||
} else {
|
||||
logger.debug(
|
||||
'IDE supports tool discovery, but no tools are available.',
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// It's okay if this fails, the IDE might not support it.
|
||||
// Don't log an error if the method is not found, which is a common case.
|
||||
if (
|
||||
error instanceof Error &&
|
||||
!error.message?.includes('Method not found')
|
||||
) {
|
||||
logger.error(`Error discovering tools from IDE: ${error.message}`);
|
||||
} else {
|
||||
logger.debug('IDE does not support tool discovery.');
|
||||
}
|
||||
this.availableTools = [];
|
||||
}
|
||||
}
|
||||
|
||||
private setState(
|
||||
status: IDEConnectionStatus,
|
||||
details?: string,
|
||||
@@ -631,6 +680,7 @@ export class IdeClient {
|
||||
);
|
||||
await this.client.connect(transport);
|
||||
this.registerClientHandlers();
|
||||
await this.discoverTools();
|
||||
this.setState(IDEConnectionStatus.Connected);
|
||||
return true;
|
||||
} catch (_error) {
|
||||
@@ -664,6 +714,7 @@ export class IdeClient {
|
||||
});
|
||||
await this.client.connect(transport);
|
||||
this.registerClientHandlers();
|
||||
await this.discoverTools();
|
||||
this.setState(IDEConnectionStatus.Connected);
|
||||
return true;
|
||||
} catch (_error) {
|
||||
|
||||
Reference in New Issue
Block a user