@agi-cli/sdk 0.1.79 → 0.1.81
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 +5 -1
- package/src/core/src/index.ts +13 -0
- package/src/core/src/tools/builtin/bash.ts +46 -14
- package/src/core/src/tools/builtin/finish.txt +9 -4
- package/src/core/src/tools/builtin/fs/read.ts +44 -5
- package/src/core/src/tools/builtin/fs/write.ts +61 -16
- package/src/core/src/tools/builtin/fs/write.txt +2 -1
- package/src/core/src/tools/builtin/patch.ts +162 -39
- package/src/core/src/tools/builtin/patch.txt +42 -5
- package/src/core/src/tools/error.ts +67 -0
- package/src/prompts/src/base.txt +12 -2
- package/src/prompts/src/providers/anthropic.txt +21 -5
- package/src/prompts/src/providers/default.txt +22 -6
- package/src/prompts/src/providers/google.txt +21 -5
- package/src/prompts/src/providers/openai.txt +22 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agi-cli/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.81",
|
|
4
4
|
"description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
|
|
5
5
|
"author": "ntishxyz",
|
|
6
6
|
"license": "MIT",
|
|
@@ -65,6 +65,10 @@
|
|
|
65
65
|
"import": "./src/core/src/tools/builtin/websearch.ts",
|
|
66
66
|
"types": "./src/core/src/tools/builtin/websearch.ts"
|
|
67
67
|
},
|
|
68
|
+
"./tools/error": {
|
|
69
|
+
"import": "./src/core/src/tools/error.ts",
|
|
70
|
+
"types": "./src/core/src/tools/error.ts"
|
|
71
|
+
},
|
|
68
72
|
"./prompts/*": "./src/prompts/src/*"
|
|
69
73
|
},
|
|
70
74
|
"files": [
|
package/src/core/src/index.ts
CHANGED
|
@@ -32,6 +32,19 @@ export type { ProviderId, ModelInfo } from '../../types/src/index.ts';
|
|
|
32
32
|
export { discoverProjectTools } from './tools/loader';
|
|
33
33
|
export type { DiscoveredTool } from './tools/loader';
|
|
34
34
|
|
|
35
|
+
// Tool error handling utilities
|
|
36
|
+
export {
|
|
37
|
+
isToolError,
|
|
38
|
+
extractToolError,
|
|
39
|
+
createToolError,
|
|
40
|
+
} from './tools/error';
|
|
41
|
+
export type {
|
|
42
|
+
ToolErrorType,
|
|
43
|
+
ToolErrorResponse,
|
|
44
|
+
ToolSuccessResponse,
|
|
45
|
+
ToolResponse,
|
|
46
|
+
} from './tools/error';
|
|
47
|
+
|
|
35
48
|
// Re-export builtin tools for direct access
|
|
36
49
|
export { buildFsTools } from './tools/builtin/fs/index';
|
|
37
50
|
export { buildGitTools } from './tools/builtin/git';
|
|
@@ -2,6 +2,7 @@ import { tool, type Tool } from 'ai';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import DESCRIPTION from './bash.txt' with { type: 'text' };
|
|
5
|
+
import { createToolError, type ToolResponse } from '../error.ts';
|
|
5
6
|
|
|
6
7
|
function normalizePath(p: string) {
|
|
7
8
|
const parts = p.replace(/\\/g, '/').split('/');
|
|
@@ -58,11 +59,16 @@ export function buildBashTool(projectRoot: string): {
|
|
|
58
59
|
cwd?: string;
|
|
59
60
|
allowNonZeroExit?: boolean;
|
|
60
61
|
timeout?: number;
|
|
61
|
-
})
|
|
62
|
+
}): Promise<
|
|
63
|
+
ToolResponse<{
|
|
64
|
+
exitCode: number;
|
|
65
|
+
stdout: string;
|
|
66
|
+
stderr: string;
|
|
67
|
+
}>
|
|
68
|
+
> {
|
|
62
69
|
const absCwd = resolveSafePath(projectRoot, cwd || '.');
|
|
63
70
|
|
|
64
|
-
return new Promise((resolve
|
|
65
|
-
// Use spawn with shell: true for cross-platform compatibility
|
|
71
|
+
return new Promise((resolve) => {
|
|
66
72
|
const proc = spawn(cmd, {
|
|
67
73
|
cwd: absCwd,
|
|
68
74
|
shell: true,
|
|
@@ -93,24 +99,50 @@ export function buildBashTool(projectRoot: string): {
|
|
|
93
99
|
if (timeoutId) clearTimeout(timeoutId);
|
|
94
100
|
|
|
95
101
|
if (didTimeout) {
|
|
96
|
-
|
|
102
|
+
resolve(
|
|
103
|
+
createToolError(
|
|
104
|
+
`Command timed out after ${timeout}ms: ${cmd}`,
|
|
105
|
+
'timeout',
|
|
106
|
+
{
|
|
107
|
+
parameter: 'timeout',
|
|
108
|
+
value: timeout,
|
|
109
|
+
suggestion: 'Increase timeout or optimize the command',
|
|
110
|
+
},
|
|
111
|
+
),
|
|
112
|
+
);
|
|
97
113
|
} else if (exitCode !== 0 && !allowNonZeroExit) {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
114
|
+
const errorDetail = stderr.trim() || stdout.trim() || '';
|
|
115
|
+
const errorMsg = `Command failed with exit code ${exitCode}${errorDetail ? `\n\n${errorDetail}` : ''}`;
|
|
116
|
+
resolve(
|
|
117
|
+
createToolError(errorMsg, 'execution', {
|
|
118
|
+
exitCode,
|
|
119
|
+
stdout,
|
|
120
|
+
stderr,
|
|
121
|
+
cmd,
|
|
122
|
+
suggestion:
|
|
123
|
+
'Check command syntax or use allowNonZeroExit: true',
|
|
124
|
+
}),
|
|
125
|
+
);
|
|
104
126
|
} else {
|
|
105
|
-
resolve({
|
|
127
|
+
resolve({
|
|
128
|
+
ok: true,
|
|
129
|
+
exitCode: exitCode ?? 0,
|
|
130
|
+
stdout,
|
|
131
|
+
stderr,
|
|
132
|
+
});
|
|
106
133
|
}
|
|
107
134
|
});
|
|
108
135
|
|
|
109
136
|
proc.on('error', (err) => {
|
|
110
137
|
if (timeoutId) clearTimeout(timeoutId);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
`Command execution failed: ${err.message}
|
|
138
|
+
resolve(
|
|
139
|
+
createToolError(
|
|
140
|
+
`Command execution failed: ${err.message}`,
|
|
141
|
+
'execution',
|
|
142
|
+
{
|
|
143
|
+
cmd,
|
|
144
|
+
originalError: err.message,
|
|
145
|
+
},
|
|
114
146
|
),
|
|
115
147
|
);
|
|
116
148
|
});
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
- Agent should stream the summary directly as assistant message not using the finish tool
|
|
1
|
+
Call this tool AFTER you have completed all your work AND streamed your final summary/response to the user.
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
This signals the end of your turn and that you are done with the current request.
|
|
4
|
+
|
|
5
|
+
**CRITICAL**: You MUST always call this tool when you are completely finished. The workflow is:
|
|
6
|
+
1. Perform all necessary tool calls (read files, make changes, etc.)
|
|
7
|
+
2. Stream your final text response/summary to the user
|
|
8
|
+
3. Call the finish tool to signal completion
|
|
9
|
+
|
|
10
|
+
Do NOT call finish before streaming your response. Do NOT forget to call finish after responding.
|
|
@@ -3,6 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
4
|
import { expandTilde, isAbsoluteLike, resolveSafePath } from './util.ts';
|
|
5
5
|
import DESCRIPTION from './read.txt' with { type: 'text' };
|
|
6
|
+
import { createToolError, type ToolResponse } from '../../error.ts';
|
|
6
7
|
|
|
7
8
|
const embeddedTextAssets: Record<string, string> = {};
|
|
8
9
|
|
|
@@ -43,7 +44,27 @@ export function buildReadTool(projectRoot: string): {
|
|
|
43
44
|
path: string;
|
|
44
45
|
startLine?: number;
|
|
45
46
|
endLine?: number;
|
|
46
|
-
})
|
|
47
|
+
}): Promise<
|
|
48
|
+
ToolResponse<{
|
|
49
|
+
path: string;
|
|
50
|
+
content: string;
|
|
51
|
+
size: number;
|
|
52
|
+
lineRange?: string;
|
|
53
|
+
totalLines?: number;
|
|
54
|
+
}>
|
|
55
|
+
> {
|
|
56
|
+
if (!path || path.trim().length === 0) {
|
|
57
|
+
return createToolError(
|
|
58
|
+
'Missing required parameter: path',
|
|
59
|
+
'validation',
|
|
60
|
+
{
|
|
61
|
+
parameter: 'path',
|
|
62
|
+
value: path,
|
|
63
|
+
suggestion: 'Provide a file path to read',
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
47
68
|
const req = expandTilde(path);
|
|
48
69
|
const abs = isAbsoluteLike(req) ? req : resolveSafePath(projectRoot, req);
|
|
49
70
|
|
|
@@ -57,6 +78,7 @@ export function buildReadTool(projectRoot: string): {
|
|
|
57
78
|
const selectedLines = lines.slice(start, end);
|
|
58
79
|
content = selectedLines.join('\n');
|
|
59
80
|
return {
|
|
81
|
+
ok: true,
|
|
60
82
|
path: req,
|
|
61
83
|
content,
|
|
62
84
|
size: content.length,
|
|
@@ -65,14 +87,31 @@ export function buildReadTool(projectRoot: string): {
|
|
|
65
87
|
};
|
|
66
88
|
}
|
|
67
89
|
|
|
68
|
-
return { path: req, content, size: content.length };
|
|
69
|
-
} catch (
|
|
90
|
+
return { ok: true, path: req, content, size: content.length };
|
|
91
|
+
} catch (error: unknown) {
|
|
70
92
|
const embedded = embeddedTextAssets[req];
|
|
71
93
|
if (embedded) {
|
|
72
94
|
const content = await readFile(embedded, 'utf-8');
|
|
73
|
-
return { path: req, content, size: content.length };
|
|
95
|
+
return { ok: true, path: req, content, size: content.length };
|
|
74
96
|
}
|
|
75
|
-
|
|
97
|
+
const isEnoent =
|
|
98
|
+
error &&
|
|
99
|
+
typeof error === 'object' &&
|
|
100
|
+
'code' in error &&
|
|
101
|
+
error.code === 'ENOENT';
|
|
102
|
+
return createToolError(
|
|
103
|
+
isEnoent
|
|
104
|
+
? `File not found: ${req}`
|
|
105
|
+
: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`,
|
|
106
|
+
isEnoent ? 'not_found' : 'execution',
|
|
107
|
+
{
|
|
108
|
+
parameter: 'path',
|
|
109
|
+
value: req,
|
|
110
|
+
suggestion: isEnoent
|
|
111
|
+
? 'Use ls or tree to find available files'
|
|
112
|
+
: undefined,
|
|
113
|
+
},
|
|
114
|
+
);
|
|
76
115
|
}
|
|
77
116
|
},
|
|
78
117
|
});
|
|
@@ -8,8 +8,7 @@ import {
|
|
|
8
8
|
isAbsoluteLike,
|
|
9
9
|
} from './util.ts';
|
|
10
10
|
import DESCRIPTION from './write.txt' with { type: 'text' };
|
|
11
|
-
|
|
12
|
-
// description imported above
|
|
11
|
+
import { createToolError, type ToolResponse } from '../../error.ts';
|
|
13
12
|
|
|
14
13
|
export function buildWriteTool(projectRoot: string): {
|
|
15
14
|
name: string;
|
|
@@ -34,27 +33,73 @@ export function buildWriteTool(projectRoot: string): {
|
|
|
34
33
|
path: string;
|
|
35
34
|
content: string;
|
|
36
35
|
createDirs?: boolean;
|
|
37
|
-
})
|
|
36
|
+
}): Promise<
|
|
37
|
+
ToolResponse<{
|
|
38
|
+
path: string;
|
|
39
|
+
bytes: number;
|
|
40
|
+
artifact: unknown;
|
|
41
|
+
}>
|
|
42
|
+
> {
|
|
43
|
+
if (!path || path.trim().length === 0) {
|
|
44
|
+
return createToolError(
|
|
45
|
+
'Missing required parameter: path',
|
|
46
|
+
'validation',
|
|
47
|
+
{
|
|
48
|
+
parameter: 'path',
|
|
49
|
+
value: path,
|
|
50
|
+
suggestion: 'Provide a file path to write',
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
38
55
|
const req = expandTilde(path);
|
|
39
56
|
if (isAbsoluteLike(req)) {
|
|
40
|
-
|
|
57
|
+
return createToolError(
|
|
41
58
|
`Refusing to write outside project root: ${req}. Use a relative path within the project.`,
|
|
59
|
+
'permission',
|
|
60
|
+
{
|
|
61
|
+
parameter: 'path',
|
|
62
|
+
value: req,
|
|
63
|
+
suggestion: 'Use a relative path within the project',
|
|
64
|
+
},
|
|
42
65
|
);
|
|
43
66
|
}
|
|
44
67
|
const abs = resolveSafePath(projectRoot, req);
|
|
45
|
-
|
|
46
|
-
const dirPath = abs.slice(0, abs.lastIndexOf('/'));
|
|
47
|
-
await mkdir(dirPath, { recursive: true });
|
|
48
|
-
}
|
|
49
|
-
let existed = false;
|
|
50
|
-
let oldText = '';
|
|
68
|
+
|
|
51
69
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
70
|
+
if (createDirs) {
|
|
71
|
+
const dirPath = abs.slice(0, abs.lastIndexOf('/'));
|
|
72
|
+
await mkdir(dirPath, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
let existed = false;
|
|
75
|
+
let oldText = '';
|
|
76
|
+
try {
|
|
77
|
+
oldText = await readFile(abs, 'utf-8');
|
|
78
|
+
existed = true;
|
|
79
|
+
} catch {}
|
|
80
|
+
await writeFile(abs, content);
|
|
81
|
+
const artifact = await buildWriteArtifact(
|
|
82
|
+
req,
|
|
83
|
+
existed,
|
|
84
|
+
oldText,
|
|
85
|
+
content,
|
|
86
|
+
);
|
|
87
|
+
return {
|
|
88
|
+
ok: true,
|
|
89
|
+
path: req,
|
|
90
|
+
bytes: content.length,
|
|
91
|
+
artifact,
|
|
92
|
+
};
|
|
93
|
+
} catch (error: unknown) {
|
|
94
|
+
return createToolError(
|
|
95
|
+
`Failed to write file: ${error instanceof Error ? error.message : String(error)}`,
|
|
96
|
+
'execution',
|
|
97
|
+
{
|
|
98
|
+
parameter: 'path',
|
|
99
|
+
value: req,
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
}
|
|
58
103
|
},
|
|
59
104
|
});
|
|
60
105
|
return { name: 'write', tool: write };
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
- Returns a compact patch artifact summarizing the change
|
|
5
5
|
|
|
6
6
|
Usage tips:
|
|
7
|
-
-
|
|
7
|
+
- Use for creating NEW files
|
|
8
|
+
- Use when replacing >70% of a file's content (almost complete rewrite)
|
|
8
9
|
- NEVER use for partial/targeted edits - use apply_patch or edit instead
|
|
9
10
|
- Using write for partial edits wastes output tokens and risks hallucinating unchanged parts
|
|
10
11
|
- Prefer idempotent writes by providing the full intended content when you do use write
|
|
@@ -3,6 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import { readFile, writeFile, unlink, mkdir } from 'node:fs/promises';
|
|
4
4
|
import { dirname, resolve, relative, isAbsolute } from 'node:path';
|
|
5
5
|
import DESCRIPTION from './patch.txt' with { type: 'text' };
|
|
6
|
+
import { createToolError, type ToolResponse } from '../error.ts';
|
|
6
7
|
|
|
7
8
|
interface PatchAddOperation {
|
|
8
9
|
kind: 'add';
|
|
@@ -106,6 +107,49 @@ function ensureTrailingNewline(lines: string[]) {
|
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Normalize whitespace for fuzzy matching.
|
|
112
|
+
* Converts tabs to spaces and trims leading/trailing whitespace.
|
|
113
|
+
*/
|
|
114
|
+
function normalizeWhitespace(line: string): string {
|
|
115
|
+
return line.replace(/\t/g, ' ').trim();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Find subsequence with optional whitespace normalization for fuzzy matching.
|
|
120
|
+
* Falls back to normalized matching if exact match fails.
|
|
121
|
+
*/
|
|
122
|
+
function findSubsequenceWithFuzzy(
|
|
123
|
+
lines: string[],
|
|
124
|
+
pattern: string[],
|
|
125
|
+
startIndex: number,
|
|
126
|
+
useFuzzy: boolean,
|
|
127
|
+
): number {
|
|
128
|
+
// Try exact match first
|
|
129
|
+
const exactMatch = findSubsequence(lines, pattern, startIndex);
|
|
130
|
+
if (exactMatch !== -1) return exactMatch;
|
|
131
|
+
|
|
132
|
+
// If fuzzy matching is enabled and exact match failed, try normalized matching
|
|
133
|
+
if (useFuzzy && pattern.length > 0) {
|
|
134
|
+
const normalizedLines = lines.map(normalizeWhitespace);
|
|
135
|
+
const normalizedPattern = pattern.map(normalizeWhitespace);
|
|
136
|
+
|
|
137
|
+
const start = Math.max(0, startIndex);
|
|
138
|
+
for (let i = start; i <= lines.length - pattern.length; i++) {
|
|
139
|
+
let matches = true;
|
|
140
|
+
for (let j = 0; j < pattern.length; j++) {
|
|
141
|
+
if (normalizedLines[i + j] !== normalizedPattern[j]) {
|
|
142
|
+
matches = false;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (matches) return i;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return -1;
|
|
151
|
+
}
|
|
152
|
+
|
|
109
153
|
function findSubsequence(
|
|
110
154
|
lines: string[],
|
|
111
155
|
pattern: string[],
|
|
@@ -307,7 +351,9 @@ function parseEnvelopedPatch(patch: string): ParsedPatchOperation[] {
|
|
|
307
351
|
} else if (prefix === ' ') {
|
|
308
352
|
currentHunk.lines.push({ kind: 'context', content: line.slice(1) });
|
|
309
353
|
} else {
|
|
310
|
-
|
|
354
|
+
// Auto-correct: treat lines without prefix as context (with implicit space)
|
|
355
|
+
// This makes the parser more forgiving for AI-generated patches
|
|
356
|
+
currentHunk.lines.push({ kind: 'context', content: line });
|
|
311
357
|
}
|
|
312
358
|
}
|
|
313
359
|
|
|
@@ -415,6 +461,7 @@ function applyHunksToLines(
|
|
|
415
461
|
originalLines: string[],
|
|
416
462
|
hunks: PatchHunk[],
|
|
417
463
|
filePath: string,
|
|
464
|
+
useFuzzy: boolean = false,
|
|
418
465
|
): { lines: string[]; applied: AppliedHunkResult[] } {
|
|
419
466
|
const lines = [...originalLines];
|
|
420
467
|
let searchIndex = 0;
|
|
@@ -445,11 +492,16 @@ function applyHunksToLines(
|
|
|
445
492
|
: searchIndex;
|
|
446
493
|
|
|
447
494
|
let matchIndex = hasExpected
|
|
448
|
-
?
|
|
495
|
+
? findSubsequenceWithFuzzy(
|
|
496
|
+
lines,
|
|
497
|
+
expected,
|
|
498
|
+
Math.max(0, hint - 3),
|
|
499
|
+
useFuzzy,
|
|
500
|
+
)
|
|
449
501
|
: -1;
|
|
450
502
|
|
|
451
503
|
if (hasExpected && matchIndex === -1) {
|
|
452
|
-
matchIndex =
|
|
504
|
+
matchIndex = findSubsequenceWithFuzzy(lines, expected, 0, useFuzzy);
|
|
453
505
|
}
|
|
454
506
|
|
|
455
507
|
if (matchIndex === -1 && hasExpected && hunk.header.context) {
|
|
@@ -471,9 +523,20 @@ function applyHunksToLines(
|
|
|
471
523
|
const contextInfo = hunk.header.context
|
|
472
524
|
? ` near context '${hunk.header.context}'`
|
|
473
525
|
: '';
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
);
|
|
526
|
+
|
|
527
|
+
// Provide helpful error with nearby context
|
|
528
|
+
const nearbyStart = Math.max(0, hint - 2);
|
|
529
|
+
const nearbyEnd = Math.min(lines.length, hint + 5);
|
|
530
|
+
const nearbyLines = lines.slice(nearbyStart, nearbyEnd);
|
|
531
|
+
const lineNumberInfo =
|
|
532
|
+
nearbyStart > 0 ? ` (around line ${nearbyStart + 1})` : '';
|
|
533
|
+
|
|
534
|
+
let errorMsg = `Failed to apply patch hunk in ${filePath}${contextInfo}.\n`;
|
|
535
|
+
errorMsg += `Expected to find:\n${expected.map((l) => ` ${l}`).join('\n')}\n`;
|
|
536
|
+
errorMsg += `Nearby context${lineNumberInfo}:\n${nearbyLines.map((l, idx) => ` ${nearbyStart + idx + 1}: ${l}`).join('\n')}\n`;
|
|
537
|
+
errorMsg += `Hint: Check for whitespace differences (tabs vs spaces). Try enabling fuzzyMatch option.`;
|
|
538
|
+
|
|
539
|
+
throw new Error(errorMsg);
|
|
477
540
|
}
|
|
478
541
|
|
|
479
542
|
const deleteCount = hasExpected ? expected.length : 0;
|
|
@@ -532,6 +595,7 @@ function computeInsertionIndex(
|
|
|
532
595
|
async function applyUpdateOperation(
|
|
533
596
|
projectRoot: string,
|
|
534
597
|
operation: PatchUpdateOperation,
|
|
598
|
+
useFuzzy: boolean = false,
|
|
535
599
|
): Promise<AppliedOperationRecord> {
|
|
536
600
|
const targetPath = resolveProjectPath(projectRoot, operation.filePath);
|
|
537
601
|
let originalContent: string;
|
|
@@ -549,6 +613,7 @@ async function applyUpdateOperation(
|
|
|
549
613
|
originalLines,
|
|
550
614
|
operation.hunks,
|
|
551
615
|
operation.filePath,
|
|
616
|
+
useFuzzy,
|
|
552
617
|
);
|
|
553
618
|
ensureTrailingNewline(updatedLines);
|
|
554
619
|
await writeFile(targetPath, joinLines(updatedLines, newline), 'utf-8');
|
|
@@ -665,7 +730,11 @@ function formatNormalizedPatch(operations: AppliedOperationRecord[]): string {
|
|
|
665
730
|
return lines.join('\n');
|
|
666
731
|
}
|
|
667
732
|
|
|
668
|
-
async function applyEnvelopedPatch(
|
|
733
|
+
async function applyEnvelopedPatch(
|
|
734
|
+
projectRoot: string,
|
|
735
|
+
patch: string,
|
|
736
|
+
useFuzzy: boolean = false,
|
|
737
|
+
) {
|
|
669
738
|
const operations = parseEnvelopedPatch(patch);
|
|
670
739
|
const applied: AppliedOperationRecord[] = [];
|
|
671
740
|
|
|
@@ -675,7 +744,9 @@ async function applyEnvelopedPatch(projectRoot: string, patch: string) {
|
|
|
675
744
|
} else if (operation.kind === 'delete') {
|
|
676
745
|
applied.push(await applyDeleteOperation(projectRoot, operation));
|
|
677
746
|
} else {
|
|
678
|
-
applied.push(
|
|
747
|
+
applied.push(
|
|
748
|
+
await applyUpdateOperation(projectRoot, operation, useFuzzy),
|
|
749
|
+
);
|
|
679
750
|
}
|
|
680
751
|
}
|
|
681
752
|
|
|
@@ -700,46 +771,98 @@ export function buildApplyPatchTool(projectRoot: string): {
|
|
|
700
771
|
.describe(
|
|
701
772
|
'Allow hunks to be rejected without failing the whole operation',
|
|
702
773
|
),
|
|
774
|
+
fuzzyMatch: z
|
|
775
|
+
.boolean()
|
|
776
|
+
.optional()
|
|
777
|
+
.default(true)
|
|
778
|
+
.describe(
|
|
779
|
+
'Enable fuzzy matching with whitespace normalization (converts tabs to spaces for matching)',
|
|
780
|
+
),
|
|
703
781
|
}),
|
|
704
|
-
async execute({
|
|
782
|
+
async execute({
|
|
783
|
+
patch,
|
|
784
|
+
fuzzyMatch,
|
|
785
|
+
}: {
|
|
786
|
+
patch: string;
|
|
787
|
+
allowRejects?: boolean;
|
|
788
|
+
fuzzyMatch?: boolean;
|
|
789
|
+
}): Promise<
|
|
790
|
+
ToolResponse<{
|
|
791
|
+
output: string;
|
|
792
|
+
changes: unknown[];
|
|
793
|
+
artifact: unknown;
|
|
794
|
+
}>
|
|
795
|
+
> {
|
|
796
|
+
if (!patch || patch.trim().length === 0) {
|
|
797
|
+
return createToolError(
|
|
798
|
+
'Missing required parameter: patch',
|
|
799
|
+
'validation',
|
|
800
|
+
{
|
|
801
|
+
parameter: 'patch',
|
|
802
|
+
value: patch,
|
|
803
|
+
suggestion: 'Provide patch content in enveloped format',
|
|
804
|
+
},
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
|
|
705
808
|
if (
|
|
706
809
|
!patch.includes(PATCH_BEGIN_MARKER) ||
|
|
707
810
|
!patch.includes(PATCH_END_MARKER)
|
|
708
811
|
) {
|
|
709
|
-
|
|
812
|
+
return createToolError(
|
|
710
813
|
'Only enveloped patch format is supported. Patch must start with "*** Begin Patch" and contain "*** Add File:", "*** Update File:", or "*** Delete File:" directives.',
|
|
814
|
+
'validation',
|
|
815
|
+
{
|
|
816
|
+
parameter: 'patch',
|
|
817
|
+
suggestion:
|
|
818
|
+
'Use enveloped patch format starting with *** Begin Patch',
|
|
819
|
+
},
|
|
711
820
|
);
|
|
712
821
|
}
|
|
713
822
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
823
|
+
try {
|
|
824
|
+
const { operations, normalizedPatch } = await applyEnvelopedPatch(
|
|
825
|
+
projectRoot,
|
|
826
|
+
patch,
|
|
827
|
+
fuzzyMatch ?? true,
|
|
828
|
+
);
|
|
829
|
+
const summary = summarizeOperations(operations);
|
|
830
|
+
const changes = operations.map((operation) => ({
|
|
831
|
+
filePath: operation.filePath,
|
|
832
|
+
kind: operation.kind,
|
|
833
|
+
hunks: operation.hunks.map((hunk) => ({
|
|
834
|
+
oldStart: hunk.oldStart,
|
|
835
|
+
oldLines: hunk.oldLines,
|
|
836
|
+
newStart: hunk.newStart,
|
|
837
|
+
newLines: hunk.newLines,
|
|
838
|
+
additions: hunk.additions,
|
|
839
|
+
deletions: hunk.deletions,
|
|
840
|
+
context: hunk.header.context,
|
|
841
|
+
})),
|
|
842
|
+
}));
|
|
843
|
+
|
|
844
|
+
return {
|
|
845
|
+
ok: true,
|
|
846
|
+
output: 'Applied enveloped patch',
|
|
847
|
+
changes,
|
|
848
|
+
artifact: {
|
|
849
|
+
kind: 'file_diff',
|
|
850
|
+
patch: normalizedPatch,
|
|
851
|
+
summary,
|
|
852
|
+
},
|
|
853
|
+
};
|
|
854
|
+
} catch (error: unknown) {
|
|
855
|
+
const errorMessage =
|
|
856
|
+
error instanceof Error ? error.message : String(error);
|
|
857
|
+
return createToolError(
|
|
858
|
+
`Failed to apply patch: ${errorMessage}`,
|
|
859
|
+
'execution',
|
|
860
|
+
{
|
|
861
|
+
suggestion:
|
|
862
|
+
'Check that the patch format is correct and target files exist',
|
|
863
|
+
},
|
|
864
|
+
);
|
|
865
|
+
}
|
|
743
866
|
},
|
|
744
867
|
});
|
|
745
868
|
return { name: 'apply_patch', tool: applyPatch };
|
|
@@ -2,13 +2,18 @@ Apply a patch to modify one or more files using the enveloped patch format.
|
|
|
2
2
|
|
|
3
3
|
**RECOMMENDED: Use apply_patch for targeted file edits to avoid rewriting entire files and wasting tokens.**
|
|
4
4
|
|
|
5
|
+
**FUZZY MATCHING**: By default, fuzzy matching is enabled to handle whitespace differences (tabs vs spaces).
|
|
6
|
+
Exact matching is tried first, then normalized matching if exact fails. Disable with `fuzzyMatch: false` if needed.
|
|
7
|
+
|
|
5
8
|
Use `apply_patch` only when:
|
|
6
9
|
- You want to make targeted edits to specific lines (primary use case)
|
|
7
10
|
- You want to make multiple related changes across different files in a single operation
|
|
8
11
|
- You need to add/delete entire files along with modifications
|
|
9
|
-
- You have JUST read the file and are confident the content hasn't changed
|
|
12
|
+
- You have JUST read the file immediately before (within the same response) and are confident the content hasn't changed
|
|
10
13
|
|
|
11
|
-
**
|
|
14
|
+
**CRITICAL - ALWAYS READ BEFORE PATCHING**: You MUST read the file content immediately before creating a patch.
|
|
15
|
+
Never rely on memory or previous reads. Even with fuzzy matching enabled (tolerates tabs vs spaces),
|
|
16
|
+
If the file content has changed significantly since you last read it, the patch may still fail.
|
|
12
17
|
|
|
13
18
|
**Alternative: Use the `edit` tool if you need fuzzy matching or structured operations.**
|
|
14
19
|
|
|
@@ -63,7 +68,33 @@ All patches must be wrapped in markers and use explicit file directives:
|
|
|
63
68
|
*** End Patch
|
|
64
69
|
```
|
|
65
70
|
|
|
66
|
-
|
|
71
|
+
**IMPORTANT**:
|
|
72
|
+
- The `@@` line is an OPTIONAL hint to help locate the change - it's a comment, not parsed as context
|
|
73
|
+
- REQUIRED: Actual context lines (starting with space ` `) that match the file exactly
|
|
74
|
+
- The context lines with space prefix are what the tool uses to find the location
|
|
75
|
+
- The `@@` line just helps humans/AI understand what section you're editing
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
### Update multiple locations in the same file:
|
|
79
|
+
```
|
|
80
|
+
*** Begin Patch
|
|
81
|
+
*** Update File: src/app.ts
|
|
82
|
+
@@ first section - near line 10
|
|
83
|
+
function init() {
|
|
84
|
+
- const port = 3000;
|
|
85
|
+
+ const port = 8080;
|
|
86
|
+
return port;
|
|
87
|
+
}
|
|
88
|
+
@@ second section - near line 25
|
|
89
|
+
function start() {
|
|
90
|
+
- console.log("Starting...");
|
|
91
|
+
+ console.log("Server starting...");
|
|
92
|
+
init();
|
|
93
|
+
}
|
|
94
|
+
*** End Patch
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**IMPORTANT**: Use separate `@@` headers for each non-consecutive change location. This allows multiple edits to the same file in one patch, saving tokens and reducing tool calls.
|
|
67
98
|
|
|
68
99
|
### Delete a file:
|
|
69
100
|
```
|
|
@@ -89,11 +120,17 @@ The `@@ context line` helps locate the exact position, but the `-` lines must st
|
|
|
89
120
|
- Lines starting with `+` are added
|
|
90
121
|
- Lines starting with `-` are removed
|
|
91
122
|
- Lines starting with ` ` (space) are context (kept unchanged)
|
|
92
|
-
- Lines starting with `@@`
|
|
123
|
+
- Lines starting with `@@` are optional hints/comments (not parsed as context)
|
|
93
124
|
|
|
94
125
|
## Common Errors
|
|
95
126
|
|
|
96
|
-
**"Failed to find expected lines"**: The file content doesn't match your patch.
|
|
127
|
+
**"Failed to find expected lines"**: The file content doesn't match your patch. Common causes:
|
|
128
|
+
- Missing context lines (lines with space prefix)
|
|
129
|
+
- Using `@@` line as context instead of real context lines
|
|
130
|
+
- The file content has changed since you read it
|
|
131
|
+
- Whitespace/indentation mismatch
|
|
132
|
+
|
|
133
|
+
**Solution**: Always read the file immediately before patching and include actual context lines with space prefix.
|
|
97
134
|
|
|
98
135
|
## Important Notes
|
|
99
136
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type ToolErrorType =
|
|
2
|
+
| 'validation'
|
|
3
|
+
| 'not_found'
|
|
4
|
+
| 'permission'
|
|
5
|
+
| 'execution'
|
|
6
|
+
| 'timeout'
|
|
7
|
+
| 'unsupported';
|
|
8
|
+
|
|
9
|
+
export type ToolErrorResponse = {
|
|
10
|
+
ok: false;
|
|
11
|
+
error: string;
|
|
12
|
+
errorType?: ToolErrorType;
|
|
13
|
+
details?: {
|
|
14
|
+
parameter?: string;
|
|
15
|
+
value?: unknown;
|
|
16
|
+
constraint?: string;
|
|
17
|
+
suggestion?: string;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
};
|
|
20
|
+
stack?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type ToolSuccessResponse<T = unknown> = {
|
|
24
|
+
ok: true;
|
|
25
|
+
} & T;
|
|
26
|
+
|
|
27
|
+
export type ToolResponse<T = unknown> =
|
|
28
|
+
| ToolSuccessResponse<T>
|
|
29
|
+
| ToolErrorResponse;
|
|
30
|
+
|
|
31
|
+
export function isToolError(result: unknown): result is ToolErrorResponse {
|
|
32
|
+
if (!result || typeof result !== 'object') return false;
|
|
33
|
+
const obj = result as Record<string, unknown>;
|
|
34
|
+
return obj.ok === false || 'error' in obj || obj.success === false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function extractToolError(
|
|
38
|
+
result: unknown,
|
|
39
|
+
topLevelError?: string,
|
|
40
|
+
): string | undefined {
|
|
41
|
+
if (topLevelError?.trim()) return topLevelError.trim();
|
|
42
|
+
if (!result || typeof result !== 'object') return undefined;
|
|
43
|
+
|
|
44
|
+
const obj = result as Record<string, unknown>;
|
|
45
|
+
const keys = ['error', 'stderr', 'message', 'detail', 'details', 'reason'];
|
|
46
|
+
for (const key of keys) {
|
|
47
|
+
const value = obj[key];
|
|
48
|
+
if (typeof value === 'string') {
|
|
49
|
+
const trimmed = value.trim();
|
|
50
|
+
if (trimmed.length) return trimmed;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function createToolError(
|
|
57
|
+
error: string,
|
|
58
|
+
errorType?: ToolErrorType,
|
|
59
|
+
details?: ToolErrorResponse['details'],
|
|
60
|
+
): ToolErrorResponse {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
error,
|
|
64
|
+
errorType,
|
|
65
|
+
details,
|
|
66
|
+
};
|
|
67
|
+
}
|
package/src/prompts/src/base.txt
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
You are a helpful, concise assistant.
|
|
2
|
-
- Stream the final answer as assistant text; call finish when done.
|
|
3
2
|
- CRITICAL: Emit progress updates using the `progress_update` tool at key milestones — at the start (planning), after initial repo discovery (discovering), before file edits (preparing), during edits (writing), and when validating (verifying). Prefer short messages (<= 80 chars).
|
|
4
3
|
- Do not print pseudo tool calls like `call:tool{}`; invoke tools directly.
|
|
5
4
|
- Use sensible default filenames when needed.
|
|
6
5
|
- Prefer minimal, precise outputs and actionable steps.
|
|
7
6
|
|
|
7
|
+
## Finish Tool - CRITICAL
|
|
8
|
+
|
|
9
|
+
You MUST call the `finish` tool at the end of every response to signal completion. The correct workflow is:
|
|
10
|
+
|
|
11
|
+
1. Perform all necessary work (tool calls, file edits, searches, etc.)
|
|
12
|
+
2. Stream your final text response or summary to the user explaining what you did
|
|
13
|
+
3. **Call the `finish` tool** to signal you are done
|
|
14
|
+
|
|
15
|
+
**IMPORTANT**: Do NOT call `finish` before streaming your response. Always stream your message first, then call `finish`. If you forget to call `finish`, the system will hang and not complete properly.
|
|
16
|
+
|
|
8
17
|
File Editing Best Practices:
|
|
18
|
+
- ALWAYS read a file immediately before using apply_patch on it - never patch from memory
|
|
9
19
|
- When making multiple edits to the same file, combine them into a single edit operation with multiple ops
|
|
10
20
|
- Each edit operation re-reads the file, so ops within a single edit call are applied sequentially to the latest content
|
|
11
21
|
- If you need to make edits based on previous edits, ensure they're in the same edit call or re-read the file between calls
|
|
12
22
|
- Never assume file content remains unchanged between separate edit operations
|
|
13
23
|
- When using apply_patch, ensure the patch is based on the current file content, not stale versions
|
|
14
|
-
- If a patch fails, read the file first
|
|
24
|
+
- If a patch fails, it means you didn't read the file first or the content doesn't match what you expected
|
|
@@ -83,12 +83,27 @@ When making changes to files, first understand the file's code conventions. Mimi
|
|
|
83
83
|
## File Editing Best Practices
|
|
84
84
|
|
|
85
85
|
**Using the `apply_patch` Tool** (Recommended):
|
|
86
|
+
- **CRITICAL**: ALWAYS read the target file immediately before creating a patch - never patch from memory
|
|
86
87
|
- Primary choice for targeted file edits - avoids rewriting entire files
|
|
87
88
|
- Only requires the specific lines you want to change
|
|
88
89
|
- Format: `*** Begin Patch` ... `*** Update File: path` ... `-old` / `+new` ... `*** End Patch`
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
90
|
+
- For multiple changes in one file: use multiple `@@` headers to separate non-consecutive hunks
|
|
91
|
+
- MUST include context lines (space prefix) - the `@@` line is just an optional hint
|
|
92
|
+
- Workflow: 1) Read file, 2) Create patch based on what you just read, 3) Apply patch
|
|
93
|
+
- The `-` lines in your patch MUST match exactly what's in the file character-for-character
|
|
94
|
+
- If patch fails, it means the file content doesn't match - read it again and retry
|
|
95
|
+
- **Best for**: Small, surgical edits to code files (< 50 line changes per file)
|
|
96
|
+
- **Struggles with**: Large restructures (> 50 lines), major section reorganizations
|
|
97
|
+
|
|
98
|
+
**Patch Format Reminder**:
|
|
99
|
+
```
|
|
100
|
+
*** Update File: path
|
|
101
|
+
@@ optional hint ← Optional comment/hint (not parsed)
|
|
102
|
+
actual line from file ← Context (space prefix) - REQUIRED
|
|
103
|
+
-line to remove ← Remove this line
|
|
104
|
+
+line to add ← Add this line
|
|
105
|
+
more context ← More context (space prefix)
|
|
106
|
+
```
|
|
92
107
|
|
|
93
108
|
**Using the `edit` Tool** (Alternative):
|
|
94
109
|
- Specify the file path and a list of operations
|
|
@@ -98,13 +113,14 @@ When making changes to files, first understand the file's code conventions. Mimi
|
|
|
98
113
|
- When making multiple changes to a file, use ONE `edit` call with multiple ops
|
|
99
114
|
|
|
100
115
|
**Using the `write` Tool** (Last Resort):
|
|
101
|
-
-
|
|
116
|
+
- Use for creating NEW files
|
|
117
|
+
- Use when replacing >70% of a file's content (almost complete rewrite)
|
|
102
118
|
- NEVER use for targeted edits - it rewrites the entire file
|
|
103
119
|
- Wastes output tokens and risks hallucinating unchanged parts
|
|
104
120
|
|
|
105
121
|
**Never**:
|
|
106
122
|
- Use `write` for partial file edits (use `apply_patch` or `edit` instead)
|
|
107
|
-
- Make multiple separate `edit` or `apply_patch` calls for the same file
|
|
123
|
+
- Make multiple separate `edit` or `apply_patch` calls for the same file (use multiple hunks with @@ headers or multiple ops instead)
|
|
108
124
|
- Assume file content remains unchanged between operations
|
|
109
125
|
- Use `bash` with `sed`/`awk` for programmatic file editing (use `edit` instead)
|
|
110
126
|
|
|
@@ -47,12 +47,27 @@ You have access to a rich set of specialized tools optimized for coding tasks:
|
|
|
47
47
|
## File Editing Best Practices
|
|
48
48
|
|
|
49
49
|
**Using the `apply_patch` Tool** (Recommended):
|
|
50
|
+
- **CRITICAL**: ALWAYS read the target file immediately before creating a patch - never patch from memory
|
|
50
51
|
- Primary choice for targeted file edits - avoids rewriting entire files
|
|
51
52
|
- Only requires the specific lines you want to change
|
|
52
53
|
- Format: `*** Begin Patch` ... `*** Update File: path` ... `-old` / `+new` ... `*** End Patch`
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
54
|
+
- For multiple changes in one file: use multiple `@@` headers to separate non-consecutive hunks
|
|
55
|
+
- MUST include context lines (space prefix) - the `@@` line is just an optional hint
|
|
56
|
+
- Workflow: 1) Read file, 2) Create patch based on what you just read, 3) Apply patch
|
|
57
|
+
- The `-` lines in your patch MUST match exactly what's in the file character-for-character
|
|
58
|
+
- If patch fails, it means the file content doesn't match - read it again and retry
|
|
59
|
+
- **Best for**: Small, surgical edits to code files (< 50 line changes per file)
|
|
60
|
+
- **Struggles with**: Large restructures (> 50 lines), major section reorganizations
|
|
61
|
+
|
|
62
|
+
**Patch Format Reminder**:
|
|
63
|
+
```
|
|
64
|
+
*** Update File: path
|
|
65
|
+
@@ optional hint ← Optional comment/hint (not parsed)
|
|
66
|
+
actual line from file ← Context (space prefix) - REQUIRED
|
|
67
|
+
-line to remove ← Remove this line
|
|
68
|
+
+line to add ← Add this line
|
|
69
|
+
more context ← More context (space prefix)
|
|
70
|
+
```
|
|
56
71
|
|
|
57
72
|
**Using the `edit` Tool** (Alternative):
|
|
58
73
|
- Specify the file path and a list of operations
|
|
@@ -62,13 +77,14 @@ You have access to a rich set of specialized tools optimized for coding tasks:
|
|
|
62
77
|
- When making multiple changes to a file, use ONE `edit` call with multiple ops
|
|
63
78
|
|
|
64
79
|
**Using the `write` Tool** (Last Resort):
|
|
65
|
-
-
|
|
80
|
+
- Use for creating NEW files
|
|
81
|
+
- Use when replacing >70% of a file's content (almost complete rewrite)
|
|
66
82
|
- NEVER use for targeted edits - it rewrites the entire file
|
|
67
83
|
- Wastes output tokens and risks hallucinating unchanged parts
|
|
68
84
|
|
|
69
85
|
**Never**:
|
|
70
86
|
- Use `write` for partial file edits (use `apply_patch` or `edit` instead)
|
|
71
|
-
- Make multiple separate `edit` or `apply_patch` calls for the same file
|
|
87
|
+
- Make multiple separate `edit` or `apply_patch` calls for the same file (use multiple hunks with @@ headers or multiple ops instead)
|
|
72
88
|
- Assume file content remains unchanged between operations
|
|
73
89
|
- Use `bash` with `sed`/`awk` for programmatic file editing (use `edit` instead)
|
|
74
90
|
|
|
@@ -238,7 +254,7 @@ You MUST adhere to the following criteria when solving queries:
|
|
|
238
254
|
- Working on the repo(s) in the current environment is allowed, even if they are proprietary.
|
|
239
255
|
- Analyzing code for vulnerabilities is allowed.
|
|
240
256
|
- Showing user code and tool call details is allowed.
|
|
241
|
-
- Use the `apply_patch` tool to edit files (NEVER try `applypatch` or `apply-patch`, only `apply_patch`): {"command":["apply_patch","*** Begin Patch\\n*** Update File: path/to/file.py\\n
|
|
257
|
+
- Use the `apply_patch` tool to edit files (NEVER try `applypatch` or `apply-patch`, only `apply_patch`): {"command":["apply_patch","*** Begin Patch\\n*** Update File: path/to/file.py\\n def example():\\n- pass\\n+ return 123\\n*** End Patch"]}
|
|
242
258
|
|
|
243
259
|
If completing the user's task requires writing or modifying files, your code and final answer should follow these coding guidelines, though user instructions (i.e. AGENTS.md) may override these guidelines:
|
|
244
260
|
|
|
@@ -29,12 +29,27 @@ call with multiple ops. Each separate `edit` operation re-reads the file fresh.
|
|
|
29
29
|
## File Editing Best Practices
|
|
30
30
|
|
|
31
31
|
**Using the `apply_patch` Tool** (Recommended):
|
|
32
|
+
- **CRITICAL**: ALWAYS read the target file immediately before creating a patch - never patch from memory
|
|
32
33
|
- Primary choice for targeted file edits - avoids rewriting entire files
|
|
33
34
|
- Only requires the specific lines you want to change
|
|
34
35
|
- Format: `*** Begin Patch` ... `*** Update File: path` ... `-old` / `+new` ... `*** End Patch`
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
36
|
+
- For multiple changes in one file: use multiple `@@` headers to separate non-consecutive hunks
|
|
37
|
+
- MUST include context lines (space prefix) - the `@@` line is just an optional hint
|
|
38
|
+
- Workflow: 1) Read file, 2) Create patch based on what you just read, 3) Apply patch
|
|
39
|
+
- The `-` lines in your patch MUST match exactly what's in the file character-for-character
|
|
40
|
+
- If patch fails, it means the file content doesn't match - read it again and retry
|
|
41
|
+
- **Best for**: Small, surgical edits to code files (< 50 line changes per file)
|
|
42
|
+
- **Struggles with**: Large restructures (> 50 lines), major section reorganizations
|
|
43
|
+
|
|
44
|
+
**Patch Format Reminder**:
|
|
45
|
+
```
|
|
46
|
+
*** Update File: path
|
|
47
|
+
@@ optional hint ← Optional comment/hint (not parsed)
|
|
48
|
+
actual line from file ← Context (space prefix) - REQUIRED
|
|
49
|
+
-line to remove ← Remove this line
|
|
50
|
+
+line to add ← Add this line
|
|
51
|
+
more context ← More context (space prefix)
|
|
52
|
+
```
|
|
38
53
|
|
|
39
54
|
**Using the `edit` Tool** (Alternative):
|
|
40
55
|
- Specify the file path and a list of operations
|
|
@@ -44,13 +59,14 @@ call with multiple ops. Each separate `edit` operation re-reads the file fresh.
|
|
|
44
59
|
- When making multiple changes to a file, use ONE `edit` call with multiple ops
|
|
45
60
|
|
|
46
61
|
**Using the `write` Tool** (Last Resort):
|
|
47
|
-
-
|
|
62
|
+
- Use for creating NEW files
|
|
63
|
+
- Use when replacing >70% of a file's content (almost complete rewrite)
|
|
48
64
|
- NEVER use for targeted edits - it rewrites the entire file
|
|
49
65
|
- Wastes output tokens and risks hallucinating unchanged parts
|
|
50
66
|
|
|
51
67
|
**Never**:
|
|
52
68
|
- Use `write` for partial file edits (use `apply_patch` or `edit` instead)
|
|
53
|
-
- Make multiple separate `edit` or `apply_patch` calls for the same file
|
|
69
|
+
- Make multiple separate `edit` or `apply_patch` calls for the same file (use multiple hunks with @@ headers or multiple ops instead)
|
|
54
70
|
- Assume file content remains unchanged between operations
|
|
55
71
|
- Use `bash` with `sed`/`awk` for programmatic file editing (use `edit` instead)
|
|
56
72
|
|
|
@@ -73,12 +73,27 @@ Your toolset includes specialized file editing and search tools. Follow these gu
|
|
|
73
73
|
## File Editing Best Practices
|
|
74
74
|
|
|
75
75
|
**Using the `apply_patch` Tool** (Recommended):
|
|
76
|
+
- **CRITICAL**: ALWAYS read the target file immediately before creating a patch - never patch from memory
|
|
76
77
|
- Primary choice for targeted file edits - avoids rewriting entire files
|
|
77
78
|
- Only requires the specific lines you want to change
|
|
78
79
|
- Format: `*** Begin Patch` ... `*** Update File: path` ... `-old` / `+new` ... `*** End Patch`
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
80
|
+
- For multiple changes in one file: use multiple `@@` headers to separate non-consecutive hunks
|
|
81
|
+
- MUST include context lines (space prefix) - the `@@` line is just an optional hint
|
|
82
|
+
- Workflow: 1) Read file, 2) Create patch based on what you just read, 3) Apply patch
|
|
83
|
+
- The `-` lines in your patch MUST match exactly what's in the file character-for-character
|
|
84
|
+
- If patch fails, it means the file content doesn't match - read it again and retry
|
|
85
|
+
- **Best for**: Small, surgical edits to code files (< 50 line changes per file)
|
|
86
|
+
- **Struggles with**: Large restructures (> 50 lines), major section reorganizations
|
|
87
|
+
|
|
88
|
+
**Patch Format Reminder**:
|
|
89
|
+
```
|
|
90
|
+
*** Update File: path
|
|
91
|
+
@@ optional hint ← Optional comment/hint (not parsed)
|
|
92
|
+
actual line from file ← Context (space prefix) - REQUIRED
|
|
93
|
+
-line to remove ← Remove this line
|
|
94
|
+
+line to add ← Add this line
|
|
95
|
+
more context ← More context (space prefix)
|
|
96
|
+
```
|
|
82
97
|
|
|
83
98
|
**Using the `edit` Tool** (Alternative):
|
|
84
99
|
- Specify the file path and a list of operations
|
|
@@ -88,13 +103,14 @@ Your toolset includes specialized file editing and search tools. Follow these gu
|
|
|
88
103
|
- When making multiple changes to a file, use ONE `edit` call with multiple ops
|
|
89
104
|
|
|
90
105
|
**Using the `write` Tool** (Last Resort):
|
|
91
|
-
-
|
|
106
|
+
- Use for creating NEW files
|
|
107
|
+
- Use when replacing >70% of a file's content (almost complete rewrite)
|
|
92
108
|
- NEVER use for targeted edits - it rewrites the entire file
|
|
93
109
|
- Wastes output tokens and risks hallucinating unchanged parts
|
|
94
110
|
|
|
95
111
|
**Never**:
|
|
96
112
|
- Use `write` for partial file edits (use `apply_patch` or `edit` instead)
|
|
97
|
-
- Make multiple separate `edit` or `apply_patch` calls for the same file
|
|
113
|
+
- Make multiple separate `edit` or `apply_patch` calls for the same file (use multiple hunks with @@ headers or multiple ops instead)
|
|
98
114
|
- Assume file content remains unchanged between operations
|
|
99
115
|
- Use `bash` with `sed`/`awk` for programmatic file editing (use `edit` instead)
|
|
100
116
|
|
|
@@ -227,7 +243,7 @@ You MUST adhere to the following criteria when solving queries:
|
|
|
227
243
|
- Working on the repo(s) in the current environment is allowed, even if they are proprietary.
|
|
228
244
|
- Analyzing code for vulnerabilities is allowed.
|
|
229
245
|
- Showing user code and tool call details is allowed.
|
|
230
|
-
- Use the `apply_patch` tool to edit files (NEVER try `applypatch` or `apply-patch`, only `apply_patch`): {"command":["apply_patch","*** Begin Patch\\n*** Update File: path/to/file.py\\n
|
|
246
|
+
- Use the `apply_patch` tool to edit files (NEVER try `applypatch` or `apply-patch`, only `apply_patch`): {"command":["apply_patch","*** Begin Patch\\n*** Update File: path/to/file.py\\n def example():\\n- pass\\n+ return 123\\n*** End Patch"]}
|
|
231
247
|
|
|
232
248
|
If completing the user's task requires writing or modifying files, your code and final answer should follow these coding guidelines, though user instructions (i.e. AGENTS.md) may override these guidelines:
|
|
233
249
|
|