mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 08:47:44 +00:00
feat: Refactor and Enhance Ripgrep Tool (#930)
This commit is contained in:
@@ -154,6 +154,11 @@ vi.mock('../core/tokenLimits.js', () => ({
|
||||
|
||||
describe('Server Config (config.ts)', () => {
|
||||
const MODEL = 'qwen3-coder-plus';
|
||||
|
||||
// Default mock for canUseRipgrep to return true (tests that care about ripgrep will override this)
|
||||
beforeEach(() => {
|
||||
vi.mocked(canUseRipgrep).mockResolvedValue(true);
|
||||
});
|
||||
const SANDBOX: SandboxConfig = {
|
||||
command: 'docker',
|
||||
image: 'qwen-code-sandbox',
|
||||
@@ -578,6 +583,40 @@ describe('Server Config (config.ts)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('UseBuiltinRipgrep Configuration', () => {
|
||||
it('should default useBuiltinRipgrep to true when not provided', () => {
|
||||
const config = new Config(baseParams);
|
||||
expect(config.getUseBuiltinRipgrep()).toBe(true);
|
||||
});
|
||||
|
||||
it('should set useBuiltinRipgrep to false when provided as false', () => {
|
||||
const paramsWithBuiltinRipgrep: ConfigParameters = {
|
||||
...baseParams,
|
||||
useBuiltinRipgrep: false,
|
||||
};
|
||||
const config = new Config(paramsWithBuiltinRipgrep);
|
||||
expect(config.getUseBuiltinRipgrep()).toBe(false);
|
||||
});
|
||||
|
||||
it('should set useBuiltinRipgrep to true when explicitly provided as true', () => {
|
||||
const paramsWithBuiltinRipgrep: ConfigParameters = {
|
||||
...baseParams,
|
||||
useBuiltinRipgrep: true,
|
||||
};
|
||||
const config = new Config(paramsWithBuiltinRipgrep);
|
||||
expect(config.getUseBuiltinRipgrep()).toBe(true);
|
||||
});
|
||||
|
||||
it('should default useBuiltinRipgrep to true when undefined', () => {
|
||||
const paramsWithUndefinedBuiltinRipgrep: ConfigParameters = {
|
||||
...baseParams,
|
||||
useBuiltinRipgrep: undefined,
|
||||
};
|
||||
const config = new Config(paramsWithUndefinedBuiltinRipgrep);
|
||||
expect(config.getUseBuiltinRipgrep()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createToolRegistry', () => {
|
||||
it('should register a tool if coreTools contains an argument-specific pattern', async () => {
|
||||
const params: ConfigParameters = {
|
||||
@@ -825,10 +864,60 @@ describe('setApprovalMode with folder trust', () => {
|
||||
|
||||
expect(wasRipGrepRegistered).toBe(true);
|
||||
expect(wasGrepRegistered).toBe(false);
|
||||
expect(logRipgrepFallback).not.toHaveBeenCalled();
|
||||
expect(canUseRipgrep).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('should register GrepTool as a fallback when useRipgrep is true but it is not available', async () => {
|
||||
it('should register RipGrepTool with system ripgrep when useBuiltinRipgrep is false', async () => {
|
||||
(canUseRipgrep as Mock).mockResolvedValue(true);
|
||||
const config = new Config({
|
||||
...baseParams,
|
||||
useRipgrep: true,
|
||||
useBuiltinRipgrep: false,
|
||||
});
|
||||
await config.initialize();
|
||||
|
||||
const calls = (ToolRegistry.prototype.registerTool as Mock).mock.calls;
|
||||
const wasRipGrepRegistered = calls.some(
|
||||
(call) => call[0] instanceof vi.mocked(RipGrepTool),
|
||||
);
|
||||
const wasGrepRegistered = calls.some(
|
||||
(call) => call[0] instanceof vi.mocked(GrepTool),
|
||||
);
|
||||
|
||||
expect(wasRipGrepRegistered).toBe(true);
|
||||
expect(wasGrepRegistered).toBe(false);
|
||||
expect(canUseRipgrep).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('should fall back to GrepTool and log error when useBuiltinRipgrep is false but system ripgrep is not available', async () => {
|
||||
(canUseRipgrep as Mock).mockResolvedValue(false);
|
||||
const config = new Config({
|
||||
...baseParams,
|
||||
useRipgrep: true,
|
||||
useBuiltinRipgrep: false,
|
||||
});
|
||||
await config.initialize();
|
||||
|
||||
const calls = (ToolRegistry.prototype.registerTool as Mock).mock.calls;
|
||||
const wasRipGrepRegistered = calls.some(
|
||||
(call) => call[0] instanceof vi.mocked(RipGrepTool),
|
||||
);
|
||||
const wasGrepRegistered = calls.some(
|
||||
(call) => call[0] instanceof vi.mocked(GrepTool),
|
||||
);
|
||||
|
||||
expect(wasRipGrepRegistered).toBe(false);
|
||||
expect(wasGrepRegistered).toBe(true);
|
||||
expect(canUseRipgrep).toHaveBeenCalledWith(false);
|
||||
expect(logRipgrepFallback).toHaveBeenCalledWith(
|
||||
config,
|
||||
expect.any(RipgrepFallbackEvent),
|
||||
);
|
||||
const event = (logRipgrepFallback as Mock).mock.calls[0][1];
|
||||
expect(event.error).toContain('Ripgrep is not available');
|
||||
});
|
||||
|
||||
it('should fall back to GrepTool and log error when useRipgrep is true and builtin ripgrep is not available', async () => {
|
||||
(canUseRipgrep as Mock).mockResolvedValue(false);
|
||||
const config = new Config({ ...baseParams, useRipgrep: true });
|
||||
await config.initialize();
|
||||
@@ -843,15 +932,16 @@ describe('setApprovalMode with folder trust', () => {
|
||||
|
||||
expect(wasRipGrepRegistered).toBe(false);
|
||||
expect(wasGrepRegistered).toBe(true);
|
||||
expect(canUseRipgrep).toHaveBeenCalledWith(true);
|
||||
expect(logRipgrepFallback).toHaveBeenCalledWith(
|
||||
config,
|
||||
expect.any(RipgrepFallbackEvent),
|
||||
);
|
||||
const event = (logRipgrepFallback as Mock).mock.calls[0][1];
|
||||
expect(event.error).toBeUndefined();
|
||||
expect(event.error).toContain('Ripgrep is not available');
|
||||
});
|
||||
|
||||
it('should register GrepTool as a fallback when canUseRipgrep throws an error', async () => {
|
||||
it('should fall back to GrepTool and log error when canUseRipgrep throws an error', async () => {
|
||||
const error = new Error('ripGrep check failed');
|
||||
(canUseRipgrep as Mock).mockRejectedValue(error);
|
||||
const config = new Config({ ...baseParams, useRipgrep: true });
|
||||
@@ -890,7 +980,6 @@ describe('setApprovalMode with folder trust', () => {
|
||||
expect(wasRipGrepRegistered).toBe(false);
|
||||
expect(wasGrepRegistered).toBe(true);
|
||||
expect(canUseRipgrep).not.toHaveBeenCalled();
|
||||
expect(logRipgrepFallback).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -267,6 +267,7 @@ export interface ConfigParameters {
|
||||
interactive?: boolean;
|
||||
trustedFolder?: boolean;
|
||||
useRipgrep?: boolean;
|
||||
useBuiltinRipgrep?: boolean;
|
||||
shouldUseNodePtyShell?: boolean;
|
||||
skipNextSpeakerCheck?: boolean;
|
||||
shellExecutionConfig?: ShellExecutionConfig;
|
||||
@@ -355,6 +356,7 @@ export class Config {
|
||||
private readonly interactive: boolean;
|
||||
private readonly trustedFolder: boolean | undefined;
|
||||
private readonly useRipgrep: boolean;
|
||||
private readonly useBuiltinRipgrep: boolean;
|
||||
private readonly shouldUseNodePtyShell: boolean;
|
||||
private readonly skipNextSpeakerCheck: boolean;
|
||||
private shellExecutionConfig: ShellExecutionConfig;
|
||||
@@ -452,13 +454,12 @@ 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;
|
||||
this.skipLoopDetection = params.skipLoopDetection ?? false;
|
||||
|
||||
// Web search
|
||||
this.tavilyApiKey = params.tavilyApiKey;
|
||||
this.useRipgrep = params.useRipgrep ?? true;
|
||||
this.useBuiltinRipgrep = params.useBuiltinRipgrep ?? true;
|
||||
this.shouldUseNodePtyShell = params.shouldUseNodePtyShell ?? false;
|
||||
this.skipNextSpeakerCheck = params.skipNextSpeakerCheck ?? true;
|
||||
this.shellExecutionConfig = {
|
||||
@@ -988,6 +989,10 @@ export class Config {
|
||||
return this.useRipgrep;
|
||||
}
|
||||
|
||||
getUseBuiltinRipgrep(): boolean {
|
||||
return this.useBuiltinRipgrep;
|
||||
}
|
||||
|
||||
getShouldUseNodePtyShell(): boolean {
|
||||
return this.shouldUseNodePtyShell;
|
||||
}
|
||||
@@ -1115,13 +1120,18 @@ export class Config {
|
||||
let useRipgrep = false;
|
||||
let errorString: undefined | string = undefined;
|
||||
try {
|
||||
useRipgrep = await canUseRipgrep();
|
||||
useRipgrep = await canUseRipgrep(this.getUseBuiltinRipgrep());
|
||||
} catch (error: unknown) {
|
||||
errorString = String(error);
|
||||
}
|
||||
if (useRipgrep) {
|
||||
registerCoreTool(RipGrepTool, this);
|
||||
} else {
|
||||
errorString =
|
||||
errorString ||
|
||||
'Ripgrep is not available. Please install ripgrep globally.';
|
||||
|
||||
// Log for telemetry
|
||||
logRipgrepFallback(this, new RipgrepFallbackEvent(errorString));
|
||||
registerCoreTool(GrepTool, this);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user