@howaboua/pi-codex-conversion 1.0.15 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/patch/core.ts +46 -13
- package/src/patch/matching.ts +30 -0
- package/src/patch/parser.ts +24 -46
- package/src/patch/types.ts +9 -2
- package/src/tools/apply-patch-tool.ts +55 -41
package/package.json
CHANGED
package/src/patch/core.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
+
import { linesMatch } from "./matching.ts";
|
|
3
4
|
import { parsePatchActions, parseUpdateFile } from "./parser.ts";
|
|
4
5
|
import { openFileAtPath, pathExists, removeFileAtPath, resolvePatchPath, writeFileAtPath } from "./paths.ts";
|
|
5
|
-
import { DiffError, ExecutePatchError, type ExecutePatchResult, type ParsedPatchAction, type ParserState, type PatchAction } from "./types.ts";
|
|
6
|
+
import { DiffError, ExecutePatchError, type ExecutePatchFailure, type ExecutePatchResult, type ParsedPatchAction, type ParserState, type PatchAction } from "./types.ts";
|
|
6
7
|
|
|
7
8
|
export const patchFsOps = {
|
|
8
9
|
mkdirSync: fs.mkdirSync,
|
|
@@ -64,7 +65,7 @@ function getUpdatedFile({ text, action, path }: { text: string; action: PatchAct
|
|
|
64
65
|
destIndex += delta;
|
|
65
66
|
|
|
66
67
|
for (const line of chunk.delLines) {
|
|
67
|
-
if (origLines[origIndex]
|
|
68
|
+
if (!linesMatch(origLines[origIndex] ?? "", line)) {
|
|
68
69
|
throw new DiffError(`_get_updated_file: ${path}: Expected ${line} but got ${origLines[origIndex]} at line ${origIndex + 1}`);
|
|
69
70
|
}
|
|
70
71
|
origIndex += 1;
|
|
@@ -217,6 +218,14 @@ function applyAction({
|
|
|
217
218
|
return fuzz;
|
|
218
219
|
}
|
|
219
220
|
|
|
221
|
+
function getActionPaths(action: ParsedPatchAction): string[] {
|
|
222
|
+
return [action.path, action.type === "update" ? action.movePath : undefined].filter((path): path is string => typeof path === "string");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function getCanonicalActionPaths({ cwd, action }: { cwd: string; action: ParsedPatchAction }): string[] {
|
|
226
|
+
return getActionPaths(action).map((path) => resolvePatchPath({ cwd, patchPath: path }));
|
|
227
|
+
}
|
|
228
|
+
|
|
220
229
|
export function executePatch({ cwd, patchText }: { cwd: string; patchText: string }): ExecutePatchResult {
|
|
221
230
|
if (!patchText.startsWith("*** Begin Patch")) {
|
|
222
231
|
throw new DiffError("Patch must start with '*** Begin Patch'");
|
|
@@ -227,9 +236,22 @@ export function executePatch({ cwd, patchText }: { cwd: string; patchText: strin
|
|
|
227
236
|
const createdFiles = new Set<string>();
|
|
228
237
|
const deletedFiles = new Set<string>();
|
|
229
238
|
const movedFiles = new Set<string>();
|
|
239
|
+
const blockedPaths = new Set<string>();
|
|
240
|
+
const failures: ExecutePatchFailure[] = [];
|
|
230
241
|
let fuzz = 0;
|
|
231
242
|
|
|
232
243
|
for (const action of actions) {
|
|
244
|
+
const actionPaths = getActionPaths(action);
|
|
245
|
+
const canonicalActionPaths = getCanonicalActionPaths({ cwd, action });
|
|
246
|
+
const overlappingPaths = canonicalActionPaths.filter((path) => blockedPaths.has(path));
|
|
247
|
+
if (overlappingPaths.length > 0) {
|
|
248
|
+
failures.push({
|
|
249
|
+
action,
|
|
250
|
+
message: `Skipped because an earlier failed action affected ${actionPaths.filter((_, index) => overlappingPaths.includes(canonicalActionPaths[index])).join(", ")}`,
|
|
251
|
+
});
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
233
255
|
try {
|
|
234
256
|
fuzz += applyAction({
|
|
235
257
|
cwd,
|
|
@@ -241,20 +263,31 @@ export function executePatch({ cwd, patchText }: { cwd: string; patchText: strin
|
|
|
241
263
|
});
|
|
242
264
|
} catch (error) {
|
|
243
265
|
const message = error instanceof Error ? error.message : String(error);
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
createdFiles,
|
|
249
|
-
deletedFiles,
|
|
250
|
-
movedFiles,
|
|
251
|
-
fuzz,
|
|
252
|
-
}),
|
|
253
|
-
action,
|
|
254
|
-
);
|
|
266
|
+
for (const path of canonicalActionPaths) {
|
|
267
|
+
blockedPaths.add(path);
|
|
268
|
+
}
|
|
269
|
+
failures.push({ action, message });
|
|
255
270
|
}
|
|
256
271
|
}
|
|
257
272
|
|
|
273
|
+
if (failures.length > 0) {
|
|
274
|
+
const message =
|
|
275
|
+
failures.length === 1
|
|
276
|
+
? failures[0].message
|
|
277
|
+
: failures.map(({ action, message: failureMessage }) => `${action.path}: ${failureMessage}`).join("\n");
|
|
278
|
+
throw new ExecutePatchError(
|
|
279
|
+
message,
|
|
280
|
+
buildExecutePatchResult({
|
|
281
|
+
changedFiles,
|
|
282
|
+
createdFiles,
|
|
283
|
+
deletedFiles,
|
|
284
|
+
movedFiles,
|
|
285
|
+
fuzz,
|
|
286
|
+
}),
|
|
287
|
+
failures,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
258
291
|
return buildExecutePatchResult({
|
|
259
292
|
changedFiles,
|
|
260
293
|
createdFiles,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type LinesMatchQuality = {
|
|
2
|
+
fuzz: number;
|
|
3
|
+
worstLineFuzz: number;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export function lineMatchFuzz(left: string, right: string): number | undefined {
|
|
7
|
+
if (left === right) return 0;
|
|
8
|
+
if (left.trimEnd() === right.trimEnd()) return 1;
|
|
9
|
+
if (left.trim() === right.trim()) return 100;
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function linesMatch(left: string, right: string): boolean {
|
|
14
|
+
return left === right || left.trimEnd() === right.trimEnd();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function linesEqualFuzz({ left, right }: { left: string[]; right: string[] }): LinesMatchQuality | undefined {
|
|
18
|
+
if (left.length !== right.length) return undefined;
|
|
19
|
+
|
|
20
|
+
let fuzz = 0;
|
|
21
|
+
let worstLineFuzz = 0;
|
|
22
|
+
for (let index = 0; index < left.length; index++) {
|
|
23
|
+
const lineFuzz = lineMatchFuzz(left[index], right[index]);
|
|
24
|
+
if (lineFuzz === undefined) return undefined;
|
|
25
|
+
fuzz += lineFuzz;
|
|
26
|
+
worstLineFuzz = Math.max(worstLineFuzz, lineFuzz);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { fuzz, worstLineFuzz };
|
|
30
|
+
}
|
package/src/patch/parser.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { lineMatchFuzz, linesEqualFuzz } from "./matching.ts";
|
|
1
2
|
import { normalizePatchPath } from "./paths.ts";
|
|
2
3
|
import { DiffError, type Chunk, type ParseMode, type ParsedPatchAction, type ParserState, type PatchAction } from "./types.ts";
|
|
3
4
|
|
|
@@ -48,14 +49,6 @@ function splitFileLines(text: string): string[] {
|
|
|
48
49
|
return lines;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
function linesEqual({ left, right }: { left: string[]; right: string[] }): boolean {
|
|
52
|
-
if (left.length !== right.length) return false;
|
|
53
|
-
for (let index = 0; index < left.length; index++) {
|
|
54
|
-
if (left[index] !== right[index]) return false;
|
|
55
|
-
}
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
52
|
function findContextCore({ lines, context, start }: { lines: string[]; context: string[]; start: number }): {
|
|
60
53
|
newIndex: number;
|
|
61
54
|
fuzz: number;
|
|
@@ -64,25 +57,30 @@ function findContextCore({ lines, context, start }: { lines: string[]; context:
|
|
|
64
57
|
return { newIndex: start, fuzz: 0 };
|
|
65
58
|
}
|
|
66
59
|
|
|
67
|
-
for (
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
for (const tier of [0, 1, 100]) {
|
|
61
|
+
for (let index = start; index <= lines.length - context.length; index++) {
|
|
62
|
+
const quality = linesEqualFuzz({ left: lines.slice(index, index + context.length), right: context });
|
|
63
|
+
if (quality?.worstLineFuzz === tier) {
|
|
64
|
+
return { newIndex: index, fuzz: quality.fuzz };
|
|
65
|
+
}
|
|
70
66
|
}
|
|
71
67
|
}
|
|
72
68
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
return { newIndex: -1, fuzz: 0 };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function findSectionAnchor({ lines, target, start }: { lines: string[]; target: string; start: number }): { newIndex: number; fuzz: number } {
|
|
73
|
+
for (const tier of [0, 1, 100]) {
|
|
74
|
+
const alreadySeen = lines.slice(0, start).some((line) => lineMatchFuzz(line, target) === tier);
|
|
75
|
+
if (alreadySeen) {
|
|
76
|
+
continue;
|
|
78
77
|
}
|
|
79
|
-
}
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
for (let index = start; index < lines.length; index++) {
|
|
80
|
+
const fuzz = lineMatchFuzz(lines[index], target);
|
|
81
|
+
if (fuzz === tier) {
|
|
82
|
+
return { newIndex: index, fuzz };
|
|
83
|
+
}
|
|
86
84
|
}
|
|
87
85
|
}
|
|
88
86
|
|
|
@@ -263,30 +261,10 @@ export function parseUpdateFile({ state, text, path }: { state: ParserState; tex
|
|
|
263
261
|
}
|
|
264
262
|
|
|
265
263
|
if (defStr.trim().length > 0) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
for (let lineIndex = index; lineIndex < lines.length; lineIndex++) {
|
|
271
|
-
if (lines[lineIndex] === defStr) {
|
|
272
|
-
index = lineIndex + 1;
|
|
273
|
-
found = true;
|
|
274
|
-
break;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (!found) {
|
|
280
|
-
const trimAlreadySeen = lines.slice(0, index).some((line) => line.trim() === defStr.trim());
|
|
281
|
-
if (!trimAlreadySeen) {
|
|
282
|
-
for (let lineIndex = index; lineIndex < lines.length; lineIndex++) {
|
|
283
|
-
if (lines[lineIndex].trim() === defStr.trim()) {
|
|
284
|
-
index = lineIndex + 1;
|
|
285
|
-
state.fuzz += 1;
|
|
286
|
-
break;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
264
|
+
const sectionAnchor = findSectionAnchor({ lines, target: defStr, start: index });
|
|
265
|
+
if (sectionAnchor.newIndex !== -1) {
|
|
266
|
+
index = sectionAnchor.newIndex + 1;
|
|
267
|
+
state.fuzz += sectionAnchor.fuzz;
|
|
290
268
|
}
|
|
291
269
|
}
|
|
292
270
|
|
package/src/patch/types.ts
CHANGED
|
@@ -36,6 +36,11 @@ export interface ExecutePatchResult {
|
|
|
36
36
|
fuzz: number;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export interface ExecutePatchFailure {
|
|
40
|
+
action: ParsedPatchAction;
|
|
41
|
+
message: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
39
44
|
export class DiffError extends Error {
|
|
40
45
|
constructor(message: string) {
|
|
41
46
|
super(message);
|
|
@@ -46,12 +51,14 @@ export class DiffError extends Error {
|
|
|
46
51
|
export class ExecutePatchError extends DiffError {
|
|
47
52
|
result: ExecutePatchResult;
|
|
48
53
|
failedAction?: ParsedPatchAction;
|
|
54
|
+
failures: ExecutePatchFailure[];
|
|
49
55
|
|
|
50
|
-
constructor(message: string, result: ExecutePatchResult,
|
|
56
|
+
constructor(message: string, result: ExecutePatchResult, failures: ExecutePatchFailure[] = []) {
|
|
51
57
|
super(message);
|
|
52
58
|
this.name = "ExecutePatchError";
|
|
53
59
|
this.result = result;
|
|
54
|
-
this.
|
|
60
|
+
this.failures = failures;
|
|
61
|
+
this.failedAction = failures[0]?.action;
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
hasPartialSuccess(): boolean {
|
|
@@ -17,7 +17,7 @@ interface ApplyPatchRenderState {
|
|
|
17
17
|
collapsed: string;
|
|
18
18
|
expanded: string;
|
|
19
19
|
status: "pending" | "partial_failure" | "failed";
|
|
20
|
-
|
|
20
|
+
failedTargets?: string[];
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
interface ApplyPatchSuccessDetails {
|
|
@@ -29,7 +29,7 @@ interface ApplyPatchPartialFailureDetails {
|
|
|
29
29
|
status: "partial_failure";
|
|
30
30
|
result: ExecutePatchResult;
|
|
31
31
|
error: string;
|
|
32
|
-
|
|
32
|
+
failedTargets?: string[];
|
|
33
33
|
appliedFiles: string[];
|
|
34
34
|
failedFiles: string[];
|
|
35
35
|
recoveryInstructions: {
|
|
@@ -65,7 +65,7 @@ function setApplyPatchRenderState(
|
|
|
65
65
|
patchText: string,
|
|
66
66
|
cwd: string,
|
|
67
67
|
status: "pending" | "partial_failure" | "failed" = "pending",
|
|
68
|
-
|
|
68
|
+
failedTargets?: string[],
|
|
69
69
|
): void {
|
|
70
70
|
const collapsed = formatApplyPatchSummary(patchText, cwd);
|
|
71
71
|
const expanded = renderApplyPatchCall(patchText, cwd);
|
|
@@ -75,15 +75,15 @@ function setApplyPatchRenderState(
|
|
|
75
75
|
collapsed,
|
|
76
76
|
expanded,
|
|
77
77
|
status,
|
|
78
|
-
|
|
78
|
+
failedTargets,
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
function markApplyPatchPartialFailure(toolCallId: string,
|
|
83
|
-
markApplyPatchFailure(toolCallId, "partial_failure",
|
|
82
|
+
function markApplyPatchPartialFailure(toolCallId: string, failedTargets?: string[]): void {
|
|
83
|
+
markApplyPatchFailure(toolCallId, "partial_failure", failedTargets);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
function markApplyPatchFailure(toolCallId: string, status: "partial_failure" | "failed",
|
|
86
|
+
function markApplyPatchFailure(toolCallId: string, status: "partial_failure" | "failed", failedTargets?: string[]): void {
|
|
87
87
|
const existing = applyPatchRenderStates.get(toolCallId);
|
|
88
88
|
if (!existing) {
|
|
89
89
|
return;
|
|
@@ -91,14 +91,14 @@ function markApplyPatchFailure(toolCallId: string, status: "partial_failure" | "
|
|
|
91
91
|
applyPatchRenderStates.set(toolCallId, {
|
|
92
92
|
...existing,
|
|
93
93
|
status,
|
|
94
|
-
|
|
94
|
+
failedTargets,
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
function renderPartialFailureCall(
|
|
99
99
|
text: string,
|
|
100
100
|
theme: { fg(role: string, text: string): string },
|
|
101
|
-
|
|
101
|
+
failedTargets?: string[],
|
|
102
102
|
): string {
|
|
103
103
|
const lines = text.split("\n");
|
|
104
104
|
if (lines.length === 0) {
|
|
@@ -106,12 +106,15 @@ function renderPartialFailureCall(
|
|
|
106
106
|
}
|
|
107
107
|
lines[0] = lines[0].replace(/^• (Added|Edited|Deleted)\b/, "• Edit partially failed");
|
|
108
108
|
const failedLineIndexes = new Set<number>();
|
|
109
|
-
if (
|
|
109
|
+
if (failedTargets) {
|
|
110
110
|
for (let i = 0; i < lines.length; i += 1) {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
for (const failedTarget of failedTargets) {
|
|
112
|
+
const failedLine = markFailedTargetLine(lines[i], failedTarget);
|
|
113
|
+
if (failedLine) {
|
|
114
|
+
lines[i] = failedLine;
|
|
115
|
+
failedLineIndexes.add(i);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
115
118
|
}
|
|
116
119
|
}
|
|
117
120
|
}
|
|
@@ -131,7 +134,7 @@ function renderPartialFailureCall(
|
|
|
131
134
|
function renderFailedCall(
|
|
132
135
|
text: string,
|
|
133
136
|
theme: { fg(role: string, text: string): string },
|
|
134
|
-
|
|
137
|
+
failedTargets?: string[],
|
|
135
138
|
): string {
|
|
136
139
|
const lines = text.split("\n");
|
|
137
140
|
if (lines.length === 0) {
|
|
@@ -139,12 +142,15 @@ function renderFailedCall(
|
|
|
139
142
|
}
|
|
140
143
|
lines[0] = lines[0].replace(/^• (Added|Edited|Deleted)\b/, "• Edit failed");
|
|
141
144
|
const failedLineIndexes = new Set<number>();
|
|
142
|
-
if (
|
|
145
|
+
if (failedTargets) {
|
|
143
146
|
for (let i = 0; i < lines.length; i += 1) {
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
147
|
+
for (const failedTarget of failedTargets) {
|
|
148
|
+
const failedLine = markFailedTargetLine(lines[i], failedTarget);
|
|
149
|
+
if (failedLine) {
|
|
150
|
+
lines[i] = failedLine;
|
|
151
|
+
failedLineIndexes.add(i);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
148
154
|
}
|
|
149
155
|
}
|
|
150
156
|
}
|
|
@@ -187,26 +193,33 @@ function uniqueStrings(values: Array<string | undefined>): string[] {
|
|
|
187
193
|
return Array.from(new Set(values.filter((value): value is string => typeof value === "string" && value.length > 0)));
|
|
188
194
|
}
|
|
189
195
|
|
|
190
|
-
function
|
|
191
|
-
|
|
192
|
-
|
|
196
|
+
function getFailedPaths(error: ExecutePatchError): string[] {
|
|
197
|
+
return uniqueStrings(
|
|
198
|
+
error.failures.flatMap(({ action }) => [action.path, action.type === "update" ? action.movePath : undefined]),
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function getAppliedPaths(result: ExecutePatchResult, failedFiles: string[]): string[] {
|
|
203
|
+
return result.changedFiles.filter((path) => !failedFiles.includes(path));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function buildPartialFailureMessage(message: string, failedFiles: string[], appliedFiles: string[]): string {
|
|
193
207
|
const lines = [message];
|
|
194
208
|
if (failedFiles.length > 0) {
|
|
195
|
-
lines.push(`Failed file: ${failedFiles.join(", ")}`);
|
|
209
|
+
lines.push(`Failed file${failedFiles.length === 1 ? "" : "s"}: ${failedFiles.join(", ")}`);
|
|
196
210
|
lines.push(`Recovery: MUST read ${failedFiles.join(", ")} before retrying.`);
|
|
197
211
|
}
|
|
198
212
|
if (appliedFiles.length > 0) {
|
|
199
|
-
lines.push(
|
|
200
|
-
lines.push(
|
|
213
|
+
lines.push("Earlier file actions in this patch were already applied.");
|
|
214
|
+
lines.push("Recovery: MUST NOT reread other files from this patch unless a specific dependency requires it.");
|
|
201
215
|
}
|
|
202
216
|
return lines.join("\n");
|
|
203
217
|
}
|
|
204
218
|
|
|
205
|
-
function
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
return formatPatchTarget(error.failedAction.path, error.failedAction.type === "update" ? error.failedAction.movePath : undefined, cwd);
|
|
219
|
+
function describeFailedActions(error: ExecutePatchError, cwd: string): string[] {
|
|
220
|
+
return uniqueStrings(
|
|
221
|
+
error.failures.map(({ action }) => formatPatchTarget(action.path, action.type === "update" ? action.movePath : undefined, cwd)),
|
|
222
|
+
);
|
|
210
223
|
}
|
|
211
224
|
|
|
212
225
|
export type { ExecutePatchResult } from "../patch/types.ts";
|
|
@@ -241,9 +254,9 @@ const renderApplyPatchCallWithOptionalContext: any = (
|
|
|
241
254
|
}
|
|
242
255
|
const text =
|
|
243
256
|
cached?.status === "partial_failure"
|
|
244
|
-
? renderPartialFailureCall(baseText, theme, cached.
|
|
257
|
+
? renderPartialFailureCall(baseText, theme, cached.failedTargets)
|
|
245
258
|
: cached?.status === "failed"
|
|
246
|
-
? renderFailedCall(baseText, theme, cached.
|
|
259
|
+
? renderFailedCall(baseText, theme, cached.failedTargets)
|
|
247
260
|
: baseText;
|
|
248
261
|
return new Text(text, 0, 0);
|
|
249
262
|
};
|
|
@@ -272,23 +285,24 @@ export function registerApplyPatchTool(pi: ExtensionAPI): void {
|
|
|
272
285
|
} catch (error) {
|
|
273
286
|
if (error instanceof ExecutePatchError) {
|
|
274
287
|
const partial = error.hasPartialSuccess();
|
|
275
|
-
const
|
|
288
|
+
const failedTargets = describeFailedActions(error, ctx.cwd);
|
|
289
|
+
const failedTargetSummary = failedTargets.join(", ");
|
|
276
290
|
const prefix = partial
|
|
277
291
|
? `apply_patch partially failed after ${summarizePatchCounts(error.result)}`
|
|
278
292
|
: "apply_patch failed";
|
|
279
|
-
const message =
|
|
293
|
+
const message = failedTargetSummary ? `${prefix} while patching ${failedTargetSummary}: ${error.message}` : `${prefix}: ${error.message}`;
|
|
280
294
|
if (partial) {
|
|
281
|
-
const failedFiles =
|
|
282
|
-
const appliedFiles = error.result
|
|
283
|
-
const recoveryMessage = buildPartialFailureMessage(message,
|
|
284
|
-
markApplyPatchPartialFailure(toolCallId,
|
|
295
|
+
const failedFiles = getFailedPaths(error);
|
|
296
|
+
const appliedFiles = getAppliedPaths(error.result, failedFiles);
|
|
297
|
+
const recoveryMessage = buildPartialFailureMessage(message, failedFiles, appliedFiles);
|
|
298
|
+
markApplyPatchPartialFailure(toolCallId, failedTargets);
|
|
285
299
|
return {
|
|
286
300
|
content: [{ type: "text", text: recoveryMessage }],
|
|
287
301
|
details: {
|
|
288
302
|
status: "partial_failure",
|
|
289
303
|
result: error.result,
|
|
290
304
|
error: recoveryMessage,
|
|
291
|
-
|
|
305
|
+
failedTargets,
|
|
292
306
|
appliedFiles,
|
|
293
307
|
failedFiles,
|
|
294
308
|
recoveryInstructions: {
|
|
@@ -298,7 +312,7 @@ export function registerApplyPatchTool(pi: ExtensionAPI): void {
|
|
|
298
312
|
} satisfies ApplyPatchPartialFailureDetails,
|
|
299
313
|
};
|
|
300
314
|
}
|
|
301
|
-
markApplyPatchFailure(toolCallId, "failed",
|
|
315
|
+
markApplyPatchFailure(toolCallId, "failed", failedTargets);
|
|
302
316
|
throw new Error(message);
|
|
303
317
|
}
|
|
304
318
|
markApplyPatchFailure(toolCallId, "failed");
|