Fix edit tool failing on files with UTF-8 BOM

Strip BOM before matching (LLM won't include invisible BOM in oldText),
restore on write.

Based on #394 by @prathamdby
This commit is contained in:
Mario Zechner 2026-01-02 01:57:24 +01:00
parent 6e4270a286
commit d9adf659ca
4 changed files with 30 additions and 4 deletions

View file

@ -24,6 +24,11 @@ export function restoreLineEndings(text: string, ending: "\r\n" | "\n"): string
return ending === "\r\n" ? text.replace(/\n/g, "\r\n") : text;
}
/** Strip UTF-8 BOM if present, return both the BOM (if any) and the text without it */
export function stripBom(content: string): { bom: string; text: string } {
return content.startsWith("\uFEFF") ? { bom: "\uFEFF", text: content.slice(1) } : { bom: "", text: content };
}
/**
* Generate a unified diff string with line numbers and context.
* Returns both the diff string and the first changed line number (in the new file).
@ -160,7 +165,10 @@ export async function computeEditDiff(
}
// Read the file
const content = await readFile(absolutePath, "utf-8");
const rawContent = await readFile(absolutePath, "utf-8");
// Strip BOM before matching (LLM won't include invisible BOM in oldText)
const { text: content } = stripBom(rawContent);
const normalizedContent = normalizeToLF(content);
const normalizedOldText = normalizeToLF(oldText);

View file

@ -2,7 +2,7 @@ import type { AgentTool } from "@mariozechner/pi-agent-core";
import { Type } from "@sinclair/typebox";
import { constants } from "fs";
import { access, readFile, writeFile } from "fs/promises";
import { detectLineEnding, generateDiffString, normalizeToLF, restoreLineEndings } from "./edit-diff.js";
import { detectLineEnding, generateDiffString, normalizeToLF, restoreLineEndings, stripBom } from "./edit-diff.js";
import { resolveToCwd } from "./path-utils.js";
const editSchema = Type.Object({
@ -74,13 +74,16 @@ export function createEditTool(cwd: string): AgentTool<typeof editSchema> {
}
// Read the file
const content = await readFile(absolutePath, "utf-8");
const rawContent = await readFile(absolutePath, "utf-8");
// Check if aborted after reading
if (aborted) {
return;
}
// Strip BOM before matching (LLM won't include invisible BOM in oldText)
const { bom, text: content } = stripBom(rawContent);
const originalEnding = detectLineEnding(content);
const normalizedContent = normalizeToLF(content);
const normalizedOldText = normalizeToLF(oldText);
@ -140,7 +143,7 @@ export function createEditTool(cwd: string): AgentTool<typeof editSchema> {
return;
}
const finalContent = restoreLineEndings(normalizedNewContent, originalEnding);
const finalContent = bom + restoreLineEndings(normalizedNewContent, originalEnding);
await writeFile(absolutePath, finalContent, "utf-8");
// Check if aborted after writing