

















@@ -920,6 +920,124 @@ describe("image tool implicit imageModel config", () => {
920920});
921921});
922922923+it("resolves providerless explicit image models from unique configured image providers", async () => {
924+await withTempAgentDir(async (agentDir) => {
925+const cfg: OpenClawConfig = {
926+agents: {
927+defaults: {
928+imageModel: {
929+primary: "moondream",
930+fallbacks: ["qwen2.5vl:7b", "G-2.5-f"],
931+},
932+},
933+},
934+models: {
935+providers: {
936+ollama: {
937+baseUrl: "http://localhost:11434",
938+models: [
939+makeModelDefinition("moondream", ["text", "image"]),
940+makeModelDefinition("qwen2.5vl:7b", ["text", "image"]),
941+makeModelDefinition("G-2.5-f", ["text", "image"]),
942+],
943+},
944+},
945+},
946+};
947+948+expect(resolveImageModelConfigForTool({ cfg, agentDir })).toEqual({
949+primary: "ollama/moondream",
950+fallbacks: ["ollama/qwen2.5vl:7b", "ollama/G-2.5-f"],
951+});
952+});
953+});
954+955+it("runs providerless explicit image models on the inferred provider", async () => {
956+await withTempAgentDir(async (agentDir) => {
957+const describeImage = vi.fn(async (params: ImageDescriptionRequest) => ({
958+text: `ok ${params.model}`,
959+model: params.model,
960+}));
961+installImageUnderstandingProviderStubs({
962+id: "ollama",
963+capabilities: ["image"],
964+ describeImage,
965+});
966+const cfg: OpenClawConfig = {
967+agents: {
968+defaults: {
969+imageModel: { primary: "moondream" },
970+},
971+},
972+models: {
973+providers: {
974+ollama: {
975+baseUrl: "http://localhost:11434",
976+models: [makeModelDefinition("moondream", ["text", "image"])],
977+},
978+},
979+},
980+};
981+982+const tool = requireImageTool(createImageTool({ config: cfg, agentDir }));
983+const result = await tool.execute("t1", {
984+prompt: "Describe this image in one word.",
985+image: `data:image/png;base64,${ONE_PIXEL_PNG_B64}`,
986+});
987+988+expect(describeImage).toHaveBeenCalledWith(
989+expect.objectContaining({ provider: "ollama", model: "moondream" }),
990+);
991+expect(result.content).toEqual(
992+expect.arrayContaining([expect.objectContaining({ type: "text", text: "ok moondream" })]),
993+);
994+});
995+});
996+997+it("rejects ambiguous providerless explicit image models", async () => {
998+await withTempAgentDir(async (agentDir) => {
999+const cfg: OpenClawConfig = {
1000+agents: {
1001+defaults: {
1002+imageModel: { primary: "moondream" },
1003+},
1004+},
1005+models: {
1006+providers: {
1007+ollama: {
1008+baseUrl: "http://localhost:11434",
1009+models: [makeModelDefinition("moondream", ["text", "image"])],
1010+},
1011+lmstudio: {
1012+baseUrl: "http://localhost:1234",
1013+models: [makeModelDefinition("moondream", ["text", "image"])],
1014+},
1015+},
1016+},
1017+};
1018+1019+expect(() => resolveImageModelConfigForTool({ cfg, agentDir })).toThrow(
1020+'Ambiguous image model "moondream"',
1021+);
1022+});
1023+});
1024+1025+it("keeps unmatched providerless explicit image models on the legacy default-provider path", async () => {
1026+await withTempAgentDir(async (agentDir) => {
1027+const cfg: OpenClawConfig = {
1028+agents: {
1029+defaults: {
1030+imageModel: { primary: "gpt-5.4-mini" },
1031+},
1032+},
1033+};
1034+1035+expect(resolveImageModelConfigForTool({ cfg, agentDir })).toEqual({
1036+primary: "gpt-5.4-mini",
1037+});
1038+});
1039+});
1040+9231041it("keeps image tool available when primary model supports images (for explicit requests)", async () => {
9241042// When the primary model supports images, we still keep the tool available
9251043// because images are auto-injected into prompts. The tool description is
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。