Only enable IDE integration if gemini-cli is running in the same path as open workspace (#5068)
This commit is contained in:
parent
1c1aa047ff
commit
83c4dddb7e
|
@ -15,7 +15,7 @@ const logger = {
|
||||||
|
|
||||||
export type IDEConnectionState = {
|
export type IDEConnectionState = {
|
||||||
status: IDEConnectionStatus;
|
status: IDEConnectionStatus;
|
||||||
details?: string;
|
details?: string; // User-facing
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum IDEConnectionStatus {
|
export enum IDEConnectionStatus {
|
||||||
|
@ -29,41 +29,82 @@ export enum IDEConnectionStatus {
|
||||||
*/
|
*/
|
||||||
export class IdeClient {
|
export class IdeClient {
|
||||||
client: Client | undefined = undefined;
|
client: Client | undefined = undefined;
|
||||||
connectionStatus: IDEConnectionStatus = IDEConnectionStatus.Disconnected;
|
private state: IDEConnectionState = {
|
||||||
|
status: IDEConnectionStatus.Disconnected,
|
||||||
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.connectToMcpServer().catch((err) => {
|
this.init().catch((err) => {
|
||||||
logger.debug('Failed to initialize IdeClient:', err);
|
logger.debug('Failed to initialize IdeClient:', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getConnectionStatus(): {
|
getConnectionStatus(): IDEConnectionState {
|
||||||
status: IDEConnectionStatus;
|
return this.state;
|
||||||
details?: string;
|
|
||||||
} {
|
|
||||||
let details: string | undefined;
|
|
||||||
if (this.connectionStatus === IDEConnectionStatus.Disconnected) {
|
|
||||||
if (!process.env['GEMINI_CLI_IDE_SERVER_PORT']) {
|
|
||||||
details = 'GEMINI_CLI_IDE_SERVER_PORT environment variable is not set.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
status: this.connectionStatus,
|
|
||||||
details,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async connectToMcpServer(): Promise<void> {
|
private setState(status: IDEConnectionStatus, details?: string) {
|
||||||
this.connectionStatus = IDEConnectionStatus.Connecting;
|
this.state = { status, details };
|
||||||
const idePort = process.env['GEMINI_CLI_IDE_SERVER_PORT'];
|
|
||||||
if (!idePort) {
|
if (status === IDEConnectionStatus.Disconnected) {
|
||||||
logger.debug(
|
logger.debug('IDE integration is disconnected. ', details);
|
||||||
'Unable to connect to IDE mode MCP server. GEMINI_CLI_IDE_SERVER_PORT environment variable is not set.',
|
ideContext.clearIdeContext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPortFromEnv(): string | undefined {
|
||||||
|
const port = process.env['GEMINI_CLI_IDE_SERVER_PORT'];
|
||||||
|
if (!port) {
|
||||||
|
this.setState(
|
||||||
|
IDEConnectionStatus.Disconnected,
|
||||||
|
'Gemini CLI Companion extension not found. Install via /ide install and restart the CLI in a fresh terminal window.',
|
||||||
);
|
);
|
||||||
this.connectionStatus = IDEConnectionStatus.Disconnected;
|
return undefined;
|
||||||
|
}
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateWorkspacePath(): boolean {
|
||||||
|
const ideWorkspacePath = process.env['GEMINI_CLI_IDE_WORKSPACE_PATH'];
|
||||||
|
if (!ideWorkspacePath) {
|
||||||
|
this.setState(
|
||||||
|
IDEConnectionStatus.Disconnected,
|
||||||
|
'IDE integration requires a single workspace folder to be open in the IDE. Please ensure one folder is open and try again.',
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ideWorkspacePath !== process.cwd()) {
|
||||||
|
this.setState(
|
||||||
|
IDEConnectionStatus.Disconnected,
|
||||||
|
`Gemini CLI is running in a different directory (${process.cwd()}) from the IDE's open workspace (${ideWorkspacePath}). Please run Gemini CLI in the same directory.`,
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerClientHandlers() {
|
||||||
|
if (!this.client) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.client.setNotificationHandler(
|
||||||
|
IdeContextNotificationSchema,
|
||||||
|
(notification) => {
|
||||||
|
ideContext.setIdeContext(notification.params);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.client.onerror = (_error) => {
|
||||||
|
this.setState(IDEConnectionStatus.Disconnected, 'Client error.');
|
||||||
|
};
|
||||||
|
|
||||||
|
this.client.onclose = () => {
|
||||||
|
this.setState(IDEConnectionStatus.Disconnected, 'Connection closed.');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async establishConnection(port: string) {
|
||||||
let transport: StreamableHTTPClientTransport | undefined;
|
let transport: StreamableHTTPClientTransport | undefined;
|
||||||
try {
|
try {
|
||||||
this.client = new Client({
|
this.client = new Client({
|
||||||
|
@ -71,32 +112,21 @@ export class IdeClient {
|
||||||
// TODO(#3487): use the CLI version here.
|
// TODO(#3487): use the CLI version here.
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
});
|
});
|
||||||
|
|
||||||
transport = new StreamableHTTPClientTransport(
|
transport = new StreamableHTTPClientTransport(
|
||||||
new URL(`http://localhost:${idePort}/mcp`),
|
new URL(`http://localhost:${port}/mcp`),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.registerClientHandlers();
|
||||||
|
|
||||||
await this.client.connect(transport);
|
await this.client.connect(transport);
|
||||||
|
|
||||||
this.client.setNotificationHandler(
|
this.setState(IDEConnectionStatus.Connected);
|
||||||
IdeContextNotificationSchema,
|
|
||||||
(notification) => {
|
|
||||||
ideContext.setIdeContext(notification.params);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
this.client.onerror = (error) => {
|
|
||||||
logger.debug('IDE MCP client error:', error);
|
|
||||||
this.connectionStatus = IDEConnectionStatus.Disconnected;
|
|
||||||
ideContext.clearIdeContext();
|
|
||||||
};
|
|
||||||
this.client.onclose = () => {
|
|
||||||
logger.debug('IDE MCP client connection closed.');
|
|
||||||
this.connectionStatus = IDEConnectionStatus.Disconnected;
|
|
||||||
ideContext.clearIdeContext();
|
|
||||||
};
|
|
||||||
|
|
||||||
this.connectionStatus = IDEConnectionStatus.Connected;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.connectionStatus = IDEConnectionStatus.Disconnected;
|
this.setState(
|
||||||
logger.debug('Failed to connect to MCP server:', error);
|
IDEConnectionStatus.Disconnected,
|
||||||
|
`Failed to connect to IDE server: ${error}`,
|
||||||
|
);
|
||||||
if (transport) {
|
if (transport) {
|
||||||
try {
|
try {
|
||||||
await transport.close();
|
await transport.close();
|
||||||
|
@ -106,4 +136,22 @@ export class IdeClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async init(): Promise<void> {
|
||||||
|
if (this.state.status === IDEConnectionStatus.Connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState(IDEConnectionStatus.Connecting);
|
||||||
|
|
||||||
|
if (!this.validateWorkspacePath()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = this.getPortFromEnv();
|
||||||
|
if (!port) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.establishConnection(port);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,35 @@ import * as vscode from 'vscode';
|
||||||
import { IDEServer } from './ide-server';
|
import { IDEServer } from './ide-server';
|
||||||
import { createLogger } from './utils/logger';
|
import { createLogger } from './utils/logger';
|
||||||
|
|
||||||
|
const IDE_WORKSPACE_PATH_ENV_VAR = 'GEMINI_CLI_IDE_WORKSPACE_PATH';
|
||||||
|
|
||||||
let ideServer: IDEServer;
|
let ideServer: IDEServer;
|
||||||
let logger: vscode.OutputChannel;
|
let logger: vscode.OutputChannel;
|
||||||
let log: (message: string) => void = () => {};
|
let log: (message: string) => void = () => {};
|
||||||
|
|
||||||
|
function updateWorkspacePath(context: vscode.ExtensionContext) {
|
||||||
|
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||||
|
if (workspaceFolders && workspaceFolders.length === 1) {
|
||||||
|
const workspaceFolder = workspaceFolders[0];
|
||||||
|
context.environmentVariableCollection.replace(
|
||||||
|
IDE_WORKSPACE_PATH_ENV_VAR,
|
||||||
|
workspaceFolder.uri.fsPath,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
context.environmentVariableCollection.replace(
|
||||||
|
IDE_WORKSPACE_PATH_ENV_VAR,
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function activate(context: vscode.ExtensionContext) {
|
export async function activate(context: vscode.ExtensionContext) {
|
||||||
logger = vscode.window.createOutputChannel('Gemini CLI IDE Companion');
|
logger = vscode.window.createOutputChannel('Gemini CLI IDE Companion');
|
||||||
log = createLogger(context, logger);
|
log = createLogger(context, logger);
|
||||||
log('Extension activated');
|
log('Extension activated');
|
||||||
|
|
||||||
|
updateWorkspacePath(context);
|
||||||
|
|
||||||
ideServer = new IDEServer(log);
|
ideServer = new IDEServer(log);
|
||||||
try {
|
try {
|
||||||
await ideServer.start(context);
|
await ideServer.start(context);
|
||||||
|
@ -25,6 +46,9 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
|
vscode.workspace.onDidChangeWorkspaceFolders(() => {
|
||||||
|
updateWorkspacePath(context);
|
||||||
|
}),
|
||||||
vscode.commands.registerCommand('gemini-cli.runGeminiCLI', () => {
|
vscode.commands.registerCommand('gemini-cli.runGeminiCLI', () => {
|
||||||
const geminiCmd = 'gemini';
|
const geminiCmd = 'gemini';
|
||||||
const terminal = vscode.window.createTerminal(`Gemini CLI`);
|
const terminal = vscode.window.createTerminal(`Gemini CLI`);
|
||||||
|
|
Loading…
Reference in New Issue