Skip to content

Commit acad27e

Browse files
committed
feat: 完善工具提示词
1 parent 3676315 commit acad27e

File tree

4 files changed

+237
-39
lines changed

4 files changed

+237
-39
lines changed

package-lock.json

Lines changed: 31 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@
408408
},
409409
"dependencies": {
410410
"@agent-infra/duckduckgo-search": "^0.0.1",
411+
"@types/xml2js": "^0.4.14",
411412
"axios": "^1.9.0",
412413
"cheerio": "^1.0.0",
413414
"execa": "^9.5.2",
@@ -416,6 +417,7 @@
416417
"mysql2": "^3.14.0",
417418
"openai": "^4.96.0",
418419
"pidusage": "^4.0.0",
419-
"proper-lockfile": "^4.1.2"
420+
"proper-lockfile": "^4.1.2",
421+
"xml2js": "^0.6.2"
420422
}
421423
}

src/apiTools.ts

Lines changed: 197 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ import * as fs from 'fs';
1212
import * as path from 'path';
1313
import * as mysql from 'mysql2/promise';
1414
import * as childProcess from 'child_process';
15+
import { exec } from 'child_process';
1516
import { promisify } from 'util';
1617
import { ragService, CONFIG as RAG_CONFIG } from './ragService';
18+
import { parseString } from 'xml2js';
1719

1820

1921
/** 定义工具的接口 */
@@ -321,6 +323,7 @@ export const searchTool: Tool = {
321323
}
322324
},
323325
};
326+
registerTool(searchTool);
324327

325328
// 2. 获取当前日期时间
326329
export const getCurrentDateTime: Tool = {
@@ -336,6 +339,7 @@ export const getCurrentDateTime: Tool = {
336339
return now.toLocaleString();
337340
},
338341
};
342+
registerTool(getCurrentDateTime);
339343

340344
// 3. 读取指定路径文本
341345
export const readTextFile: Tool = {
@@ -360,6 +364,7 @@ export const readTextFile: Tool = {
360364
}
361365
},
362366
};
367+
registerTool(readTextFile);
363368

