












@@ -5,6 +5,7 @@ import path from "node:path";
55import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
66import type { OpenClawConfig } from "../../config/config.js";
77import type { ModelDefinitionConfig } from "../../config/types.models.js";
8+import { isInboundPathAllowed } from "../../media/inbound-path-policy.js";
89import { encodePngRgba, fillPixel } from "../../media/png-encode.js";
910import type {
1011ImageDescriptionRequest,
@@ -537,7 +538,17 @@ const moonshotProvider = {
537538describeImages: describeMoonshotImages,
538539} satisfies MediaUnderstandingProvider;
539540540-function installImageUnderstandingProviderStubs(...providers: MediaUnderstandingProvider[]) {
541+function installImageUnderstandingProviderDeps(
542+providers: MediaUnderstandingProvider[],
543+options?: {
544+loadImageWebMediaRuntime?: NonNullable<
545+Parameters<typeof testing.setProviderDepsForTest>[0]
546+>["loadImageWebMediaRuntime"];
547+resolveModelAsync?: NonNullable<
548+Parameters<typeof testing.setProviderDepsForTest>[0]
549+>["resolveModelAsync"];
550+},
551+) {
541552imageProviderHarness.setProviders(providers);
542553const defaultImageModels = new Map<string, string>([
543554["anthropic", "claude-opus-4-6"],
@@ -563,6 +574,52 @@ function installImageUnderstandingProviderStubs(...providers: MediaUnderstanding
563574capability === "image" ? ["openai", "anthropic"] : [],
564575resolveDefaultMediaModel: ({ providerId, capability }) =>
565576capability === "image" ? defaultImageModels.get(providerId.toLowerCase()) : undefined,
577+ ...(options?.resolveModelAsync ? { resolveModelAsync: options.resolveModelAsync } : {}),
578+ ...(options?.loadImageWebMediaRuntime
579+ ? { loadImageWebMediaRuntime: options.loadImageWebMediaRuntime }
580+ : {}),
581+});
582+}
583+584+function installImageUnderstandingProviderStubs(...providers: MediaUnderstandingProvider[]) {
585+installImageUnderstandingProviderDeps(providers);
586+}
587+588+function installFastLocalImageProviderStubs(...providers: MediaUnderstandingProvider[]) {
589+installImageUnderstandingProviderDeps(providers, {
590+resolveModelAsync: async (provider, model) => ({
591+model: {
592+id: model,
593+ provider,
594+input: ["text", "image"],
595+} as never,
596+authStorage: {} as never,
597+modelRegistry: {} as never,
598+}),
599+loadImageWebMediaRuntime: async () => ({
600+loadWebMedia: async (mediaUrl, options) => {
601+if (
602+!isInboundPathAllowed({
603+filePath: mediaUrl,
604+roots: options?.inboundRoots ?? [],
605+})
606+) {
607+throw new Error(`Local media path is not under an allowed directory: ${mediaUrl}`);
608+}
609+return {
610+buffer: await fs.readFile(mediaUrl),
611+contentType: "image/png",
612+kind: "image",
613+fileName: path.basename(mediaUrl),
614+};
615+},
616+optimizeImageBufferForWebMedia: async ({ buffer, contentType, fileName }) => ({
617+ buffer,
618+contentType: contentType ?? "image/png",
619+kind: "image",
620+ fileName,
621+}),
622+}),
566623});
567624}
568625@@ -1644,14 +1701,34 @@ describe("image tool implicit imageModel config", () => {
16441701});
1645170216461703it("allows image paths from the current iMessage account attachment roots", async () => {
1647-const fetch = stubMinimaxOkFetch();
16481704await withTempAgentDir(async (agentDir) => {
1705+const describeImage = vi.fn(async (params: ImageDescriptionRequest) => ({
1706+text: "ok",
1707+model: params.model,
1708+}));
1709+installFastLocalImageProviderStubs({
1710+id: "ollama",
1711+capabilities: ["image"],
1712+ describeImage,
1713+});
16491714const attachmentRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-imessage-root-"));
16501715const imagePath = path.join(attachmentRoot, "photo.png");
16511716await fs.writeFile(imagePath, Buffer.from(ONE_PIXEL_PNG_B64, "base64"));
16521717try {
16531718const cfg: OpenClawConfig = {
1654- ...createMinimaxImageConfig(),
1719+agents: {
1720+defaults: {
1721+imageModel: { primary: "ollama/moondream" },
1722+},
1723+},
1724+models: {
1725+providers: {
1726+ollama: {
1727+baseUrl: "http://localhost:11434",
1728+models: [makeModelDefinition("moondream", ["text", "image"])],
1729+},
1730+},
1731+},
16551732channels: {
16561733imessage: {
16571734accounts: {
@@ -1676,16 +1753,24 @@ describe("image tool implicit imageModel config", () => {
16761753});
1677175416781755await expectImageToolExecOk(withImessage, imagePath);
1679-expect(fetch).toHaveBeenCalledTimes(1);
1756+expect(describeImage).toHaveBeenCalledTimes(1);
16801757} finally {
16811758await fs.rm(attachmentRoot, { recursive: true, force: true });
16821759}
16831760});
16841761}, 240_000);
1685176216861763it("allows image paths from current iMessage wildcard attachment roots", async () => {
1687-const fetch = stubMinimaxOkFetch();
16881764await withTempAgentDir(async (agentDir) => {
1765+const describeImage = vi.fn(async (params: ImageDescriptionRequest) => ({
1766+text: "ok",
1767+model: params.model,
1768+}));
1769+installFastLocalImageProviderStubs({
1770+id: "ollama",
1771+capabilities: ["image"],
1772+ describeImage,
1773+});
16891774const attachmentRootParent = await fs.mkdtemp(
16901775path.join(os.tmpdir(), "openclaw-imessage-wildcard-root-"),
16911776);
@@ -1695,7 +1780,19 @@ describe("image tool implicit imageModel config", () => {
16951780await fs.writeFile(imagePath, Buffer.from(ONE_PIXEL_PNG_B64, "base64"));
16961781try {
16971782const cfg: OpenClawConfig = {
1698- ...createMinimaxImageConfig(),
1783+agents: {
1784+defaults: {
1785+imageModel: { primary: "ollama/moondream" },
1786+},
1787+},
1788+models: {
1789+providers: {
1790+ollama: {
1791+baseUrl: "http://localhost:11434",
1792+models: [makeModelDefinition("moondream", ["text", "image"])],
1793+},
1794+},
1795+},
16991796channels: {
17001797imessage: {
17011798accounts: {
@@ -1715,7 +1812,7 @@ describe("image tool implicit imageModel config", () => {
17151812});
1716181317171814await expectImageToolExecOk(withImessage, imagePath);
1718-expect(fetch).toHaveBeenCalledTimes(1);
1815+expect(describeImage).toHaveBeenCalledTimes(1);
17191816} finally {
17201817await fs.rm(attachmentRootParent, { recursive: true, force: true });
17211818}
此內容由慣性聚合(RSS閱讀器)自動聚合整理,僅供閱讀參考。 原文來自 — 版權歸原作者所有。