























@@ -13,9 +13,15 @@ import {
13131414const mocks = vi.hoisted(() => {
1515const cleanup = vi.fn(async () => {});
16+const getBuffer = vi.fn(async () => ({
17+buffer: Buffer.from("remote-image"),
18+fileName: "photo.png",
19+mime: "image/png",
20+size: 12,
21+}));
1622return {
1723buildProviderRegistry: vi.fn(() => new Map()),
18-createMediaAttachmentCache: vi.fn(() => ({ cleanup })),
24+createMediaAttachmentCache: vi.fn(() => ({ cleanup, getBuffer })),
1925normalizeMediaAttachments: vi.fn<() => MediaAttachment[]>(() => []),
2026normalizeMediaProviderId: vi.fn((provider: string) => provider.trim().toLowerCase()),
2127buildMediaUnderstandingRegistry: vi.fn(() => new Map()),
@@ -24,6 +30,7 @@ const mocks = vi.hoisted(() => {
2430describeImageWithModel: vi.fn(async () => ({ text: "generic image ok", model: "vision" })),
2531runCapability: vi.fn(),
2632 cleanup,
33+ getBuffer,
2734};
2835});
2936@@ -71,6 +78,13 @@ describe("media-understanding runtime", () => {
7178mocks.runCapability.mockReset();
7279mocks.cleanup.mockReset();
7380mocks.cleanup.mockResolvedValue(undefined);
81+mocks.getBuffer.mockReset();
82+mocks.getBuffer.mockResolvedValue({
83+buffer: Buffer.from("remote-image"),
84+fileName: "photo.png",
85+mime: "image/png",
86+size: 12,
87+});
7488});
75897690it("returns disabled state without loading providers", async () => {
@@ -201,6 +215,36 @@ describe("media-understanding runtime", () => {
201215});
202216});
203217218+it("passes image file URLs as remote media understanding inputs", async () => {
219+const output: MediaUnderstandingOutput = {
220+kind: "image.description",
221+attachmentIndex: 0,
222+provider: "vision-plugin",
223+model: "vision-v1",
224+text: "image ok",
225+};
226+const media = [{ index: 0, url: "https://example.com/photo.png", mime: "image/png" }];
227+mocks.normalizeMediaAttachments.mockReturnValue(media);
228+mocks.runCapability.mockResolvedValue({ outputs: [output] });
229+230+await describeImageFile({
231+filePath: "https://example.com/photo.png",
232+mediaUrl: "https://example.com/photo.png",
233+mime: "image/png",
234+cfg: {} as OpenClawConfig,
235+agentDir: "/tmp/agent",
236+});
237+238+expect(mocks.normalizeMediaAttachments).toHaveBeenCalledWith({
239+MediaUrl: "https://example.com/photo.png",
240+MediaType: "image/png",
241+});
242+expect(requireRunCapabilityRequest()).toMatchObject({
243+ctx: { MediaUrl: "https://example.com/photo.png", MediaType: "image/png" },
244+ media,
245+});
246+});
247+204248it("passes workspaceDir through audio and video file helpers", async () => {
205249mocks.runCapability.mockResolvedValue({
206250outputs: [],
@@ -251,7 +295,7 @@ describe("media-understanding runtime", () => {
251295it("passes per-request image prompts into media understanding config", async () => {
252296const media = [{ index: 0, path: "/tmp/sample.jpg", mime: "image/jpeg" }];
253297const providerRegistry = new Map();
254-const cache = { cleanup: mocks.cleanup };
298+const cache = { cleanup: mocks.cleanup, getBuffer: mocks.getBuffer };
255299const output: MediaUnderstandingOutput = {
256300kind: "image.description",
257301attachmentIndex: 0,
@@ -347,6 +391,27 @@ describe("media-understanding runtime", () => {
347391});
348392});
349393394+it("preserves fetched metadata for explicit model URL inputs", async () => {
395+await describeImageFileWithModel({
396+filePath: "https://example.com/photo.png",
397+mediaUrl: "https://example.com/photo.png",
398+provider: "zai",
399+model: "glm-4.6v",
400+prompt: "Describe it",
401+cfg: {} as OpenClawConfig,
402+agentDir: "/tmp/agent",
403+});
404+405+expect(mocks.describeImageWithModel).toHaveBeenCalledWith(
406+expect.objectContaining({
407+buffer: Buffer.from("remote-image"),
408+fileName: "photo.png",
409+mime: "image/png",
410+}),
411+);
412+expect(mocks.cleanup).toHaveBeenCalledTimes(1);
413+});
414+350415it("routes direct image description through a provider-specific image hook", async () => {
351416const describeImage = vi.fn(async () => ({
352417text: "image ok",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。