364369
// 4. 连接 MySQL 查表
365370
export const queryMySQL: Tool = {
@@ -392,28 +397,170 @@ export const queryMySQL: Tool = {
392397
}
393398
},
394399
};
395-
396-
// 5. 读取 SVN 日志
400+
registerTool(queryMySQL);
401+
402+
// SVN 日志条目类型
403+
type LogEntry = {
404+
revision: string;
405+
author?: string;
406+
date?: string;
407+
message?: string;
408+
paths?: Array<{ action: string; path: string }>;
409+
};
410+
411+
// 增强错误类型
412+
type SVNError = Error & { stderr?: string };
413+
const execAsync = promisify(exec);
397414
export const getSVNLog: Tool = {
398415
name: 'get_svn_log',
399-
description: '获取 SVN 仓库的日志。',
416+
description: '获取SVN日志,支持多种查询条件',
400417
parameters: {
401418
type: 'object',
402419
properties: {
403-
repoPath: { type: 'string', description: 'SVN 仓库的本地路径。' },
420+
path: {
421+
type: 'string',
422+
description: `仓库路径,支持以下格式:
423+
- 本地工作副本路径:'/projects/myapp'
424+
- 仓库URL:'http://svn.example.com/repo'
425+
- 仓库子目录:'http://svn.example.com/repo/trunk/src'`
426+
},
427+
author: {
428+
type: 'string',
429+
description: '提交者名称筛选(支持模糊匹配)'
430+
},
431+
keyword: {
432+
type: 'string',
433+
description: '提交信息关键词筛选(支持模糊匹配)'
434+
},
435+
startDate: {
436+
type: 'string',
437+
description: `开始时间,支持格式:
438+
- 绝对时间:'2024-03-01'
439+
- 相对时间:'1h'(最近1小时)/'24h'(最近1天)/'7d'(最近7天)/'30d'(最近30天)`
440+
},
441+
endDate: {
442+
type: 'string',
443+
description: `结束时间,支持格式:
444+
- 绝对时间:'2024-03-15'
445+
- 相对时间:'1h'(1小时前)`
446+
},
447+
revisionStart: {
448+
type: 'number',
449+
description: '起始版本号(包含)'
450+
},
451+
revisionEnd: {
452+
type: 'number',
453+
description: '结束版本号(包含)'
454+
},
455+
limit: {
456+
type: 'number',
457+
description: '返回结果最大数量'
458+
}
404459
},
405-
required: ['repoPath'],
460+
required: ['path'],
406461
},
407-
function: async (args: { repoPath: string }) => {
408-
try {
409-
const exec = promisify(childProcess.exec);
410-
const { stdout } = await exec(`svn log "${args.repoPath}"`);
411-
return stdout;
412-
} catch (error: any) {
413-
return `获取 SVN 日志失败: ${error.message}`;
462+
function: async (args): Promise<string> => {
463+
try {
464+
const params: string[] = [];
465+
let repoPath = args.path;
466+
467+
// 时间处理函数
468+
const parseTime = (timeStr?: string, isEndTime = false): string => {
469+
if (!timeStr) { return isEndTime ? 'HEAD' : '1'; }
470+
471+
const relativeMatch = timeStr.match(/^(\d+)([hd])$/);
472+
if (relativeMatch) {
473+
const now = new Date();
474+
const [, value, unit] = relativeMatch;
475+
const ms = parseInt(value) * (unit === 'h' ? 3600 : 86400) * 1000;
476+
return `{${new Date(now.getTime() - ms).toISOString().slice(0, 19)}}`;
477+
}
478+
479+
if (/^\d{4}-\d{2}-\d{2}$/.test(timeStr)) {
480+
return isEndTime
481+
? `{${timeStr} 23:59:59}`
482+
: `{${timeStr} 00:00:00}`;
483+
}
484+
485+
throw new Error(`Invalid time format: ${timeStr}`);
486+
};
487+
488+
// 构建查询参数
489+
if (args.author) { params.push(`--search "author:${args.author}"`); }
490+
if (args.keyword) { params.push(`--search "message:${args.keyword}"`); }
491+
492+
const startRev = parseTime(args.startDate);
493+
const endRev = parseTime(args.endDate, true);
494+
if (startRev !== '1' || endRev !== 'HEAD') {
495+
params.push(`--revision ${startRev}:${endRev}`);
414496
}
415-
},
497+
498+
if (args.revisionStart || args.revisionEnd) {
499+
const start = args.revisionStart ?? 1;
500+
const end = args.revisionEnd ?? 'HEAD';
501+
params.push(`--revision ${start}:${end}`);
502+
}
503+
504+
if (args.limit) { params.push(`-l ${args.limit}`); }
505+
506+
// 获取仓库根地址
507+
if (!/^(http|https|svn):\/\//.test(repoPath)) {
508+
const { stdout } = await execAsync(
509+
`svn info "${repoPath}" --show-item repos-root-url`,
510+
{ encoding: 'utf8' }
511+
);
512+
repoPath = stdout.trim();
513+
}
514+
515+
// 执行命令
516+
const { stdout } = await execAsync(
517+
`svn log "${repoPath}" --xml ${params.join(' ')}`,
518+
{ encoding: 'gbk' as BufferEncoding, maxBuffer: 1024 * 1024 * 10 }
519+
);
520+
521+
// 解析XML
522+
const parsed = await new Promise<any>((resolve, reject) => {
523+
parseString(stdout, (err, result) => {
524+
err ? reject(err) : resolve(result);
525+
});
526+
});
527+
528+
// 转换为LogEntry数组
529+
const entries: LogEntry[] = (parsed.log.logentry || []).map(
530+
(entry: any) => ({
531+
revision: entry.$.revision,
532+
author: entry.author?.[0],
533+
date: entry.date?.[0],
534+
message: entry.msg?.[0]?.trim(),
535+
paths: (entry.paths?.[0]?.path || []).map((p: any) => ({
536+
action: p.$.action,
537+
path: p._
538+
}))
539+
})
540+
);
541+
542+
return JSON.stringify(entries, null, 2);
543+
544+
} catch (error) {
545+
const err = error as SVNError;
546+
const errorMapping: Record<string, string> = {
547+
'175002': '认证失败',
548+
'160013': '路径不存在',
549+
'200009': '无效时间格式',
550+
'205000': '无效版本号'
551+
};
552+
553+
const errorCode = err.stderr?.match(/svn: E(\d+):/)?.[1] || '';
554+
const message = errorCode in errorMapping
555+
? `SVN错误 [E${errorCode}]: ${errorMapping[errorCode]}`
556+
: `操作失败: ${err.message?.replace(/^svn: E\d+: /, '') || '未知错误'}`;
557+
558+
return message;
559+
}
560+
}
416561
};
562+
563+
registerTool(getSVNLog);
417564

418565
// 6. 比对 SVN 本地差异的 diff
419566
export const getSVNDiff: Tool = {
@@ -436,6 +583,7 @@ export const getSVNDiff: Tool = {
436583
}
437584
},
438585
};
586+
registerTool(getSVNDiff);
439587

