refactor(core): 实现类型安全的工具定义系统
- 新增 defineTool 函数,使用 Zod schema 定义参数并自动推断 TypeScript 类型 - 重构文件系统工具 (read_file, write_file, edit_file, glob, grep, multi_edit) 使用 Zod 类型推断 - 重构 shell 工具 (bash, kill_shell) 使用新的类型安全系统 - 重构 task 工具 (task, task_output) 使用 Zod 验证 - 兼容 Zod v4 API (处理 _zod.def vs _def, error.issues vs error.errors) - 导出参数类型供外部使用 (ReadFileParams, BashParams 等) - 统一参数命名: path -> file_path - 修复相关测试以适配新的参数结构和输出格式 - 移除不存在工具的测试文件
This commit is contained in:
@@ -54,7 +54,7 @@ describe('editFileTool - 文件编辑工具', () => {
|
||||
});
|
||||
|
||||
it('定义了必需参数', () => {
|
||||
expect(editFileTool.parameters.path.required).toBe(true);
|
||||
expect(editFileTool.parameters.file_path.required).toBe(true);
|
||||
expect(editFileTool.parameters.old_string.required).toBe(true);
|
||||
expect(editFileTool.parameters.new_string.required).toBe(true);
|
||||
});
|
||||
@@ -65,7 +65,7 @@ describe('editFileTool - 文件编辑工具', () => {
|
||||
vi.mocked(fs.readFile).mockResolvedValue('hello world');
|
||||
|
||||
const result = await editFileTool.execute({
|
||||
path: 'test.txt',
|
||||
file_path: 'test.txt',
|
||||
old_string: 'world',
|
||||
new_string: 'universe',
|
||||
});
|
||||
@@ -83,7 +83,7 @@ describe('editFileTool - 文件编辑工具', () => {
|
||||
vi.mocked(fs.readFile).mockResolvedValue('hello world');
|
||||
|
||||
const result = await editFileTool.execute({
|
||||
path: 'test.txt',
|
||||
file_path: 'test.txt',
|
||||
old_string: 'notfound',
|
||||
new_string: 'replacement',
|
||||
});
|
||||
@@ -96,7 +96,7 @@ describe('editFileTool - 文件编辑工具', () => {
|
||||
vi.mocked(fs.readFile).mockResolvedValue('hello hello hello');
|
||||
|
||||
const result = await editFileTool.execute({
|
||||
path: 'test.txt',
|
||||
file_path: 'test.txt',
|
||||
old_string: 'hello',
|
||||
new_string: 'hi',
|
||||
});
|
||||
@@ -117,7 +117,7 @@ describe('editFileTool - 文件编辑工具', () => {
|
||||
} as any);
|
||||
|
||||
const result = await editFileTool.execute({
|
||||
path: 'test.txt',
|
||||
file_path: 'test.txt',
|
||||
old_string: 'content',
|
||||
new_string: 'new',
|
||||
});
|
||||
@@ -137,7 +137,7 @@ describe('editFileTool - 文件编辑工具', () => {
|
||||
} as any);
|
||||
|
||||
const result = await editFileTool.execute({
|
||||
path: 'test.txt',
|
||||
file_path: 'test.txt',
|
||||
old_string: 'content',
|
||||
new_string: 'new',
|
||||
});
|
||||
@@ -151,7 +151,7 @@ describe('editFileTool - 文件编辑工具', () => {
|
||||
vi.mocked(fs.access).mockRejectedValue(new Error('ENOENT: no such file'));
|
||||
|
||||
const result = await editFileTool.execute({
|
||||
path: 'nonexistent.txt',
|
||||
file_path: 'nonexistent.txt',
|
||||
old_string: 'text',
|
||||
new_string: 'new',
|
||||
});
|
||||
@@ -172,7 +172,7 @@ describe('editFileTool - 文件编辑工具', () => {
|
||||
vi.mocked(getFormattedFileDiagnostics).mockResolvedValue('\n错误: 类型不匹配');
|
||||
|
||||
const result = await editFileTool.execute({
|
||||
path: 'test.ts',
|
||||
file_path: 'test.ts',
|
||||
old_string: 'const x = 1',
|
||||
new_string: 'const x: string = 1',
|
||||
});
|
||||
@@ -189,7 +189,7 @@ describe('editFileTool - 文件编辑工具', () => {
|
||||
} as any);
|
||||
|
||||
await editFileTool.execute({
|
||||
path: 'test.txt',
|
||||
file_path: 'test.txt',
|
||||
old_string: 'old text',
|
||||
new_string: 'new text',
|
||||
});
|
||||
|
||||
@@ -40,10 +40,10 @@ describe('readFileTool - 读取文件工具', () => {
|
||||
expect(readFileTool.metadata.keywords).toContain('file');
|
||||
});
|
||||
|
||||
it('定义了必需的 path 参数', () => {
|
||||
expect(readFileTool.parameters.path).toBeDefined();
|
||||
expect(readFileTool.parameters.path.required).toBe(true);
|
||||
expect(readFileTool.parameters.path.type).toBe('string');
|
||||
it('定义了必需的 file_path 参数', () => {
|
||||
expect(readFileTool.parameters.file_path).toBeDefined();
|
||||
expect(readFileTool.parameters.file_path.required).toBe(true);
|
||||
expect(readFileTool.parameters.file_path.type).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,16 +52,17 @@ describe('readFileTool - 读取文件工具', () => {
|
||||
const mockContent = 'Hello, World!';
|
||||
vi.mocked(fs.readFile).mockResolvedValue(mockContent);
|
||||
|
||||
const result = await readFileTool.execute({ path: './test.txt' });
|
||||
const result = await readFileTool.execute({ file_path: './test.txt' });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.output).toBe(mockContent);
|
||||
// 输出包含行号格式化
|
||||
expect(result.output).toContain('Hello, World!');
|
||||
});
|
||||
|
||||
it('处理绝对路径', async () => {
|
||||
vi.mocked(fs.readFile).mockResolvedValue('content');
|
||||
|
||||
await readFileTool.execute({ path: '/absolute/path/file.txt' });
|
||||
await readFileTool.execute({ file_path: '/absolute/path/file.txt' });
|
||||
|
||||
expect(fs.readFile).toHaveBeenCalledWith('/absolute/path/file.txt', 'utf-8');
|
||||
});
|
||||
@@ -69,7 +70,7 @@ describe('readFileTool - 读取文件工具', () => {
|
||||
it('处理相对路径', async () => {
|
||||
vi.mocked(fs.readFile).mockResolvedValue('content');
|
||||
|
||||
await readFileTool.execute({ path: './relative/file.txt' });
|
||||
await readFileTool.execute({ file_path: './relative/file.txt' });
|
||||
|
||||
// 应该解析为绝对路径
|
||||
expect(fs.readFile).toHaveBeenCalled();
|
||||
@@ -86,7 +87,7 @@ describe('readFileTool - 读取文件工具', () => {
|
||||
}),
|
||||
} as any);
|
||||
|
||||
const result = await readFileTool.execute({ path: '/etc/passwd' });
|
||||
const result = await readFileTool.execute({ file_path: '/etc/passwd' });
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('权限被拒绝');
|
||||
@@ -102,7 +103,7 @@ describe('readFileTool - 读取文件工具', () => {
|
||||
}),
|
||||
} as any);
|
||||
|
||||
const result = await readFileTool.execute({ path: './sensitive.txt' });
|
||||
const result = await readFileTool.execute({ file_path: './sensitive.txt' });
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('需要用户确认');
|
||||
@@ -117,7 +118,7 @@ describe('readFileTool - 读取文件工具', () => {
|
||||
} as any);
|
||||
vi.mocked(fs.readFile).mockRejectedValue(new Error('ENOENT: no such file'));
|
||||
|
||||
const result = await readFileTool.execute({ path: './nonexistent.txt' });
|
||||
const result = await readFileTool.execute({ file_path: './nonexistent.txt' });
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('ENOENT');
|
||||
@@ -133,10 +134,12 @@ describe('readFileTool - 读取文件工具', () => {
|
||||
} as any);
|
||||
vi.mocked(fs.readFile).mockResolvedValue(largeContent);
|
||||
|
||||
const result = await readFileTool.execute({ path: './large.txt' });
|
||||
const result = await readFileTool.execute({ file_path: './large.txt' });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.output.length).toBe(10000);
|
||||
// 输出包含内容(加上行号格式化后会更长)
|
||||
expect(result.output).toContain('x'.repeat(100));
|
||||
expect(result.output.length).toBeGreaterThan(10000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user