Skip to content

Commit eba1f76

Browse files
committed
🤖 refactor: remove line_offset from insert tool
Generated with
1 parent 8112e5a commit eba1f76

File tree

4 files changed

+19
-158
lines changed

4 files changed

+19
-158
lines changed

src/services/tools/file_edit_insert.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,18 +96,18 @@ describe("file_edit_insert tool", () => {
9696
}
9797
});
9898

99-
it("creates new file when create flag and line_offset provided", async () => {
100-
const newFile = path.join(testDir, "new.txt");
99+
it("fails when no guards are provided", async () => {
101100
const tool = createTestTool(testDir);
102101
const args: FileEditInsertToolArgs = {
103-
file_path: path.relative(testDir, newFile),
104-
content: "Hello world!\n",
105-
line_offset: 0,
106-
create: true,
102+
file_path: path.relative(testDir, testFilePath),
103+
content: "noop",
107104
};
108105

109106
const result = (await tool.execute!(args, mockToolCallOptions)) as FileEditInsertToolResult;
110-
expect(result.success).toBe(true);
111-
expect(await fs.readFile(newFile, "utf-8")).toBe("Hello world!\n");
107+
expect(result.success).toBe(false);
108+
if (!result.success) {
109+
expect(result.error).toContain("Provide either a before or after guard");
110+
}
112111
});
112+
113113
});
Lines changed: 5 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
import { tool } from "ai";
2-
import { RuntimeError } from "@/runtime/Runtime";
32
import type { FileEditInsertToolArgs, FileEditInsertToolResult } from "@/types/tools";
43
import { EDIT_FAILED_NOTE_PREFIX, NOTE_READ_FILE_RETRY } from "@/types/tools";
54
import type { ToolConfiguration, ToolFactory } from "@/utils/tools/tools";
65
import { TOOL_DEFINITIONS } from "@/utils/tools/toolDefinitions";
76
import { validateAndCorrectPath, validatePathInCwd } from "./fileCommon";
87
import { executeFileEditOperation } from "./file_edit_operation";
9-
import { fileExists } from "@/utils/runtime/fileExists";
10-
import { writeFileString } from "@/utils/runtime/helpers";
118

129
const READ_AND_RETRY_NOTE = `${EDIT_FAILED_NOTE_PREFIX} ${NOTE_READ_FILE_RETRY}`;
1310

