Add toggleable IDE mode setting (#5146)

This commit is contained in:
christine betts 2025-07-30 22:36:24 +00:00 committed by GitHub
parent ac1bb5ee42
commit 325bb89137
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 231 additions and 123 deletions

View File

@ -916,7 +916,7 @@ describe('loadCliConfig extensions', () => {
}); });
}); });
describe('loadCliConfig ideMode', () => { describe('loadCliConfig ideModeFeature', () => {
const originalArgv = process.argv; const originalArgv = process.argv;
const originalEnv = { ...process.env }; const originalEnv = { ...process.env };
@ -939,16 +939,16 @@ describe('loadCliConfig ideMode', () => {
const settings: Settings = {}; const settings: Settings = {};
const argv = await parseArguments(); const argv = await parseArguments();
const config = await loadCliConfig(settings, [], 'test-session', argv); const config = await loadCliConfig(settings, [], 'test-session', argv);
expect(config.getIdeMode()).toBe(false); expect(config.getIdeModeFeature()).toBe(false);
}); });
it('should be false when settings.ideMode is true, but SANDBOX is set', async () => { it('should be false when settings.ideModeFeature is true, but SANDBOX is set', async () => {
process.argv = ['node', 'script.js']; process.argv = ['node', 'script.js'];
const argv = await parseArguments(); const argv = await parseArguments();
process.env.TERM_PROGRAM = 'vscode'; process.env.TERM_PROGRAM = 'vscode';
process.env.SANDBOX = 'true'; process.env.SANDBOX = 'true';
const settings: Settings = { ideMode: true }; const settings: Settings = { ideModeFeature: true };
const config = await loadCliConfig(settings, [], 'test-session', argv); const config = await loadCliConfig(settings, [], 'test-session', argv);
expect(config.getIdeMode()).toBe(false); expect(config.getIdeModeFeature()).toBe(false);
}); });
}); });

View File

@ -59,7 +59,7 @@ export interface CliArgs {
experimentalAcp: boolean | undefined; experimentalAcp: boolean | undefined;
extensions: string[] | undefined; extensions: string[] | undefined;
listExtensions: boolean | undefined; listExtensions: boolean | undefined;
ideMode: boolean | undefined; ideModeFeature: boolean | undefined;
proxy: string | undefined; proxy: string | undefined;
includeDirectories: string[] | undefined; includeDirectories: string[] | undefined;
} }
@ -191,7 +191,7 @@ export async function parseArguments(): Promise<CliArgs> {
type: 'boolean', type: 'boolean',
description: 'List all available extensions and exit.', description: 'List all available extensions and exit.',
}) })
.option('ide-mode', { .option('ide-mode-feature', {
type: 'boolean', type: 'boolean',
description: 'Run in IDE mode?', description: 'Run in IDE mode?',
}) })
@ -268,10 +268,13 @@ export async function loadCliConfig(
(v) => v === 'true' || v === '1', (v) => v === 'true' || v === '1',
); );
const ideMode = const ideMode = settings.ideMode ?? false;
(argv.ideMode ?? settings.ideMode ?? false) && !process.env.SANDBOX;
const ideClient = IdeClient.getInstance(ideMode); const ideModeFeature =
(argv.ideModeFeature ?? settings.ideModeFeature ?? false) &&
!process.env.SANDBOX;
const ideClient = IdeClient.getInstance(ideMode && ideModeFeature);
const allExtensions = annotateActiveExtensions( const allExtensions = annotateActiveExtensions(
extensions, extensions,
@ -429,6 +432,7 @@ export async function loadCliConfig(
noBrowser: !!process.env.NO_BROWSER, noBrowser: !!process.env.NO_BROWSER,
summarizeToolOutput: settings.summarizeToolOutput, summarizeToolOutput: settings.summarizeToolOutput,
ideMode, ideMode,
ideModeFeature,
ideClient, ideClient,
}); });
} }

View File

