| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| import { readFileSync, existsSync } from 'fs';
|
|
|
| const SMART_DOUBLE_QUOTES = new Set([
|
| '\u00ab', '\u201c', '\u201d', '\u275e',
|
| '\u201f', '\u201e', '\u275d', '\u00bb',
|
| ]);
|
|
|
| const SMART_SINGLE_QUOTES = new Set([
|
| '\u2018', '\u2019', '\u201a', '\u201b',
|
| ]);
|
|
|
| |
| |
|
|
| export function normalizeToolArguments(args: Record<string, unknown>): Record<string, unknown> {
|
| if (!args || typeof args !== 'object') return args;
|
|
|
|
|
|
|
|
|
|
|
| return args;
|
| }
|
|
|
| |
| |
|
|
| export function replaceSmartQuotes(text: string): string {
|
| const chars = [...text];
|
| return chars.map(ch => {
|
| if (SMART_DOUBLE_QUOTES.has(ch)) return '"';
|
| if (SMART_SINGLE_QUOTES.has(ch)) return "'";
|
| return ch;
|
| }).join('');
|
| }
|
|
|
| function buildFuzzyPattern(text: string): string {
|
| const parts: string[] = [];
|
| for (const ch of text) {
|
| if (SMART_DOUBLE_QUOTES.has(ch) || ch === '"') {
|
| parts.push('["\u00ab\u201c\u201d\u275e\u201f\u201e\u275d\u00bb]');
|
| } else if (SMART_SINGLE_QUOTES.has(ch) || ch === "'") {
|
| parts.push("['\u2018\u2019\u201a\u201b]");
|
| } else if (ch === ' ' || ch === '\t') {
|
| parts.push('\\s+');
|
| } else if (ch === '\\') {
|
| parts.push('\\\\{1,2}');
|
| } else {
|
| parts.push(escapeRegExp(ch));
|
| }
|
| }
|
| return parts.join('');
|
| }
|
|
|
| function escapeRegExp(str: string): string {
|
| return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
| }
|
|
|
| |
| |
| |
| |
| |
|
|
| export function repairExactMatchToolArguments(
|
| toolName: string,
|
| args: Record<string, unknown>,
|
| ): Record<string, unknown> {
|
| if (!args || typeof args !== 'object') return args;
|
|
|
| const lowerName = (toolName || '').toLowerCase();
|
| if (!lowerName.includes('str_replace') && !lowerName.includes('search_replace') && !lowerName.includes('strreplace')) {
|
| return args;
|
| }
|
|
|
| const oldString = (args.old_string ?? args.old_str) as string | undefined;
|
| if (!oldString) return args;
|
|
|
| const filePath = (args.path ?? args.file_path) as string | undefined;
|
| if (!filePath) return args;
|
|
|
| try {
|
| if (!existsSync(filePath)) return args;
|
| const content = readFileSync(filePath, 'utf-8');
|
|
|
| if (content.includes(oldString)) return args;
|
|
|
| const pattern = buildFuzzyPattern(oldString);
|
| const regex = new RegExp(pattern, 'g');
|
| const matches = [...content.matchAll(regex)];
|
|
|
| if (matches.length !== 1) return args;
|
|
|
| const matchedText = matches[0][0];
|
|
|
| if ('old_string' in args) args.old_string = matchedText;
|
| else if ('old_str' in args) args.old_str = matchedText;
|
|
|
| const newString = (args.new_string ?? args.new_str) as string | undefined;
|
| if (newString) {
|
| const fixed = replaceSmartQuotes(newString);
|
| if ('new_string' in args) args.new_string = fixed;
|
| else if ('new_str' in args) args.new_str = fixed;
|
| }
|
|
|
| console.log(`[ToolFixer] 修复了 ${toolName} 的 old_string 精确匹配`);
|
| } catch {
|
|
|
| }
|
|
|
| return args;
|
| }
|
|
|
| |
| |
|
|
| export function fixToolCallArguments(
|
| toolName: string,
|
| args: Record<string, unknown>,
|
| ): Record<string, unknown> {
|
| args = normalizeToolArguments(args);
|
| args = repairExactMatchToolArguments(toolName, args);
|
| return args;
|
| }
|
|
|