fix(acp): include tool image attachments in updates (#25128)

This commit is contained in:
Steffen Deusch
2026-05-09 18:45:44 +02:00
committed by GitHub
parent 347526e9fd
commit 817cb076a8
2 changed files with 246 additions and 63 deletions

View File

@@ -347,33 +347,7 @@ export class Agent implements ACPAgent {
this.toolStarts.delete(part.callID)
this.shellSnapshots.delete(part.callID)
const kind = toToolKind(part.tool)
const content: ToolCallContent[] = [
{
type: "content",
content: {
type: "text",
text: part.state.output,
},
},
]
if (kind === "edit") {
const input = part.state.input
const filePath = typeof input["filePath"] === "string" ? input["filePath"] : ""
const oldText = typeof input["oldString"] === "string" ? input["oldString"] : ""
const newText =
typeof input["newString"] === "string"
? input["newString"]
: typeof input["content"] === "string"
? input["content"]
: ""
content.push({
type: "diff",
path: filePath,
oldText,
newText,
})
}
const content = completedToolContent(part, kind)
if (part.tool === "todowrite") {
const parsedTodos = decodeTodos(part.state.output)
@@ -413,10 +387,7 @@ export class Agent implements ACPAgent {
content,
title: part.state.title,
rawInput: part.state.input,
rawOutput: {
output: part.state.output,
metadata: part.state.metadata,
},
rawOutput: completedToolRawOutput(part),
},
})
.catch((error) => {
@@ -860,33 +831,7 @@ export class Agent implements ACPAgent {
this.toolStarts.delete(part.callID)
this.shellSnapshots.delete(part.callID)
const kind = toToolKind(part.tool)
const content: ToolCallContent[] = [
{
type: "content",
content: {
type: "text",
text: part.state.output,
},
},
]
if (kind === "edit") {
const input = part.state.input
const filePath = typeof input["filePath"] === "string" ? input["filePath"] : ""
const oldText = typeof input["oldString"] === "string" ? input["oldString"] : ""
const newText =
typeof input["newString"] === "string"
? input["newString"]
: typeof input["content"] === "string"
? input["content"]
: ""
content.push({
type: "diff",
path: filePath,
oldText,
newText,
})
}
const content = completedToolContent(part, kind)
if (part.tool === "todowrite") {
const parsedTodos = decodeTodos(part.state.output)
@@ -926,10 +871,7 @@ export class Agent implements ACPAgent {
content,
title: part.state.title,
rawInput: part.state.input,
rawOutput: {
output: part.state.output,
metadata: part.state.metadata,
},
rawOutput: completedToolRawOutput(part),
},
})
.catch((err) => {
@@ -1655,6 +1597,70 @@ function toLocations(toolName: string, input: Record<string, any>): { path: stri
}
}
function completedToolContent(part: ToolPart, kind: ToolKind): ToolCallContent[] {
if (part.state.status !== "completed") return []
const content: ToolCallContent[] = [
{
type: "content",
content: {
type: "text",
text: part.state.output,
},
},
]
if (kind === "edit") {
const input = part.state.input
const filePath = typeof input["filePath"] === "string" ? input["filePath"] : ""
const oldText = typeof input["oldString"] === "string" ? input["oldString"] : ""
const newText =
typeof input["newString"] === "string"
? input["newString"]
: typeof input["content"] === "string"
? input["content"]
: ""
content.push({
type: "diff",
path: filePath,
oldText,
newText,
})
}
content.push(...imageContents(part.state.attachments ?? []))
return content
}
function completedToolRawOutput(part: ToolPart) {
if (part.state.status !== "completed") return {}
return {
output: part.state.output,
metadata: part.state.metadata,
...(part.state.attachments?.length ? { attachments: part.state.attachments } : {}),
}
}
function imageContents(attachments: Array<{ mime: string; url: string }>): ToolCallContent[] {
return attachments.flatMap((attachment): ToolCallContent[] => {
const match = attachment.url.match(/^data:([^;,]+)(?:;[^,]*)*;base64,(.*)$/)
const mime = match?.[1] ?? attachment.mime
if (!mime.startsWith("image/")) return []
const data = match?.[2]
if (data === undefined) return []
return [
{
type: "content" as const,
content: {
type: "image" as const,
mimeType: mime,
data,
},
},
]
})
}
async function defaultModel(config: ACPConfig, cwd?: string): Promise<{ providerID: ProviderID; modelID: ModelID }> {
const sdk = config.sdk
const configured = config.defaultModel