fix(acp): clean read tool display content (#30569)
This commit is contained in:
@@ -251,6 +251,11 @@ function completedTool(
|
||||
callID: string,
|
||||
output = "done",
|
||||
attachments: Extract<ToolPart["state"], { status: "completed" }>["attachments"] = [],
|
||||
options: {
|
||||
readonly tool?: string
|
||||
readonly input?: Record<string, unknown>
|
||||
readonly metadata?: Record<string, unknown>
|
||||
} = {},
|
||||
) {
|
||||
return {
|
||||
id: `part_${callID}`,
|
||||
@@ -258,13 +263,13 @@ function completedTool(
|
||||
messageID: `msg_${callID}`,
|
||||
type: "tool",
|
||||
callID,
|
||||
tool: "bash",
|
||||
tool: options.tool ?? "bash",
|
||||
state: {
|
||||
status: "completed",
|
||||
input: { cmd: "printf done" },
|
||||
input: options.input ?? { cmd: "printf done" },
|
||||
output,
|
||||
title: "bash",
|
||||
metadata: { exit: 0 },
|
||||
metadata: options.metadata ?? { exit: 0 },
|
||||
time: { start: Date.now() - 1, end: Date.now() },
|
||||
...(attachments.length ? { attachments } : {}),
|
||||
},
|
||||
@@ -605,6 +610,55 @@ describe("acp event routing", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("emits clean read display content and preserves rawOutput", async () => {
|
||||
const harness = createHarness()
|
||||
await Effect.runPromise(harness.session.create({ id: "ses_read", cwd: "/workspace" }))
|
||||
const output = [
|
||||
"<path>/workspace/file.ts</path>",
|
||||
"<type>file</type>",
|
||||
"<content>",
|
||||
"1: import { value } from './value'",
|
||||
"2: export { value }",
|
||||
"",
|
||||
"(End of file - total 2 lines)",
|
||||
"</content>",
|
||||
].join("\n")
|
||||
const metadata = {
|
||||
display: {
|
||||
type: "file",
|
||||
path: "/workspace/file.ts",
|
||||
text: "import { value } from './value'\nexport { value }",
|
||||
lineStart: 1,
|
||||
lineEnd: 2,
|
||||
totalLines: 2,
|
||||
truncated: false,
|
||||
},
|
||||
}
|
||||
|
||||
await harness.subscription.handle(
|
||||
toolUpdated(
|
||||
completedTool("ses_read", "call_read", output, [], {
|
||||
tool: "read",
|
||||
input: { filePath: "/workspace/file.ts" },
|
||||
metadata,
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
expect(harness.updates.at(-1)?.update).toMatchObject({
|
||||
sessionUpdate: "tool_call_update",
|
||||
toolCallId: "call_read",
|
||||
status: "completed",
|
||||
content: [
|
||||
{
|
||||
type: "content",
|
||||
content: { type: "text", text: "import { value } from './value'\nexport { value }" },
|
||||
},
|
||||
],
|
||||
rawOutput: { output, metadata },
|
||||
})
|
||||
})
|
||||
|
||||
it("emits error tool output", async () => {
|
||||
const harness = createHarness()
|
||||
await Effect.runPromise(harness.session.create({ id: "ses_error", cwd: "/workspace" }))
|
||||
|
||||
@@ -104,6 +104,46 @@ describe("acp tool conversion", () => {
|
||||
])
|
||||
})
|
||||
|
||||
test("uses clean read display text for completed content", () => {
|
||||
const output = [
|
||||
"<path>/tmp/file.ts</path>",
|
||||
"<type>file</type>",
|
||||
"<content>",
|
||||
"7: first",
|
||||
"8: second",
|
||||
"",
|
||||
"(End of file - total 8 lines)",
|
||||
"</content>",
|
||||
].join("\n")
|
||||
const state = {
|
||||
status: "completed" as const,
|
||||
input: { filePath: "/tmp/file.ts" },
|
||||
output,
|
||||
metadata: {
|
||||
display: {
|
||||
type: "file",
|
||||
path: "/tmp/file.ts",
|
||||
text: "first\nsecond",
|
||||
lineStart: 7,
|
||||
lineEnd: 8,
|
||||
totalLines: 8,
|
||||
truncated: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expect(completedToolContent("read", state)).toEqual([
|
||||
{
|
||||
type: "content",
|
||||
content: { type: "text", text: "first\nsecond" },
|
||||
},
|
||||
])
|
||||
expect(completedToolRawOutput(state)).toEqual({
|
||||
output,
|
||||
metadata: state.metadata,
|
||||
})
|
||||
})
|
||||
|
||||
test("builds completed raw output with optional metadata and attachments", () => {
|
||||
const attachments = [
|
||||
{
|
||||
|
||||
@@ -428,6 +428,15 @@ describe("tool.read truncation", () => {
|
||||
const result = yield* run({ filePath: path.join(test.directory, "small.txt") })
|
||||
expect(result.metadata.truncated).toBe(false)
|
||||
expect(result.output).toContain("End of file")
|
||||
expect(result.metadata.display).toMatchObject({
|
||||
type: "file",
|
||||
path: path.join(test.directory, "small.txt"),
|
||||
text: "hello world",
|
||||
lineStart: 1,
|
||||
lineEnd: 1,
|
||||
totalLines: 1,
|
||||
truncated: false,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -495,6 +504,14 @@ describe("tool.read truncation", () => {
|
||||
const result = yield* exec(dir, { filePath: path.join(dir, "dir"), offset: 6, limit: 5 })
|
||||
expect(result.metadata.truncated).toBe(false)
|
||||
expect(result.output).not.toContain("Showing 5 of 10 entries")
|
||||
expect(result.metadata.display).toMatchObject({
|
||||
type: "directory",
|
||||
path: path.join(dir, "dir"),
|
||||
entries: ["file-5.txt", "file-6.txt", "file-7.txt", "file-8.txt", "file-9.txt"],
|
||||
offset: 6,
|
||||
totalEntries: 10,
|
||||
truncated: false,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user