OpenClaw工具拆解之apply_patch+sandboxed_read

一、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
  }
}
相关推荐
user29876982706541 小时前
四、深入 Claude Code CLI 源码:服务层的架构设计
人工智能
iAm_Ike1 小时前
怎么关闭MongoDB不需要的HTTP管理接口及REST API
jvm·数据库·python
志栋智能1 小时前
跨越人机边界:超自动化巡检如何重塑工作流程?
运维·网络·人工智能·安全·自动化
悠悠121381 小时前
从零到一搭建AI智能体:hello-agents项目实战部署全记录
人工智能
kishu_iOS&AI1 小时前
NLP —— LSTM/GRU模型
人工智能·pytorch·深度学习·自然语言处理·gru·lstm
马优晨1 小时前
大语言模型(LLM)、Embedding 模型、reranker重排序模型 有什么关系
人工智能·语言模型·embedding·embedding 模型·大语言模型(llm)
OpenCSG1 小时前
Kimi K2.6:月之暗面发布的原生多模态智能体模型
人工智能·开源·大模型·ai技术·kimi k2.6
hrhcode1 小时前
【LangChain】一.LangChain v1.0-快速上手(核心组件、工具、中间件)
python·ai·langchain·agent
steven_yzx1 小时前
自动驾驶相机坐标系转换
人工智能·数码相机·自动驾驶