mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 16:57:46 +00:00
Merge tag 'v0.3.0' into chore/sync-gemini-cli-v0.3.0
This commit is contained in:
@@ -5,17 +5,18 @@
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { Mock } from 'vitest';
|
||||
import { Config, ConfigParameters, SandboxConfig } from './config.js';
|
||||
import * as path from 'path';
|
||||
import type { Mock } from 'vitest';
|
||||
import type { ConfigParameters, SandboxConfig } from './config.js';
|
||||
import { Config, ApprovalMode } from './config.js';
|
||||
import * as path from 'node:path';
|
||||
import { setGeminiMdFilename as mockSetGeminiMdFilename } from '../tools/memoryTool.js';
|
||||
import {
|
||||
DEFAULT_TELEMETRY_TARGET,
|
||||
DEFAULT_OTLP_ENDPOINT,
|
||||
} from '../telemetry/index.js';
|
||||
import type { ContentGeneratorConfig } from '../core/contentGenerator.js';
|
||||
import {
|
||||
AuthType,
|
||||
ContentGeneratorConfig,
|
||||
createContentGeneratorConfig,
|
||||
} from '../core/contentGenerator.js';
|
||||
import { GeminiClient } from '../core/client.js';
|
||||
@@ -33,6 +34,9 @@ vi.mock('fs', async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import { ShellTool } from '../tools/shell.js';
|
||||
import { ReadFileTool } from '../tools/read-file.js';
|
||||
|
||||
vi.mock('fs', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('fs')>();
|
||||
return {
|
||||
@@ -589,4 +593,138 @@ describe('Server Config (config.ts)', () => {
|
||||
expect(config.getTelemetryOtlpProtocol()).toBe('grpc');
|
||||
});
|
||||
});
|
||||
|
||||
describe('UseRipgrep Configuration', () => {
|
||||
it('should default useRipgrep to false when not provided', () => {
|
||||
const config = new Config(baseParams);
|
||||
expect(config.getUseRipgrep()).toBe(false);
|
||||
});
|
||||
|
||||
it('should set useRipgrep to true when provided as true', () => {
|
||||
const paramsWithRipgrep: ConfigParameters = {
|
||||
...baseParams,
|
||||
useRipgrep: true,
|
||||
};
|
||||
const config = new Config(paramsWithRipgrep);
|
||||
expect(config.getUseRipgrep()).toBe(true);
|
||||
});
|
||||
|
||||
it('should set useRipgrep to false when explicitly provided as false', () => {
|
||||
const paramsWithRipgrep: ConfigParameters = {
|
||||
...baseParams,
|
||||
useRipgrep: false,
|
||||
};
|
||||
const config = new Config(paramsWithRipgrep);
|
||||
expect(config.getUseRipgrep()).toBe(false);
|
||||
});
|
||||
|
||||
it('should default useRipgrep to false when undefined', () => {
|
||||
const paramsWithUndefinedRipgrep: ConfigParameters = {
|
||||
...baseParams,
|
||||
useRipgrep: undefined,
|
||||
};
|
||||
const config = new Config(paramsWithUndefinedRipgrep);
|
||||
expect(config.getUseRipgrep()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createToolRegistry', () => {
|
||||
it('should register a tool if coreTools contains an argument-specific pattern', async () => {
|
||||
const params: ConfigParameters = {
|
||||
...baseParams,
|
||||
coreTools: ['ShellTool(git status)'],
|
||||
};
|
||||
const config = new Config(params);
|
||||
await config.initialize();
|
||||
|
||||
// The ToolRegistry class is mocked, so we can inspect its prototype's methods.
|
||||
const registerToolMock = (
|
||||
(await vi.importMock('../tools/tool-registry')) as {
|
||||
ToolRegistry: { prototype: { registerTool: Mock } };
|
||||
}
|
||||
).ToolRegistry.prototype.registerTool;
|
||||
|
||||
// Check that registerTool was called for ShellTool
|
||||
const wasShellToolRegistered = (registerToolMock as Mock).mock.calls.some(
|
||||
(call) => call[0] instanceof vi.mocked(ShellTool),
|
||||
);
|
||||
expect(wasShellToolRegistered).toBe(true);
|
||||
|
||||
// Check that registerTool was NOT called for ReadFileTool
|
||||
const wasReadFileToolRegistered = (
|
||||
registerToolMock as Mock
|
||||
).mock.calls.some((call) => call[0] instanceof vi.mocked(ReadFileTool));
|
||||
expect(wasReadFileToolRegistered).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setApprovalMode with folder trust', () => {
|
||||
it('should throw an error when setting YOLO mode in an untrusted folder', () => {
|
||||
const config = new Config({
|
||||
sessionId: 'test',
|
||||
targetDir: '.',
|
||||
debugMode: false,
|
||||
model: 'test-model',
|
||||
cwd: '.',
|
||||
trustedFolder: false, // Untrusted
|
||||
});
|
||||
expect(() => config.setApprovalMode(ApprovalMode.YOLO)).toThrow(
|
||||
'Cannot enable privileged approval modes in an untrusted folder.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error when setting AUTO_EDIT mode in an untrusted folder', () => {
|
||||
const config = new Config({
|
||||
sessionId: 'test',
|
||||
targetDir: '.',
|
||||
debugMode: false,
|
||||
model: 'test-model',
|
||||
cwd: '.',
|
||||
trustedFolder: false, // Untrusted
|
||||
});
|
||||
expect(() => config.setApprovalMode(ApprovalMode.AUTO_EDIT)).toThrow(
|
||||
'Cannot enable privileged approval modes in an untrusted folder.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should NOT throw an error when setting DEFAULT mode in an untrusted folder', () => {
|
||||
const config = new Config({
|
||||
sessionId: 'test',
|
||||
targetDir: '.',
|
||||
debugMode: false,
|
||||
model: 'test-model',
|
||||
cwd: '.',
|
||||
trustedFolder: false, // Untrusted
|
||||
});
|
||||
expect(() => config.setApprovalMode(ApprovalMode.DEFAULT)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should NOT throw an error when setting any mode in a trusted folder', () => {
|
||||
const config = new Config({
|
||||
sessionId: 'test',
|
||||
targetDir: '.',
|
||||
debugMode: false,
|
||||
model: 'test-model',
|
||||
cwd: '.',
|
||||
trustedFolder: true, // Trusted
|
||||
});
|
||||
expect(() => config.setApprovalMode(ApprovalMode.YOLO)).not.toThrow();
|
||||
expect(() => config.setApprovalMode(ApprovalMode.AUTO_EDIT)).not.toThrow();
|
||||
expect(() => config.setApprovalMode(ApprovalMode.DEFAULT)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should NOT throw an error when setting any mode if trustedFolder is undefined', () => {
|
||||
const config = new Config({
|
||||
sessionId: 'test',
|
||||
targetDir: '.',
|
||||
debugMode: false,
|
||||
model: 'test-model',
|
||||
cwd: '.',
|
||||
trustedFolder: undefined, // Undefined
|
||||
});
|
||||
expect(() => config.setApprovalMode(ApprovalMode.YOLO)).not.toThrow();
|
||||
expect(() => config.setApprovalMode(ApprovalMode.AUTO_EDIT)).not.toThrow();
|
||||
expect(() => config.setApprovalMode(ApprovalMode.DEFAULT)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,60 +4,59 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { Content } from '@google/genai';
|
||||
import * as path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { GeminiClient } from '../core/client.js';
|
||||
import type { ContentGeneratorConfig } from '../core/contentGenerator.js';
|
||||
import {
|
||||
AuthType,
|
||||
ContentGeneratorConfig,
|
||||
createContentGeneratorConfig,
|
||||
} from '../core/contentGenerator.js';
|
||||
import { IdeClient } from '../ide/ide-client.js';
|
||||
import type { MCPOAuthConfig } from '../mcp/oauth-provider.js';
|
||||
import { PromptRegistry } from '../prompts/prompt-registry.js';
|
||||
import { ToolRegistry } from '../tools/tool-registry.js';
|
||||
import { LSTool } from '../tools/ls.js';
|
||||
import { ReadFileTool } from '../tools/read-file.js';
|
||||
import { GrepTool } from '../tools/grep.js';
|
||||
import { GlobTool } from '../tools/glob.js';
|
||||
import { EditTool } from '../tools/edit.js';
|
||||
import { ShellTool } from '../tools/shell.js';
|
||||
import { WriteFileTool } from '../tools/write-file.js';
|
||||
import { WebFetchTool } from '../tools/web-fetch.js';
|
||||
import { ReadManyFilesTool } from '../tools/read-many-files.js';
|
||||
import {
|
||||
MemoryTool,
|
||||
setGeminiMdFilename,
|
||||
GEMINI_CONFIG_DIR as GEMINI_DIR,
|
||||
} from '../tools/memoryTool.js';
|
||||
import { TodoWriteTool } from '../tools/todoWrite.js';
|
||||
import { WebSearchTool } from '../tools/web-search.js';
|
||||
import { GeminiClient } from '../core/client.js';
|
||||
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
|
||||
import { GitService } from '../services/gitService.js';
|
||||
import { getProjectTempDir } from '../utils/paths.js';
|
||||
import {
|
||||
initializeTelemetry,
|
||||
DEFAULT_TELEMETRY_TARGET,
|
||||
type FileSystemService,
|
||||
StandardFileSystemService,
|
||||
} from '../services/fileSystemService.js';
|
||||
import { GitService } from '../services/gitService.js';
|
||||
import type { TelemetryTarget } from '../telemetry/index.js';
|
||||
import {
|
||||
DEFAULT_OTLP_ENDPOINT,
|
||||
TelemetryTarget,
|
||||
DEFAULT_TELEMETRY_TARGET,
|
||||
initializeTelemetry,
|
||||
StartSessionEvent,
|
||||
} from '../telemetry/index.js';
|
||||
import { logCliConfiguration, logIdeConnection } from '../telemetry/loggers.js';
|
||||
import { IdeConnectionEvent, IdeConnectionType } from '../telemetry/types.js';
|
||||
import { EditTool } from '../tools/edit.js';
|
||||
import { GlobTool } from '../tools/glob.js';
|
||||
import { GrepTool } from '../tools/grep.js';
|
||||
import { LSTool } from '../tools/ls.js';
|
||||
import { MemoryTool, setGeminiMdFilename } from '../tools/memoryTool.js';
|
||||
import { ReadFileTool } from '../tools/read-file.js';
|
||||
import { ReadManyFilesTool } from '../tools/read-many-files.js';
|
||||
import { RipGrepTool } from '../tools/ripGrep.js';
|
||||
import { ShellTool } from '../tools/shell.js';
|
||||
import { TodoWriteTool } from '../tools/todoWrite.js';
|
||||
import { ToolRegistry } from '../tools/tool-registry.js';
|
||||
import type { AnyToolInvocation } from '../tools/tools.js';
|
||||
import { WebFetchTool } from '../tools/web-fetch.js';
|
||||
import { WebSearchTool } from '../tools/web-search.js';
|
||||
import { WriteFileTool } from '../tools/write-file.js';
|
||||
import { shouldAttemptBrowserLaunch } from '../utils/browser.js';
|
||||
import { FileExclusions } from '../utils/ignorePatterns.js';
|
||||
import { WorkspaceContext } from '../utils/workspaceContext.js';
|
||||
import {
|
||||
DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||
DEFAULT_GEMINI_FLASH_MODEL,
|
||||
} from './models.js';
|
||||
import { shouldAttemptBrowserLaunch } from '../utils/browser.js';
|
||||
import { MCPOAuthConfig } from '../mcp/oauth-provider.js';
|
||||
import { IdeClient } from '../ide/ide-client.js';
|
||||
import type { Content } from '@google/genai';
|
||||
import {
|
||||
FileSystemService,
|
||||
StandardFileSystemService,
|
||||
} from '../services/fileSystemService.js';
|
||||
import { logCliConfiguration, logIdeConnection } from '../telemetry/loggers.js';
|
||||
import { IdeConnectionEvent, IdeConnectionType } from '../telemetry/types.js';
|
||||
import { Storage } from './storage.js';
|
||||
|
||||
// Re-export OAuth config type
|
||||
export type { MCPOAuthConfig };
|
||||
import { WorkspaceContext } from '../utils/workspaceContext.js';
|
||||
export type { AnyToolInvocation, MCPOAuthConfig };
|
||||
|
||||
export enum ApprovalMode {
|
||||
DEFAULT = 'default',
|
||||
@@ -67,6 +66,7 @@ export enum ApprovalMode {
|
||||
|
||||
export interface AccessibilitySettings {
|
||||
disableLoadingPhrases?: boolean;
|
||||
screenReader?: boolean;
|
||||
}
|
||||
|
||||
export interface BugCommandSettings {
|
||||
@@ -169,6 +169,7 @@ export interface ConfigParameters {
|
||||
question?: string;
|
||||
fullContext?: boolean;
|
||||
coreTools?: string[];
|
||||
allowedTools?: string[];
|
||||
excludeTools?: string[];
|
||||
toolDiscoveryCommand?: string;
|
||||
toolCallCommand?: string;
|
||||
@@ -187,6 +188,7 @@ export interface ConfigParameters {
|
||||
respectGitIgnore?: boolean;
|
||||
respectGeminiIgnore?: boolean;
|
||||
enableRecursiveFileSearch?: boolean;
|
||||
disableFuzzySearch?: boolean;
|
||||
};
|
||||
checkpointing?: boolean;
|
||||
proxy?: string;
|
||||
@@ -229,8 +231,11 @@ export interface ConfigParameters {
|
||||
chatCompression?: ChatCompressionSettings;
|
||||
interactive?: boolean;
|
||||
trustedFolder?: boolean;
|
||||
useRipgrep?: boolean;
|
||||
shouldUseNodePtyShell?: boolean;
|
||||
skipNextSpeakerCheck?: boolean;
|
||||
extensionManagement?: boolean;
|
||||
enablePromptCompletion?: boolean;
|
||||
}
|
||||
|
||||
export class Config {
|
||||
@@ -247,6 +252,7 @@ export class Config {
|
||||
private readonly question: string | undefined;
|
||||
private readonly fullContext: boolean;
|
||||
private readonly coreTools: string[] | undefined;
|
||||
private readonly allowedTools: string[] | undefined;
|
||||
private readonly excludeTools: string[] | undefined;
|
||||
private readonly toolDiscoveryCommand: string | undefined;
|
||||
private readonly toolCallCommand: string | undefined;
|
||||
@@ -265,6 +271,7 @@ export class Config {
|
||||
respectGitIgnore: boolean;
|
||||
respectGeminiIgnore: boolean;
|
||||
enableRecursiveFileSearch: boolean;
|
||||
disableFuzzySearch: boolean;
|
||||
};
|
||||
private fileDiscoveryService: FileDiscoveryService | null = null;
|
||||
private gitService: GitService | undefined = undefined;
|
||||
@@ -278,7 +285,7 @@ export class Config {
|
||||
private readonly folderTrustFeature: boolean;
|
||||
private readonly folderTrust: boolean;
|
||||
private ideMode: boolean;
|
||||
private ideClient: IdeClient;
|
||||
private ideClient!: IdeClient;
|
||||
private inFallbackMode = false;
|
||||
private readonly systemPromptMappings?: Array<{
|
||||
baseUrls?: string[];
|
||||
@@ -313,9 +320,14 @@ export class Config {
|
||||
private readonly chatCompression: ChatCompressionSettings | undefined;
|
||||
private readonly interactive: boolean;
|
||||
private readonly trustedFolder: boolean | undefined;
|
||||
private readonly useRipgrep: boolean;
|
||||
private readonly shouldUseNodePtyShell: boolean;
|
||||
private readonly skipNextSpeakerCheck: boolean;
|
||||
private readonly extensionManagement: boolean;
|
||||
private readonly enablePromptCompletion: boolean = false;
|
||||
private initialized: boolean = false;
|
||||
readonly storage: Storage;
|
||||
private readonly fileExclusions: FileExclusions;
|
||||
|
||||
constructor(params: ConfigParameters) {
|
||||
this.sessionId = params.sessionId;
|
||||
@@ -332,6 +344,7 @@ export class Config {
|
||||
this.question = params.question;
|
||||
this.fullContext = params.fullContext ?? false;
|
||||
this.coreTools = params.coreTools;
|
||||
this.allowedTools = params.allowedTools;
|
||||
this.excludeTools = params.excludeTools;
|
||||
this.toolDiscoveryCommand = params.toolDiscoveryCommand;
|
||||
this.toolCallCommand = params.toolCallCommand;
|
||||
@@ -362,6 +375,7 @@ export class Config {
|
||||
respectGeminiIgnore: params.fileFiltering?.respectGeminiIgnore ?? true,
|
||||
enableRecursiveFileSearch:
|
||||
params.fileFiltering?.enableRecursiveFileSearch ?? true,
|
||||
disableFuzzySearch: params.fileFiltering?.disableFuzzySearch ?? false,
|
||||
};
|
||||
this.checkpointing = params.checkpointing ?? false;
|
||||
this.proxy = params.proxy;
|
||||
@@ -382,7 +396,6 @@ export class Config {
|
||||
this.folderTrustFeature = params.folderTrustFeature ?? false;
|
||||
this.folderTrust = params.folderTrust ?? false;
|
||||
this.ideMode = params.ideMode ?? false;
|
||||
this.ideClient = IdeClient.getInstance();
|
||||
this.systemPromptMappings = params.systemPromptMappings;
|
||||
this.authType = params.authType;
|
||||
this.enableOpenAILogging = params.enableOpenAILogging ?? false;
|
||||
@@ -394,11 +407,16 @@ export class Config {
|
||||
this.chatCompression = params.chatCompression;
|
||||
this.interactive = params.interactive ?? false;
|
||||
this.trustedFolder = params.trustedFolder;
|
||||
this.shouldUseNodePtyShell = params.shouldUseNodePtyShell ?? false;
|
||||
this.skipNextSpeakerCheck = params.skipNextSpeakerCheck ?? false;
|
||||
|
||||
// Web search
|
||||
this.tavilyApiKey = params.tavilyApiKey;
|
||||
this.useRipgrep = params.useRipgrep ?? false;
|
||||
this.shouldUseNodePtyShell = params.shouldUseNodePtyShell ?? false;
|
||||
this.skipNextSpeakerCheck = params.skipNextSpeakerCheck ?? false;
|
||||
this.extensionManagement = params.extensionManagement ?? false;
|
||||
this.storage = new Storage(this.targetDir);
|
||||
this.enablePromptCompletion = params.enablePromptCompletion ?? false;
|
||||
this.fileExclusions = new FileExclusions(this);
|
||||
|
||||
if (params.contextFileName) {
|
||||
setGeminiMdFilename(params.contextFileName);
|
||||
@@ -419,6 +437,7 @@ export class Config {
|
||||
throw Error('Config was already initialized');
|
||||
}
|
||||
this.initialized = true;
|
||||
this.ideClient = await IdeClient.getInstance();
|
||||
// Initialize centralized FileDiscoveryService
|
||||
this.getFileService();
|
||||
if (this.getCheckpointingEnabled()) {
|
||||
@@ -426,6 +445,7 @@ export class Config {
|
||||
}
|
||||
this.promptRegistry = new PromptRegistry();
|
||||
this.toolRegistry = await this.createToolRegistry();
|
||||
logCliConfiguration(this, new StartSessionEvent(this, this.toolRegistry));
|
||||
}
|
||||
|
||||
async refreshAuth(authMethod: AuthType) {
|
||||
@@ -577,6 +597,10 @@ export class Config {
|
||||
return this.coreTools;
|
||||
}
|
||||
|
||||
getAllowedTools(): string[] | undefined {
|
||||
return this.allowedTools;
|
||||
}
|
||||
|
||||
getExcludeTools(): string[] | undefined {
|
||||
return this.excludeTools;
|
||||
}
|
||||
@@ -618,6 +642,11 @@ export class Config {
|
||||
}
|
||||
|
||||
setApprovalMode(mode: ApprovalMode): void {
|
||||
if (this.isTrustedFolder() === false && mode !== ApprovalMode.DEFAULT) {
|
||||
throw new Error(
|
||||
'Cannot enable privileged approval modes in an untrusted folder.',
|
||||
);
|
||||
}
|
||||
this.approvalMode = mode;
|
||||
}
|
||||
|
||||
@@ -661,18 +690,14 @@ export class Config {
|
||||
return this.geminiClient;
|
||||
}
|
||||
|
||||
getGeminiDir(): string {
|
||||
return path.join(this.targetDir, GEMINI_DIR);
|
||||
}
|
||||
|
||||
getProjectTempDir(): string {
|
||||
return getProjectTempDir(this.getProjectRoot());
|
||||
}
|
||||
|
||||
getEnableRecursiveFileSearch(): boolean {
|
||||
return this.fileFiltering.enableRecursiveFileSearch;
|
||||
}
|
||||
|
||||
getFileFilteringDisableFuzzySearch(): boolean {
|
||||
return this.fileFiltering.disableFuzzySearch;
|
||||
}
|
||||
|
||||
getFileFilteringRespectGitIgnore(): boolean {
|
||||
return this.fileFiltering.respectGitIgnore;
|
||||
}
|
||||
@@ -687,6 +712,21 @@ export class Config {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets custom file exclusion patterns from configuration.
|
||||
* TODO: This is a placeholder implementation. In the future, this could
|
||||
* read from settings files, CLI arguments, or environment variables.
|
||||
*/
|
||||
getCustomExcludes(): string[] {
|
||||
// Placeholder implementation - returns empty array for now
|
||||
// Future implementation could read from:
|
||||
// - User settings file
|
||||
// - Project-specific configuration
|
||||
// - Environment variables
|
||||
// - CLI arguments
|
||||
return [];
|
||||
}
|
||||
|
||||
getCheckpointingEnabled(): boolean {
|
||||
return this.checkpointing;
|
||||
}
|
||||
@@ -726,6 +766,10 @@ export class Config {
|
||||
return this.listExtensions;
|
||||
}
|
||||
|
||||
getExtensionManagement(): boolean {
|
||||
return this.extensionManagement;
|
||||
}
|
||||
|
||||
getExtensions(): GeminiCLIExtension[] {
|
||||
return this._extensions;
|
||||
}
|
||||
@@ -849,6 +893,10 @@ export class Config {
|
||||
return this.interactive;
|
||||
}
|
||||
|
||||
getUseRipgrep(): boolean {
|
||||
return this.useRipgrep;
|
||||
}
|
||||
|
||||
getShouldUseNodePtyShell(): boolean {
|
||||
return this.shouldUseNodePtyShell;
|
||||
}
|
||||
@@ -857,14 +905,26 @@ export class Config {
|
||||
return this.skipNextSpeakerCheck;
|
||||
}
|
||||
|
||||
getScreenReader(): boolean {
|
||||
return this.accessibility.screenReader ?? false;
|
||||
}
|
||||
|
||||
getEnablePromptCompletion(): boolean {
|
||||
return this.enablePromptCompletion;
|
||||
}
|
||||
|
||||
async getGitService(): Promise<GitService> {
|
||||
if (!this.gitService) {
|
||||
this.gitService = new GitService(this.targetDir);
|
||||
this.gitService = new GitService(this.targetDir, this.storage);
|
||||
await this.gitService.initialize();
|
||||
}
|
||||
return this.gitService;
|
||||
}
|
||||
|
||||
getFileExclusions(): FileExclusions {
|
||||
return this.fileExclusions;
|
||||
}
|
||||
|
||||
async createToolRegistry(): Promise<ToolRegistry> {
|
||||
const registry = new ToolRegistry(this);
|
||||
|
||||
@@ -874,12 +934,10 @@ export class Config {
|
||||
const className = ToolClass.name;
|
||||
const toolName = ToolClass.Name || className;
|
||||
const coreTools = this.getCoreTools();
|
||||
const excludeTools = this.getExcludeTools();
|
||||
const excludeTools = this.getExcludeTools() || [];
|
||||
|
||||
let isEnabled = false;
|
||||
if (coreTools === undefined) {
|
||||
isEnabled = true;
|
||||
} else {
|
||||
let isEnabled = true; // Enabled by default if coreTools is not set.
|
||||
if (coreTools) {
|
||||
isEnabled = coreTools.some(
|
||||
(tool) =>
|
||||
tool === className ||
|
||||
@@ -889,10 +947,11 @@ export class Config {
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
excludeTools?.includes(className) ||
|
||||
excludeTools?.includes(toolName)
|
||||
) {
|
||||
const isExcluded = excludeTools.some(
|
||||
(tool) => tool === className || tool === toolName,
|
||||
);
|
||||
|
||||
if (isExcluded) {
|
||||
isEnabled = false;
|
||||
}
|
||||
|
||||
@@ -903,7 +962,13 @@ export class Config {
|
||||
|
||||
registerCoreTool(LSTool, this);
|
||||
registerCoreTool(ReadFileTool, this);
|
||||
registerCoreTool(GrepTool, this);
|
||||
|
||||
if (this.getUseRipgrep()) {
|
||||
registerCoreTool(RipGrepTool, this);
|
||||
} else {
|
||||
registerCoreTool(GrepTool, this);
|
||||
}
|
||||
|
||||
registerCoreTool(GlobTool, this);
|
||||
registerCoreTool(EditTool, this);
|
||||
registerCoreTool(WriteFileTool, this);
|
||||
|
||||
51
packages/core/src/config/storage.test.ts
Normal file
51
packages/core/src/config/storage.test.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
|
||||
vi.mock('fs', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('fs')>();
|
||||
return {
|
||||
...actual,
|
||||
mkdirSync: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
import { Storage } from './storage.js';
|
||||
|
||||
describe('Storage – getGlobalSettingsPath', () => {
|
||||
it('returns path to ~/.qwen/settings.json', () => {
|
||||
const expected = path.join(os.homedir(), '.qwen', 'settings.json');
|
||||
expect(Storage.getGlobalSettingsPath()).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Storage – additional helpers', () => {
|
||||
const projectRoot = '/tmp/project';
|
||||
const storage = new Storage(projectRoot);
|
||||
|
||||
it('getWorkspaceSettingsPath returns project/.qwen/settings.json', () => {
|
||||
const expected = path.join(projectRoot, '.qwen', 'settings.json');
|
||||
expect(storage.getWorkspaceSettingsPath()).toBe(expected);
|
||||
});
|
||||
|
||||
it('getUserCommandsDir returns ~/.qwen/commands', () => {
|
||||
const expected = path.join(os.homedir(), '.qwen', 'commands');
|
||||
expect(Storage.getUserCommandsDir()).toBe(expected);
|
||||
});
|
||||
|
||||
it('getProjectCommandsDir returns project/.qwen/commands', () => {
|
||||
const expected = path.join(projectRoot, '.qwen', 'commands');
|
||||
expect(storage.getProjectCommandsDir()).toBe(expected);
|
||||
});
|
||||
|
||||
it('getMcpOAuthTokensPath returns ~/.qwen/mcp-oauth-tokens.json', () => {
|
||||
const expected = path.join(os.homedir(), '.qwen', 'mcp-oauth-tokens.json');
|
||||
expect(Storage.getMcpOAuthTokensPath()).toBe(expected);
|
||||
});
|
||||
});
|
||||
114
packages/core/src/config/storage.ts
Normal file
114
packages/core/src/config/storage.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import * as path from 'node:path';
|
||||
import * as os from 'node:os';
|
||||
import * as crypto from 'node:crypto';
|
||||
import * as fs from 'node:fs';
|
||||
|
||||
export const GEMINI_DIR = '.qwen';
|
||||
export const GOOGLE_ACCOUNTS_FILENAME = 'google_accounts.json';
|
||||
const TMP_DIR_NAME = 'tmp';
|
||||
|
||||
export class Storage {
|
||||
private readonly targetDir: string;
|
||||
|
||||
constructor(targetDir: string) {
|
||||
this.targetDir = targetDir;
|
||||
}
|
||||
|
||||
static getGlobalGeminiDir(): string {
|
||||
const homeDir = os.homedir();
|
||||
if (!homeDir) {
|
||||
return path.join(os.tmpdir(), '.qwen');
|
||||
}
|
||||
return path.join(homeDir, GEMINI_DIR);
|
||||
}
|
||||
|
||||
static getMcpOAuthTokensPath(): string {
|
||||
return path.join(Storage.getGlobalGeminiDir(), 'mcp-oauth-tokens.json');
|
||||
}
|
||||
|
||||
static getGlobalSettingsPath(): string {
|
||||
return path.join(Storage.getGlobalGeminiDir(), 'settings.json');
|
||||
}
|
||||
|
||||
static getInstallationIdPath(): string {
|
||||
return path.join(Storage.getGlobalGeminiDir(), 'installation_id');
|
||||
}
|
||||
|
||||
static getGoogleAccountsPath(): string {
|
||||
return path.join(Storage.getGlobalGeminiDir(), GOOGLE_ACCOUNTS_FILENAME);
|
||||
}
|
||||
|
||||
static getUserCommandsDir(): string {
|
||||
return path.join(Storage.getGlobalGeminiDir(), 'commands');
|
||||
}
|
||||
|
||||
static getGlobalMemoryFilePath(): string {
|
||||
return path.join(Storage.getGlobalGeminiDir(), 'memory.md');
|
||||
}
|
||||
|
||||
static getGlobalTempDir(): string {
|
||||
return path.join(Storage.getGlobalGeminiDir(), TMP_DIR_NAME);
|
||||
}
|
||||
|
||||
getGeminiDir(): string {
|
||||
return path.join(this.targetDir, GEMINI_DIR);
|
||||
}
|
||||
|
||||
getProjectTempDir(): string {
|
||||
const hash = this.getFilePathHash(this.getProjectRoot());
|
||||
const tempDir = Storage.getGlobalTempDir();
|
||||
return path.join(tempDir, hash);
|
||||
}
|
||||
|
||||
ensureProjectTempDirExists(): void {
|
||||
fs.mkdirSync(this.getProjectTempDir(), { recursive: true });
|
||||
}
|
||||
|
||||
static getOAuthCredsPath(): string {
|
||||
return path.join(Storage.getGlobalGeminiDir(), 'oauth_creds.json');
|
||||
}
|
||||
|
||||
getProjectRoot(): string {
|
||||
return this.targetDir;
|
||||
}
|
||||
|
||||
private getFilePathHash(filePath: string): string {
|
||||
return crypto.createHash('sha256').update(filePath).digest('hex');
|
||||
}
|
||||
|
||||
getHistoryDir(): string {
|
||||
const hash = this.getFilePathHash(this.getProjectRoot());
|
||||
const historyDir = path.join(Storage.getGlobalGeminiDir(), 'history');
|
||||
return path.join(historyDir, hash);
|
||||
}
|
||||
|
||||
getWorkspaceSettingsPath(): string {
|
||||
return path.join(this.getGeminiDir(), 'settings.json');
|
||||
}
|
||||
|
||||
getProjectCommandsDir(): string {
|
||||
return path.join(this.getGeminiDir(), 'commands');
|
||||
}
|
||||
|
||||
getProjectTempCheckpointsDir(): string {
|
||||
return path.join(this.getProjectTempDir(), 'checkpoints');
|
||||
}
|
||||
|
||||
getExtensionsDir(): string {
|
||||
return path.join(this.getGeminiDir(), 'extensions');
|
||||
}
|
||||
|
||||
getExtensionsConfigPath(): string {
|
||||
return path.join(this.getExtensionsDir(), 'gemini-extension.json');
|
||||
}
|
||||
|
||||
getHistoryFilePath(): string {
|
||||
return path.join(this.getProjectTempDir(), 'shell_history');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user