mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
chore: sync gemini-cli v0.1.19
This commit is contained in:
@@ -10,6 +10,7 @@ import { homedir } from 'node:os';
|
||||
import yargs from 'yargs/yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
import process from 'node:process';
|
||||
import { mcpCommand } from '../commands/mcp.js';
|
||||
import {
|
||||
Config,
|
||||
loadServerHierarchicalMemory,
|
||||
@@ -22,6 +23,11 @@ import {
|
||||
FileDiscoveryService,
|
||||
TelemetryTarget,
|
||||
FileFilteringOptions,
|
||||
ShellTool,
|
||||
EditTool,
|
||||
WriteFileTool,
|
||||
MCPServerConfig,
|
||||
ConfigParameters,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { Settings } from './settings.js';
|
||||
|
||||
@@ -68,7 +74,6 @@ export interface CliArgs {
|
||||
openaiBaseUrl: string | undefined;
|
||||
proxy: string | undefined;
|
||||
includeDirectories: string[] | undefined;
|
||||
loadMemoryFromIncludeDirectories: boolean | undefined;
|
||||
tavilyApiKey: string | undefined;
|
||||
}
|
||||
|
||||
@@ -76,190 +81,196 @@ export async function parseArguments(): Promise<CliArgs> {
|
||||
const yargsInstance = yargs(hideBin(process.argv))
|
||||
.scriptName('qwen')
|
||||
.usage(
|
||||
'$0 [options]',
|
||||
'Qwen Code - Launch an interactive CLI, use -p/--prompt for non-interactive mode',
|
||||
'Usage: qwen [options] [command]\n\nQwen Code - Launch an interactive CLI, use -p/--prompt for non-interactive mode',
|
||||
)
|
||||
.option('model', {
|
||||
alias: 'm',
|
||||
type: 'string',
|
||||
description: `Model`,
|
||||
default: process.env.GEMINI_MODEL,
|
||||
})
|
||||
.option('prompt', {
|
||||
alias: 'p',
|
||||
type: 'string',
|
||||
description: 'Prompt. Appended to input on stdin (if any).',
|
||||
})
|
||||
.option('prompt-interactive', {
|
||||
alias: 'i',
|
||||
type: 'string',
|
||||
description:
|
||||
'Execute the provided prompt and continue in interactive mode',
|
||||
})
|
||||
.option('sandbox', {
|
||||
alias: 's',
|
||||
type: 'boolean',
|
||||
description: 'Run in sandbox?',
|
||||
})
|
||||
.option('sandbox-image', {
|
||||
type: 'string',
|
||||
description: 'Sandbox image URI.',
|
||||
})
|
||||
.option('debug', {
|
||||
alias: 'd',
|
||||
type: 'boolean',
|
||||
description: 'Run in debug mode?',
|
||||
default: false,
|
||||
})
|
||||
.option('all-files', {
|
||||
alias: ['a'],
|
||||
type: 'boolean',
|
||||
description: 'Include ALL files in context?',
|
||||
default: false,
|
||||
})
|
||||
.option('all_files', {
|
||||
type: 'boolean',
|
||||
description: 'Include ALL files in context?',
|
||||
default: false,
|
||||
})
|
||||
.deprecateOption(
|
||||
'all_files',
|
||||
'Use --all-files instead. We will be removing --all_files in the coming weeks.',
|
||||
.command('$0', 'Launch Qwen Code', (yargsInstance) =>
|
||||
yargsInstance
|
||||
.option('model', {
|
||||
alias: 'm',
|
||||
type: 'string',
|
||||
description: `Model`,
|
||||
default: process.env.GEMINI_MODEL,
|
||||
})
|
||||
.option('prompt', {
|
||||
alias: 'p',
|
||||
type: 'string',
|
||||
description: 'Prompt. Appended to input on stdin (if any).',
|
||||
})
|
||||
.option('prompt-interactive', {
|
||||
alias: 'i',
|
||||
type: 'string',
|
||||
description:
|
||||
'Execute the provided prompt and continue in interactive mode',
|
||||
})
|
||||
.option('sandbox', {
|
||||
alias: 's',
|
||||
type: 'boolean',
|
||||
description: 'Run in sandbox?',
|
||||
})
|
||||
.option('sandbox-image', {
|
||||
type: 'string',
|
||||
description: 'Sandbox image URI.',
|
||||
})
|
||||
.option('debug', {
|
||||
alias: 'd',
|
||||
type: 'boolean',
|
||||
description: 'Run in debug mode?',
|
||||
default: false,
|
||||
})
|
||||
.option('all-files', {
|
||||
alias: ['a'],
|
||||
type: 'boolean',
|
||||
description: 'Include ALL files in context?',
|
||||
default: false,
|
||||
})
|
||||
.option('all_files', {
|
||||
type: 'boolean',
|
||||
description: 'Include ALL files in context?',
|
||||
default: false,
|
||||
})
|
||||
.deprecateOption(
|
||||
'all_files',
|
||||
'Use --all-files instead. We will be removing --all_files in the coming weeks.',
|
||||
)
|
||||
.option('show-memory-usage', {
|
||||
type: 'boolean',
|
||||
description: 'Show memory usage in status bar',
|
||||
default: false,
|
||||
})
|
||||
.option('show_memory_usage', {
|
||||
type: 'boolean',
|
||||
description: 'Show memory usage in status bar',
|
||||
default: false,
|
||||
})
|
||||
.deprecateOption(
|
||||
'show_memory_usage',
|
||||
'Use --show-memory-usage instead. We will be removing --show_memory_usage in the coming weeks.',
|
||||
)
|
||||
.option('yolo', {
|
||||
alias: 'y',
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Automatically accept all actions (aka YOLO mode, see https://www.youtube.com/watch?v=xvFZjo5PgG0 for more details)?',
|
||||
default: false,
|
||||
})
|
||||
.option('telemetry', {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Enable telemetry? This flag specifically controls if telemetry is sent. Other --telemetry-* flags set specific values but do not enable telemetry on their own.',
|
||||
})
|
||||
.option('telemetry-target', {
|
||||
type: 'string',
|
||||
choices: ['local', 'gcp'],
|
||||
description:
|
||||
'Set the telemetry target (local or gcp). Overrides settings files.',
|
||||
})
|
||||
.option('telemetry-otlp-endpoint', {
|
||||
type: 'string',
|
||||
description:
|
||||
'Set the OTLP endpoint for telemetry. Overrides environment variables and settings files.',
|
||||
})
|
||||
.option('telemetry-log-prompts', {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Enable or disable logging of user prompts for telemetry. Overrides settings files.',
|
||||
})
|
||||
.option('telemetry-outfile', {
|
||||
type: 'string',
|
||||
description: 'Redirect all telemetry output to the specified file.',
|
||||
})
|
||||
.option('checkpointing', {
|
||||
alias: 'c',
|
||||
type: 'boolean',
|
||||
description: 'Enables checkpointing of file edits',
|
||||
default: false,
|
||||
})
|
||||
.option('experimental-acp', {
|
||||
type: 'boolean',
|
||||
description: 'Starts the agent in ACP mode',
|
||||
})
|
||||
.option('allowed-mcp-server-names', {
|
||||
type: 'array',
|
||||
string: true,
|
||||
description: 'Allowed MCP server names',
|
||||
})
|
||||
.option('extensions', {
|
||||
alias: 'e',
|
||||
type: 'array',
|
||||
string: true,
|
||||
description:
|
||||
'A list of extensions to use. If not provided, all extensions are used.',
|
||||
})
|
||||
.option('list-extensions', {
|
||||
alias: 'l',
|
||||
type: 'boolean',
|
||||
description: 'List all available extensions and exit.',
|
||||
})
|
||||
.option('ide-mode-feature', {
|
||||
type: 'boolean',
|
||||
description: 'Run in IDE mode?',
|
||||
})
|
||||
.option('proxy', {
|
||||
type: 'string',
|
||||
description:
|
||||
'Proxy for gemini client, like schema://user:password@host:port',
|
||||
})
|
||||
.option('include-directories', {
|
||||
type: 'array',
|
||||
string: true,
|
||||
description:
|
||||
'Additional directories to include in the workspace (comma-separated or multiple --include-directories)',
|
||||
coerce: (dirs: string[]) =>
|
||||
// Handle comma-separated values
|
||||
dirs.flatMap((dir) => dir.split(',').map((d) => d.trim())),
|
||||
})
|
||||
.option('openai-logging', {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Enable logging of OpenAI API calls for debugging and analysis',
|
||||
})
|
||||
.option('openai-api-key', {
|
||||
type: 'string',
|
||||
description: 'OpenAI API key to use for authentication',
|
||||
})
|
||||
.option('openai-base-url', {
|
||||
type: 'string',
|
||||
description: 'OpenAI base URL (for custom endpoints)',
|
||||
})
|
||||
.option('tavily-api-key', {
|
||||
type: 'string',
|
||||
description: 'Tavily API key for web search functionality',
|
||||
})
|
||||
.check((argv) => {
|
||||
if (argv.prompt && argv.promptInteractive) {
|
||||
throw new Error(
|
||||
'Cannot use both --prompt (-p) and --prompt-interactive (-i) together',
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
)
|
||||
.option('show-memory-usage', {
|
||||
type: 'boolean',
|
||||
description: 'Show memory usage in status bar',
|
||||
default: false,
|
||||
})
|
||||
.option('show_memory_usage', {
|
||||
type: 'boolean',
|
||||
description: 'Show memory usage in status bar',
|
||||
default: false,
|
||||
})
|
||||
.deprecateOption(
|
||||
'show_memory_usage',
|
||||
'Use --show-memory-usage instead. We will be removing --show_memory_usage in the coming weeks.',
|
||||
)
|
||||
.option('yolo', {
|
||||
alias: 'y',
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Automatically accept all actions (aka YOLO mode, see https://www.youtube.com/watch?v=xvFZjo5PgG0 for more details)?',
|
||||
default: false,
|
||||
})
|
||||
.option('telemetry', {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Enable telemetry? This flag specifically controls if telemetry is sent. Other --telemetry-* flags set specific values but do not enable telemetry on their own.',
|
||||
})
|
||||
.option('telemetry-target', {
|
||||
type: 'string',
|
||||
choices: ['local', 'gcp'],
|
||||
description:
|
||||
'Set the telemetry target (local or gcp). Overrides settings files.',
|
||||
})
|
||||
.option('telemetry-otlp-endpoint', {
|
||||
type: 'string',
|
||||
description:
|
||||
'Set the OTLP endpoint for telemetry. Overrides environment variables and settings files.',
|
||||
})
|
||||
.option('telemetry-log-prompts', {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Enable or disable logging of user prompts for telemetry. Overrides settings files.',
|
||||
})
|
||||
.option('telemetry-outfile', {
|
||||
type: 'string',
|
||||
description: 'Redirect all telemetry output to the specified file.',
|
||||
})
|
||||
.option('checkpointing', {
|
||||
alias: 'c',
|
||||
type: 'boolean',
|
||||
description: 'Enables checkpointing of file edits',
|
||||
default: false,
|
||||
})
|
||||
.option('experimental-acp', {
|
||||
type: 'boolean',
|
||||
description: 'Starts the agent in ACP mode',
|
||||
})
|
||||
.option('allowed-mcp-server-names', {
|
||||
type: 'array',
|
||||
string: true,
|
||||
description: 'Allowed MCP server names',
|
||||
})
|
||||
.option('extensions', {
|
||||
alias: 'e',
|
||||
type: 'array',
|
||||
string: true,
|
||||
description:
|
||||
'A list of extensions to use. If not provided, all extensions are used.',
|
||||
})
|
||||
.option('list-extensions', {
|
||||
alias: 'l',
|
||||
type: 'boolean',
|
||||
description: 'List all available extensions and exit.',
|
||||
})
|
||||
.option('ide-mode-feature', {
|
||||
type: 'boolean',
|
||||
description: 'Run in IDE mode?',
|
||||
})
|
||||
.option('openai-logging', {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Enable logging of OpenAI API calls for debugging and analysis',
|
||||
})
|
||||
.option('openai-api-key', {
|
||||
type: 'string',
|
||||
description: 'OpenAI API key to use for authentication',
|
||||
})
|
||||
.option('openai-base-url', {
|
||||
type: 'string',
|
||||
description: 'OpenAI base URL (for custom endpoints)',
|
||||
})
|
||||
.option('tavily-api-key', {
|
||||
type: 'string',
|
||||
description: 'Tavily API key for web search functionality',
|
||||
})
|
||||
.option('proxy', {
|
||||
type: 'string',
|
||||
description:
|
||||
'Proxy for gemini client, like schema://user:password@host:port',
|
||||
})
|
||||
.option('include-directories', {
|
||||
type: 'array',
|
||||
string: true,
|
||||
description:
|
||||
'Additional directories to include in the workspace (comma-separated or multiple --include-directories)',
|
||||
coerce: (dirs: string[]) =>
|
||||
// Handle comma-separated values
|
||||
dirs.flatMap((dir) => dir.split(',').map((d) => d.trim())),
|
||||
})
|
||||
.option('load-memory-from-include-directories', {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'If true, when refreshing memory, QWEN.md files should be loaded from all directories that are added. If false, QWEN.md files should only be loaded from the primary working directory.',
|
||||
default: false,
|
||||
})
|
||||
// Register MCP subcommands
|
||||
.command(mcpCommand)
|
||||
.version(await getCliVersion()) // This will enable the --version flag based on package.json
|
||||
.alias('v', 'version')
|
||||
.help()
|
||||
.alias('h', 'help')
|
||||
.strict()
|
||||
.check((argv) => {
|
||||
if (argv.prompt && argv.promptInteractive) {
|
||||
throw new Error(
|
||||
'Cannot use both --prompt (-p) and --prompt-interactive (-i) together',
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
.demandCommand(0, 0); // Allow base command to run with no subcommands
|
||||
|
||||
yargsInstance.wrap(yargsInstance.terminalWidth());
|
||||
const result = yargsInstance.parseSync();
|
||||
const result = await yargsInstance.parse();
|
||||
|
||||
// Handle case where MCP subcommands are executed - they should exit the process
|
||||
// and not return to main CLI logic
|
||||
if (result._.length > 0 && result._[0] === 'mcp') {
|
||||
// MCP commands handle their own execution and process exit
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// The import format is now only controlled by settings.memoryImportFormat
|
||||
// We no longer accept it as a CLI argument
|
||||
return result as CliArgs;
|
||||
return result as unknown as CliArgs;
|
||||
}
|
||||
|
||||
// This function is now a thin wrapper around the server's implementation.
|
||||
@@ -321,6 +332,10 @@ export async function loadCliConfig(
|
||||
const ideModeFeature =
|
||||
argv.ideModeFeature ?? settings.ideModeFeature ?? false;
|
||||
|
||||
const folderTrustFeature = settings.folderTrustFeature ?? false;
|
||||
const folderTrustSetting = settings.folderTrust ?? false;
|
||||
const folderTrust = folderTrustFeature && folderTrustSetting;
|
||||
|
||||
const allExtensions = annotateActiveExtensions(
|
||||
extensions,
|
||||
argv.extensions || [],
|
||||
@@ -383,17 +398,31 @@ export async function loadCliConfig(
|
||||
);
|
||||
|
||||
let mcpServers = mergeMcpServers(settings, activeExtensions);
|
||||
const excludeTools = mergeExcludeTools(settings, activeExtensions);
|
||||
const question = argv.promptInteractive || argv.prompt || '';
|
||||
const approvalMode =
|
||||
argv.yolo || false ? ApprovalMode.YOLO : ApprovalMode.DEFAULT;
|
||||
const interactive =
|
||||
!!argv.promptInteractive || (process.stdin.isTTY && question.length === 0);
|
||||
// In non-interactive and non-yolo mode, exclude interactive built in tools.
|
||||
const extraExcludes =
|
||||
!interactive && approvalMode !== ApprovalMode.YOLO
|
||||
? [ShellTool.Name, EditTool.Name, WriteFileTool.Name]
|
||||
: undefined;
|
||||
|
||||
const excludeTools = mergeExcludeTools(
|
||||
settings,
|
||||
activeExtensions,
|
||||
extraExcludes,
|
||||
);
|
||||
const blockedMcpServers: Array<{ name: string; extensionName: string }> = [];
|
||||
|
||||
if (!argv.allowedMcpServerNames) {
|
||||
if (settings.allowMCPServers) {
|
||||
const allowedNames = new Set(settings.allowMCPServers.filter(Boolean));
|
||||
if (allowedNames.size > 0) {
|
||||
mcpServers = Object.fromEntries(
|
||||
Object.entries(mcpServers).filter(([key]) => allowedNames.has(key)),
|
||||
);
|
||||
}
|
||||
mcpServers = allowedMcpServers(
|
||||
mcpServers,
|
||||
settings.allowMCPServers,
|
||||
blockedMcpServers,
|
||||
);
|
||||
}
|
||||
|
||||
if (settings.excludeMCPServers) {
|
||||
@@ -407,29 +436,11 @@ export async function loadCliConfig(
|
||||
}
|
||||
|
||||
if (argv.allowedMcpServerNames) {
|
||||
const allowedNames = new Set(argv.allowedMcpServerNames.filter(Boolean));
|
||||
if (allowedNames.size > 0) {
|
||||
mcpServers = Object.fromEntries(
|
||||
Object.entries(mcpServers).filter(([key, server]) => {
|
||||
const isAllowed = allowedNames.has(key);
|
||||
if (!isAllowed) {
|
||||
blockedMcpServers.push({
|
||||
name: key,
|
||||
extensionName: server.extensionName || '',
|
||||
});
|
||||
}
|
||||
return isAllowed;
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
blockedMcpServers.push(
|
||||
...Object.entries(mcpServers).map(([key, server]) => ({
|
||||
name: key,
|
||||
extensionName: server.extensionName || '',
|
||||
})),
|
||||
);
|
||||
mcpServers = {};
|
||||
}
|
||||
mcpServers = allowedMcpServers(
|
||||
mcpServers,
|
||||
argv.allowedMcpServerNames,
|
||||
blockedMcpServers,
|
||||
);
|
||||
}
|
||||
|
||||
const sandboxConfig = await loadSandboxConfig(settings, argv);
|
||||
@@ -442,11 +453,9 @@ export async function loadCliConfig(
|
||||
targetDir: process.cwd(),
|
||||
includeDirectories,
|
||||
loadMemoryFromIncludeDirectories:
|
||||
argv.loadMemoryFromIncludeDirectories ||
|
||||
settings.loadMemoryFromIncludeDirectories ||
|
||||
false,
|
||||
settings.loadMemoryFromIncludeDirectories || false,
|
||||
debugMode,
|
||||
question: argv.promptInteractive || argv.prompt || '',
|
||||
question,
|
||||
fullContext: argv.allFiles || argv.all_files || false,
|
||||
coreTools: settings.coreTools || undefined,
|
||||
excludeTools,
|
||||
@@ -456,7 +465,7 @@ export async function loadCliConfig(
|
||||
mcpServers,
|
||||
userMemory: memoryContent,
|
||||
geminiMdFileCount: fileCount,
|
||||
approvalMode: argv.yolo || false ? ApprovalMode.YOLO : ApprovalMode.DEFAULT,
|
||||
approvalMode,
|
||||
showMemoryUsage:
|
||||
argv.showMemoryUsage ||
|
||||
argv.show_memory_usage ||
|
||||
@@ -496,7 +505,6 @@ export async function loadCliConfig(
|
||||
extensionContextFilePaths,
|
||||
maxSessionTurns: settings.maxSessionTurns ?? -1,
|
||||
sessionTokenLimit: settings.sessionTokenLimit ?? -1,
|
||||
maxFolderItems: settings.maxFolderItems ?? 20,
|
||||
experimentalAcp: argv.experimentalAcp || false,
|
||||
listExtensions: argv.listExtensions || false,
|
||||
extensions: allExtensions,
|
||||
@@ -510,7 +518,7 @@ export async function loadCliConfig(
|
||||
? settings.enableOpenAILogging
|
||||
: argv.openaiLogging) ?? false,
|
||||
sampling_params: settings.sampling_params,
|
||||
systemPromptMappings: settings.systemPromptMappings ?? [
|
||||
systemPromptMappings: (settings.systemPromptMappings ?? [
|
||||
{
|
||||
baseUrls: [
|
||||
'https://dashscope.aliyuncs.com/compatible-mode/v1/',
|
||||
@@ -519,15 +527,50 @@ export async function loadCliConfig(
|
||||
modelNames: ['qwen3-coder-plus'],
|
||||
template:
|
||||
'SYSTEM_TEMPLATE:{"name":"qwen3_coder","params":{"is_git_repository":{RUNTIME_VARS_IS_GIT_REPO},"sandbox":"{RUNTIME_VARS_SANDBOX}"}}',
|
||||
},
|
||||
],
|
||||
}
|
||||
]) as ConfigParameters['systemPromptMappings'],
|
||||
contentGenerator: settings.contentGenerator,
|
||||
cliVersion,
|
||||
tavilyApiKey:
|
||||
argv.tavilyApiKey || settings.tavilyApiKey || process.env.TAVILY_API_KEY,
|
||||
chatCompression: settings.chatCompression,
|
||||
folderTrustFeature,
|
||||
folderTrust,
|
||||
interactive,
|
||||
});
|
||||
}
|
||||
|
||||
function allowedMcpServers(
|
||||
mcpServers: { [x: string]: MCPServerConfig },
|
||||
allowMCPServers: string[],
|
||||
blockedMcpServers: Array<{ name: string; extensionName: string }>,
|
||||
) {
|
||||
const allowedNames = new Set(allowMCPServers.filter(Boolean));
|
||||
if (allowedNames.size > 0) {
|
||||
mcpServers = Object.fromEntries(
|
||||
Object.entries(mcpServers).filter(([key, server]) => {
|
||||
const isAllowed = allowedNames.has(key);
|
||||
if (!isAllowed) {
|
||||
blockedMcpServers.push({
|
||||
name: key,
|
||||
extensionName: server.extensionName || '',
|
||||
});
|
||||
}
|
||||
return isAllowed;
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
blockedMcpServers.push(
|
||||
...Object.entries(mcpServers).map(([key, server]) => ({
|
||||
name: key,
|
||||
extensionName: server.extensionName || '',
|
||||
})),
|
||||
);
|
||||
mcpServers = {};
|
||||
}
|
||||
return mcpServers;
|
||||
}
|
||||
|
||||
function mergeMcpServers(settings: Settings, extensions: Extension[]) {
|
||||
const mcpServers = { ...(settings.mcpServers || {}) };
|
||||
for (const extension of extensions) {
|
||||
@@ -552,8 +595,12 @@ function mergeMcpServers(settings: Settings, extensions: Extension[]) {
|
||||
function mergeExcludeTools(
|
||||
settings: Settings,
|
||||
extensions: Extension[],
|
||||
extraExcludes?: string[] | undefined,
|
||||
): string[] {
|
||||
const allExcludeTools = new Set(settings.excludeTools || []);
|
||||
const allExcludeTools = new Set([
|
||||
...(settings.excludeTools || []),
|
||||
...(extraExcludes || []),
|
||||
]);
|
||||
for (const extension of extensions) {
|
||||
for (const tool of extension.config.excludeTools || []) {
|
||||
allExcludeTools.add(tool);
|
||||
|
||||
Reference in New Issue
Block a user