fix(coding-agent): routed custom images as user content

Split image-bearing custom messages into developer text plus user image content before provider conversion so queued skill images stay out of developer slots.
This commit is contained in:
roboomp
2026-06-28 04:56:18 +00:00
parent 796bf51f46
commit bd05303fed
2 changed files with 58 additions and 1 deletions
+30 -1
View File
@@ -481,6 +481,30 @@ export function sanitizeRehydratedOpenAIResponsesAssistantMessage(message: Assis
};
}
function convertImageBearingCustomMessage(message: CustomMessage | HookMessage): Message[] | undefined {
if (typeof message.content === "string") return undefined;
const textBlocks = message.content.filter((content): content is TextContent => content.type === "text");
const imageBlocks = message.content.filter((content): content is ImageContent => content.type === "image");
if (imageBlocks.length === 0) return undefined;
const converted: Message[] = [];
if (textBlocks.length > 0) {
converted.push({
role: "developer",
content: textBlocks,
attribution: message.attribution,
timestamp: message.timestamp,
});
}
converted.push({
role: "user",
content: [{ type: "text", text: `Images attached to ${message.customType}.` }, ...imageBlocks],
attribution: message.attribution,
timestamp: message.timestamp,
});
return converted;
}
/**
* Transform AgentMessages (including custom types) to LLM-compatible Messages.
*
@@ -556,7 +580,12 @@ export function convertToLlm(messages: AgentMessage[]): Message[] {
return out;
}
case "custom":
case "hookMessage":
case "hookMessage": {
const split = convertImageBearingCustomMessage(m);
if (split) return split;
const converted = convertMessageToLlm(m);
return converted ? [converted] : [];
}
case "branchSummary":
case "compactionSummary":
case "user":
@@ -259,6 +259,34 @@ describe("convertToLlm custom message mapping", () => {
expectAttribution(converted[0], "user");
expect(inferCopilotInitiator(converted)).toBe("user");
});
it("routes custom-message images through a user message", () => {
const image: ImageContent = { type: "image", data: "c2tpbGw=", mimeType: "image/png" };
const messages: AgentMessage[] = [
{
role: "custom",
customType: "skill-prompt",
content: [{ type: "text", text: "Skill body" }, image],
display: true,
attribution: "user",
timestamp: Date.now(),
},
];
const converted = convertToLlm(messages);
expect(converted.map(message => message.role)).toEqual(["developer", "user"]);
expectAttribution(converted[0], "user");
expectAttribution(converted[1], "user");
if (converted[0]?.role !== "developer" || !Array.isArray(converted[0].content)) {
throw new Error("Expected developer skill text");
}
expect(converted[0].content).toEqual([{ type: "text", text: "Skill body" }]);
if (converted[1]?.role !== "user" || !Array.isArray(converted[1].content)) {
throw new Error("Expected user skill images");
}
expect(converted[1].content.filter(content => content.type === "image")).toEqual([image]);
});
});
function getUserText(message: AgentMessage | undefined): string {