@ -99,7 +99,9 @@ export interface Settings {
vimMode?: boolean; vimMode?: boolean;
// Add other settings here. // Flag to be removed post-launch.
ideModeFeature?: boolean;
/// IDE mode setting configured via slash command toggle.
ideMode?: boolean; ideMode?: boolean;
// Setting for disabling auto-update. // Setting for disabling auto-update.

View File

@ -151,6 +151,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
setFlashFallbackHandler: vi.fn(), setFlashFallbackHandler: vi.fn(),
getSessionId: vi.fn(() => 'test-session-id'), getSessionId: vi.fn(() => 'test-session-id'),
getUserTier: vi.fn().mockResolvedValue(undefined), getUserTier: vi.fn().mockResolvedValue(undefined),
getIdeModeFeature: vi.fn(() => false),
getIdeMode: vi.fn(() => false), getIdeMode: vi.fn(() => false),
getWorkspaceContext: vi.fn(() => ({ getWorkspaceContext: vi.fn(() => ({
getDirectories: vi.fn(() => []), getDirectories: vi.fn(() => []),

View File

@ -573,7 +573,12 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
if (Object.keys(mcpServers || {}).length > 0) { if (Object.keys(mcpServers || {}).length > 0) {
handleSlashCommand(newValue ? '/mcp desc' : '/mcp nodesc'); handleSlashCommand(newValue ? '/mcp desc' : '/mcp nodesc');
} }
} else if (key.ctrl && input === 'e' && ideContextState) { } else if (
key.ctrl &&
input === 'e' &&
config.getIdeMode() &&
ideContextState
) {
setShowIDEContextDetail((prev) => !prev); setShowIDEContextDetail((prev) => !prev);
} else if (key.ctrl && (input === 'c' || input === 'C')) { } else if (key.ctrl && (input === 'c' || input === 'C')) {
handleExit(ctrlCPressedOnce, setCtrlCPressedOnce, ctrlCTimerRef); handleExit(ctrlCPressedOnce, setCtrlCPressedOnce, ctrlCTimerRef);

View File

@ -32,11 +32,19 @@ describe('ideCommand', () => {
ui: { ui: {
addItem: vi.fn(), addItem: vi.fn(),
}, },
services: {
settings: {
setValue: vi.fn(),
},
},
} as unknown as CommandContext; } as unknown as CommandContext;
mockConfig = { mockConfig = {
getIdeModeFeature: vi.fn(),
getIdeMode: vi.fn(), getIdeMode: vi.fn(),
getIdeClient: vi.fn(), getIdeClient: vi.fn(),
setIdeMode: vi.fn(),
setIdeClientDisconnected: vi.fn(),
} as unknown as Config; } as unknown as Config;
platformSpy = vi.spyOn(process, 'platform', 'get'); platformSpy = vi.spyOn(process, 'platform', 'get');
@ -46,13 +54,14 @@ describe('ideCommand', () => {
vi.restoreAllMocks(); vi.restoreAllMocks();
}); });
it('should return null if ideMode is not enabled', () => { it('should return null if ideModeFeature is not enabled', () => {
vi.mocked(mockConfig.getIdeMode).mockReturnValue(false); vi.mocked(mockConfig.getIdeModeFeature).mockReturnValue(false);
const command = ideCommand(mockConfig); const command = ideCommand(mockConfig);
expect(command).toBeNull(); expect(command).toBeNull();
}); });
it('should return the ide command if ideMode is enabled', () => { it('should return the ide command if ideModeFeature is enabled', () => {
vi.mocked(mockConfig.getIdeModeFeature).mockReturnValue(true);
vi.mocked(mockConfig.getIdeMode).mockReturnValue(true); vi.mocked(mockConfig.getIdeMode).mockReturnValue(true);
vi.mocked(mockConfig.getIdeClient).mockReturnValue({ vi.mocked(mockConfig.getIdeClient).mockReturnValue({
getCurrentIde: () => DetectedIde.VSCode, getCurrentIde: () => DetectedIde.VSCode,
@ -60,19 +69,20 @@ describe('ideCommand', () => {
const command = ideCommand(mockConfig); const command = ideCommand(mockConfig);
expect(command).not.toBeNull(); expect(command).not.toBeNull();
expect(command?.name).toBe('ide'); expect(command?.name).toBe('ide');
expect(command?.subCommands).toHaveLength(2); expect(command?.subCommands).toHaveLength(3);
expect(command?.subCommands?.[0].name).toBe('status'); expect(command?.subCommands?.[0].name).toBe('disable');
expect(command?.subCommands?.[1].name).toBe('install'); expect(command?.subCommands?.[1].name).toBe('status');
expect(command?.subCommands?.[2].name).toBe('install');
}); });
describe('status subcommand', () => { describe('status subcommand', () => {
const mockGetConnectionStatus = vi.fn(); const mockGetConnectionStatus = vi.fn();
beforeEach(() => { beforeEach(() => {
vi.mocked(mockConfig.getIdeMode).mockReturnValue(true); vi.mocked(mockConfig.getIdeModeFeature).mockReturnValue(true);
vi.mocked(mockConfig.getIdeClient).mockReturnValue({ vi.mocked(mockConfig.getIdeClient).mockReturnValue({
getConnectionStatus: mockGetConnectionStatus, getConnectionStatus: mockGetConnectionStatus,
getCurrentIde: () => DetectedIde.VSCode, getCurrentIde: () => DetectedIde.VSCode,
} as ReturnType<Config['getIdeClient']>); } as unknown as ReturnType<Config['getIdeClient']>);
}); });
it('should show connected status', () => { it('should show connected status', () => {
@ -80,7 +90,8 @@ describe('ideCommand', () => {
status: core.IDEConnectionStatus.Connected, status: core.IDEConnectionStatus.Connected,
}); });
const command = ideCommand(mockConfig); const command = ideCommand(mockConfig);
const result = command!.subCommands![0].action!(mockContext, ''); const result = command!.subCommands!.find((c) => c.name === 'status')!
.action!(mockContext, '');
expect(mockGetConnectionStatus).toHaveBeenCalled(); expect(mockGetConnectionStatus).toHaveBeenCalled();
expect(result).toEqual({ expect(result).toEqual({
type: 'message', type: 'message',
@ -94,7 +105,8 @@ describe('ideCommand', () => {
status: core.IDEConnectionStatus.Connecting, status: core.IDEConnectionStatus.Connecting,
}); });
const command = ideCommand(mockConfig); const command = ideCommand(mockConfig);
const result = command!.subCommands![0].action!(mockContext, ''); const result = command!.subCommands!.find((c) => c.name === 'status')!
.action!(mockContext, '');
expect(mockGetConnectionStatus).toHaveBeenCalled(); expect(mockGetConnectionStatus).toHaveBeenCalled();
expect(result).toEqual({ expect(result).toEqual({
type: 'message', type: 'message',
@ -107,7 +119,8 @@ describe('ideCommand', () => {
status: core.IDEConnectionStatus.Disconnected, status: core.IDEConnectionStatus.Disconnected,
}); });
const command = ideCommand(mockConfig); const command = ideCommand(mockConfig);
const result = command!.subCommands![0].action!(mockContext, ''); const result = command!.subCommands!.find((c) => c.name === 'status')!
.action!(mockContext, '');
expect(mockGetConnectionStatus).toHaveBeenCalled(); expect(mockGetConnectionStatus).toHaveBeenCalled();
expect(result).toEqual({ expect(result).toEqual({
type: 'message', type: 'message',
@ -123,7 +136,8 @@ describe('ideCommand', () => {
details, details,
}); });
const command = ideCommand(mockConfig); const command = ideCommand(mockConfig);
const result = command!.subCommands![0].action!(mockContext, ''); const result = command!.subCommands!.find((c) => c.name === 'status')!
.action!(mockContext, '');
expect(mockGetConnectionStatus).toHaveBeenCalled(); expect(mockGetConnectionStatus).toHaveBeenCalled();
expect(result).toEqual({ expect(result).toEqual({
type: 'message', type: 'message',
@ -136,10 +150,12 @@ describe('ideCommand', () => {
describe('install subcommand', () => { describe('install subcommand', () => {
const mockInstall = vi.fn(); const mockInstall = vi.fn();
beforeEach(() => { beforeEach(() => {
vi.mocked(mockConfig.getIdeModeFeature).mockReturnValue(true);
vi.mocked(mockConfig.getIdeMode).mockReturnValue(true); vi.mocked(mockConfig.getIdeMode).mockReturnValue(true);
vi.mocked(mockConfig.getIdeClient).mockReturnValue({ vi.mocked(mockConfig.getIdeClient).mockReturnValue({
getCurrentIde: () => DetectedIde.VSCode, getCurrentIde: () => DetectedIde.VSCode,
} as ReturnType<Config['getIdeClient']>); getConnectionStatus: vi.fn(),
} as unknown as ReturnType<Config['getIdeClient']>);
vi.mocked(core.getIdeInstaller).mockReturnValue({ vi.mocked(core.getIdeInstaller).mockReturnValue({
install: mockInstall, install: mockInstall,
isInstalled: vi.fn(), isInstalled: vi.fn(),
@ -154,7 +170,10 @@ describe('ideCommand', () => {
}); });
const command = ideCommand(mockConfig); const command = ideCommand(mockConfig);
await command!.subCommands![1].action!(mockContext, ''); await command!.subCommands!.find((c) => c.name === 'install')!.action!(
mockContext,
'',
);
expect(core.getIdeInstaller).toHaveBeenCalledWith('vscode'); expect(core.getIdeInstaller).toHaveBeenCalledWith('vscode');
expect(mockInstall).toHaveBeenCalled(); expect(mockInstall).toHaveBeenCalled();
@ -181,7 +200,10 @@ describe('ideCommand', () => {
}); });
const command = ideCommand(mockConfig); const command = ideCommand(mockConfig);
await command!.subCommands![1].action!(mockContext, ''); await command!.subCommands!.find((c) => c.name === 'install')!.action!(
mockContext,
'',
);
expect(core.getIdeInstaller).toHaveBeenCalledWith('vscode'); expect(core.getIdeInstaller).toHaveBeenCalledWith('vscode');
expect(mockInstall).toHaveBeenCalled(); expect(mockInstall).toHaveBeenCalled();

View File

@ -6,9 +6,9 @@
import { import {
Config, Config,
IDEConnectionStatus,
getIdeDisplayName, getIdeDisplayName,
getIdeInstaller, getIdeInstaller,
IDEConnectionStatus,
} from '@google/gemini-cli-core'; } from '@google/gemini-cli-core';
import { import {
CommandContext, CommandContext,
@ -16,24 +16,25 @@ import {
SlashCommandActionReturn, SlashCommandActionReturn,
CommandKind, CommandKind,
} from './types.js'; } from './types.js';
import { SettingScope } from '../../config/settings.js';
export const ideCommand = (config: Config | null): SlashCommand | null => { export const ideCommand = (config: Config | null): SlashCommand | null => {
if (!config?.getIdeMode()) { if (!config?.getIdeModeFeature()) {
return null; return null;
} }
const currentIDE = config.getIdeClient().getCurrentIde(); const currentIDE = config.getIdeClient().getCurrentIde();
if (!currentIDE) { if (!currentIDE) {
throw new Error( return null;
'IDE slash command should not be available if not running in an IDE',
);
} }
return { const ideSlashCommand: SlashCommand = {
name: 'ide', name: 'ide',
description: 'manage IDE integration', description: 'manage IDE integration',
kind: CommandKind.BUILT_IN, kind: CommandKind.BUILT_IN,
subCommands: [ subCommands: [],
{ };
const statusCommand: SlashCommand = {
name: 'status', name: 'status',
description: 'check status of IDE integration', description: 'check status of IDE integration',
kind: CommandKind.BUILT_IN, kind: CommandKind.BUILT_IN,
@ -65,8 +66,9 @@ export const ideCommand = (config: Config | null): SlashCommand | null => {
} }
} }
}, },
}, };
{
const installCommand: SlashCommand = {
name: 'install', name: 'install',
description: `install required IDE companion ${getIdeDisplayName(currentIDE)} extension `, description: `install required IDE companion ${getIdeDisplayName(currentIDE)} extension `,
kind: CommandKind.BUILT_IN, kind: CommandKind.BUILT_IN,
@ -100,7 +102,44 @@ export const ideCommand = (config: Config | null): SlashCommand | null => {
Date.now(), Date.now(),
); );
}, },
},
],
}; };
const enableCommand: SlashCommand = {
name: 'enable',
description: 'enable IDE integration',
kind: CommandKind.BUILT_IN,
action: async (context: CommandContext) => {
context.services.settings.setValue(SettingScope.User, 'ideMode', true);
config.setIdeMode(true);
config.setIdeClientConnected();
},
};
const disableCommand: SlashCommand = {
name: 'disable',
description: 'disable IDE integration',
kind: CommandKind.BUILT_IN,
action: async (context: CommandContext) => {
context.services.settings.setValue(SettingScope.User, 'ideMode', false);
config.setIdeMode(false);
config.setIdeClientDisconnected();
},
};
const ideModeEnabled = config.getIdeMode();
if (ideModeEnabled) {
ideSlashCommand.subCommands = [
disableCommand,
statusCommand,
installCommand,
];
} else {
ideSlashCommand.subCommands = [
enableCommand,
statusCommand,
installCommand,
];
}
return ideSlashCommand;
}; };

View File

@ -101,6 +101,7 @@ describe('useSlashCommandProcessor', () => {
setHistory: vi.fn().mockResolvedValue(undefined), setHistory: vi.fn().mockResolvedValue(undefined),
})), })),
getExtensions: vi.fn(() => []), getExtensions: vi.fn(() => []),
getIdeMode: vi.fn(() => false),
} as unknown as Config; } as unknown as Config;
const mockSettings = {} as LoadedSettings; const mockSettings = {} as LoadedSettings;

View File

@ -185,6 +185,8 @@ export const useSlashCommandProcessor = (
], ],
); );
const ideMode = config?.getIdeMode();
useEffect(() => { useEffect(() => {
const controller = new AbortController(); const controller = new AbortController();
const load = async () => { const load = async () => {
@ -205,7 +207,7 @@ export const useSlashCommandProcessor = (
return () => { return () => {
controller.abort(); controller.abort();
}; };
}, [config]); }, [config, ideMode]);
const handleSlashCommand = useCallback( const handleSlashCommand = useCallback(
async ( async (

View File

@ -184,6 +184,7 @@ export interface ConfigParameters {
blockedMcpServers?: Array<{ name: string; extensionName: string }>; blockedMcpServers?: Array<{ name: string; extensionName: string }>;
noBrowser?: boolean; noBrowser?: boolean;
summarizeToolOutput?: Record<string, SummarizeToolOutputSettings>; summarizeToolOutput?: Record<string, SummarizeToolOutputSettings>;
ideModeFeature?: boolean;
ideMode?: boolean; ideMode?: boolean;
ideClient: IdeClient; ideClient: IdeClient;
} }
@ -228,8 +229,9 @@ export class Config {
private readonly model: string; private readonly model: string;
private readonly extensionContextFilePaths: string[]; private readonly extensionContextFilePaths: string[];
private readonly noBrowser: boolean; private readonly noBrowser: boolean;
private readonly ideMode: boolean; private readonly ideModeFeature: boolean;
private readonly ideClient: IdeClient; private ideMode: boolean;
private ideClient: IdeClient;
private inFallbackMode = false; private inFallbackMode = false;
private readonly maxSessionTurns: number; private readonly maxSessionTurns: number;
private readonly listExtensions: boolean; private readonly listExtensions: boolean;
@ -298,7 +300,8 @@ export class Config {
this._blockedMcpServers = params.blockedMcpServers ?? []; this._blockedMcpServers = params.blockedMcpServers ?? [];
this.noBrowser = params.noBrowser ?? false; this.noBrowser = params.noBrowser ?? false;
this.summarizeToolOutput = params.summarizeToolOutput; this.summarizeToolOutput = params.summarizeToolOutput;
this.ideMode = params.ideMode ?? false; this.ideModeFeature = params.ideModeFeature ?? false;
this.ideMode = params.ideMode ?? true;
this.ideClient = params.ideClient; this.ideClient = params.ideClient;
if (params.contextFileName) { if (params.contextFileName) {
@ -589,14 +592,30 @@ export class Config {
return this.summarizeToolOutput; return this.summarizeToolOutput;
} }
getIdeMode(): boolean { getIdeModeFeature(): boolean {
return this.ideMode; return this.ideModeFeature;
} }
getIdeClient(): IdeClient { getIdeClient(): IdeClient {
return this.ideClient; return this.ideClient;
} }
getIdeMode(): boolean {
return this.ideMode;
}
setIdeMode(value: boolean): void {
this.ideMode = value;
}
setIdeClientDisconnected(): void {
this.ideClient.setDisconnected();
}
setIdeClientConnected(): void {
this.ideClient.reconnect(this.ideMode && this.ideModeFeature);
}
async getGitService(): Promise<GitService> { async getGitService(): Promise<GitService> {
if (!this.gitService) { if (!this.gitService) {
this.gitService = new GitService(this.targetDir); this.gitService = new GitService(this.targetDir);

View File

@ -199,7 +199,8 @@ describe('Gemini Client (client.ts)', () => {
setQuotaErrorOccurred: vi.fn(), setQuotaErrorOccurred: vi.fn(),
getNoBrowser: vi.fn().mockReturnValue(false), getNoBrowser: vi.fn().mockReturnValue(false),
getUsageStatisticsEnabled: vi.fn().mockReturnValue(true), getUsageStatisticsEnabled: vi.fn().mockReturnValue(true),
getIdeMode: vi.fn().mockReturnValue(false), getIdeModeFeature: vi.fn().mockReturnValue(false),
getIdeMode: vi.fn().mockReturnValue(true),
getWorkspaceContext: vi.fn().mockReturnValue({ getWorkspaceContext: vi.fn().mockReturnValue({
getDirectories: vi.fn().mockReturnValue(['/test/dir']), getDirectories: vi.fn().mockReturnValue(['/test/dir']),
}), }),
@ -649,7 +650,7 @@ describe('Gemini Client (client.ts)', () => {
}); });
describe('sendMessageStream', () => { describe('sendMessageStream', () => {
it('should include IDE context when ideMode is enabled', async () => { it('should include IDE context when ideModeFeature is enabled', async () => {
// Arrange // Arrange
vi.mocked(ideContext.getIdeContext).mockReturnValue({ vi.mocked(ideContext.getIdeContext).mockReturnValue({
workspaceState: { workspaceState: {
@ -673,7 +674,7 @@ describe('Gemini Client (client.ts)', () => {
}, },
}); });
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true); vi.spyOn(client['config'], 'getIdeModeFeature').mockReturnValue(true);
const mockStream = (async function* () { const mockStream = (async function* () {
yield { type: 'content', value: 'Hello' }; yield { type: 'content', value: 'Hello' };
@ -724,7 +725,7 @@ Here are some other files the user has open, with the most recent at the top:
); );
}); });
it('should not add context if ideMode is enabled but no open files', async () => { it('should not add context if ideModeFeature is enabled but no open files', async () => {
// Arrange // Arrange
vi.mocked(ideContext.getIdeContext).mockReturnValue({ vi.mocked(ideContext.getIdeContext).mockReturnValue({
workspaceState: { workspaceState: {
@ -732,7 +733,7 @@ Here are some other files the user has open, with the most recent at the top:
}, },
}); });
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true); vi.spyOn(client['config'], 'getIdeModeFeature').mockReturnValue(true);
const mockStream = (async function* () { const mockStream = (async function* () {
yield { type: 'content', value: 'Hello' }; yield { type: 'content', value: 'Hello' };
@ -771,7 +772,7 @@ Here are some other files the user has open, with the most recent at the top:
); );
}); });
it('should add context if ideMode is enabled and there is one active file', async () => { it('should add context if ideModeFeature is enabled and there is one active file', async () => {
// Arrange // Arrange
vi.mocked(ideContext.getIdeContext).mockReturnValue({ vi.mocked(ideContext.getIdeContext).mockReturnValue({
workspaceState: { workspaceState: {
@ -787,7 +788,7 @@ Here are some other files the user has open, with the most recent at the top:
}, },
}); });
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true); vi.spyOn(client['config'], 'getIdeModeFeature').mockReturnValue(true);
const mockStream = (async function* () { const mockStream = (async function* () {
yield { type: 'content', value: 'Hello' }; yield { type: 'content', value: 'Hello' };
@ -835,7 +836,7 @@ This is the selected text in the file:
); );
}); });
it('should add context if ideMode is enabled and there are open files but no active file', async () => { it('should add context if ideModeFeature is enabled and there are open files but no active file', async () => {
// Arrange // Arrange
vi.mocked(ideContext.getIdeContext).mockReturnValue({ vi.mocked(ideContext.getIdeContext).mockReturnValue({
workspaceState: { workspaceState: {
@ -852,7 +853,7 @@ This is the selected text in the file:
}, },
}); });
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true); vi.spyOn(client['config'], 'getIdeModeFeature').mockReturnValue(true);
const mockStream = (async function* () { const mockStream = (async function* () {
yield { type: 'content', value: 'Hello' }; yield { type: 'content', value: 'Hello' };

View File

@ -339,7 +339,7 @@ export class GeminiClient {
yield { type: GeminiEventType.ChatCompressed, value: compressed }; yield { type: GeminiEventType.ChatCompressed, value: compressed };
} }
if (this.config.getIdeMode()) { if (this.config.getIdeModeFeature() && this.config.getIdeMode()) {
const ideContextState = ideContext.getIdeContext(); const ideContextState = ideContext.getIdeContext();
const openFiles = ideContextState?.workspaceState?.openFiles; const openFiles = ideContextState?.workspaceState?.openFiles;

View File

@ -41,14 +41,14 @@ export class IdeClient {
private readonly currentIde: DetectedIde | undefined; private readonly currentIde: DetectedIde | undefined;
private readonly currentIdeDisplayName: string | undefined; private readonly currentIdeDisplayName: string | undefined;
private constructor(ideMode: boolean) { constructor(ideMode: boolean) {
if (!ideMode) {
return;
}
this.currentIde = detectIde(); this.currentIde = detectIde();
if (this.currentIde) { if (this.currentIde) {
this.currentIdeDisplayName = getIdeDisplayName(this.currentIde); this.currentIdeDisplayName = getIdeDisplayName(this.currentIde);
} }
if (!ideMode) {
return;
}
this.init().catch((err) => { this.init().catch((err) => {
logger.debug('Failed to initialize IdeClient:', err); logger.debug('Failed to initialize IdeClient:', err);
}); });
@ -130,6 +130,10 @@ export class IdeClient {
}; };
} }
async reconnect(ideMode: boolean) {
IdeClient.instance = new IdeClient(ideMode);
}
private async establishConnection(port: string) { private async establishConnection(port: string) {
let transport: StreamableHTTPClientTransport | undefined; let transport: StreamableHTTPClientTransport | undefined;
try { try {
@ -189,7 +193,15 @@ export class IdeClient {
await this.establishConnection(port); await this.establishConnection(port);
} }
dispose() {
this.client?.close();
}
getDetectedIdeDisplayName(): string | undefined { getDetectedIdeDisplayName(): string | undefined {
return this.currentIdeDisplayName; return this.currentIdeDisplayName;
} }
setDisconnected() {
this.setState(IDEConnectionStatus.Disconnected);
}
} }