diff --git a/src/image-generation/provider-registry.test.ts b/src/image-generation/provider-registry.test.ts index 65ea49b1b05..8ca5e397c33 100644 --- a/src/image-generation/provider-registry.test.ts +++ b/src/image-generation/provider-registry.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createEmptyPluginRegistry } from "../plugins/registry.js"; import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../plugins/runtime.js"; @@ -10,7 +10,8 @@ vi.mock("../plugins/loader.js", () => ({ loadOpenClawPlugins: loadOpenClawPluginsMock, })); -import { getImageGenerationProvider, listImageGenerationProviders } from "./provider-registry.js"; +let getImageGenerationProvider: typeof import("./provider-registry.js").getImageGenerationProvider; +let listImageGenerationProviders: typeof import("./provider-registry.js").listImageGenerationProviders; describe("image-generation provider registry", () => { afterEach(() => { @@ -19,6 +20,13 @@ describe("image-generation provider registry", () => { resetPluginRuntimeStateForTest(); }); + beforeEach(async () => { + vi.resetModules(); + ({ getImageGenerationProvider, listImageGenerationProviders } = await import( + "./provider-registry.js" + )); + }); + it("does not load plugins when listing without config", () => { expect(listImageGenerationProviders()).toEqual([]); expect(loadOpenClawPluginsMock).not.toHaveBeenCalled(); diff --git a/src/image-generation/provider-registry.ts b/src/image-generation/provider-registry.ts index cf6b5c2d874..83696445933 100644 --- a/src/image-generation/provider-registry.ts +++ b/src/image-generation/provider-registry.ts @@ -1,5 +1,6 @@ import { normalizeProviderId } from "../agents/model-selection.js"; import type { OpenClawConfig } from "../config/config.js"; +import { isBlockedObjectKey } from "../infra/prototype-keys.js"; import { loadOpenClawPlugins } from "../plugins/loader.js"; import { getActivePluginRegistry, getActivePluginRegistryKey } from "../plugins/runtime.js"; import type { ImageGenerationProviderPlugin } from "../plugins/types.js"; @@ -9,7 +10,10 @@ const UNSAFE_PROVIDER_IDS = new Set(["__proto__", "constructor", "prototype"]); function normalizeImageGenerationProviderId(id: string | undefined): string | undefined { const normalized = normalizeProviderId(id ?? ""); - return normalized || undefined; + if (!normalized || isBlockedObjectKey(normalized)) { + return undefined; + } + return normalized; } function isSafeImageGenerationProviderId(id: string | undefined): id is string { diff --git a/src/image-generation/runtime.test.ts b/src/image-generation/runtime.test.ts index 91780260152..4cb1d62182d 100644 --- a/src/image-generation/runtime.test.ts +++ b/src/image-generation/runtime.test.ts @@ -1,14 +1,22 @@ -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import { createEmptyPluginRegistry } from "../plugins/registry.js"; import { setActivePluginRegistry } from "../plugins/runtime.js"; -import { generateImage, listRuntimeImageGenerationProviders } from "./runtime.js"; +import { vi } from "vitest"; + +let generateImage: typeof import("./runtime.js").generateImage; +let listRuntimeImageGenerationProviders: typeof import("./runtime.js").listRuntimeImageGenerationProviders; describe("image-generation runtime helpers", () => { afterEach(() => { setActivePluginRegistry(createEmptyPluginRegistry()); }); + beforeEach(async () => { + vi.resetModules(); + ({ generateImage, listRuntimeImageGenerationProviders } = await import("./runtime.js")); + }); + it("generates images through the active image-generation registry", async () => { const pluginRegistry = createEmptyPluginRegistry(); const authStore = { version: 1, profiles: {} } as const; diff --git a/src/media-understanding/image.test.ts b/src/media-understanding/image.test.ts index 893d1577560..5a93e7b59cf 100644 --- a/src/media-understanding/image.test.ts +++ b/src/media-understanding/image.test.ts @@ -29,6 +29,39 @@ const { discoverModelsMock, } = hoisted; +vi.mock("@mariozechner/pi-ai", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + complete: completeMock, + }; +}); + +vi.mock("../agents/minimax-vlm.js", () => ({ + isMinimaxVlmProvider: (provider: string) => + provider === "minimax" || provider === "minimax-portal", + isMinimaxVlmModel: (provider: string, modelId: string) => + (provider === "minimax" || provider === "minimax-portal") && modelId === "MiniMax-VL-01", + minimaxUnderstandImage: minimaxUnderstandImageMock, +})); + +vi.mock("../agents/models-config.js", () => ({ + ensureOpenClawModelsJson: ensureOpenClawModelsJsonMock, +})); + +vi.mock("../agents/model-auth.js", () => ({ + getApiKeyForModel: getApiKeyForModelMock, + resolveApiKeyForProvider: resolveApiKeyForProviderMock, + requireApiKey: requireApiKeyMock, +})); + +vi.mock("../agents/pi-model-discovery-runtime.js", () => ({ + discoverAuthStorage: () => ({ + setRuntimeApiKey: setRuntimeApiKeyMock, + }), + discoverModels: discoverModelsMock, +})); + let describeImageWithModel: typeof import("./image.js").describeImageWithModel; describe("describeImageWithModel", () => { diff --git a/src/media-understanding/runner.vision-skip.test.ts b/src/media-understanding/runner.vision-skip.test.ts index 28b30683df8..df380571139 100644 --- a/src/media-understanding/runner.vision-skip.test.ts +++ b/src/media-understanding/runner.vision-skip.test.ts @@ -2,11 +2,6 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { MsgContext } from "../auto-reply/templating.js"; import type { OpenClawConfig } from "../config/config.js"; -let buildProviderRegistry: typeof import("./runner.js").buildProviderRegistry; -let createMediaAttachmentCache: typeof import("./runner.js").createMediaAttachmentCache; -let normalizeMediaAttachments: typeof import("./runner.js").normalizeMediaAttachments; -let runCapability: typeof import("./runner.js").runCapability; - const catalog = [ { id: "gpt-4.1", @@ -28,6 +23,11 @@ vi.mock("../agents/model-catalog.js", async () => { }; }); +let buildProviderRegistry: typeof import("./runner.js").buildProviderRegistry; +let createMediaAttachmentCache: typeof import("./runner.js").createMediaAttachmentCache; +let normalizeMediaAttachments: typeof import("./runner.js").normalizeMediaAttachments; +let runCapability: typeof import("./runner.js").runCapability; + describe("runCapability image skip", () => { beforeEach(async () => { vi.resetModules(); diff --git a/src/tts/tts.test.ts b/src/tts/tts.test.ts index dcc8ece9818..18987eb87f5 100644 --- a/src/tts/tts.test.ts +++ b/src/tts/tts.test.ts @@ -1,17 +1,16 @@ -import { completeSimple, type AssistantMessage } from "@mariozechner/pi-ai"; +import type { AssistantMessage } from "@mariozechner/pi-ai"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { buildElevenLabsSpeechProvider } from "../../extensions/elevenlabs/speech-provider.ts"; import { buildMicrosoftSpeechProvider } from "../../extensions/microsoft/speech-provider.ts"; import { buildOpenAISpeechProvider } from "../../extensions/openai/speech-provider.ts"; -import { ensureCustomApiRegistered } from "../agents/custom-api-registry.js"; -import { getApiKeyForModel } from "../agents/model-auth.js"; -import { resolveModelAsync } from "../agents/pi-embedded-runner/model.js"; import type { OpenClawConfig } from "../config/config.js"; import { createEmptyPluginRegistry } from "../plugins/registry-empty.js"; import { setActivePluginRegistry } from "../plugins/runtime.js"; import { withEnv } from "../test-utils/env.js"; import * as tts from "./tts.js"; +let completeSimple: typeof import("@mariozechner/pi-ai").completeSimple; + vi.mock("@mariozechner/pi-ai", async (importOriginal) => { const original = await importOriginal(); return { @@ -128,7 +127,8 @@ function createOpenAiTelephonyCfg(model: "tts-1" | "gpt-4o-mini-tts"): OpenClawC } describe("tts", () => { - beforeEach(() => { + beforeEach(async () => { + ({ completeSimple } = await import("@mariozechner/pi-ai")); const registry = createEmptyPluginRegistry(); registry.speechProviders = [ { pluginId: "openai", provider: buildOpenAISpeechProvider(), source: "test" }, @@ -391,10 +391,10 @@ describe("tts", () => { describe("summarizeText", () => { let summarizeTextForTest: typeof summarizeText; let resolveTtsConfigForTest: typeof resolveTtsConfig; - let completeSimpleForTest: typeof completeSimple; - let getApiKeyForModelForTest: typeof getApiKeyForModel; - let resolveModelAsyncForTest: typeof resolveModelAsync; - let ensureCustomApiRegisteredForTest: typeof ensureCustomApiRegistered; + let completeSimpleForTest: typeof import("@mariozechner/pi-ai").completeSimple; + let getApiKeyForModelForTest: typeof import("../agents/model-auth.js").getApiKeyForModel; + let resolveModelAsyncForTest: typeof import("../agents/pi-embedded-runner/model.js").resolveModelAsync; + let ensureCustomApiRegisteredForTest: typeof import("../agents/custom-api-registry.js").ensureCustomApiRegistered; const baseCfg: OpenClawConfig = { agents: { defaults: { model: { primary: "openai/gpt-4o-mini" } } },