fix(coding-agent): coerce stringified JSON edits in edit tool (#3370)
Some models (Opus 4.6, GLM-5.1) send the edits parameter as a JSON string instead of a parsed array. This fails AJV validation with 'must be array' and models fall back to sed/python. Parse stringified edits in prepareEditArguments before validation.
This commit is contained in:
@@ -92,14 +92,24 @@ function prepareEditArguments(input: unknown): EditToolInput {
|
|||||||
return input as EditToolInput;
|
return input as EditToolInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
const args = input as LegacyEditToolInput;
|
const args = input as Record<string, unknown>;
|
||||||
if (typeof args.oldText !== "string" || typeof args.newText !== "string") {
|
|
||||||
return input as EditToolInput;
|
// Some models (Opus 4.6, GLM-5.1) send edits as a JSON string instead of an array
|
||||||
|
if (typeof args.edits === "string") {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(args.edits);
|
||||||
|
if (Array.isArray(parsed)) args.edits = parsed;
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const edits = Array.isArray(args.edits) ? [...args.edits] : [];
|
const legacy = args as LegacyEditToolInput;
|
||||||
edits.push({ oldText: args.oldText, newText: args.newText });
|
if (typeof legacy.oldText !== "string" || typeof legacy.newText !== "string") {
|
||||||
const { oldText: _oldText, newText: _newText, ...rest } = args;
|
return args as EditToolInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
const edits = Array.isArray(legacy.edits) ? [...legacy.edits] : [];
|
||||||
|
edits.push({ oldText: legacy.oldText, newText: legacy.newText });
|
||||||
|
const { oldText: _oldText, newText: _newText, ...rest } = legacy;
|
||||||
return { ...rest, edits } as EditToolInput;
|
return { ...rest, edits } as EditToolInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,3 +88,29 @@ describe("edit tool prepareArguments", () => {
|
|||||||
expect(await readFile(filePath, "utf8")).toBe("after\n");
|
expect(await readFile(filePath, "utf8")).toBe("after\n");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("edit tool stringified edits", () => {
|
||||||
|
it("parses edits from a JSON string", () => {
|
||||||
|
const definition = createEditToolDefinition(process.cwd());
|
||||||
|
const prepared = definition.prepareArguments!({
|
||||||
|
path: "file.txt",
|
||||||
|
edits: JSON.stringify([{ oldText: "a", newText: "b" }]),
|
||||||
|
});
|
||||||
|
expect(prepared).toEqual({
|
||||||
|
path: "file.txt",
|
||||||
|
edits: [{ oldText: "a", newText: "b" }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("leaves edits alone when the string is not valid JSON", () => {
|
||||||
|
const definition = createEditToolDefinition(process.cwd());
|
||||||
|
const prepared = definition.prepareArguments!({
|
||||||
|
path: "file.txt",
|
||||||
|
edits: "not json",
|
||||||
|
});
|
||||||
|
expect(prepared).toEqual({
|
||||||
|
path: "file.txt",
|
||||||
|
edits: "not json",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user