优化工具
This commit is contained in:
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc && cp -r src/tools/descriptions/*.txt dist/tools/descriptions/",
|
||||||
"start": "node dist/index.js",
|
"start": "node dist/index.js",
|
||||||
"dev": "tsx src/index.ts",
|
"dev": "tsx src/index.ts",
|
||||||
"lint": "eslint src/**/*.ts"
|
"lint": "eslint src/**/*.ts"
|
||||||
|
|||||||
+8
-1
@@ -35,11 +35,18 @@ export class Agent {
|
|||||||
this.getModel = providerFactory(config.apiKey);
|
this.getModel = providerFactory(config.apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册工具
|
// 注册单个工具
|
||||||
registerTool(customTool: Tool): void {
|
registerTool(customTool: Tool): void {
|
||||||
this.tools.set(customTool.name, customTool);
|
this.tools.set(customTool.name, customTool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量注册工具
|
||||||
|
registerTools(tools: Tool[]): void {
|
||||||
|
for (const tool of tools) {
|
||||||
|
this.tools.set(tool.name, tool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 将自定义工具转换为 Vercel AI SDK 的工具格式
|
// 将自定义工具转换为 Vercel AI SDK 的工具格式
|
||||||
private getVercelTools(): Record<string, AITool> {
|
private getVercelTools(): Record<string, AITool> {
|
||||||
const vercelTools: Record<string, AITool> = {};
|
const vercelTools: Record<string, AITool> = {};
|
||||||
|
|||||||
+5
-19
@@ -4,13 +4,7 @@ import { Command } from 'commander';
|
|||||||
import { Agent } from './core/agent.js';
|
import { Agent } from './core/agent.js';
|
||||||
import { TerminalUI } from './ui/terminal.js';
|
import { TerminalUI } from './ui/terminal.js';
|
||||||
import { loadConfig, initConfig } from './utils/config.js';
|
import { loadConfig, initConfig } from './utils/config.js';
|
||||||
import {
|
import { allTools } from './tools/index.js';
|
||||||
bashTool,
|
|
||||||
readFileTool,
|
|
||||||
writeFileTool,
|
|
||||||
listDirTool,
|
|
||||||
searchFilesTool,
|
|
||||||
} from './tools/index.js';
|
|
||||||
|
|
||||||
const program = new Command();
|
const program = new Command();
|
||||||
|
|
||||||
@@ -35,15 +29,11 @@ program
|
|||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
const agent = new Agent(config);
|
const agent = new Agent(config);
|
||||||
|
|
||||||
// 注册工具
|
// 注册所有工具
|
||||||
agent.registerTool(bashTool);
|
agent.registerTools(allTools);
|
||||||
agent.registerTool(readFileTool);
|
|
||||||
agent.registerTool(writeFileTool);
|
|
||||||
agent.registerTool(listDirTool);
|
|
||||||
agent.registerTool(searchFilesTool);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await agent.chat(question, (text) => {
|
await agent.chat(question, (text) => {
|
||||||
process.stdout.write(text);
|
process.stdout.write(text);
|
||||||
});
|
});
|
||||||
console.log('');
|
console.log('');
|
||||||
@@ -62,11 +52,7 @@ program.action(async () => {
|
|||||||
const agent = new Agent(config);
|
const agent = new Agent(config);
|
||||||
|
|
||||||
// 注册所有工具
|
// 注册所有工具
|
||||||
agent.registerTool(bashTool);
|
agent.registerTools(allTools);
|
||||||
agent.registerTool(readFileTool);
|
|
||||||
agent.registerTool(writeFileTool);
|
|
||||||
agent.registerTool(listDirTool);
|
|
||||||
agent.registerTool(searchFilesTool);
|
|
||||||
|
|
||||||
// 启动终端 UI
|
// 启动终端 UI
|
||||||
const ui = new TerminalUI(agent);
|
const ui = new TerminalUI(agent);
|
||||||
|
|||||||
+2
-1
@@ -1,12 +1,13 @@
|
|||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import type { Tool, ToolResult } from '../types/index.js';
|
import type { Tool, ToolResult } from '../types/index.js';
|
||||||
|
import { loadDescription } from './load_description.js';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
export const bashTool: Tool = {
|
export const bashTool: Tool = {
|
||||||
name: 'bash',
|
name: 'bash',
|
||||||
description: '执行 bash 命令。可以用于运行系统命令、安装包、git 操作等。',
|
description: loadDescription('bash'),
|
||||||
parameters: {
|
parameters: {
|
||||||
command: {
|
command: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
执行 bash 命令。可以用于运行系统命令、安装包、git 操作等。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
通过字符串替换编辑文件的部分内容。比 write_file 更高效,适合修改文件的一小部分。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
列出指定目录下的文件和文件夹
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
读取指定文件的内容
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
在目录中搜索匹配模式的文件
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
创建新文件或完全覆盖现有文件。如果只需修改文件的一部分,请使用 edit_file。
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import * as path from 'path';
|
||||||
|
import type { Tool, ToolResult } from '../types/index.js';
|
||||||
|
import { loadDescription } from './load_description.js';
|
||||||
|
|
||||||
|
export const editFileTool: Tool = {
|
||||||
|
name: 'edit_file',
|
||||||
|
description: loadDescription('edit_file'),
|
||||||
|
parameters: {
|
||||||
|
path: {
|
||||||
|
type: 'string',
|
||||||
|
description: '要编辑的文件路径',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
old_string: {
|
||||||
|
type: 'string',
|
||||||
|
description: '要被替换的原始字符串(必须精确匹配)',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
new_string: {
|
||||||
|
type: 'string',
|
||||||
|
description: '替换后的新字符串',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||||
|
const filePath = params.path as string;
|
||||||
|
const oldString = params.old_string as string;
|
||||||
|
const newString = params.new_string as string;
|
||||||
|
const absolutePath = path.isAbsolute(filePath)
|
||||||
|
? filePath
|
||||||
|
: path.join(process.cwd(), filePath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = await fs.readFile(absolutePath, 'utf-8');
|
||||||
|
|
||||||
|
if (!content.includes(oldString)) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `未找到要替换的字符串。请确保 old_string 与文件中的内容完全匹配(包括空格和换行)。`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const occurrences = content.split(oldString).length - 1;
|
||||||
|
if (occurrences > 1) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: `找到 ${occurrences} 处匹配。old_string 必须唯一,请提供更多上下文使其唯一。`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const newContent = content.replace(oldString, newString);
|
||||||
|
await fs.writeFile(absolutePath, newContent, 'utf-8');
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
output: `文件已编辑: ${absolutePath}`,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
import * as fs from 'fs/promises';
|
|
||||||
import * as path from 'path';
|
|
||||||
import type { Tool, ToolResult } from '../types/index.js';
|
|
||||||
|
|
||||||
// 读取文件工具
|
|
||||||
export const readFileTool: Tool = {
|
|
||||||
name: 'read_file',
|
|
||||||
description: '读取指定文件的内容',
|
|
||||||
parameters: {
|
|
||||||
path: {
|
|
||||||
type: 'string',
|
|
||||||
description: '要读取的文件路径(相对或绝对路径)',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
|
||||||
const filePath = params.path as string;
|
|
||||||
const absolutePath = path.isAbsolute(filePath)
|
|
||||||
? filePath
|
|
||||||
: path.join(process.cwd(), filePath);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const content = await fs.readFile(absolutePath, 'utf-8');
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: content,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
output: '',
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// 写入文件工具
|
|
||||||
export const writeFileTool: Tool = {
|
|
||||||
name: 'write_file',
|
|
||||||
description: '创建或覆盖文件内容',
|
|
||||||
parameters: {
|
|
||||||
path: {
|
|
||||||
type: 'string',
|
|
||||||
description: '要写入的文件路径',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
type: 'string',
|
|
||||||
description: '要写入的内容',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
|
||||||
const filePath = params.path as string;
|
|
||||||
const content = params.content as string;
|
|
||||||
const absolutePath = path.isAbsolute(filePath)
|
|
||||||
? filePath
|
|
||||||
: path.join(process.cwd(), filePath);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 确保目录存在
|
|
||||||
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
||||||
await fs.writeFile(absolutePath, content, 'utf-8');
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: `文件已写入: ${absolutePath}`,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
output: '',
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// 列出目录工具
|
|
||||||
export const listDirTool: Tool = {
|
|
||||||
name: 'list_directory',
|
|
||||||
description: '列出指定目录下的文件和文件夹',
|
|
||||||
parameters: {
|
|
||||||
path: {
|
|
||||||
type: 'string',
|
|
||||||
description: '要列出的目录路径',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
|
||||||
const dirPath = params.path as string;
|
|
||||||
const absolutePath = path.isAbsolute(dirPath)
|
|
||||||
? dirPath
|
|
||||||
: path.join(process.cwd(), dirPath);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const entries = await fs.readdir(absolutePath, { withFileTypes: true });
|
|
||||||
const result = entries
|
|
||||||
.map((entry) => {
|
|
||||||
const prefix = entry.isDirectory() ? '📁' : '📄';
|
|
||||||
return `${prefix} ${entry.name}`;
|
|
||||||
})
|
|
||||||
.join('\n');
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: result || '(空目录)',
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
output: '',
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// 搜索文件工具
|
|
||||||
export const searchFilesTool: Tool = {
|
|
||||||
name: 'search_files',
|
|
||||||
description: '在目录中搜索匹配模式的文件',
|
|
||||||
parameters: {
|
|
||||||
directory: {
|
|
||||||
type: 'string',
|
|
||||||
description: '搜索的起始目录',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
pattern: {
|
|
||||||
type: 'string',
|
|
||||||
description: '文件名匹配模式(支持 glob 模式,如 *.ts)',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
|
||||||
const directory = params.directory as string;
|
|
||||||
const pattern = params.pattern as string;
|
|
||||||
const absolutePath = path.isAbsolute(directory)
|
|
||||||
? directory
|
|
||||||
: path.join(process.cwd(), directory);
|
|
||||||
|
|
||||||
const matches: string[] = [];
|
|
||||||
const regex = new RegExp(
|
|
||||||
pattern.replace(/\*/g, '.*').replace(/\?/g, '.'),
|
|
||||||
'i'
|
|
||||||
);
|
|
||||||
|
|
||||||
async function searchRecursive(dir: string, depth = 0): Promise<void> {
|
|
||||||
if (depth > 10) return; // 限制递归深度
|
|
||||||
|
|
||||||
try {
|
|
||||||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
||||||
for (const entry of entries) {
|
|
||||||
const fullPath = path.join(dir, entry.name);
|
|
||||||
|
|
||||||
// 跳过 node_modules 和隐藏目录
|
|
||||||
if (entry.name.startsWith('.') || entry.name === 'node_modules') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.isDirectory()) {
|
|
||||||
await searchRecursive(fullPath, depth + 1);
|
|
||||||
} else if (regex.test(entry.name)) {
|
|
||||||
matches.push(fullPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// 忽略权限错误
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await searchRecursive(absolutePath);
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output:
|
|
||||||
matches.length > 0
|
|
||||||
? matches.join('\n')
|
|
||||||
: '没有找到匹配的文件',
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
output: '',
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
+14
-3
@@ -1,7 +1,18 @@
|
|||||||
export { bashTool } from './bash.js';
|
import type { Tool } from '../types/index.js';
|
||||||
export {
|
import { bashTool } from './bash.js';
|
||||||
|
import { readFileTool } from './read_file.js';
|
||||||
|
import { writeFileTool } from './write_file.js';
|
||||||
|
import { editFileTool } from './edit_file.js';
|
||||||
|
import { listDirTool } from './list_directory.js';
|
||||||
|
import { searchFilesTool } from './search_files.js';
|
||||||
|
|
||||||
|
// 所有可用工具的注册中心
|
||||||
|
// 添加新工具只需在此数组中添加一行
|
||||||
|
export const allTools: Tool[] = [
|
||||||
|
bashTool,
|
||||||
readFileTool,
|
readFileTool,
|
||||||
writeFileTool,
|
writeFileTool,
|
||||||
|
editFileTool,
|
||||||
listDirTool,
|
listDirTool,
|
||||||
searchFilesTool,
|
searchFilesTool,
|
||||||
} from './file.js';
|
];
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import * as path from 'path';
|
||||||
|
import type { Tool, ToolResult } from '../types/index.js';
|
||||||
|
import { loadDescription } from './load_description.js';
|
||||||
|
|
||||||
|
export const listDirTool: Tool = {
|
||||||
|
name: 'list_directory',
|
||||||
|
description: loadDescription('list_directory'),
|
||||||
|
parameters: {
|
||||||
|
path: {
|
||||||
|
type: 'string',
|
||||||
|
description: '要列出的目录路径',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||||
|
const dirPath = params.path as string;
|
||||||
|
const absolutePath = path.isAbsolute(dirPath)
|
||||||
|
? dirPath
|
||||||
|
: path.join(process.cwd(), dirPath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const entries = await fs.readdir(absolutePath, { withFileTypes: true });
|
||||||
|
const result = entries
|
||||||
|
.map((entry) => {
|
||||||
|
const prefix = entry.isDirectory() ? '📁' : '📄';
|
||||||
|
return `${prefix} ${entry.name}`;
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
output: result || '(空目录)',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
export function loadDescription(toolName: string): string {
|
||||||
|
const filePath = path.join(__dirname, 'descriptions', `${toolName}.txt`);
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(filePath, 'utf-8').trim();
|
||||||
|
} catch {
|
||||||
|
throw new Error(`无法加载工具描述文件: ${filePath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import * as path from 'path';
|
||||||
|
import type { Tool, ToolResult } from '../types/index.js';
|
||||||
|
import { loadDescription } from './load_description.js';
|
||||||
|
|
||||||
|
export const readFileTool: Tool = {
|
||||||
|
name: 'read_file',
|
||||||
|
description: loadDescription('read_file'),
|
||||||
|
parameters: {
|
||||||
|
path: {
|
||||||
|
type: 'string',
|
||||||
|
description: '要读取的文件路径(相对或绝对路径)',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||||
|
const filePath = params.path as string;
|
||||||
|
const absolutePath = path.isAbsolute(filePath)
|
||||||
|
? filePath
|
||||||
|
: path.join(process.cwd(), filePath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = await fs.readFile(absolutePath, 'utf-8');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
output: content,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import * as path from 'path';
|
||||||
|
import type { Tool, ToolResult } from '../types/index.js';
|
||||||
|
import { loadDescription } from './load_description.js';
|
||||||
|
|
||||||
|
export const searchFilesTool: Tool = {
|
||||||
|
name: 'search_files',
|
||||||
|
description: loadDescription('search_files'),
|
||||||
|
parameters: {
|
||||||
|
directory: {
|
||||||
|
type: 'string',
|
||||||
|
description: '搜索的起始目录',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
pattern: {
|
||||||
|
type: 'string',
|
||||||
|
description: '文件名匹配模式(支持 glob 模式,如 *.ts)',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||||
|
const directory = params.directory as string;
|
||||||
|
const pattern = params.pattern as string;
|
||||||
|
const absolutePath = path.isAbsolute(directory)
|
||||||
|
? directory
|
||||||
|
: path.join(process.cwd(), directory);
|
||||||
|
|
||||||
|
const matches: string[] = [];
|
||||||
|
const regex = new RegExp(
|
||||||
|
pattern.replace(/\*/g, '.*').replace(/\?/g, '.'),
|
||||||
|
'i'
|
||||||
|
);
|
||||||
|
|
||||||
|
async function searchRecursive(dir: string, depth = 0): Promise<void> {
|
||||||
|
if (depth > 10) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
const fullPath = path.join(dir, entry.name);
|
||||||
|
|
||||||
|
if (entry.name.startsWith('.') || entry.name === 'node_modules') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
await searchRecursive(fullPath, depth + 1);
|
||||||
|
} else if (regex.test(entry.name)) {
|
||||||
|
matches.push(fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 忽略权限错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await searchRecursive(absolutePath);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
output:
|
||||||
|
matches.length > 0
|
||||||
|
? matches.join('\n')
|
||||||
|
: '没有找到匹配的文件',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import * as path from 'path';
|
||||||
|
import type { Tool, ToolResult } from '../types/index.js';
|
||||||
|
import { loadDescription } from './load_description.js';
|
||||||
|
|
||||||
|
export const writeFileTool: Tool = {
|
||||||
|
name: 'write_file',
|
||||||
|
description: loadDescription('write_file'),
|
||||||
|
parameters: {
|
||||||
|
path: {
|
||||||
|
type: 'string',
|
||||||
|
description: '要写入的文件路径',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
type: 'string',
|
||||||
|
description: '要写入的内容',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||||
|
const filePath = params.path as string;
|
||||||
|
const content = params.content as string;
|
||||||
|
const absolutePath = path.isAbsolute(filePath)
|
||||||
|
? filePath
|
||||||
|
: path.join(process.cwd(), filePath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
||||||
|
await fs.writeFile(absolutePath, content, 'utf-8');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
output: `文件已写入: ${absolutePath}`,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user