Skip to content

Commit a0e6048

Browse files
authored
fix(agents): preserve mixed image attachment order (#99902)
1 parent f27d494 commit a0e6048

2 files changed

Lines changed: 79 additions & 6 deletions

File tree

src/auto-reply/reply/current-turn-images.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,59 @@ describe("resolveCurrentTurnImages", () => {
6161
});
6262
});
6363

64+
it("preserves the full order when only inline image payloads are present", async () => {
65+
const inlineImage = {
66+
type: "image" as const,
67+
data: Buffer.from("inline").toString("base64"),
68+
mimeType: "image/png",
69+
};
70+
71+
const result = await resolveCurrentTurnImages({
72+
ctx: { Body: "compare these" } satisfies MsgContext,
73+
cfg: {} as OpenClawConfig,
74+
images: [inlineImage],
75+
imageOrder: ["offloaded", "inline", "offloaded"],
76+
});
77+
78+
expect(result).toEqual({
79+
images: [inlineImage],
80+
imageOrder: ["offloaded", "inline", "offloaded"],
81+
});
82+
});
83+
84+
it("preserves all-offloaded image order without inline payloads", async () => {
85+
const result = await resolveCurrentTurnImages({
86+
ctx: { Body: "compare these" } satisfies MsgContext,
87+
cfg: {} as OpenClawConfig,
88+
images: [],
89+
imageOrder: ["offloaded", "offloaded"],
90+
});
91+
92+
expect(result).toEqual({
93+
imageOrder: ["offloaded", "offloaded"],
94+
});
95+
});
96+
97+
it("preserves interleaved offloaded slots around inline image payloads", async () => {
98+
const inlineImages = ["first", "second"].map((data) => ({
99+
type: "image" as const,
100+
data: Buffer.from(data).toString("base64"),
101+
mimeType: "image/png",
102+
}));
103+
104+
const result = await resolveCurrentTurnImages({
105+
ctx: { Body: "compare these" } satisfies MsgContext,
106+
cfg: {} as OpenClawConfig,
107+
images: inlineImages,
108+
imageOrder: ["inline", "offloaded", "inline"],
109+
});
110+
111+
expect(result).toEqual({
112+
images: inlineImages,
113+
imageOrder: ["inline", "offloaded", "inline"],
114+
});
115+
});
116+
64117
it("appends extracted PDF page images without dropping current image attachments", async () => {
65118
await withTempDir({ prefix: "openclaw-current-turn-pdf-images-" }, async (base) => {
66119
const imagePath = path.join(base, "photo.png");

src/auto-reply/reply/current-turn-images.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type CurrentImageAttachment = {
2020
};
2121

2222
type OrderedTurnImage = {
23-
image: ImageContent;
23+
image?: ImageContent;
2424
imageOrder: PromptImageOrderEntry;
2525
sourceIndex?: number;
2626
sequence: number;
@@ -105,13 +105,32 @@ function appendOrderedImages(params: {
105105
imageOrder?: PromptImageOrderEntry[];
106106
sourceIndex?: number;
107107
}) {
108-
if (!params.images || params.images.length === 0) {
108+
const images = params.images ?? [];
109+
if (!params.imageOrder || params.imageOrder.length === 0) {
110+
for (const image of images) {
111+
params.entries.push({
112+
image,
113+
imageOrder: "inline",
114+
sourceIndex: params.sourceIndex,
115+
sequence: params.entries.length,
116+
});
117+
}
109118
return;
110119
}
111-
for (const [index, image] of params.images.entries()) {
120+
121+
let inlineIndex = 0;
122+
for (const imageOrder of params.imageOrder) {
123+
params.entries.push({
124+
image: imageOrder === "inline" ? images[inlineIndex++] : undefined,
125+
imageOrder,
126+
sourceIndex: params.sourceIndex,
127+
sequence: params.entries.length,
128+
});
129+
}
130+
while (inlineIndex < images.length) {
112131
params.entries.push({
113-
image,
114-
imageOrder: params.imageOrder?.[index] ?? "inline",
132+
image: images[inlineIndex++],
133+
imageOrder: "inline",
115134
sourceIndex: params.sourceIndex,
116135
sequence: params.entries.length,
117136
});
@@ -134,8 +153,9 @@ function resolveMergedTurnImages(entries: OrderedTurnImage[]): {
134153
}
135154
return left.sequence - right.sequence;
136155
});
156+
const images = merged.flatMap((entry) => (entry.image ? [entry.image] : []));
137157
return {
138-
images: merged.map((entry) => entry.image),
158+
...(images.length > 0 ? { images } : {}),
139159
imageOrder: merged.map((entry) => entry.imageOrder),
140160
};
141161
}

0 commit comments

Comments
 (0)