本页目录

Oh-My-Pi 的 `edit` / `write`:协议、模式与模型适配

内容由GPT-6 Astra生成。

本文基于 OMP coding-agent 当前源码与文档整理。重点是区分模型输出的 wire contract、OMP 的解析/校验/落盘链路,以及不同模型为什么可能使用不同 edit mode。

总览

OMP 有两个文件修改工具:

  • edit:对已有源码做结构化或局部修改,有多种可切换的输入协议。
  • write:创建文件或完整覆盖文件内容,不是 edit 的一个 mode。

默认 edit mode 是 hashline,但实际 mode 还会受到模型变体、环境变量和配置影响。

# 显式固定协议
edit:
  mode: apply_patch
  modelVariants: {}
PI_EDIT_VARIANT=apply_patch \
PI_STRICT_EDIT_MODE=1 \
/root/.local/bin/omp

PI_STRICT_EDIT_MODE=1 禁用内置的模型专用 fallback;它不会覆盖显式的 PI_EDIT_VARIANT 或 edit.modelVariants。

write:完整内容写入

核心参数:

{
  "path": "src/main.py",
  "content": "完整文件内容\n"
}

适用于:

  • 创建新文件;
  • 明确要求整体重写文件;
  • 某些 archive/SQLite 内部资源操作。

OMP 的 write prompt 建议:修改已有源码时优先使用 edit,避免完整覆盖导致未提及的内容丢失。

优点:schema 简单、新文件自然、容易实现完整文件 diff UI。

风险:覆盖范围是整个文件;模型必须提供完整最终内容;绕过 OMP 原生执行会绕过 LSP writethrough、诊断、formatter 和 file-write fallback 等机制。

edit mode

当前 EditMode 枚举为:

export type EditMode = "replace" | "patch" | "hashline" | "apply_patch" | "sloppy";

resolveEditMode() 的选择顺序:

1. edit.modelVariants 中匹配当前模型的 mode
2. PI_EDIT_VARIANT
3. edit.mode
4. 默认 hashline

例如:

edit:
  mode: hashline
  modelVariants:
    claude: apply_patch
    gpt-5: replace

这不是让同一个调用同时接受多个 schema,而是按当前模型选择一个有效 edit tool 协议。

没有 PI_STRICT_EDIT_MODE=1 时,默认 hashline 可能因为模型兼容性规则自动切换为 sloppy。当前源码将这些模型类别视为不适合 hashline:

  • kimi
  • mimo
  • deepseek 的 flash family
  • stepfun

因此不能只根据“配置没有写 mode”推断实际 schema。

replace:字符串查找替换

{
  "path": "src/main.py",
  "old_string": "return items",
  "new_string": "return sorted(items)",
  "replace_all": false
}

replace_all: false 通常替换一个匹配;replace_all: true 替换所有匹配。OMP 还可以通过 fuzzy matching 容忍高置信度的空白差异:

edit:
  mode: replace
  fuzzyMatch: true
  fuzzyThreshold: 0.95

环境变量:PI_EDIT_FUZZY、PI_EDIT_FUZZY_THRESHOLD。

适合:小而明确、目标字符串唯一的修改,以及不能稳定生成复杂 patch 的模型。

不适合:同一文件多处不相邻修改、跨文件移动、需要重排代码的任务。模型必须准确复制 old_string,复杂修改通常会被拆成多个 tool call。

patch:结构化 JSON patch

{
  "path": "src/main.py",
  "edits": [
    {
      "op": "update",
      "diff": "@@ -1,3 +1,3 @@\n old\n-old line\n+new line\n"
    }
  ]
}

字段:

{
  op?: "create" | "delete" | "update";
  rename?: string;
  diff?: string;
}

它适合单文件多 hunk,比 replace 灵活;但顶层主要围绕一个 path,多文件表达能力不如 apply_patch。

apply_patch:Codex 风格多文件 patch

schema:

{
  "input": "*** Begin Patch\n...\n*** End Patch"
}

示例:

*** Begin Patch
*** Update File: src/main.py
@@ -1,3 +1,3 @@
 def greet():
-    return "hello"
+    return "hello, world"
*** Add File: tests/test_main.py
+def test_greet():
+    assert True
*** Delete File: obsolete.py
*** End Patch

能力:

  • 一次修改多个文件;
  • 一个文件包含多个 hunk;
  • Add、Update、Delete、Move/rename;
  • 由 OMP 原生 parser、fuzzy matcher 和执行器处理。

适合能稳定生成结构化文本的强模型,以及经常需要多文件修改的 coding agent。

apply_patch 不是任意 unified diff 的别名。必须有完整的 *** Begin Patch / *** End Patch envelope,文件 header、上下文行和操作语义都必须满足当前 OMP parser。

hashline:版本化行锚定

