实现mini-cursor
# 实现 mini cursor:大模型自动调 Tool 执行命令
上节只加了读文件的 Tool,如果把写文件、执行命令、列目录都加上,不就能做 Cursor 的事了?
这节实现:大模型根据 prompt 生成项目 → 自动读写文件 → 安装依赖 → 跑起来,全程自己调 Tool。
# Node.js 执行命令
用内置模块 child_process 的 spawn:
import { spawn } from 'node:child_process';
const child = spawn('pnpm', ['install'], {
cwd: './my-project', // 工作目录
stdio: 'inherit', // 子进程输出直接显示在控制台
shell: true,
});
# 封装四个 Tool
读文件、写文件、执行命令、列目录,全部放在 all-tools.mjs:
import { tool } from '@langchain/core/tools';
import fs from 'node:fs/promises';
import path from 'node:path';
import { spawn } from 'node:child_process';
import { z } from 'zod';
// 读文件
const readFileTool = tool(
async ({ filePath }) => {
const content = await fs.readFile(filePath, 'utf-8');
return `文件内容:\n${content}`;
},
{
name: 'read_file',
description: '读取文件内容',
schema: z.object({
filePath: z.string().describe('文件路径'),
}),
}
);
// 写文件(自动创建目录)
const writeFileTool = tool(
async ({ filePath, content }) => {
const dir = path.dirname(filePath);
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(filePath, content, 'utf-8');
return `文件写入成功: ${filePath}`;
},
{
name: 'write_file',
description: '向指定路径写入文件内容,自动创建目录',
schema: z.object({
filePath: z.string().describe('文件路径'),
content: z.string().describe('要写入的文件内容'),
}),
}
);
// 执行命令
const executeCommandTool = tool(
async ({ command, workingDirectory }) => {
const cwd = workingDirectory || process.cwd();
return new Promise((resolve, reject) => {
const child = spawn(command, [], {
cwd,
stdio: 'inherit',
shell: true,
});
child.on('close', (code) => {
if (code === 0) resolve(`命令执行成功: ${command}`);
else reject(`命令执行失败,退出码: ${code}`);
});
});
},
{
name: 'execute_command',
description: '执行终端命令',
schema: z.object({
command: z.string().describe('要执行的命令'),
workingDirectory: z.string().optional().describe('工作目录'),
}),
}
);
// 列目录
const listDirectoryTool = tool(
async ({ directoryPath }) => {
const files = await fs.readdir(directoryPath);
return `目录内容:\n${files.map(f => `- ${f}`).join('\n')}`;
},
{
name: 'list_directory',
description: '列出目录下的文件和文件夹',
schema: z.object({
directoryPath: z.string().describe('目录路径'),
}),
}
);
# Agent 调用循环
把四个 Tool 绑定到模型,循环执行直到没有新的 tool_calls:
const tools = [readFileTool, writeFileTool, executeCommandTool, listDirectoryTool];
const modelWithTools = model.bindTools(tools);
async function runAgentWithTools(query, maxIterations = 30) {
const messages = [
new SystemMessage('你是一个项目管理助手,使用工具完成任务。回复要简洁。'),
new HumanMessage(query),
];
for (let i = 0; i < maxIterations; i++) {
const response = await modelWithTools.invoke(messages);
messages.push(response);
// 没有 tool_calls 就结束,输出最终回复
if (!response.tool_calls || response.tool_calls.length === 0) {
return response.content;
}
// 执行所有工具调用
for (const toolCall of response.tool_calls) {
const tool = tools.find(t => t.name === toolCall.name);
const result = await tool.invoke(toolCall.args);
messages.push(new ToolMessage({
content: result,
tool_call_id: toolCall.id,
}));
}
}
}
# 跑起来
const task = `
请创建一个 React + Vite 的 TodoList 项目到 react-todo-app 目录:
1. 用 pnpm create vite 创建项目
2. 写入完整的 TodoList 组件(增删改查、动画、渐变背景)
3. pnpm install 安装依赖
4. pnpm run dev 启动服务
`;
await runAgentWithTools(task);
大模型会自动调用 list_directory、write_file、read_file、execute_command,把项目创建好、跑起来。
# 注意点
- workingDirectory 和 cd 不要同时用 —
spawn的cwd已经切换了目录,命令里再cd会找不到路径 - maxIterations 设上限 — 防止死循环,30 次够用了
- temperature: 0 — 让模型严格按指令执行
编辑 (opens new window)
上次更新: 2026/06/09, 01:51:57