@@ -26,7 +23,6 @@ interface InsertOperationFailure {
2623
interface InsertContentOptions {
2724
before?: string;
2825
after?: string;
29-
lineOffset?: number;
3026
}
3127

3228
interface GuardResolutionSuccess {
@@ -49,7 +45,7 @@ export const createFileEditInsertTool: ToolFactory = (config: ToolConfiguration)
4945
description: TOOL_DEFINITIONS.file_edit_insert.description,
5046
inputSchema: TOOL_DEFINITIONS.file_edit_insert.schema,
5147
execute: async (
52-
{ file_path, content, line_offset, create, before, after }: FileEditInsertToolArgs,
48+
{ file_path, content, before, after }: FileEditInsertToolArgs,
5349
{ abortSignal }
5450
): Promise<FileEditInsertToolResult> => {
5551
try {
@@ -64,39 +60,6 @@ export const createFileEditInsertTool: ToolFactory = (config: ToolConfiguration)
6460
};
6561
}
6662

67-
if (line_offset !== undefined && line_offset < 0) {
68-
return {
69-
success: false,
70-
error: `line_offset must be non-negative (got ${line_offset})`,
71-
note: `${EDIT_FAILED_NOTE_PREFIX} The line_offset must be >= 0.`,
72-
};
73-
}
74-
75-
const resolvedPath = config.runtime.normalizePath(file_path, config.cwd);
76-
const exists = await fileExists(config.runtime, resolvedPath, abortSignal);
77-
78-
if (!exists) {
79-
if (!create) {
80-
return {
81-
success: false,
82-
error: `File not found: ${file_path}. Set create: true to create it.`,
83-
note: `${EDIT_FAILED_NOTE_PREFIX} File does not exist. Set create: true to create it, or check the file path.`,
84-
};
85-
}
86-
87-
try {
88-
await writeFileString(config.runtime, resolvedPath, "", abortSignal);
89-
} catch (err) {
90-
if (err instanceof RuntimeError) {
91-
return {
92-
success: false,
93-
error: err.message,
94-
};
95-
}
96-
throw err;
97-
}
98-
}
99-
10063
return executeFileEditOperation({
10164
config,
10265
filePath: file_path,
@@ -105,7 +68,6 @@ export const createFileEditInsertTool: ToolFactory = (config: ToolConfiguration)
10568
insertContent(originalContent, content, {
10669
before,
10770
after,
108-
lineOffset: line_offset,
10971
}),
11072
});
11173
} catch (error) {
@@ -131,21 +93,17 @@ function insertContent(
13193
contentToInsert: string,
13294
options: InsertContentOptions
13395
): InsertOperationSuccess | InsertOperationFailure {
134-
const { before, after, lineOffset } = options;
96+
const { before, after } = options;
13597

13698
if (before !== undefined && after !== undefined) {
13799
return guardFailure("Provide only one of before or after (not both).");
138100
}
139101

140-
if (before !== undefined || after !== undefined) {
141-
return insertWithGuards(originalContent, contentToInsert, { before, after });
102+
if (before === undefined && after === undefined) {
103+
return guardFailure("Provide either a before or after guard to anchor the insertion point.");
142104
}
143105

144-
if (lineOffset === undefined) {
145-
return guardFailure("line_offset must be provided when before/after guards are omitted.");
146-
}
147-
148-
return insertWithLineOffset(originalContent, contentToInsert, lineOffset);
106+
return insertWithGuards(originalContent, contentToInsert, { before, after });
149107
}
150108

151109
function insertWithGuards(
@@ -212,76 +170,3 @@ function resolveGuardAnchor(
212170

213171
return guardFailure("Unable to determine insertion point from guards.");
214172
}
215-
216-
function insertWithLineOffset(
217-
originalContent: string,
218-
contentToInsert: string,
219-
lineOffset: number
220-
): InsertOperationSuccess | InsertOperationFailure {
221-
const lineCount = countLines(originalContent);
222-
if (lineOffset > lineCount) {
223-
return {
224-
success: false,
225-
error: `line_offset ${lineOffset} is beyond file length (${lineCount} lines)`,
226-
note: `${EDIT_FAILED_NOTE_PREFIX} The file has ${lineCount} lines. ${NOTE_READ_FILE_RETRY}`,
227-
};
228-
}
229-
230-
const insertionIndex = getIndexForLineOffset(originalContent, lineOffset);
231-
if (insertionIndex === null) {
232-
return guardFailure(`Unable to compute insertion point for line_offset ${lineOffset}.`);
233-
}
234-
235-
const newContent =
236-
originalContent.slice(0, insertionIndex) +
237-
contentToInsert +
238-
originalContent.slice(insertionIndex);
239-
240-
return {
241-
success: true,
242-
newContent,
243-
metadata: {},
244-
};
245-
}
246-
247-
function getIndexForLineOffset(content: string, lineOffset: number): number | null {
248-
if (lineOffset === 0) {
249-
return 0;
250-
}
251-
252-
let linesAdvanced = 0;
253-
let cursor = 0;
254-
255-
while (linesAdvanced < lineOffset) {
256-
const nextNewline = content.indexOf("\n", cursor);
257-
if (nextNewline === -1) {
258-
if (linesAdvanced + 1 === lineOffset) {
259-
return content.length;
260-
}
261-
return null;
262-
}
263-
264-
linesAdvanced += 1;
265-
cursor = nextNewline + 1;
266-
267-
if (linesAdvanced === lineOffset) {
268-
return cursor;
269-
}
270-
}
271-
272-
return null;
273-
}
274-
275-
function countLines(content: string): number {
276-
if (content.length === 0) {
277-
return 1;
278-
}
279-
280-
let count = 1;
281-
for (const char of content) {
282-
if (char === "\n") {
283-
count += 1;
284-
}
285-
}
286-
return count;
287-
}

src/types/tools.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,6 @@ export interface FileEditErrorResult {
7474
export interface FileEditInsertToolArgs {
7575
file_path: string;
7676
content: string;
77-
/**
78-
* Optional line offset fallback when guards are not provided.
79-
* 1-indexed: 0 inserts at the top, 1 after line 1, etc.
80-
*/
81-
line_offset?: number;
82-
create?: boolean;
8377
/** Optional substring that must appear immediately before the insertion point */
8478
before?: string;
8579
/** Optional substring that must appear immediately after the insertion point */

src/utils/tools/toolDefinitions.ts

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -110,26 +110,13 @@ export const TOOL_DEFINITIONS = {
110110
},
111111
file_edit_insert: {
112112
description:
113-
"Insert content into a file using either a line offset or substring guards. " +
114-
"Provide at least one of before/after/line_offset so the operation is anchored. " +
115-
"When using guards, supply exactly one of before or after (not both). " +
113+
"Insert content into a file using substring guards. " +
114+
"Provide exactly one of before or after to anchor the operation. " +
116115
`Optional before/after substrings must uniquely match surrounding content. ${TOOL_EDIT_WARNING}`,
117116
schema: z
118117
.object({
119118
file_path: z.string().describe("The absolute path to the file to edit"),
120119
content: z.string().describe("The content to insert"),
121-
line_offset: z
122-
.number()
123-
.int()
124-
.min(0)
125-
.optional()
126-
.describe(
127-
"Optional 1-indexed line position (0 = insert at top, 1 inserts after line 1, etc.)"
128-
),
129-
create: z
130-
.boolean()
131-
.optional()
132-
.describe("If true, create the file if it doesn't exist (default: false)"),
133120
before: z
134121
.string()
135122
.min(1)
@@ -141,15 +128,10 @@ export const TOOL_DEFINITIONS = {
141128
.optional()
142129
.describe("Optional substring that must appear immediately after the insertion point"),
143130
})
144-
.refine(
145-
(data) =>
146-
data.line_offset !== undefined || data.before !== undefined || data.after !== undefined,
147-
{
148-
message:
149-
"Provide at least one of line_offset, before, or after to anchor the insertion point.",
150-
path: ["line_offset"],
151-
}
152-
)
131+
.refine((data) => data.before !== undefined || data.after !== undefined, {
132+
message: "Provide either before or after to anchor the insertion point.",
133+
path: ["before"],
134+
})
153135
.refine((data) => !(data.before !== undefined && data.after !== undefined), {
154136
message: "Provide only one of before or after (not both).",
155137
path: ["before"],

0 commit comments

Comments
 (0)