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:
2025-12-18 15:46:11 +08:00
parent b2bb26a92b
commit 2c8a95daeb
21 changed files with 769 additions and 623 deletions
@@ -1,103 +0,0 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
// 使用可变的引用对象来绕过 hoisting 问题
const mockState = {
isInitialized: vi.fn().mockReturnValue(true),
getTodos: vi.fn().mockReturnValue([]),
};
vi.mock('../../../../src/tools/todo/todo-manager.js', () => ({
todoManager: {
isInitialized: () => mockState.isInitialized(),
getTodos: () => mockState.getTodos(),
},
}));
import { todoReadTool } from '../../../../src/tools/todo/todoread.js';
describe('todoReadTool - Todo 读取工具', () => {
beforeEach(() => {
vi.clearAllMocks();
mockState.isInitialized.mockReturnValue(true);
mockState.getTodos.mockReturnValue([]);
});
describe('工具定义', () => {
it('有正确的名称', () => {
expect(todoReadTool.name).toBe('todoread');
});
it('有正确的元数据', () => {
expect(todoReadTool.metadata.category).toBe('core');
expect(todoReadTool.metadata.keywords).toContain('todo');
expect(todoReadTool.metadata.keywords).toContain('task');
expect(todoReadTool.metadata.keywords).toContain('list');
});
it('无必需参数', () => {
expect(Object.keys(todoReadTool.parameters)).toHaveLength(0);
});
});
describe('execute - 执行', () => {
it('成功读取空列表', async () => {
const result = await todoReadTool.execute({});
expect(result.success).toBe(true);
expect(result.output).toBe('[]');
expect(result.metadata?.totalCount).toBe(0);
expect(result.metadata?.pendingCount).toBe(0);
});
it('成功读取待办列表', async () => {
const todos = [
{ id: '1', content: '任务1', status: 'pending', createdAt: '2024-01-01', updatedAt: '2024-01-01' },
{ id: '2', content: '任务2', status: 'in_progress', createdAt: '2024-01-01', updatedAt: '2024-01-01' },
{ id: '3', content: '任务3', status: 'completed', createdAt: '2024-01-01', updatedAt: '2024-01-01' },
];
mockState.getTodos.mockReturnValue(todos);
const result = await todoReadTool.execute({});
expect(result.success).toBe(true);
expect(result.metadata?.totalCount).toBe(3);
expect(result.metadata?.pendingCount).toBe(2); // pending + in_progress
});
it('返回 JSON 格式输出', async () => {
const todos = [
{ id: '1', content: '任务1', status: 'pending', createdAt: '2024-01-01', updatedAt: '2024-01-01' },
];
mockState.getTodos.mockReturnValue(todos);
const result = await todoReadTool.execute({});
const parsed = JSON.parse(result.output);
expect(parsed).toHaveLength(1);
expect(parsed[0].content).toBe('任务1');
});
it('未初始化时返回错误', async () => {
mockState.isInitialized.mockReturnValue(false);
const result = await todoReadTool.execute({});
expect(result.success).toBe(false);
expect(result.error).toContain('会话管理器未初始化');
});
it('返回正确的元数据', async () => {
const todos = [
{ id: '1', content: '任务1', status: 'pending' },
{ id: '2', content: '任务2', status: 'completed' },
];
mockState.getTodos.mockReturnValue(todos);
const result = await todoReadTool.execute({});
expect(result.metadata?.todos).toEqual(todos);
expect(result.metadata?.pendingCount).toBe(1);
expect(result.metadata?.totalCount).toBe(2);
});
});
});