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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user