mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
Merge tag 'v0.3.0' into chore/sync-gemini-cli-v0.3.0
This commit is contained in:
@@ -4,49 +4,47 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from 'ink';
|
||||
import { AppWrapper } from './ui/App.js';
|
||||
import { loadCliConfig, parseArguments } 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 { Config } from '@qwen-code/qwen-code-core';
|
||||
import {
|
||||
DnsResolutionOrder,
|
||||
LoadedSettings,
|
||||
loadSettings,
|
||||
SettingScope,
|
||||
} from './config/settings.js';
|
||||
import { themeManager } from './ui/themes/theme-manager.js';
|
||||
import { getStartupWarnings } from './utils/startupWarnings.js';
|
||||
import { getUserStartupWarnings } from './utils/userStartupWarnings.js';
|
||||
import { ConsolePatcher } from './ui/utils/ConsolePatcher.js';
|
||||
import { runNonInteractive } from './nonInteractiveCli.js';
|
||||
import { loadExtensions } from './config/extension.js';
|
||||
import { cleanupCheckpoints, registerCleanup } from './utils/cleanup.js';
|
||||
import { getCliVersion } from './utils/version.js';
|
||||
import {
|
||||
Config,
|
||||
sessionId,
|
||||
logUserPrompt,
|
||||
AuthType,
|
||||
FatalConfigError,
|
||||
getOauthClient,
|
||||
logIdeConnection,
|
||||
IdeConnectionEvent,
|
||||
IdeConnectionType,
|
||||
logIdeConnection,
|
||||
logUserPrompt,
|
||||
sessionId,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { render } from 'ink';
|
||||
import { spawn } from 'node:child_process';
|
||||
import dns from 'node:dns';
|
||||
import os from 'node:os';
|
||||
import { basename } from 'node:path';
|
||||
import v8 from 'node:v8';
|
||||
import React from 'react';
|
||||
import { validateAuthMethod } from './config/auth.js';
|
||||
import { loadCliConfig, parseArguments } from './config/config.js';
|
||||
import { loadExtensions } from './config/extension.js';
|
||||
import type { DnsResolutionOrder, LoadedSettings } from './config/settings.js';
|
||||
import { loadSettings, SettingScope } from './config/settings.js';
|
||||
import { runNonInteractive } from './nonInteractiveCli.js';
|
||||
import { AppWrapper } from './ui/App.js';
|
||||
import { setMaxSizedBoxDebugging } from './ui/components/shared/MaxSizedBox.js';
|
||||
import { validateNonInteractiveAuth } from './validateNonInterActiveAuth.js';
|
||||
import { SettingsContext } from './ui/contexts/SettingsContext.js';
|
||||
import { themeManager } from './ui/themes/theme-manager.js';
|
||||
import { ConsolePatcher } from './ui/utils/ConsolePatcher.js';
|
||||
import { detectAndEnableKittyProtocol } from './ui/utils/kittyProtocolDetector.js';
|
||||
import { checkForUpdates } from './ui/utils/updateCheck.js';
|
||||
import { cleanupCheckpoints, registerCleanup } from './utils/cleanup.js';
|
||||
import { AppEvent, appEvents } from './utils/events.js';
|
||||
import { handleAutoUpdate } from './utils/handleAutoUpdate.js';
|
||||
import { appEvents, AppEvent } from './utils/events.js';
|
||||
import { SettingsContext } from './ui/contexts/SettingsContext.js';
|
||||
import { readStdin } from './utils/readStdin.js';
|
||||
import { start_sandbox } from './utils/sandbox.js';
|
||||
import { getStartupWarnings } from './utils/startupWarnings.js';
|
||||
import { getUserStartupWarnings } from './utils/userStartupWarnings.js';
|
||||
import { getCliVersion } from './utils/version.js';
|
||||
import { validateNonInteractiveAuth } from './validateNonInterActiveAuth.js';
|
||||
import { runZedIntegration } from './zed-integration/zedIntegration.js';
|
||||
|
||||
export function validateDnsResolutionOrder(
|
||||
order: string | undefined,
|
||||
@@ -108,7 +106,6 @@ async function relaunchWithAdditionalArgs(additionalArgs: string[]) {
|
||||
await new Promise((resolve) => child.on('close', resolve));
|
||||
process.exit(0);
|
||||
}
|
||||
import { runZedIntegration } from './zed-integration/zedIntegration.js';
|
||||
|
||||
export function setupUnhandledRejectionHandler() {
|
||||
let unhandledRejectionOccurred = false;
|
||||
@@ -132,6 +129,44 @@ ${reason.stack}`
|
||||
});
|
||||
}
|
||||
|
||||
export async function startInteractiveUI(
|
||||
config: Config,
|
||||
settings: LoadedSettings,
|
||||
startupWarnings: string[],
|
||||
workspaceRoot: string,
|
||||
) {
|
||||
const version = await getCliVersion();
|
||||
// Detect and enable Kitty keyboard protocol once at startup
|
||||
await detectAndEnableKittyProtocol();
|
||||
setWindowTitle(basename(workspaceRoot), settings);
|
||||
const instance = render(
|
||||
<React.StrictMode>
|
||||
<SettingsContext.Provider value={settings}>
|
||||
<AppWrapper
|
||||
config={config}
|
||||
settings={settings}
|
||||
startupWarnings={startupWarnings}
|
||||
version={version}
|
||||
/>
|
||||
</SettingsContext.Provider>
|
||||
</React.StrictMode>,
|
||||
{ exitOnCtrlC: false, isScreenReaderEnabled: config.getScreenReader() },
|
||||
);
|
||||
|
||||
checkForUpdates()
|
||||
.then((info) => {
|
||||
handleAutoUpdate(info, settings, config.getProjectRoot());
|
||||
})
|
||||
.catch((err) => {
|
||||
// Silently ignore update check errors.
|
||||
if (config.getDebugMode()) {
|
||||
console.error('Update check failed:', err);
|
||||
}
|
||||
});
|
||||
|
||||
registerCleanup(() => instance.unmount());
|
||||
}
|
||||
|
||||
export async function main() {
|
||||
setupUnhandledRejectionHandler();
|
||||
const workspaceRoot = process.cwd();
|
||||
@@ -139,18 +174,15 @@ export async function main() {
|
||||
|
||||
await cleanupCheckpoints();
|
||||
if (settings.errors.length > 0) {
|
||||
for (const error of settings.errors) {
|
||||
let errorMessage = `Error in ${error.path}: ${error.message}`;
|
||||
if (!process.env['NO_COLOR']) {
|
||||
errorMessage = `\x1b[31m${errorMessage}\x1b[0m`;
|
||||
}
|
||||
console.error(errorMessage);
|
||||
console.error(`Please fix ${error.path} and try again.`);
|
||||
}
|
||||
process.exit(1);
|
||||
const errorMessages = settings.errors.map(
|
||||
(error) => `Error in ${error.path}: ${error.message}`,
|
||||
);
|
||||
throw new FatalConfigError(
|
||||
`${errorMessages.join('\n')}\nPlease fix the configuration file(s) and try again.`,
|
||||
);
|
||||
}
|
||||
|
||||
const argv = await parseArguments();
|
||||
const argv = await parseArguments(settings.merged);
|
||||
const extensions = loadExtensions(workspaceRoot);
|
||||
const config = await loadCliConfig(
|
||||
settings.merged,
|
||||
@@ -167,7 +199,7 @@ export async function main() {
|
||||
registerCleanup(consolePatcher.cleanup);
|
||||
|
||||
dns.setDefaultResultOrder(
|
||||
validateDnsResolutionOrder(settings.merged.dnsResolutionOrder),
|
||||
validateDnsResolutionOrder(settings.merged.advanced?.dnsResolutionOrder),
|
||||
);
|
||||
|
||||
if (argv.promptInteractive && !process.stdin.isTTY) {
|
||||
@@ -186,7 +218,7 @@ export async function main() {
|
||||
}
|
||||
|
||||
// Set a default auth type if one isn't set.
|
||||
if (!settings.merged.selectedAuthType) {
|
||||
if (!settings.merged.security?.auth?.selectedType) {
|
||||
if (process.env['CLOUD_SHELL'] === 'true') {
|
||||
settings.setValue(
|
||||
SettingScope.User,
|
||||
@@ -195,6 +227,14 @@ export async function main() {
|
||||
);
|
||||
}
|
||||
}
|
||||
// Empty key causes issues with the GoogleGenAI package.
|
||||
if (process.env['GEMINI_API_KEY']?.trim() === '') {
|
||||
delete process.env['GEMINI_API_KEY'];
|
||||
}
|
||||
|
||||
if (process.env['GOOGLE_API_KEY']?.trim() === '') {
|
||||
delete process.env['GOOGLE_API_KEY'];
|
||||
}
|
||||
|
||||
setMaxSizedBoxDebugging(config.getDebugMode());
|
||||
|
||||
@@ -206,40 +246,72 @@ export async function main() {
|
||||
}
|
||||
|
||||
// Load custom themes from settings
|
||||
themeManager.loadCustomThemes(settings.merged.customThemes);
|
||||
themeManager.loadCustomThemes(settings.merged.ui?.customThemes);
|
||||
|
||||
if (settings.merged.theme) {
|
||||
if (!themeManager.setActiveTheme(settings.merged.theme)) {
|
||||
if (settings.merged.ui?.theme) {
|
||||
if (!themeManager.setActiveTheme(settings.merged.ui?.theme)) {
|
||||
// If the theme is not found during initial load, log a warning and continue.
|
||||
// The useThemeCommand hook in App.tsx will handle opening the dialog.
|
||||
console.warn(`Warning: Theme "${settings.merged.theme}" not found.`);
|
||||
console.warn(`Warning: Theme "${settings.merged.ui?.theme}" not found.`);
|
||||
}
|
||||
}
|
||||
|
||||
// hop into sandbox if we are outside and sandboxing is enabled
|
||||
if (!process.env['SANDBOX']) {
|
||||
const memoryArgs = settings.merged.autoConfigureMaxOldSpaceSize
|
||||
const memoryArgs = settings.merged.advanced?.autoConfigureMemory
|
||||
? getNodeMemoryArgs(config)
|
||||
: [];
|
||||
const sandboxConfig = config.getSandbox();
|
||||
if (sandboxConfig) {
|
||||
if (
|
||||
settings.merged.selectedAuthType &&
|
||||
!settings.merged.useExternalAuth
|
||||
settings.merged.security?.auth?.selectedType &&
|
||||
!settings.merged.security?.auth?.useExternal
|
||||
) {
|
||||
// Validate authentication here because the sandbox will interfere with the Oauth2 web redirect.
|
||||
try {
|
||||
const err = validateAuthMethod(settings.merged.selectedAuthType);
|
||||
const err = validateAuthMethod(
|
||||
settings.merged.security.auth.selectedType,
|
||||
);
|
||||
if (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
await config.refreshAuth(settings.merged.selectedAuthType);
|
||||
await config.refreshAuth(settings.merged.security.auth.selectedType);
|
||||
} catch (err) {
|
||||
console.error('Error authenticating:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
await start_sandbox(sandboxConfig, memoryArgs, config);
|
||||
let stdinData = '';
|
||||
if (!process.stdin.isTTY) {
|
||||
stdinData = await readStdin();
|
||||
}
|
||||
|
||||
// This function is a copy of the one from sandbox.ts
|
||||
// It is moved here to decouple sandbox.ts from the CLI's argument structure.
|
||||
const injectStdinIntoArgs = (
|
||||
args: string[],
|
||||
stdinData?: string,
|
||||
): string[] => {
|
||||
const finalArgs = [...args];
|
||||
if (stdinData) {
|
||||
const promptIndex = finalArgs.findIndex(
|
||||
(arg) => arg === '--prompt' || arg === '-p',
|
||||
);
|
||||
if (promptIndex > -1 && finalArgs.length > promptIndex + 1) {
|
||||
// If there's a prompt argument, prepend stdin to it
|
||||
finalArgs[promptIndex + 1] =
|
||||
`${stdinData}\n\n${finalArgs[promptIndex + 1]}`;
|
||||
} else {
|
||||
// If there's no prompt argument, add stdin as the prompt
|
||||
finalArgs.push('--prompt', stdinData);
|
||||
}
|
||||
}
|
||||
return finalArgs;
|
||||
};
|
||||
|
||||
const sandboxArgs = injectStdinIntoArgs(process.argv, stdinData);
|
||||
|
||||
await start_sandbox(sandboxConfig, memoryArgs, config, sandboxArgs);
|
||||
process.exit(0);
|
||||
} else {
|
||||
// Not in a sandbox and not entering one, so relaunch with additional
|
||||
@@ -252,11 +324,12 @@ export async function main() {
|
||||
}
|
||||
|
||||
if (
|
||||
settings.merged.selectedAuthType === AuthType.LOGIN_WITH_GOOGLE &&
|
||||
settings.merged.security?.auth?.selectedType ===
|
||||
AuthType.LOGIN_WITH_GOOGLE &&
|
||||
config.isBrowserLaunchSuppressed()
|
||||
) {
|
||||
// Do oauth before app renders to make copying the link possible.
|
||||
await getOauthClient(settings.merged.selectedAuthType, config);
|
||||
await getOauthClient(settings.merged.security.auth.selectedType, config);
|
||||
}
|
||||
|
||||
if (config.getExperimentalZedIntegration()) {
|
||||
@@ -271,36 +344,7 @@ export async function main() {
|
||||
|
||||
// Render UI, passing necessary config values. Check that there is no command line question.
|
||||
if (config.isInteractive()) {
|
||||
const version = await getCliVersion();
|
||||
// Detect and enable Kitty keyboard protocol once at startup
|
||||
await detectAndEnableKittyProtocol();
|
||||
setWindowTitle(basename(workspaceRoot), settings);
|
||||
const instance = render(
|
||||
<React.StrictMode>
|
||||
<SettingsContext.Provider value={settings}>
|
||||
<AppWrapper
|
||||
config={config}
|
||||
settings={settings}
|
||||
startupWarnings={startupWarnings}
|
||||
version={version}
|
||||
/>
|
||||
</SettingsContext.Provider>
|
||||
</React.StrictMode>,
|
||||
{ exitOnCtrlC: false },
|
||||
);
|
||||
|
||||
checkForUpdates()
|
||||
.then((info) => {
|
||||
handleAutoUpdate(info, settings, config.getProjectRoot());
|
||||
})
|
||||
.catch((err) => {
|
||||
// Silently ignore update check errors.
|
||||
if (config.getDebugMode()) {
|
||||
console.error('Update check failed:', err);
|
||||
}
|
||||
});
|
||||
|
||||
registerCleanup(() => instance.unmount());
|
||||
await startInteractiveUI(config, settings, startupWarnings, workspaceRoot);
|
||||
return;
|
||||
}
|
||||
// If not a TTY, read from stdin
|
||||
@@ -312,7 +356,9 @@ export async function main() {
|
||||
}
|
||||
}
|
||||
if (!input) {
|
||||
console.error('No input provided via stdin.');
|
||||
console.error(
|
||||
`No input provided via stdin. Input can be provided by piping data into gemini or using the --prompt option.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -327,17 +373,21 @@ export async function main() {
|
||||
});
|
||||
|
||||
const nonInteractiveConfig = await validateNonInteractiveAuth(
|
||||
settings.merged.selectedAuthType,
|
||||
settings.merged.useExternalAuth,
|
||||
settings.merged.security?.auth?.selectedType,
|
||||
settings.merged.security?.auth?.useExternal,
|
||||
config,
|
||||
);
|
||||
|
||||
if (config.getDebugMode()) {
|
||||
console.log('Session ID: %s', sessionId);
|
||||
}
|
||||
|
||||
await runNonInteractive(nonInteractiveConfig, input, prompt_id);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
function setWindowTitle(title: string, settings: LoadedSettings) {
|
||||
if (!settings.merged.hideWindowTitle) {
|
||||
if (!settings.merged.ui?.hideWindowTitle) {
|
||||
const windowTitle = (process.env['CLI_TITLE'] || `Qwen - ${title}`).replace(
|
||||
// eslint-disable-next-line no-control-regex
|
||||
/[\x00-\x1F\x7F]/g,
|
||||
|
||||
Reference in New Issue
Block a user