mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-27 21:44:25 -07:00
Refactor to defer initialization. (#8925)
This commit is contained in:
+72
-94
@@ -8,15 +8,14 @@ import React from 'react';
|
||||
import { render } from 'ink';
|
||||
import { AppContainer } from './ui/AppContainer.js';
|
||||
import { loadCliConfig, parseArguments } from './config/config.js';
|
||||
import * as cliConfig from './config/config.js';
|
||||
import { readStdin } from './utils/readStdin.js';
|
||||
import { basename } from 'node:path';
|
||||
import v8 from 'node:v8';
|
||||
import os from 'node:os';
|
||||
import dns from 'node:dns';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { start_sandbox } from './utils/sandbox.js';
|
||||
import type { DnsResolutionOrder, LoadedSettings } from './config/settings.js';
|
||||
import { RELAUNCH_EXIT_CODE } from './utils/processUtils.js';
|
||||
import {
|
||||
loadSettings,
|
||||
migrateDeprecatedSettings,
|
||||
@@ -58,6 +57,10 @@ import { SessionStatsProvider } from './ui/contexts/SessionContext.js';
|
||||
import { VimModeProvider } from './ui/contexts/VimModeContext.js';
|
||||
import { KeypressProvider } from './ui/contexts/KeypressContext.js';
|
||||
import { useKittyKeyboardProtocol } from './ui/hooks/useKittyKeyboardProtocol.js';
|
||||
import {
|
||||
relaunchAppInChildProcess,
|
||||
relaunchOnExitCode,
|
||||
} from './utils/relaunch.js';
|
||||
|
||||
export function validateDnsResolutionOrder(
|
||||
order: string | undefined,
|
||||
@@ -76,7 +79,7 @@ export function validateDnsResolutionOrder(
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
function getNodeMemoryArgs(config: Config): string[] {
|
||||
function getNodeMemoryArgs(isDebugMode: boolean): string[] {
|
||||
const totalMemoryMB = os.totalmem() / (1024 * 1024);
|
||||
const heapStats = v8.getHeapStatistics();
|
||||
const currentMaxOldSpaceSizeMb = Math.floor(
|
||||
@@ -85,7 +88,7 @@ function getNodeMemoryArgs(config: Config): string[] {
|
||||
|
||||
// Set target to 50% of total memory
|
||||
const targetMaxOldSpaceSizeInMB = Math.floor(totalMemoryMB * 0.5);
|
||||
if (config.getDebugMode()) {
|
||||
if (isDebugMode) {
|
||||
console.debug(
|
||||
`Current heap size ${currentMaxOldSpaceSizeMb.toFixed(2)} MB`,
|
||||
);
|
||||
@@ -96,7 +99,7 @@ function getNodeMemoryArgs(config: Config): string[] {
|
||||
}
|
||||
|
||||
if (targetMaxOldSpaceSizeInMB > currentMaxOldSpaceSizeMb) {
|
||||
if (config.getDebugMode()) {
|
||||
if (isDebugMode) {
|
||||
console.debug(
|
||||
`Need to relaunch with more memory: ${targetMaxOldSpaceSizeInMB.toFixed(2)} MB`,
|
||||
);
|
||||
@@ -107,53 +110,8 @@ function getNodeMemoryArgs(config: Config): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
async function relaunchOnExitCode(runner: () => Promise<number>) {
|
||||
while (true) {
|
||||
try {
|
||||
const exitCode = await runner();
|
||||
|
||||
if (exitCode !== RELAUNCH_EXIT_CODE) {
|
||||
process.exit(exitCode);
|
||||
}
|
||||
} catch (error) {
|
||||
process.stdin.resume();
|
||||
console.error('Fatal error: Failed to relaunch the CLI process.', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function relaunchAppInChildProcess(additionalArgs: string[]) {
|
||||
if (process.env['GEMINI_CLI_NO_RELAUNCH']) {
|
||||
return;
|
||||
}
|
||||
|
||||
const runner = () => {
|
||||
const nodeArgs = [...additionalArgs, ...process.argv.slice(1)];
|
||||
const newEnv = { ...process.env, GEMINI_CLI_NO_RELAUNCH: 'true' };
|
||||
|
||||
// The parent process should not be reading from stdin while the child is running.
|
||||
process.stdin.pause();
|
||||
|
||||
const child = spawn(process.execPath, nodeArgs, {
|
||||
stdio: 'inherit',
|
||||
env: newEnv,
|
||||
});
|
||||
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
child.on('error', reject);
|
||||
child.on('close', (code) => {
|
||||
// Resume stdin before the parent process exits.
|
||||
process.stdin.resume();
|
||||
resolve(code ?? 1);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
await relaunchOnExitCode(runner);
|
||||
}
|
||||
|
||||
import { runZedIntegration } from './zed-integration/zedIntegration.js';
|
||||
import { loadSandboxConfig } from './config/sandboxConfig.js';
|
||||
|
||||
export function setupUnhandledRejectionHandler() {
|
||||
let unhandledRejectionOccurred = false;
|
||||
@@ -248,13 +206,6 @@ export async function main() {
|
||||
await cleanupCheckpoints();
|
||||
|
||||
const argv = await parseArguments(settings.merged);
|
||||
const extensions = loadExtensions();
|
||||
const config = await loadCliConfig(
|
||||
settings.merged,
|
||||
extensions,
|
||||
sessionId,
|
||||
argv,
|
||||
);
|
||||
|
||||
// Check for invalid input combinations early to prevent crashes
|
||||
if (argv.promptInteractive && !process.stdin.isTTY) {
|
||||
@@ -264,28 +215,10 @@ export async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const wasRaw = process.stdin.isRaw;
|
||||
let kittyProtocolDetectionComplete: Promise<boolean> | undefined;
|
||||
if (config.isInteractive() && !wasRaw && process.stdin.isTTY) {
|
||||
// Set this as early as possible to avoid spurious characters from
|
||||
// input showing up in the output.
|
||||
process.stdin.setRawMode(true);
|
||||
|
||||
// This cleanup isn't strictly needed but may help in certain situations.
|
||||
process.on('SIGTERM', () => {
|
||||
process.stdin.setRawMode(wasRaw);
|
||||
});
|
||||
process.on('SIGINT', () => {
|
||||
process.stdin.setRawMode(wasRaw);
|
||||
});
|
||||
|
||||
// Detect and enable Kitty keyboard protocol once at startup.
|
||||
kittyProtocolDetectionComplete = detectAndEnableKittyProtocol();
|
||||
}
|
||||
|
||||
const isDebugMode = cliConfig.isDebugMode(argv);
|
||||
const consolePatcher = new ConsolePatcher({
|
||||
stderr: true,
|
||||
debugMode: config.getDebugMode(),
|
||||
debugMode: isDebugMode,
|
||||
});
|
||||
consolePatcher.patch();
|
||||
registerCleanup(consolePatcher.cleanup);
|
||||
@@ -294,14 +227,6 @@ export async function main() {
|
||||
validateDnsResolutionOrder(settings.merged.advanced?.dnsResolutionOrder),
|
||||
);
|
||||
|
||||
if (config.getListExtensions()) {
|
||||
console.log('Installed extensions:');
|
||||
for (const extension of extensions) {
|
||||
console.log(`- ${extension.config.name}`);
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Set a default auth type if one isn't set.
|
||||
if (!settings.merged.security?.auth?.selectedType) {
|
||||
if (process.env['CLOUD_SHELL'] === 'true') {
|
||||
@@ -313,8 +238,6 @@ export async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
setMaxSizedBoxDebugging(config.getDebugMode());
|
||||
|
||||
// Load custom themes from settings
|
||||
themeManager.loadCustomThemes(settings.merged.ui?.customThemes);
|
||||
|
||||
@@ -326,14 +249,13 @@ export async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
const initializationResult = await initializeApp(config, settings);
|
||||
|
||||
// hop into sandbox if we are outside and sandboxing is enabled
|
||||
if (!process.env['SANDBOX']) {
|
||||
const memoryArgs = settings.merged.advanced?.autoConfigureMemory
|
||||
? getNodeMemoryArgs(config)
|
||||
? getNodeMemoryArgs(isDebugMode)
|
||||
: [];
|
||||
const sandboxConfig = config.getSandbox();
|
||||
const sandboxConfig = await loadSandboxConfig(settings.merged, argv);
|
||||
|
||||
if (sandboxConfig) {
|
||||
if (
|
||||
settings.merged.security?.auth?.selectedType &&
|
||||
@@ -347,7 +269,21 @@ export async function main() {
|
||||
if (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
await config.refreshAuth(settings.merged.security.auth.selectedType);
|
||||
// We intentially omit the list of extensions here because extensions
|
||||
// should not impact auth.
|
||||
// TODO(jacobr): refactor loadCliConfig so there is a minimal version
|
||||
// that only initializes enough config to enable refreshAuth or find
|
||||
// another way to decouple refreshAuth from requiring a config.
|
||||
const partialConfig = await loadCliConfig(
|
||||
settings.merged,
|
||||
[],
|
||||
sessionId,
|
||||
argv,
|
||||
);
|
||||
|
||||
await partialConfig.refreshAuth(
|
||||
settings.merged.security.auth.selectedType,
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('Error authenticating:', err);
|
||||
process.exit(1);
|
||||
@@ -390,10 +326,52 @@ export async function main() {
|
||||
} else {
|
||||
// Relaunch app so we always have a child process that can be internally
|
||||
// restarted if needed.
|
||||
await relaunchAppInChildProcess(memoryArgs);
|
||||
await relaunchAppInChildProcess(memoryArgs, []);
|
||||
}
|
||||
}
|
||||
|
||||
// We are now past the logic handling potentially launching a child process
|
||||
// to run Gemini CLI. It is now safe to perform expensive initialization that
|
||||
// may have side effects.
|
||||
const extensions = loadExtensions();
|
||||
const config = await loadCliConfig(
|
||||
settings.merged,
|
||||
extensions,
|
||||
sessionId,
|
||||
argv,
|
||||
);
|
||||
|
||||
if (config.getListExtensions()) {
|
||||
console.log('Installed extensions:');
|
||||
for (const extension of extensions) {
|
||||
console.log(`- ${extension.config.name}`);
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const wasRaw = process.stdin.isRaw;
|
||||
let kittyProtocolDetectionComplete: Promise<boolean> | undefined;
|
||||
if (config.isInteractive() && !wasRaw && process.stdin.isTTY) {
|
||||
// Set this as early as possible to avoid spurious characters from
|
||||
// input showing up in the output.
|
||||
process.stdin.setRawMode(true);
|
||||
|
||||
// This cleanup isn't strictly needed but may help in certain situations.
|
||||
process.on('SIGTERM', () => {
|
||||
process.stdin.setRawMode(wasRaw);
|
||||
});
|
||||
process.on('SIGINT', () => {
|
||||
process.stdin.setRawMode(wasRaw);
|
||||
});
|
||||
|
||||
// Detect and enable Kitty keyboard protocol once at startup.
|
||||
kittyProtocolDetectionComplete = detectAndEnableKittyProtocol();
|
||||
}
|
||||
|
||||
setMaxSizedBoxDebugging(isDebugMode);
|
||||
|
||||
const initializationResult = await initializeApp(config, settings);
|
||||
|
||||
if (
|
||||
settings.merged.security?.auth?.selectedType ===
|
||||
AuthType.LOGIN_WITH_GOOGLE &&
|
||||
|
||||
Reference in New Issue
Block a user