Skip to content

Commit c141d7e

Browse files
committed
feat: add apply_patch native tool
Implements a new native tool called apply_patch based on the Codex apply_patch specification. This tool supports creating, deleting, and updating files using a stripped-down, file-oriented diff format. Key features: - Patch parser with lenient mode for heredoc-wrapped patches - Fuzzy context matching with Unicode normalization - Support for Add File, Delete File, and Update File operations - Move to (rename) support for Update File - End of File markers for precise positioning Files: - src/core/tools/apply-patch/parser.ts - Patch parsing logic - src/core/tools/apply-patch/seek-sequence.ts - Fuzzy line matching - src/core/tools/apply-patch/apply.ts - Patch application core - src/core/tools/ApplyPatchTool.ts - Tool handler - src/core/prompts/tools/native-tools/apply_patch.ts - OpenAI tool definition 42 tests added covering parser, seek-sequence, and apply logic.
1 parent e682c03 commit c141d7e

File tree

15 files changed

+1835
-1
lines changed

15 files changed

+1835
-1
lines changed

packages/types/src/providers/openai.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export const openAiNativeModels = {
1212
supportsNativeTools: true,
1313
supportsImages: true,
1414
supportsPromptCache: true,
15+
includedTools: ["apply_patch"],
16+
excludedTools: ["apply_diff", "write_to_file", "insert_content"],
1517
promptCacheRetention: "24h",
1618
supportsReasoningEffort: ["none", "low", "medium", "high"],
1719
reasoningEffort: "medium",
@@ -32,6 +34,8 @@ export const openAiNativeModels = {
3234
supportsNativeTools: true,
3335
supportsImages: true,
3436
supportsPromptCache: true,
37+
includedTools: ["apply_patch"],
38+
excludedTools: ["apply_diff", "write_to_file", "insert_content"],
3539
promptCacheRetention: "24h",
3640
supportsReasoningEffort: ["low", "medium", "high"],
3741
reasoningEffort: "medium",
@@ -48,6 +52,8 @@ export const openAiNativeModels = {
4852
supportsNativeTools: true,
4953
supportsImages: true,
5054
supportsPromptCache: true,
55+
includedTools: ["apply_patch"],
56+
excludedTools: ["apply_diff", "write_to_file", "insert_content"],
5157
promptCacheRetention: "24h",
5258
supportsReasoningEffort: ["low", "medium", "high"],
5359
reasoningEffort: "medium",
@@ -63,6 +69,8 @@ export const openAiNativeModels = {
6369
supportsNativeTools: true,
6470
supportsImages: true,
6571
supportsPromptCache: true,
72+
includedTools: ["apply_patch"],
73+
excludedTools: ["apply_diff", "write_to_file", "insert_content"],
6674
supportsReasoningEffort: ["minimal", "low", "medium", "high"],
6775
reasoningEffort: "medium",
6876
inputPrice: 1.25,
@@ -82,6 +90,8 @@ export const openAiNativeModels = {
8290
supportsNativeTools: true,
8391
supportsImages: true,
8492
supportsPromptCache: true,
93+
includedTools: ["apply_patch"],
94+
excludedTools: ["apply_diff", "write_to_file", "insert_content"],
8595
supportsReasoningEffort: ["minimal", "low", "medium", "high"],
8696
reasoningEffort: "medium",
8797
inputPrice: 0.25,
@@ -101,6 +111,8 @@ export const openAiNativeModels = {
101111
supportsNativeTools: true,
102112
supportsImages: true,
103113
supportsPromptCache: true,
114+
includedTools: ["apply_patch"],
115+
excludedTools: ["apply_diff", "write_to_file", "insert_content"],
104116
supportsReasoningEffort: ["low", "medium", "high"],
105117
reasoningEffort: "medium",
106118
inputPrice: 1.25,
@@ -116,6 +128,8 @@ export const openAiNativeModels = {
116128
supportsNativeTools: true,
117129
supportsImages: true,
118130
supportsPromptCache: true,
131+
includedTools: ["apply_patch"],
132+
excludedTools: ["apply_diff", "write_to_file", "insert_content"],
119133
supportsReasoningEffort: ["minimal", "low", "medium", "high"],
120134
reasoningEffort: "medium",
121135
inputPrice: 0.05,

packages/types/src/tool.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const toolNames = [
2121
"apply_diff",
2222
"insert_content",
2323
"search_and_replace",
24+
"apply_patch",
2425
"search_files",
2526
"list_files",
2627
"list_code_definition_names",

src/core/assistant-message/NativeToolCallParser.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,14 @@ export class NativeToolCallParser {
519519
}
520520
break
521521

522+
case "apply_patch":
523+
if (partialArgs.patch !== undefined) {
524+
nativeArgs = {
525+
patch: partialArgs.patch,
526+
}
527+
}
528+
break
529+
522530
// Add other tools as needed
523531
default:
524532
break
@@ -768,6 +776,14 @@ export class NativeToolCallParser {
768776
}
769777
break
770778

779+
case "apply_patch":
780+
if (args.patch !== undefined) {
781+
nativeArgs = {
782+
patch: args.patch,
783+
} as NativeArgsFor<TName>
784+
}
785+
break
786+
771787
default:
772788
break
773789
}

src/core/assistant-message/presentAssistantMessage.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { writeToFileTool } from "../tools/WriteToFileTool"
1818
import { applyDiffTool } from "../tools/MultiApplyDiffTool"
1919
import { insertContentTool } from "../tools/InsertContentTool"
2020
import { searchAndReplaceTool } from "../tools/SearchAndReplaceTool"
21+
import { applyPatchTool } from "../tools/ApplyPatchTool"
2122
import { listCodeDefinitionNamesTool } from "../tools/ListCodeDefinitionNamesTool"
2223
import { searchFilesTool } from "../tools/SearchFilesTool"
2324
import { browserActionTool } from "../tools/BrowserActionTool"
@@ -382,6 +383,8 @@ export async function presentAssistantMessage(cline: Task) {
382383
return `[${block.name} for '${block.params.path}']`
383384
case "search_and_replace":
384385
return `[${block.name} for '${block.params.path}']`
386+
case "apply_patch":
387+
return `[${block.name}]`
385388
case "list_files":
386389
return `[${block.name} for '${block.params.path}']`
387390
case "list_code_definition_names":
@@ -828,6 +831,16 @@ export async function presentAssistantMessage(cline: Task) {
828831
toolProtocol,
829832
})
830833
break
834+
case "apply_patch":
835+
await checkpointSaveAndMark(cline)
836+
await applyPatchTool.handle(cline, block as ToolUse<"apply_patch">, {
837+
askApproval,
838+
handleError,
839+
pushToolResult,
840+
removeClosingTag,
841+
toolProtocol,
842+
})
843+
break
831844
case "read_file":
832845
// Check if this model should use the simplified single-file read tool
833846
// Only use simplified tool for XML protocol - native protocol works with standard tool
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type OpenAI from "openai"
2+
3+
const apply_patch_DESCRIPTION = `Apply patches to files using a stripped-down, file-oriented diff format. This tool supports creating new files, deleting files, and updating existing files with precise changes.
4+
5+
The patch format uses a simple, human-readable structure:
6+
7+
*** Begin Patch
8+
[ one or more file sections ]
9+
*** End Patch
10+
11+
Each file section starts with one of three headers:
12+
- *** Add File: <path> - Create a new file. Every following line is a + line (the initial contents).
13+
- *** Delete File: <path> - Remove an existing file. Nothing follows.
14+
- *** Update File: <path> - Patch an existing file in place.
15+
16+
For Update File operations:
17+
- May be immediately followed by *** Move to: <new path> if you want to rename the file.
18+
- Then one or more "hunks", each introduced by @@ (optionally followed by context like a class or function name).
19+
- Within a hunk each line starts with:
20+
- ' ' (space) for context lines (unchanged)
21+
- '-' for lines to remove
22+
- '+' for lines to add
23+
24+
Context guidelines:
25+
- Show 3 lines of code above and below each change.
26+
- Use @@ with a class/function name if 3 lines of context is insufficient to uniquely identify the location.
27+
- Multiple @@ statements can be used for deeply nested code.
28+
29+
Example patch:
30+
*** Begin Patch
31+
*** Add File: hello.txt
32+
+Hello world
33+
*** Update File: src/app.py
34+
*** Move to: src/main.py
35+
@@ def greet():
36+
-print("Hi")
37+
+print("Hello, world!")
38+
*** Delete File: obsolete.txt
39+
*** End Patch`
40+
41+
const apply_patch = {
42+
type: "function",
43+
function: {
44+
name: "apply_patch",
45+
description: apply_patch_DESCRIPTION,
46+
parameters: {
47+
type: "object",
48+
properties: {
49+
patch: {
50+
type: "string",
51+
description:
52+
"The complete patch text in the apply_patch format, starting with '*** Begin Patch' and ending with '*** End Patch'.",
53+
},
54+
},
55+
required: ["patch"],
56+
additionalProperties: false,
57+
},
58+
},
59+
} satisfies OpenAI.Chat.ChatCompletionTool
60+
61+
export default apply_patch

src/core/prompts/tools/native-tools/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type OpenAI from "openai"
22
import accessMcpResource from "./access_mcp_resource"
33
import { apply_diff } from "./apply_diff"
4+
import applyPatch from "./apply_patch"
45
import askFollowupQuestion from "./ask_followup_question"
56
import attemptCompletion from "./attempt_completion"
67
import browserAction from "./browser_action"
@@ -33,6 +34,7 @@ export function getNativeTools(partialReadsEnabled: boolean = true): OpenAI.Chat
3334
return [
3435
accessMcpResource,
3536
apply_diff,
37+
applyPatch,
3638
askFollowupQuestion,
3739
attemptCompletion,
3840
browserAction,

0 commit comments

Comments
 (0)