440588
// 7. 比对 GitHub 本地差异的 diff
441589
export const getGitDiff: Tool = {
@@ -458,6 +606,7 @@ export const getGitDiff: Tool = {
458606
}
459607
},
460608
};
609+
registerTool(getGitDiff);
461610

462611
// 8. Grep 搜索指定目录文本
463612
export const grepSearch: Tool = {
@@ -481,6 +630,7 @@ export const grepSearch: Tool = {
481630
}
482631
},
483632
};
633+
registerTool(grepSearch);
484634

485635
// 9. 在路径下递归搜索文件
486636
export const findFiles: Tool = {
@@ -507,39 +657,50 @@ export const findFiles: Tool = {
507657
function: async (args: { directory: string; pattern: string; useRegex: boolean }) => {
508658
try {
509659
const { directory, pattern, useRegex } = args;
510-
const results: string[] = [];
511-
512-
// 递归搜索函数
513-
const searchDir = async (currentDir: string) => {
514-
const files = await fs.promises.readdir(currentDir);
660+
const queue: string[] = [directory];
661+
const results: string[] = []; // 声明结果数组 <--- 修复点
662+
663+
while (queue.length > 0) {
664+
const currentDir = queue.shift()!;
665+
const files = await fs.promises.readdir(currentDir, { withFileTypes: true });
666+
515667
for (const file of files) {
516-
const filePath = path.join(currentDir, file);
517-
const stat = await fs.promises.stat(filePath);
518-
if (stat.isDirectory()) {
519-
await searchDir(filePath); // 递归搜索子目录
520-
} else {
521-
const fileName = path.basename(filePath);
522-
if (useRegex) {
523-
const regex = new RegExp(pattern);
524-
if (regex.test(fileName)) {
525-
results.push(filePath);
526-
}
668+
try {
669+
const filePath = path.join(currentDir, file.name);
670+
671+
if (file.isDirectory()) {
672+
queue.push(filePath);
527673
} else {
528-
if (fileName === pattern) {
529-
results.push(filePath);
674+
// 严格匹配模式
675+
if (!useRegex) {
676+
if (file.name === pattern) {
677+
return filePath; // 直接返回首个匹配项
678+
}
679+
}
680+
// 正则表达式模式
681+
else {
682+
const regex = new RegExp(pattern);
683+
if (regex.test(file.name)) {
684+
results.push(filePath); // 收集所有匹配项
685+
}
530686
}
531687
}
688+
} catch (error) {
689+
continue;
532690
}
533691
}
534-
};
535-
536-
await searchDir(directory);
537-
return results.join('\n');
692+
}
693+
694+
// 根据模式返回不同结果
695+
return useRegex
696+
? results.join('\n') // 正则模式返回所有结果
697+
: ''; // 严格模式未找到返回空
538698
} catch (error: any) {
539-
return `搜索文件失败: ${error.message}`;
699+
return `搜索失败: ${error.message}`;
540700
}
541701
},
542702
};
703+
registerTool(findFiles);
543704

544705
// 修改后的writeMemory工具(移除本地嵌入生成和存储)
545706
export const writeMemory: Tool = {

0 commit comments

Comments
 (0)