[src/example.ts#1A2B]
PUT 4.=4:
+const value = 2;

#1A2B 是四位十六进制 snapshot tag,来自此前的 read、grep 或成功 edit 结果。

主要操作:

PUT N.=M:       替换原始快照中的 N..M 行
PUT N*:         替换从 N 开始的 tree-sitter 语法块
PUT <N:         在 N 行前插入
PUT >N:         在 N 行后插入
PUT >$:         文件尾追加
CUT N.=M        删除并放入匿名寄存器
CUT N.=M @name  删除并存入具名寄存器
PUT <N @name    粘贴具名寄存器
REM             删除文件
MV DEST         移动/重命名

hashline 的安全能力包括:

  • snapshot tag 校验版本;
  • seen-line guard 限制模型只能编辑已展示内容;
  • stale tag 安全恢复;
  • tree-sitter block anchor;
  • clipboard/register 跨 section 搬运代码;
  • 多 section 预解析和冲突检查。

代价是模型必须持续维护最新 tag、原始行号、展示范围、block opener、body 语法和 register 生命周期。常见失败包括 stale tag、编辑未展示行、错误 block 边界、统一 diff 污染、范围重叠和 no-op。

适合安全优先、需要版本校验和最小上下文暴露的 agent。不适合经常忘记更新 tag 或不稳定复制严格结构化 anchor 的模型。

sloppy:兼容性编辑格式

典型形式:

*** Edit File: src/main.py
*** Find
old text
*** Replace
new text

sloppy 的目标是兼容宽松编辑文本,而不是提供 hashline 的版本安全语义。它适合兼容旧式文本编辑输出;如果需要可预测行为,通常应显式选择 replace 或 apply_patch。

五种 mode 对比

Mode多文件多 hunkCreate/Delete/Move版本校验主要优点
replace弱弱否否schema 最简单
patch有限是是否单文件结构化修改
apply_patch是是是否多文件和操作 envelope
hashline是是是是快照、seen-line、block、register
sloppy取决于输入取决于输入有限否兼容宽松文本

write 不属于上表,因为它是完整内容写入工具。

不同基模如何选择

强模型:apply_patch

推荐:

edit:
  mode: apply_patch
  modelVariants: {}

它能一次表达多文件、多 hunk、创建、删除和移动,适合生成 diff UI,也避免反复复制长 old_string。

中等模型:replace 或 patch

无法稳定生成复杂 envelope 时使用:

edit:
  mode: replace

它适合小而明确的局部修改。如果模型能生成基本 unified hunk,但多文件需求不高,可以选 patch。

安全优先:hashline

如果首要目标是防止编辑过时内容、限制模型只能修改已读行,并需要 block/register,应使用:

edit:
  mode: hashline

兼容旧模型:sloppy

只有在模型对严格 schema 持续失败,且接受较弱结构安全时才考虑 sloppy。它不是更强的 apply_patch。

不同模型在预训练期间接触的edit工具不同

“不同模型家族熟悉的编辑接口不一样”是code agent的一个核心设计前提。其依据主要来自各个模型厂商的官方工具文档、开源实现。Pi为适配它们做了不少工程实践。

DeepSeek 系列 → str_replace_editor

DeepSeek 官方在其 deepseek-harness 项目中提供了独立的 str_replace_editor 工具包,其 README 明确指出这是为了提供“Claude Code 风格”的文件编辑能力。

  • 官方来源:DeepSeek 官方发布的 @deepseek-ai/dsh-tool-str-replace-editor npm 包,其 schema 提供 view、create、str_replace、insert 四个命令,操作绝对路径。
  • 训练证据:第三方分析指出,DeepSeek 的官方 minimal preset 将 bash 和 str_replace_editor 的组合称为“exact RL prompt and schemas”,表明该模型在训练/评测中大量接触这套工具组合。
  • Pi 的适配:Pi 的扩展 @kennyfrc/pi-str-replace-editor 仅对 DeepSeek 系列模型启用,并会替换掉内置的 read/edit/write 工具,以提供“模型被训练过的工具”。

GPT / Codex 系列 → apply_patch

OpenAI 为 GPT 和 Codex 模型提供了结构化的 apply_patch 工具,官方描述其“适用于多文件或多 hunk 编辑,在这些场景下单一的 edit 调用会非常脆弱”。

  • 官方文档:OpenClaw 的文档明确说明 apply_patch 默认对 OpenAI 和 OpenAI Codex 模型可用。
  • 训练证据:OpenAI 的 Codex 仓库中有一个 PR,专门指示模型使用 apply_patch 进行单文件编辑。Simon Willison 也指出,OpenAI 曾谈论过他们的模型是如何被训练来有效使用该工具的。
  • Pi 的适配:Pi 的扩展 pi-model-suitable-tools 会为 GPT/Codex 模型提供 Codex 风格的 shell_command 和 apply_patch,其 apply_patch 使用 Codex 风格的补丁语法。

Claude 系列 → str_replace_based_edit_tool

Anthropic 官方定义了一个名为 str_replace_based_edit_tool 的文本编辑器工具,通过 Messages API 提供给 Claude,支持 view、str_replace、create、insert 命令。

  • 官方文档:Anthropic 平台文档详细说明了该工具的使用方式,并指出 max_characters 参数仅与 text_editor_20250728 及更高版本兼容。
  • 训练证据:Simon Willison 的博客指出,较新的 Claude 模型(如 Opus 4.8、Sonnet 5)反而比旧模型更容易错误调用 Pi 的自定义编辑工具。他引用 Armin 的理论认为,这是因为这些新模型被专门训练(很可能通过强化学习)以更好地使用 Claude Code 内置的编辑工具,导致它们对第三方 harness 的自定义 schema 更不适应。
  • Pi 的适配:pi-model-suitable-tools 会为 Claude 模型提供 Claude Code 风格的别名(如 Read、Edit、Write),这些别名会委托给 Pi 的内置工具。

💡 总结

这些来源共同指向一个事实:主流模型厂商都在围绕自家编码代理(Claude Code、Codex)的编辑接口对其模型进行训练或微调。因此,第三方代理工具为了获得最佳的模型表现,最理性的策略就是按模型家族路由到它们各自最熟悉的编辑协议。这不仅是 Pi 的设计,也是 Cursor 等工具在“为不同模型定制 harness”时所遵循的原则。