OpenClaw工具拆解之apply_patch+sandboxed_read
一、apply_patch 工具
1.1 工具概述
功能:应用补丁到多个文件
核心特性:
-
• 支持添加/修改/删除文件 -
• 使用 *** Begin Patch/End Patch 格式 -
• 支持文件移动 -
• 仅 OpenAI 模型启用 -
• 支持沙盒模式
1.2 Schema 定义
位置:第 12935 行
const applyPatchSchema = Type.Object({
input: Type.String({
description: "Patch content using the *** Begin Patch/End Patch format."
})
});
1.3 完整执行代码
位置:第 12936 行
functioncreateApplyPatchTool(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()) {
thrownewError("Provide a patch input.");
}
// 3. 检查中止信号
if (signal?.aborted) {
const err = newError("Aborted");
err.name = "AbortError";
throw err;
}
// 4. 应用补丁
const result = awaitapplyPatch(input, {
cwd,
sandbox,
workspaceOnly,
signal
});
// 5. 返回结果
return {
content: [{
type: "text",
text: result.text
}],
details: { summary: result.summary }
};
}
};
}
// 补丁应用核心函数
asyncfunctionapplyPatch(input, options) {
// 1. 解析补丁文本
const parsed = parsePatchText(input);
if (parsed.hunks.length === 0) {
thrownewError("No files were modified.");
}
// 2. 构建摘要
const summary = {
added: [],
modified: [],
deleted: []
};
const seen = {
added: newSet(),
modified: newSet(),
deleted: newSet()
};
// 3. 解析文件操作
const fileOps = resolvePatchFileOps(options);
// 4. 处理每个补丁块
for (const hunk of parsed.hunks) {
if (options.signal?.aborted) {
const err = newError("Aborted");
err.name = "AbortError";
throw err;
}
// 4.1 添加文件
if (hunk.kind === "add") {
const target = awaitresolvePatchPath(hunk.path, options);
awaitensureDir(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 = awaitresolvePatchPath(hunk.path, options, PATH_ALIAS_POLICIES.unlinkTarget);
await fileOps.remove(target.resolved);
recordSummary(summary, seen, "deleted", target.display);
continue;
}
// 4.3 修改文件
const target = awaitresolvePatchPath(hunk.path, options);
const applied = awaitapplyUpdateHunk(target.resolved, hunk.chunks, {
readFile: (path) => fileOps.readFile(path)
});
// 4.4 移动文件
if (hunk.movePath) {
const moveTarget = awaitresolvePatchPath(hunk.movePath, options);
awaitensureDir(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)
};
}
// 记录摘要
functionrecordSummary(summary, seen, bucket, value) {
if (seen[bucket].has(value)) return;
seen[bucket].add(value);
summary[bucket].push(value);
}
// 格式化摘要
functionformatSummary(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 返回结果格式
成功:
{
"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"]
}
}
}
失败:
{
"error":"No files were modified."
}
二、sandboxed_read 工具
2.1 工具概述
功能:沙盒中读取文件
核心特性:
-
• 通过 bridge 访问隔离文件系统 -
• 支持图片 MIME 检测 -
• 支持模型上下文窗口限制 -
• 图片清理
2.2 创建函数
位置:第 115000 行
functioncreateSandboxedReadTool(params) {
returncreateOpenClawReadTool(createReadTool(params.root, {
operations: createSandboxReadOperations(params)
}), {
modelContextWindowTokens: params.modelContextWindowTokens,
imageSanitization: params.imageSanitization
});
}
2.3 沙盒读取操作
位置:第 115034 行
functioncreateSandboxReadOperations(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
})) {
throwcreateFsAccessError("ENOENT", absolutePath);
}
},
// 3. 检测图片 MIME 类型
detectImageMimeType: async (absolutePath) => {
const mime = awaitdetectMime({
buffer: await params.bridge.readFile({
filePath: absolutePath,
cwd: params.root
}),
filePath: absolutePath
});
return mime && mime.startsWith("image/") ? mime : void0;
}
};
}
2.4 包装链
createReadTool (外部库)
↓ (使用沙盒操作)
createOpenClawReadTool (OpenClaw 包装)
↓ (模型上下文限制)
wrapToolWorkspaceRootGuard (工作目录限制)
↓
最终工具
2.5 执行流程图
sandboxed_read 工具调用
↓
1. 参数标准化
↓
2. 验证必填参数
↓
3. 沙盒桥接读取
├─ bridge.readFile
├─ bridge.stat
└─ detectMime
↓
4. 自适应分页(如果需要)
↓
5. 图片处理(如果是图片)
↓
6. 返回结果
三、关键机制对比
3.1 功能定位
|
|
|
|
| 用途 |
|
|
| 写操作 |
|
|
| 沙盒支持 |
|
|
3.2 启用条件
|
|
|
|
| 模型限制 |
|
|
| 沙盒必需 |
|
|
| 配置启用 |
|
|
3.3 安全限制
|
|
|
|
| 工作目录 |
|
|
| 桥接访问 |
|
|
| 路径解析 |
|
|
四、使用示例
4.1 apply_patch 工具调用
用户:添加一个新文件并修改现有文件
大模型返回:
{
"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"
}
}
}
执行结果:
{
"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 工具调用
用户:读取沙盒中的文件
大模型返回:
{
"tool_call":{
"name":"sandboxed_read",
"arguments":{
"path":"src/main.js"
}
}
}
执行结果:
{
"content":[{
"type":"text",
"text":"function main() {\n console.log(\"Main\");\n}"
}],
"details":{
"path":"src/main.js",
"sandbox":true
}
}
夜雨聆风