qwen-code/packages/cli/src/config/config.ts

244 lines
8.3 KiB
TypeScript
Raw Normal View History

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import yargs from 'yargs/yargs';
import { hideBin } from 'yargs/helpers';
import process from 'node:process';
import {
Config,
loadEnvironment,
loadServerHierarchicalMemory,
setGeminiMdFilename as setServerGeminiMdFilename,
getCurrentGeminiMdFilename,
ApprovalMode,
ContentGeneratorConfig,
} from '@gemini-cli/core';
2025-05-02 08:15:46 -07:00
import { Settings } from './settings.js';
import { getEffectiveModel } from '../utils/modelCheck.js';
import { ExtensionConfig } from './extension.js';
// Simple console logger for now - replace with actual logger if available
const logger = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
debug: (...args: any[]) => console.debug('[DEBUG]', ...args),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
warn: (...args: any[]) => console.warn('[WARN]', ...args),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error: (...args: any[]) => console.error('[ERROR]', ...args),
};
export const DEFAULT_GEMINI_MODEL = 'gemini-2.5-pro-preview-06-05';
export const DEFAULT_GEMINI_FLASH_MODEL = 'gemini-2.5-flash-preview-05-20';
2025-06-07 13:38:05 -07:00
export const DEFAULT_GEMINI_EMBEDDING_MODEL = 'gemini-embedding-001';
interface CliArgs {
model: string | undefined;
2025-05-02 08:15:46 -07:00
sandbox: boolean | string | undefined;
2025-05-15 11:38:33 -07:00
debug: boolean | undefined;
prompt: string | undefined;
all_files: boolean | undefined;
show_memory_usage: boolean | undefined;
yolo: boolean | undefined;
telemetry: boolean | undefined;
}
2025-05-01 01:00:53 +00:00
async function parseArguments(): Promise<CliArgs> {
const argv = await yargs(hideBin(process.argv))
.option('model', {
alias: 'm',
type: 'string',
2025-05-15 11:38:33 -07:00
description: `Model`,
default: process.env.GEMINI_MODEL || DEFAULT_GEMINI_MODEL,
})
2025-05-15 11:38:33 -07:00
.option('prompt', {
alias: 'p',
type: 'string',
description: 'Prompt. Appended to input on stdin (if any).',
})
2025-05-02 08:15:46 -07:00
.option('sandbox', {
alias: 's',
type: 'boolean',
2025-05-15 11:38:33 -07:00
description: 'Run in sandbox?',
2025-05-02 08:15:46 -07:00
})
2025-05-15 11:38:33 -07:00
.option('debug', {
alias: 'd',
type: 'boolean',
2025-05-15 11:38:33 -07:00
description: 'Run in debug mode?',
default: false,
})
.option('all_files', {
2025-05-15 11:38:33 -07:00
alias: 'a',
type: 'boolean',
2025-05-15 11:38:33 -07:00
description: 'Include ALL files in context?',
default: false,
})
.option('show_memory_usage', {
type: 'boolean',
description: 'Show memory usage in status bar',
default: false,
})
.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?',
})
.version(process.env.CLI_VERSION || '0.0.0') // This will enable the --version flag based on package.json
.help()
.alias('h', 'help')
.strict().argv;
2025-06-03 13:47:53 -07:00
return argv;
}
// This function is now a thin wrapper around the server's implementation.
// It's kept in the CLI for now as App.tsx directly calls it for memory refresh.
// TODO: Consider if App.tsx should get memory via a server call or if Config should refresh itself.
export async function loadHierarchicalGeminiMemory(
currentWorkingDirectory: string,
debugMode: boolean,
): Promise<{ memoryContent: string; fileCount: number }> {
if (debugMode) {
logger.debug(
`CLI: Delegating hierarchical memory load to server for CWD: ${currentWorkingDirectory}`,
);
}
// Directly call the server function.
// The server function will use its own homedir() for the global path.
return loadServerHierarchicalMemory(currentWorkingDirectory, debugMode);
}
export async function loadCliConfig(
settings: Settings,
extensions: ExtensionConfig[],
geminiIgnorePatterns: string[],
sessionId: string,
): Promise<Config> {
loadEnvironment();
2025-05-01 01:00:53 +00:00
const argv = await parseArguments();
2025-05-15 11:38:33 -07:00
const debugMode = argv.debug || false;
// Set the context filename in the server's memoryTool module BEFORE loading memory
// TODO(b/343434939): This is a bit of a hack. The contextFileName should ideally be passed
// directly to the Config constructor in core, and have core handle setGeminiMdFilename.
// However, loadHierarchicalGeminiMemory is called *before* createServerConfig.
if (settings.contextFileName) {
setServerGeminiMdFilename(settings.contextFileName);
} else {
// Reset to default if not provided in settings.
setServerGeminiMdFilename(getCurrentGeminiMdFilename());
}
// Call the (now wrapper) loadHierarchicalGeminiMemory which calls the server's version
const { memoryContent, fileCount } = await loadHierarchicalGeminiMemory(
process.cwd(),
debugMode,
);
const contentGeneratorConfig = await createContentGeneratorConfig(argv);
const mcpServers = mergeMcpServers(settings, extensions);
2025-06-07 13:49:00 -07:00
return new Config({
sessionId,
contentGeneratorConfig,
2025-06-07 13:38:05 -07:00
embeddingModel: DEFAULT_GEMINI_EMBEDDING_MODEL,
sandbox: argv.sandbox ?? settings.sandbox,
targetDir: process.cwd(),
debugMode,
question: argv.prompt || '',
fullContext: argv.all_files || false,
coreTools: settings.coreTools || undefined,
toolDiscoveryCommand: settings.toolDiscoveryCommand,
toolCallCommand: settings.toolCallCommand,
mcpServerCommand: settings.mcpServerCommand,
mcpServers,
userMemory: memoryContent,
geminiMdFileCount: fileCount,
approvalMode: argv.yolo || false ? ApprovalMode.YOLO : ApprovalMode.DEFAULT,
showMemoryUsage:
argv.show_memory_usage || settings.showMemoryUsage || false,
geminiIgnorePatterns,
accessibility: settings.accessibility,
telemetry:
argv.telemetry !== undefined
? argv.telemetry
: (settings.telemetry ?? false),
Ignore folders files (#651) # Add .gitignore-Aware File Filtering to gemini-cli This pull request introduces .gitignore-based file filtering to the gemini-cli, ensuring that git-ignored files are automatically excluded from file-related operations and suggestions throughout the CLI. The update enhances usability, reduces noise from build artifacts and dependencies, and provides new configuration options for fine-tuning file discovery. Key Improvements .gitignore File Filtering All @ (at) commands, file completions, and core discovery tools now honor .gitignore patterns by default. Git-ignored files (such as node_modules/, dist/, .env, and .git) are excluded from results unless explicitly overridden. The behavior can be customized via a new fileFiltering section in settings.json, including options for: Turning .gitignore respect on/off. Adding custom ignore patterns. Allowing or excluding build artifacts. Configuration & Documentation Updates settings.json schema extended with fileFiltering options. Documentation updated to explain new filtering controls and usage patterns. Testing New and updated integration/unit tests for file filtering logic, configuration merging, and edge cases. Test coverage ensures .gitignore filtering works as intended across different workflows. Internal Refactoring Core file discovery logic refactored for maintainability and extensibility. Underlying tools (ls, glob, read-many-files) now support git-aware filtering out of the box. Co-authored-by: N. Taylor Mullen <ntaylormullen@google.com>
2025-06-03 21:40:46 -07:00
// Git-aware file filtering settings
fileFilteringRespectGitIgnore: settings.fileFiltering?.respectGitIgnore,
fileFilteringAllowBuildArtifacts:
settings.fileFiltering?.allowBuildArtifacts,
enableModifyWithExternalEditors: settings.enableModifyWithExternalEditors,
2025-06-07 13:49:00 -07:00
});
}
function mergeMcpServers(settings: Settings, extensions: ExtensionConfig[]) {
const mcpServers = settings.mcpServers || {};
for (const extension of extensions) {
Object.entries(extension.mcpServers || {}).forEach(([key, server]) => {
if (mcpServers[key]) {
logger.warn(
`Skipping extension MCP config for server with key "${key}" as it already exists.`,
);
return;
}
mcpServers[key] = server;
});
}
return mcpServers;
}
async function createContentGeneratorConfig(
argv: CliArgs,
): Promise<ContentGeneratorConfig> {
const geminiApiKey = process.env.GEMINI_API_KEY;
const googleApiKey = process.env.GOOGLE_API_KEY;
const googleCloudProject = process.env.GOOGLE_CLOUD_PROJECT;
const googleCloudLocation = process.env.GOOGLE_CLOUD_LOCATION;
const hasGeminiApiKey = !!geminiApiKey;
const hasGoogleApiKey = !!googleApiKey;
const hasVertexProjectLocationConfig =
!!googleCloudProject && !!googleCloudLocation;
if (hasGeminiApiKey && hasGoogleApiKey) {
logger.warn(
'Both GEMINI_API_KEY and GOOGLE_API_KEY are set. Using GOOGLE_API_KEY.',
);
}
if (!hasGeminiApiKey && !hasGoogleApiKey && !hasVertexProjectLocationConfig) {
logger.error(
'No valid API authentication configuration found. Please set ONE of the following combinations in your environment variables or .env file:\n' +
'1. GEMINI_API_KEY (for Gemini API access).\n' +
'2. GOOGLE_API_KEY (for Gemini API or Vertex AI Express Mode access).\n' +
'3. GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION (for Vertex AI access).\n\n' +
'For Gemini API keys, visit: https://ai.google.dev/gemini-api/docs/api-key\n' +
'For Vertex AI authentication, visit: https://cloud.google.com/vertex-ai/docs/start/authentication\n' +
'The GOOGLE_GENAI_USE_VERTEXAI environment variable can also be set to true/false to influence service selection when ambiguity exists.',
);
process.exit(1);
}
const config: ContentGeneratorConfig = {
model: argv.model || DEFAULT_GEMINI_MODEL,
apiKey: googleApiKey || geminiApiKey || '',
vertexai: hasGeminiApiKey ? false : undefined,
2025-06-10 16:00:13 -07:00
codeAssist: !!process.env.GEMINI_CODE_ASSIST,
};
if (config.apiKey) {
config.model = await getEffectiveModel(config.apiKey, config.model);
}
return config;
}