mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-28 04:51:50 +00:00
fix: render Write tool calls that use path/file_text aliases (#1161)
Claude Code 2.1.280 (SDK 0.3.280, bumped in #1157) now accepts Write calls where the model sends `path` instead of `file_path`, or `file_text`/`file_content` instead of `content`. The CLI renames these for its own validation, but the streamed `tool_use` block keeps the model's original field names. So we showed the call as "Preparing file…" with no diff or location, even though the write went through. `toolInfoFromToolUse` now reads those fields the same way the CLI does. When both spellings are present, the canonical fields win. Nothing else in the 0.3.280 / 2.1.280 changelog needed an adapter change. I checked Opus 5.5 model rows, the hand-back frame header, `requires_action` during MCP elicitation, and headless elicitation cancellation. ## Test plan - New `toolInfoFromToolUse - Write input aliases` tests - `npm run build`, `npm run check`, `npm run test:run` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1867,6 +1867,43 @@ describe("toolInfoFromToolUse - ExitPlanMode", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("toolInfoFromToolUse - Write input aliases", () => {
|
||||
it.each([
|
||||
["path + file_text", { path: "/repo/a.ts", file_text: "x" }],
|
||||
["path + file_content", { path: "/repo/a.ts", file_content: "x" }],
|
||||
["file_path + file_text", { file_path: "/repo/a.ts", file_text: "x" }],
|
||||
])("renders %s like file_path + content", (_label, input) => {
|
||||
const info = toolInfoFromToolUse(
|
||||
{ name: "Write", id: "toolu_write_alias", input },
|
||||
false,
|
||||
"/repo",
|
||||
);
|
||||
|
||||
expect(info.title).toBe("Write a.ts");
|
||||
expect(info.locations).toEqual([{ path: "/repo/a.ts" }]);
|
||||
expect(info.content).toEqual([
|
||||
{ type: "diff", path: "/repo/a.ts", oldText: null, newText: "x" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("prefers the canonical fields when both spellings are present", () => {
|
||||
const info = toolInfoFromToolUse(
|
||||
{
|
||||
name: "Write",
|
||||
id: "toolu_write_both",
|
||||
input: { file_path: "/repo/a.ts", path: "/repo/b.ts", content: "a", file_text: "b" },
|
||||
},
|
||||
false,
|
||||
"/repo",
|
||||
);
|
||||
|
||||
expect(info.title).toBe("Write a.ts");
|
||||
expect(info.content).toEqual([
|
||||
{ type: "diff", path: "/repo/a.ts", oldText: null, newText: "a" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("toolInfoFromToolUse - undefined input regression", () => {
|
||||
it("Read with undefined input should not throw", () => {
|
||||
const toolUse = { name: "Read", id: "toolu_read_undef", input: undefined };
|
||||
|
||||
15
src/tools.ts
15
src/tools.ts
@@ -134,6 +134,19 @@ export function toDisplayPath(filePath: string, cwd?: string): string {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/** Read a Write tool_use input the way the CLI validates it. Since 2.1.280 the
|
||||
* CLI accepts `path` for `file_path` and `file_text`/`file_content` for
|
||||
* `content` when a model sends those spellings, but the streamed tool_use
|
||||
* block still carries them raw — without this the call renders as
|
||||
* "Preparing file…" with no diff while the write goes through. */
|
||||
function normalizeWriteInput(input: unknown): FileWriteInput | undefined {
|
||||
if (!input || typeof input !== "object") return undefined;
|
||||
const raw = input as Record<string, unknown>;
|
||||
const filePath = raw.file_path ?? (typeof raw.path === "string" ? raw.path : undefined);
|
||||
const content = raw.content ?? raw.file_text ?? raw.file_content;
|
||||
return { ...raw, file_path: filePath, content } as FileWriteInput;
|
||||
}
|
||||
|
||||
export function toolInfoFromToolUse(
|
||||
toolUse: any,
|
||||
supportsTerminalOutput: boolean = false,
|
||||
@@ -204,7 +217,7 @@ export function toolInfoFromToolUse(
|
||||
}
|
||||
|
||||
case "Write": {
|
||||
const input = toolUse.input as FileWriteInput | undefined;
|
||||
const input = normalizeWriteInput(toolUse.input);
|
||||
let content: ToolCallContent[] = [];
|
||||
if (input && input.file_path) {
|
||||
content = [
|
||||
|
||||
Reference in New Issue
Block a user