一、apply_patch 工具
1.1 工具概述
功能 :应用补丁到多个文件
核心特性:
- 支持添加/修改/删除文件
- 使用 *** Begin Patch/End Patch 格式
- 支持文件移动
- 仅 OpenAI 模型启用
- 支持沙盒模式
1.2 Schema 定义
位置:第 12935 行
javascript
const applyPatchSchema = Type.Object({
input: Type.String({
description: "Patch content using the *** Begin Patch/End Patch format."
})
});
1.3 完整执行代码
位置:第 12936 行
javascript
function createApplyPatchTool(options = {}) {
// 1. 解析配置
const cwd = options.cwd ?? process.cwd();
const sandbox = options.sandbox;
const workspaceOnly = options.workspaceOnly !== false;
return {
name: "apply_patch",
label: "apply_patch",
description: "Apply a patch to one or more files using the apply_patch format. The input should include *** Begin Patch and *** End Patch markers.",
parameters: applyPatchSchema,
execute: async (_toolCallId, args, signal) => {
const params = args;
// 2. 解析输入
const input = typeof params.input === "string" ? params.input : "";
if (!input.trim()) {
throw new Error("Provide a patch input.");
}
// 3. 检查中止信号
if (signal?.aborted) {
const err = new Error("Aborted");
err.name = "AbortError";
throw err;
}
// 4. 应用补丁
const result = await applyPatch(input, {
cwd,
sandbox,
workspaceOnly,
signal
});
// 5. 返回结果
return {
content: [{
type: "text",
text: result.text
}],
details: { summary: result.summary }
};
}
};
}
// 补丁应用核心函数
async function applyPatch(input, options) {
// 1. 解析补丁文本
const parsed = parsePatchText(input);
if (parsed.hunks.length === 0) {
throw new Error("No files were modified.");
}
// 2. 构建摘要
const summary = {
added: [],
modified: [],
deleted: []
};
const seen = {
added: new Set(),
modified: new Set(),
deleted: new Set()
};
// 3. 解析文件操作
const fileOps = resolvePatchFileOps(options);
// 4. 处理每个补丁块
for (const hunk of parsed.hunks) {
if (options.signal?.aborted) {
const err = new Error("Aborted");
err.name = "AbortError";
throw err;
}
// 4.1 添加文件
if (hunk.kind === "add") {
const target = await resolvePatchPath(hunk.path, options);
await ensureDir(target.resolved, fileOps);
await fileOps.writeFile(target.resolved, hunk.contents);
recordSummary(summary, seen, "added", target.display);
continue;
}
// 4.2 删除文件
if (hunk.kind === "delete") {
const target = await resolvePatchPath(hunk.path, options, PATH_ALIAS_POLICIES.unlinkTarget);
await fileOps.remove(target.resolved);
recordSummary(summary, seen, "deleted", target.display);
continue;
}
// 4.3 修改文件
const target = await resolvePatchPath(hunk.path, options);
const applied = await applyUpdateHunk(target.resolved, hunk.chunks, {
readFile: (path) => fileOps.readFile(path)
});
// 4.4 移动文件
if (hunk.movePath) {
const moveTarget = await resolvePatchPath(hunk.movePath, options);
await ensureDir(moveTarget.resolved, fileOps);
await fileOps.writeFile(moveTarget.resolved, applied);
await fileOps.remove(target.resolved);
recordSummary(summary, seen, "modified", moveTarget.display);
} else {
await fileOps.writeFile(target.resolved, applied);
recordSummary(summary, seen, "modified", target.display);
}
}
// 5. 返回结果
return {
summary,
text: formatSummary(summary)
};
}
// 记录摘要
function recordSummary(summary, seen, bucket, value) {
if (seen[bucket].has(value)) return;
seen[bucket].add(value);
summary[bucket].push(value);
}
// 格式化摘要
function formatSummary(summary) {
const lines = ["Success. Updated the following files:"];
for (const file of summary.added) lines.push(`A ${file}`);
for (const file of summary.modified) lines.push(`M ${file}`);
for (const file of summary.deleted) lines.push(`D ${file}`);
return lines.join("\n");
}
1.4 补丁格式
*** Begin Patch
*** Add File: src/new_file.js
console.log("Hello, World!");
*** End Patch
*** Begin Patch
*** Modify File: src/existing_file.js
@@ -1,3 +1,4 @@
function hello() {
- console.log("Hi");
+ console.log("Hello, World!");
}
*** End Patch
*** Begin Patch
*** Delete File: src/old_file.js
*** End Patch
*** Begin Patch
*** Move File: src/old_path.js -> src/new_path.js
*** End Patch
1.5 补丁操作类型
| 操作 | 说明 | 格式 |
|---|---|---|
| Add | 添加新文件 | *** Add File: path |
| Modify | 修改文件 | *** Modify File: path + 差异内容 |
| Delete | 删除文件 | *** Delete File: path |
| Move | 移动文件 | *** Move File: old -> new |
1.6 执行流程图
apply_patch 工具调用
↓
1. 解析输入
↓
2. 检查中止信号
↓
3. 解析补丁文本
↓
4. 处理每个补丁块
├─ Add → 创建文件
├─ Delete → 删除文件
├─ Modify → 应用差异
└─ Move → 移动文件
↓
5. 记录摘要
↓
6. 返回结果
1.7 返回结果格式
成功:
json
{
"content": [{
"type": "text",
"text": "Success. Updated the following files:\nA src/new_file.js\nM src/existing_file.js\nD src/old_file.js"
}],
"details": {
"summary": {
"added": ["src/new_file.js"],
"modified": ["src/existing_file.js"],
"deleted": ["src/old_file.js"]
}
}
}
失败:
json
{
"error": "No files were modified."
}
二、sandboxed_read 工具
2.1 工具概述
功能 :沙盒中读取文件
核心特性:
- 通过 bridge 访问隔离文件系统
- 支持图片 MIME 检测
- 支持模型上下文窗口限制
- 图片清理
2.2 创建函数
位置:第 115000 行
javascript
function createSandboxedReadTool(params) {
return createOpenClawReadTool(createReadTool(params.root, {
operations: createSandboxReadOperations(params)
}), {
modelContextWindowTokens: params.modelContextWindowTokens,
imageSanitization: params.imageSanitization
});
}
2.3 沙盒读取操作
位置:第 115034 行
javascript
function createSandboxReadOperations(params) {
return {
// 1. 读取文件
readFile: (absolutePath) => params.bridge.readFile({
filePath: absolutePath,
cwd: params.root
}),
// 2. 访问检查
access: async (absolutePath) => {
if (!await params.bridge.stat({
filePath: absolutePath,
cwd: params.root
})) {
throw createFsAccessError("ENOENT", absolutePath);
}
},
// 3. 检测图片 MIME 类型
detectImageMimeType: async (absolutePath) => {
const mime = await detectMime({
buffer: await params.bridge.readFile({
filePath: absolutePath,
cwd: params.root
}),
filePath: absolutePath
});
return mime && mime.startsWith("image/") ? mime : void 0;
}
};
}
2.4 包装链
createReadTool (外部库)
↓ (使用沙盒操作)
createOpenClawReadTool (OpenClaw 包装)
↓ (模型上下文限制)
wrapToolWorkspaceRootGuard (工作目录限制)
↓
最终工具
2.5 执行流程图
sandboxed_read 工具调用
↓
1. 参数标准化
↓
2. 验证必填参数
↓
3. 沙盒桥接读取
├─ bridge.readFile
├─ bridge.stat
└─ detectMime
↓
4. 自适应分页(如果需要)
↓
5. 图片处理(如果是图片)
↓
6. 返回结果
三、关键机制对比
3.1 功能定位
| 特性 | apply_patch | sandboxed_read |
|---|---|---|
| 用途 | 批量文件修改 | 沙盒文件读取 |
| 写操作 | Add/Modify/Delete | 只读 |
| 沙盒支持 | 支持 | 必需 |
3.2 启用条件
| 特性 | apply_patch | sandboxed_read |
|---|---|---|
| 模型限制 | 仅 OpenAI | 无 |
| 沙盒必需 | 可选 | 必需 |
| 配置启用 | 需要 | 自动 |
3.3 安全限制
| 限制类型 | apply_patch | sandboxed_read |
|---|---|---|
| 工作目录 | workspaceOnly | root 限制 |
| 桥接访问 | 支持 | 必需 |
| 路径解析 | resolvePatchPath | bridge 路径 |
四、使用示例
4.1 apply_patch 工具调用
用户 :添加一个新文件并修改现有文件
大模型返回:
json
{
"tool_call": {
"name": "apply_patch",
"arguments": {
"input": "*** Begin Patch\n*** Add File: src/hello.js\nconsole.log(\"Hello\");\n*** End Patch\n\n*** Begin Patch\n*** Modify File: src/main.js\n@@ -1,2 +1,3 @@\n function main() {\n+ console.log(\"Main\");\n }\n*** End Patch"
}
}
}
执行结果:
json
{
"content": [{
"type": "text",
"text": "Success. Updated the following files:\nA src/hello.js\nM src/main.js"
}],
"details": {
"summary": {
"added": ["src/hello.js"],
"modified": ["src/main.js"],
"deleted": []
}
}
}
4.2 sandboxed_read 工具调用
用户 :读取沙盒中的文件
大模型返回:
json
{
"tool_call": {
"name": "sandboxed_read",
"arguments": {
"path": "src/main.js"
}
}
}
执行结果:
json
{
"content": [{
"type": "text",
"text": "function main() {\n console.log(\"Main\");\n}"
}],
"details": {
"path": "src/main.js",
"sandbox": true
}
}