Skip to content
33 changes: 33 additions & 0 deletions packages/core/src/ide/ide-connection-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,39 @@ describe('ide-connection-utils', () => {
expect(result).toEqual(validConfig);
});

it('should fall back to a different PID if it matches the current workspace', async () => {
const targetPid = 12345;
const otherPid = 67890;
const validConfig = {
port: '5678',
workspacePath: '/test/workspace',
};
vi.mocked(fs.promises.readFile).mockRejectedValueOnce(
new Error('not found'),
);
(
vi.mocked(fs.promises.readdir) as Mock<
(path: fs.PathLike) => Promise<string[]>
>
).mockResolvedValue([`gemini-ide-server-${otherPid}-111.json`]);
vi.mocked(fs.promises.readFile).mockResolvedValueOnce(
JSON.stringify(validConfig),
);

const result = await getConnectionConfigFromFile(targetPid);

expect(result).toEqual(validConfig);
expect(fs.promises.readFile).toHaveBeenCalledWith(
path.join(
'/tmp',
'gemini',
'ide',
`gemini-ide-server-${otherPid}-111.json`,
),
'utf8',
);
});
Comment thread
This conversation was marked as resolved.

it('should return the first valid config when multiple workspaces are valid', async () => {
const config1 = { port: '1111', workspacePath: '/test/workspace' };
const config2 = { port: '2222', workspacePath: '/test/workspace2' };
Expand Down
86 changes: 79 additions & 7 deletions packages/core/src/ide/ide-connection-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ export function getStdioConfigFromEnv(): StdioConfig | undefined {
return { command, args };
}

const IDE_SERVER_FILE_REGEX = /^gemini-ide-server-(\d+)-\d+\.json$/;

export async function getConnectionConfigFromFile(
pid: number,
): Promise<
Expand Down Expand Up @@ -139,12 +141,16 @@ export async function getConnectionConfigFromFile(
return undefined;
}

const fileRegex = new RegExp(`^gemini-ide-server-${pid}-\\d+\\.json$`);
const matchingFiles = portFiles.filter((file) => fileRegex.test(file)).sort();
const matchingFiles = portFiles.filter((file) =>
IDE_SERVER_FILE_REGEX.test(file),
);

if (matchingFiles.length === 0) {
return undefined;
}

sortConnectionFiles(matchingFiles, pid);

let fileContents: string[];
try {
fileContents = await Promise.all(
Expand Down Expand Up @@ -181,20 +187,86 @@ export async function getConnectionConfigFromFile(
}

if (validWorkspaces.length === 1) {
return validWorkspaces[0];
const selected = validWorkspaces[0];
const fileIndex = parsedContents.indexOf(selected);
if (fileIndex !== -1) {
logger.debug(`Selected IDE connection file: ${matchingFiles[fileIndex]}`);
}
return selected;
}

const portFromEnv = getPortFromEnv();
if (portFromEnv) {
const matchingPort = validWorkspaces.find(
const matchingPortIndex = validWorkspaces.findIndex(
(content) => String(content.port) === portFromEnv,
);
if (matchingPort) {
return matchingPort;
if (matchingPortIndex !== -1) {
const selected = validWorkspaces[matchingPortIndex];
const fileIndex = parsedContents.indexOf(selected);
if (fileIndex !== -1) {
logger.debug(
`Selected IDE connection file (matched port from env): ${matchingFiles[fileIndex]}`,
);
}
return selected;
}
}

return validWorkspaces[0];
const selected = validWorkspaces[0];
const fileIndex = parsedContents.indexOf(selected);
if (fileIndex !== -1) {
logger.debug(
`Selected first valid IDE connection file: ${matchingFiles[fileIndex]}`,
);
}
return selected;
}

// Sort files to prioritize the one matching the target pid,
// then by whether the process is still alive, then by newest (largest PID).
function sortConnectionFiles(files: string[], targetPid: number) {
files.sort((a, b) => {
const aMatch = a.match(IDE_SERVER_FILE_REGEX);
const bMatch = b.match(IDE_SERVER_FILE_REGEX);
const aPid = aMatch ? parseInt(aMatch[1], 10) : 0;
const bPid = bMatch ? parseInt(bMatch[1], 10) : 0;

if (aPid === targetPid && bPid !== targetPid) {
return -1;
}
if (bPid === targetPid && aPid !== targetPid) {
return 1;
}

const aIsAlive = isPidAlive(aPid);
const bIsAlive = isPidAlive(bPid);

if (aIsAlive && !bIsAlive) {
return -1;
}
if (bIsAlive && !aIsAlive) {
return 1;
}

// Newest PIDs first as a heuristic
return bPid - aPid;
});
}

function isPidAlive(pid: number): boolean {
if (pid <= 0) {
return false;
}
// Assume the process is alive since checking would introduce significant overhead.
if (os.platform() === 'win32') {
return true;
}
Comment thread
This conversation was marked as resolved.
try {
process.kill(pid, 0);
return true;
} catch (e) {
return (e as { code?: string }).code === 'EPERM';
}
}

export async function createProxyAwareFetch(ideServerHost: string) {
Expand Down
Loading