mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 16:57:46 +00:00
Refactor: Standardize Tool Naming and Configuration System (#1004)
This commit is contained in:
@@ -5,9 +5,10 @@
|
||||
*/
|
||||
|
||||
import { expect, describe, it } from 'vitest';
|
||||
import { doesToolInvocationMatch } from './tool-utils.js';
|
||||
import { doesToolInvocationMatch, isToolEnabled } from './tool-utils.js';
|
||||
import type { AnyToolInvocation, Config } from '../index.js';
|
||||
import { ReadFileTool } from '../tools/read-file.js';
|
||||
import { ToolNames } from '../tools/tool-names.js';
|
||||
|
||||
describe('doesToolInvocationMatch', () => {
|
||||
it('should not match a partial command prefix', () => {
|
||||
@@ -92,3 +93,67 @@ describe('doesToolInvocationMatch', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isToolEnabled', () => {
|
||||
it('enables tool when coreTools is undefined and tool is not excluded', () => {
|
||||
expect(isToolEnabled(ToolNames.SHELL, undefined, undefined)).toBe(true);
|
||||
});
|
||||
|
||||
it('disables tool when excluded by canonical tool name', () => {
|
||||
expect(
|
||||
isToolEnabled(ToolNames.SHELL, undefined, ['run_shell_command']),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('enables tool when explicitly listed by display name', () => {
|
||||
expect(isToolEnabled(ToolNames.SHELL, ['Shell'], undefined)).toBe(true);
|
||||
});
|
||||
|
||||
it('enables tool when explicitly listed by class name', () => {
|
||||
expect(isToolEnabled(ToolNames.SHELL, ['ShellTool'], undefined)).toBe(true);
|
||||
});
|
||||
|
||||
it('supports class names with leading underscores', () => {
|
||||
expect(isToolEnabled(ToolNames.SHELL, ['__ShellTool'], undefined)).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('enables tool when coreTools contains a legacy tool name alias', () => {
|
||||
expect(
|
||||
isToolEnabled(ToolNames.GREP, ['search_file_content'], undefined),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('enables tool when coreTools contains a legacy display name alias', () => {
|
||||
expect(isToolEnabled(ToolNames.GLOB, ['FindFiles'], undefined)).toBe(true);
|
||||
});
|
||||
|
||||
it('enables tool when coreTools contains an argument-specific pattern', () => {
|
||||
expect(
|
||||
isToolEnabled(ToolNames.SHELL, ['Shell(git status)'], undefined),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('disables tool when not present in coreTools', () => {
|
||||
expect(isToolEnabled(ToolNames.SHELL, ['Edit'], undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it('uses legacy display name aliases when excluding tools', () => {
|
||||
expect(isToolEnabled(ToolNames.GREP, undefined, ['SearchFiles'])).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('does not treat argument-specific exclusions as matches', () => {
|
||||
expect(
|
||||
isToolEnabled(ToolNames.SHELL, undefined, ['Shell(git status)']),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('considers excludeTools even when tool is explicitly enabled', () => {
|
||||
expect(isToolEnabled(ToolNames.SHELL, ['Shell'], ['ShellTool'])).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,111 @@
|
||||
|
||||
import type { AnyDeclarativeTool, AnyToolInvocation } from '../index.js';
|
||||
import { isTool } from '../index.js';
|
||||
import {
|
||||
ToolNames,
|
||||
ToolDisplayNames,
|
||||
ToolNamesMigration,
|
||||
ToolDisplayNamesMigration,
|
||||
} from '../tools/tool-names.js';
|
||||
|
||||
export type ToolName = (typeof ToolNames)[keyof typeof ToolNames];
|
||||
|
||||
const normalizeIdentifier = (identifier: string): string =>
|
||||
identifier.trim().replace(/^_+/, '');
|
||||
|
||||
const toolNameKeys = Object.keys(ToolNames) as Array<keyof typeof ToolNames>;
|
||||
|
||||
const TOOL_ALIAS_MAP: Map<ToolName, Set<string>> = (() => {
|
||||
const map = new Map<ToolName, Set<string>>();
|
||||
|
||||
const addAlias = (set: Set<string>, alias?: string) => {
|
||||
if (!alias) {
|
||||
return;
|
||||
}
|
||||
set.add(normalizeIdentifier(alias));
|
||||
};
|
||||
|
||||
for (const key of toolNameKeys) {
|
||||
const canonicalName = ToolNames[key];
|
||||
const displayName = ToolDisplayNames[key];
|
||||
const aliases = new Set<string>();
|
||||
|
||||
addAlias(aliases, canonicalName);
|
||||
addAlias(aliases, displayName);
|
||||
addAlias(aliases, `${displayName}Tool`);
|
||||
|
||||
for (const [legacyName, mappedName] of Object.entries(ToolNamesMigration)) {
|
||||
if (mappedName === canonicalName) {
|
||||
addAlias(aliases, legacyName);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [legacyDisplay, mappedDisplay] of Object.entries(
|
||||
ToolDisplayNamesMigration,
|
||||
)) {
|
||||
if (mappedDisplay === displayName) {
|
||||
addAlias(aliases, legacyDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
map.set(canonicalName, aliases);
|
||||
}
|
||||
|
||||
return map;
|
||||
})();
|
||||
|
||||
const getAliasSetForTool = (toolName: ToolName): Set<string> => {
|
||||
const aliases = TOOL_ALIAS_MAP.get(toolName);
|
||||
if (!aliases) {
|
||||
return new Set([normalizeIdentifier(toolName)]);
|
||||
}
|
||||
return aliases;
|
||||
};
|
||||
|
||||
const sanitizeExactIdentifier = (value: string): string =>
|
||||
normalizeIdentifier(value);
|
||||
|
||||
const sanitizePatternIdentifier = (value: string): string => {
|
||||
const openParenIndex = value.indexOf('(');
|
||||
if (openParenIndex === -1) {
|
||||
return normalizeIdentifier(value);
|
||||
}
|
||||
return normalizeIdentifier(value.slice(0, openParenIndex));
|
||||
};
|
||||
|
||||
const filterList = (list?: string[]): string[] =>
|
||||
(list ?? []).filter((entry): entry is string =>
|
||||
Boolean(entry && entry.trim()),
|
||||
);
|
||||
|
||||
export function isToolEnabled(
|
||||
toolName: ToolName,
|
||||
coreTools?: string[],
|
||||
excludeTools?: string[],
|
||||
): boolean {
|
||||
const aliasSet = getAliasSetForTool(toolName);
|
||||
const matchesIdentifier = (value: string): boolean =>
|
||||
aliasSet.has(sanitizeExactIdentifier(value));
|
||||
const matchesIdentifierWithArgs = (value: string): boolean =>
|
||||
aliasSet.has(sanitizePatternIdentifier(value));
|
||||
|
||||
const filteredCore = filterList(coreTools);
|
||||
const filteredExclude = filterList(excludeTools);
|
||||
|
||||
if (filteredCore.length === 0) {
|
||||
return !filteredExclude.some((entry) => matchesIdentifier(entry));
|
||||
}
|
||||
|
||||
const isExplicitlyEnabled = filteredCore.some(
|
||||
(entry) => matchesIdentifier(entry) || matchesIdentifierWithArgs(entry),
|
||||
);
|
||||
|
||||
if (!isExplicitlyEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !filteredExclude.some((entry) => matchesIdentifier(entry));
|
||||
}
|
||||
|
||||
const SHELL_TOOL_NAMES = ['run_shell_command', 'ShellTool'];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user