






















@@ -27,13 +27,15 @@ const hasAvailableAuthForProviderMock = vi.hoisted(() =>
2727);
2828const readRemoteMediaBufferMock = vi.hoisted(() => vi.fn());
2929const runFfmpegMock = vi.hoisted(() => vi.fn());
30+const convertHeicToJpegMock = vi.hoisted(() => vi.fn());
3031const runExecMock = vi.hoisted(() => vi.fn());
31323233let applyMediaUnderstanding: typeof import("./apply.js").applyMediaUnderstanding;
3334let clearMediaUnderstandingBinaryCacheForTests: typeof import("./runner.js").clearMediaUnderstandingBinaryCacheForTests;
3435const mockedResolveApiKey = resolveApiKeyForProviderMock;
3536const mockedReadRemoteMediaBuffer = readRemoteMediaBufferMock;
3637const mockedRunFfmpeg = runFfmpegMock;
38+const mockedConvertHeicToJpeg = convertHeicToJpegMock;
3739const mockedRunExec = runExecMock;
38403941const TEMP_MEDIA_PREFIX = "openclaw-media-";
@@ -284,6 +286,7 @@ describe("applyMediaUnderstanding", () => {
284286}));
285287vi.doMock("../media/media-services.js", () => ({
286288runFfmpeg: runFfmpegMock,
289+convertHeicToJpeg: convertHeicToJpegMock,
287290}));
288291vi.doMock("../process/exec.js", () => ({
289292runExec: runExecMock,
@@ -336,6 +339,8 @@ describe("applyMediaUnderstanding", () => {
336339hasAvailableAuthForProviderMock.mockClear();
337340mockedReadRemoteMediaBuffer.mockClear();
338341mockedRunFfmpeg.mockReset();
342+mockedConvertHeicToJpeg.mockReset();
343+mockedConvertHeicToJpeg.mockResolvedValue(Buffer.from("jpeg-normalized"));
339344mockedRunExec.mockReset();
340345mockedReadRemoteMediaBuffer.mockResolvedValue({
341346buffer: createSafeAudioFixtureBuffer(2048),
@@ -1160,6 +1165,53 @@ describe("applyMediaUnderstanding", () => {
11601165);
11611166});
116211671168+it("normalizes HEIC images before tools.media.image provider execution", async () => {
1169+const imagePath = await createTempMediaFile({
1170+fileName: "photo.heic",
1171+content: "heic-source",
1172+});
1173+const describeImage = vi.fn(async () => ({ text: "normalized image" }));
1174+const ctx: MsgContext = {
1175+Body: "<media:image>",
1176+MediaPath: imagePath,
1177+MediaType: "image/heic",
1178+};
1179+const cfg: OpenClawConfig = {
1180+tools: {
1181+media: {
1182+image: {
1183+enabled: true,
1184+models: [{ provider: "openai", model: "gpt-5.4" }],
1185+},
1186+},
1187+},
1188+};
1189+1190+const result = await applyMediaUnderstanding({
1191+ ctx,
1192+ cfg,
1193+agentDir: "/tmp/openclaw-agent",
1194+providers: {
1195+openai: {
1196+id: "openai",
1197+capabilities: ["image"],
1198+ describeImage,
1199+},
1200+},
1201+});
1202+1203+expect(result.appliedImage).toBe(true);
1204+expect(mockedConvertHeicToJpeg).toHaveBeenCalledWith(Buffer.from("heic-source"));
1205+expect(describeImage).toHaveBeenCalledWith(
1206+expect.objectContaining({
1207+buffer: Buffer.from("jpeg-normalized"),
1208+fileName: "photo.heic",
1209+mime: "image/jpeg",
1210+}),
1211+);
1212+expect(ctx.Body).toBe("[Image]\nDescription:\nnormalized image");
1213+});
1214+11631215it("uses active model when enabled and models are missing", async () => {
11641216const audioPath = await createTempMediaFile({
11651217fileName: "fallback.ogg",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。