mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 09:01:14 +00:00
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:
parent
6e4270a286
commit
d9adf659ca
4 changed files with 30 additions and 4 deletions
|
|
@ -236,6 +236,7 @@ Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) fo
|
|||
- **Resuming session resets thinking level to off**: Initial model and thinking level were not saved to session file, causing `--resume`/`--continue` to default to `off`. ([#342](https://github.com/badlogic/pi-mono/issues/342) by [@aliou](https://github.com/aliou))
|
||||
- **Hook `tool_result` event ignores errors from custom tools**: The `tool_result` hook event was never emitted when tools threw errors, and always had `isError: false` for successful executions. Now emits the event with correct `isError` value in both success and error cases. ([#374](https://github.com/badlogic/pi-mono/issues/374) by [@nicobailon](https://github.com/nicobailon))
|
||||
- **Edit tool fails on Windows due to CRLF line endings**: Files with CRLF line endings now match correctly when LLMs send LF-only text. Line endings are normalized before matching and restored to original style on write. ([#355](https://github.com/badlogic/pi-mono/issues/355) by [@Pratham-Dubey](https://github.com/Pratham-Dubey))
|
||||
- **Edit tool fails on files with UTF-8 BOM**: Files with UTF-8 BOM marker could cause "text not found" errors since the LLM doesn't include the invisible BOM character. BOM is now stripped before matching and restored on write. ([#394](https://github.com/badlogic/pi-mono/pull/394) by [@prathamdby](https://github.com/prathamdby))
|
||||
- **Use bash instead of sh on Unix**: Fixed shell commands using `/bin/sh` instead of `/bin/bash` on Unix systems. ([#328](https://github.com/badlogic/pi-mono/pull/328) by [@dnouri](https://github.com/dnouri))
|
||||
- **OAuth login URL clickable**: Made OAuth login URLs clickable in terminal. ([#349](https://github.com/badlogic/pi-mono/pull/349) by [@Cursivez](https://github.com/Cursivez))
|
||||
- **Improved error messages**: Better error messages when `apiKey` or `model` are missing. ([#346](https://github.com/badlogic/pi-mono/pull/346) by [@ronyrus](https://github.com/ronyrus))
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -433,4 +433,18 @@ describe("edit tool CRLF handling", () => {
|
|||
}),
|
||||
).rejects.toThrow(/Found 2 occurrences/);
|
||||
});
|
||||
|
||||
it("should preserve UTF-8 BOM after edit", async () => {
|
||||
const testFile = join(testDir, "bom-test.txt");
|
||||
writeFileSync(testFile, "\uFEFFfirst\r\nsecond\r\nthird\r\n");
|
||||
|
||||
await editTool.execute("test-bom", {
|
||||
path: testFile,
|
||||
oldText: "second\n",
|
||||
newText: "REPLACED\n",
|
||||
});
|
||||
|
||||
const content = readFileSync(testFile, "utf-8");
|
||||
expect(content).toBe("\uFEFFfirst\r\nREPLACED\r\nthird\r\n");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue