不用装任何第三方包,Node.js 自带了一套"操作系统级"工具箱。这是 Agent 能干活的基础。
开发 AI Coding Agent 时,有一个很容易被忽略的事实:Agent 的"大脑"是模型,但"四肢"全是 Node.js 的内置模块。
读文件用 fs,拼路径用 path,执行命令用 spawn,读用户输入用 readline------全都是 import 就能用的东西,不需要 npm install。
这篇文章逐一拆解 Agent 开发中最关键的六个内置模块,每个都附带项目中的真实代码。
一、fs/promises --- 文件系统
Agent 最核心的能力:读写文件。fs/promises 是 Promise 版本的 fs,配合 async/await 使用。
1.1 读取文件
js
import fs from 'node:fs/promises'
const content = await fs.readFile('/project/src/App.tsx', 'utf8');
// 返回文件全部内容,编码 utf8
// 文件不存在会抛异常 → catch 后返回错误信息给模型
项目中的完整实现(readFileTool.js):
js
export const readFileTool = tool(
async ({ filePath }) => {
try {
const content = await fs.readFile(filePath, 'utf8');
return content; // 原样返回给模型
} catch (err) {
return `读取文件 ${filePath} 失败: ${err.message}`; // 不抛异常
}
},
{ name: 'read_file', /* ... */ }
)
1.2 写入文件
js
// 自动创建不存在的父目录
await fs.mkdir('/project/src/components', { recursive: true });
// recursive: true → 相当于 mkdir -p,父目录不存在就逐级创建
// 写入内容
await fs.writeFile('/project/src/App.tsx', content, 'utf8');
recursive: true 是精华:Agent 经常要在尚不存在的深层目录创建文件,这个参数让它不需要先检查目录是否存在。
1.3 列出目录
js
const files = await fs.readdir('/project/src');
// → ['App.tsx', 'main.tsx', 'components', 'utils']
三个方法就构成了 Agent 的文件操作闭环:查看有哪些文件 → 读取内容 → 修改后写回。
二、path --- 路径操作
Windows 用 \,Linux/Mac 用 /。手写字符串拼接迟早出问题。path 模块的存在意义就是跨平台处理路径。
2.1 三个核心方法
js
import path from 'node:path'
// 拼接路径 --- 自动适配系统分隔符
path.join('project', 'src', 'App.tsx')
// Windows → 'project\\src\\App.tsx'
// Linux → 'project/src/App.tsx'
// 取文件所在目录
path.dirname('/project/src/App.tsx')
// → '/project/src'
// 取文件名
path.basename('/project/src/App.tsx')
// → 'App.tsx'
2.2 在 writeFileTool 中的应用
Agent 写文件时,需要从路径字符串提取目录部分来创建文件夹:
js
// 用户想写 '/project/src/components/Header.tsx'
const dir = path.dirname(filePath);
// dir = '/project/src/components'
await fs.mkdir(dir, { recursive: true });
// 确保目录存在,不存在就逐级创建
await fs.writeFile(filePath, content, 'utf8');
// 写入文件
用 path.dirname 而不是正则或字符串替换,因为路径格式多变:./src/file、/project/file、src\\file------只有 path 模块能稳定处理所有情况。
2.3 扩展方法
js
path.extname('/project/App.tsx') // → '.tsx'
path.resolve('src', 'tools') // → 'D:/project/min-cursor/src/tools' (绝对路径)
path.relative('/a/b/c', '/a/b/d') // → '../d'
三、child_process --- 执行命令
这是 Agent 最复杂的能力------替用户在终端里敲命令。
3.1 spawn:启动子进程
js
import { spawn } from 'node:child_process'
const child = spawn('pnpm', ['install', '--save', 'react'], {
cwd: '/project/subdir', // 工作目录(相当于先 cd 进去)
env: process.env, // 继承当前环境变量(PATH 等)
stdio: 'inherit', // 子进程输出直接显示在终端
shell: 'bash.exe', // 用哪个 shell 解析命令
});
五个参数逐个解释:
| 参数 | 类型 | 含义 |
|---|---|---|
cmd |
string | 要执行的程序名 |
args |
string\[\] | 传给程序的参数 |
cwd |
string | Current Working Directory,在哪个目录执行 |
env |
object | 环境变量,通常传 process.env 继承当前 |
stdio |
string | 'inherit' = 实时输出到终端;'pipe' = 用代码收集 |
shell |
string|boolean | 用哪个 shell 解析命令 |
3.2 shell 为什么重要
AI 生成的不只是 pnpm install 这么简单的命令。它经常写:
bash
echo -e "n\nn" | pnpm create vite app --template react-ts
这行命令包含:
echo:shell 内置命令,不是可执行文件|:管道,把一个命令的输出传给另一个-e:让 echo 解析\n为换行符
不加 shell 参数,这几个特性全不能工作。Windows 上默认 shell 是 cmd.exe(不兼容 bash 语法),所以要显式指定 Git Bash 路径。
3.3 监听子进程结果
js
child.on('error', (error) => {
// 子进程启动失败(如文件找不到)
});
child.on('close', (code) => {
if (code === 0) {
// 成功
} else {
// 失败,但不 reject------把错误作为文本返回给模型
}
});
关键设计:退出码不为 0 也不 reject。返回错误文本让模型自己判断下一步怎么做------这是 ReAct Agent 的核心优势。
3.4 exec vs spawn
Node.js 还有个 exec,区别在于缓冲策略:
| spawn | exec | |
|---|---|---|
| 输出方式 | 流式(实时) | 缓冲(全部完成后才返回) |
| 适用场景 | 长时间运行(dev server) | 短命令(ls、echo) |
| 数据量 | 不限 | 默认限制 1MB |
Agent 场景选 spawn:因为 pnpm run dev 这类服务需要持续运行,不能等它"结束"。
四、readline --- 交互式输入
让 Agent 不是硬编码任务,而是从用户那里"问"来任务。
js
import { createInterface } from 'node:readline'
const rl = createInterface({
input: process.stdin, // 从键盘读
output: process.stdout, // 提示信息打到屏幕
});
rl.question('📝 请输入任务:', (answer) => {
console.log(`收到:${answer}`);
rl.close(); // 用完要关
});
项目中的实现支持三种输入方式:
bash
node src/main.js # 交互式 --- 用 readline 问用户
node src/main.js "创建一个 Express 项目" # 直接命令行传参
node src/main.js -f task.md # 从文件读取
五、process --- 进程级信息
process 是全局变量,不需要 import,直接就能用。
5.1 三个最常用的
js
// 当前工作目录 --- Agent 所有文件操作的"原点"
process.cwd()
// → 'D:/project/min-cursor'
// 环境变量 --- 读 API key、模型配置
process.env.DEEPSEEK_API_KEY
process.env.MODEL_NAME
// 命令行参数 --- 解析用户输入
process.argv
// → ['node', 'src/main.js', '创建一个项目']
5.2 在 Agent 中的角色
process.cwd() 是整个项目的"锚点":
js
// execCommandTool 中
cwd: directoryPath || process.cwd()
// ↑ 模型指定了就用它的 ↑ 没指定就用户启动 node 时的目录
// system prompt 中
当前工作目录: ${process.cwd()}
// 告诉模型它现在在哪个目录,所有操作据此展开
5.3 platform:判断操作系统
js
process.platform
// 'win32' | 'darwin' | 'linux'
// 项目中用来判断 shell 路径
shell: process.platform === 'win32'
? 'D:\\Git\\Git\\bin\\bash.exe'
: true
六、os --- 系统信息
js
import os from 'node:os'
os.cpus().length // CPU 核心数 → 16
os.platform() // 同 process.platform → 'win32'
os.homedir() // 用户目录 → 'C:\\Users\\27499'
os.totalmem() // 总内存(字节)
os.freemem() // 可用内存
在 Agent 场景中,os 的价值在于让模型了解运行环境:
js
// 可以在 system prompt 里追加
操作系统: ${os.platform()}
CPU 核心: ${os.cpus().length}
用户目录: ${os.homedir()}
这样模型就知道自己在 Windows 上,不会试图用 /home/user/ 这种 Linux 路径。
七、六个模块的关系
把这些模块串起来,就是一个完整的 Agent 工作流:
lua
readline 读取用户任务
↓
process.cwd() 确定工作锚点
↓
模型决策(大脑)
↓
┌─────────────────────────┐
│ spawn 执行命令 │ ← child_process
│ path 拼路径 │ ← path
│ fs 读写文件 / 列目录 │ ← fs
│ process.env 传递配置 │ ← process
│ os.platform 判断系统 │ ← os
└─────────────────────────┘
↓
结果反馈给模型 → 继续循环
八、总结
| 模块 | Agent 中的角色 | 不用会怎样 |
|---|---|---|
fs/promises |
读写文件、创建目录 | Agent 没法碰文件 |
path |
跨平台路径操作 | Windows/Linux 路径不兼容 |
child_process |
执行终端命令 | Agent 只能聊不能干 |
readline |
交互式输入 | 任务全靠硬编码 |
process |
进程信息、环境变量 | 不知道在哪、不知道干嘛 |
os |
系统信息 | 模型可能用错路径语法 |
这些模块每个都只有几十行,但它们组合起来,就是一个 Agent 全部的行动能力。