diff --git a/src/media-understanding/defaults.constants.ts b/src/media-understanding/defaults.constants.ts new file mode 100644 index 00000000000..2dc0c119d98 --- /dev/null +++ b/src/media-understanding/defaults.constants.ts @@ -0,0 +1,32 @@ +import type { MediaUnderstandingCapability } from "./types.js"; + +const MB = 1024 * 1024; + +export const DEFAULT_MAX_CHARS = 500; +export const DEFAULT_MAX_CHARS_BY_CAPABILITY: Record< + MediaUnderstandingCapability, + number | undefined +> = { + image: DEFAULT_MAX_CHARS, + audio: undefined, + video: DEFAULT_MAX_CHARS, +}; +export const DEFAULT_MAX_BYTES: Record = { + image: 10 * MB, + audio: 20 * MB, + video: 50 * MB, +}; +export const DEFAULT_TIMEOUT_SECONDS: Record = { + image: 60, + audio: 60, + video: 120, +}; +export const DEFAULT_PROMPT: Record = { + image: "Describe the image.", + audio: "Transcribe the audio.", + video: "Describe the video.", +}; +export const DEFAULT_VIDEO_MAX_BASE64_BYTES = 70 * MB; +export const CLI_OUTPUT_MAX_BUFFER = 5 * MB; +export const DEFAULT_MEDIA_CONCURRENCY = 2; +export const MIN_AUDIO_FILE_BYTES = 1024; diff --git a/src/media-understanding/defaults.ts b/src/media-understanding/defaults.ts index 0e11d77469d..f071fd36103 100644 --- a/src/media-understanding/defaults.ts +++ b/src/media-understanding/defaults.ts @@ -5,36 +5,17 @@ import { buildMediaUnderstandingManifestMetadataRegistry } from "./manifest-meta import { normalizeMediaProviderId } from "./provider-registry.js"; import { providerSupportsCapability } from "./provider-supports.js"; import type { MediaUnderstandingCapability, MediaUnderstandingProvider } from "./types.js"; - -const MB = 1024 * 1024; - -export const DEFAULT_MAX_CHARS = 500; -export const DEFAULT_MAX_CHARS_BY_CAPABILITY: Record< - MediaUnderstandingCapability, - number | undefined -> = { - image: DEFAULT_MAX_CHARS, - audio: undefined, - video: DEFAULT_MAX_CHARS, -}; -export const DEFAULT_MAX_BYTES: Record = { - image: 10 * MB, - audio: 20 * MB, - video: 50 * MB, -}; -export const DEFAULT_TIMEOUT_SECONDS: Record = { - image: 60, - audio: 60, - video: 120, -}; -export const DEFAULT_PROMPT: Record = { - image: "Describe the image.", - audio: "Transcribe the audio.", - video: "Describe the video.", -}; -export const DEFAULT_VIDEO_MAX_BASE64_BYTES = 70 * MB; -export const CLI_OUTPUT_MAX_BUFFER = 5 * MB; -export const DEFAULT_MEDIA_CONCURRENCY = 2; +export { + CLI_OUTPUT_MAX_BUFFER, + DEFAULT_MAX_BYTES, + DEFAULT_MAX_CHARS, + DEFAULT_MAX_CHARS_BY_CAPABILITY, + DEFAULT_MEDIA_CONCURRENCY, + DEFAULT_PROMPT, + DEFAULT_TIMEOUT_SECONDS, + DEFAULT_VIDEO_MAX_BASE64_BYTES, + MIN_AUDIO_FILE_BYTES, +} from "./defaults.constants.js"; let defaultRegistryCache: Map | null = null; const configRegistryCache = new Map>(); @@ -192,10 +173,3 @@ export function providerSupportsNativePdfDocument(params: { const provider = registry.get(normalizeMediaProviderId(params.providerId)); return provider?.nativeDocumentInputs?.includes("pdf") ?? false; } - -/** - * Minimum audio file size in bytes below which transcription is skipped. - * Files smaller than this threshold are almost certainly empty or corrupt - * and would cause unhelpful API errors from Whisper/transcription providers. - */ -export const MIN_AUDIO_FILE_BYTES = 1024; diff --git a/src/media-understanding/resolve.ts b/src/media-understanding/resolve.ts index 0b28a776e20..334ef4357aa 100644 --- a/src/media-understanding/resolve.ts +++ b/src/media-understanding/resolve.ts @@ -11,7 +11,7 @@ import { DEFAULT_MAX_CHARS_BY_CAPABILITY, DEFAULT_MEDIA_CONCURRENCY, DEFAULT_PROMPT, -} from "./defaults.js"; +} from "./defaults.constants.js"; import { resolveEffectiveMediaEntryCapabilities } from "./entry-capabilities.js"; import { normalizeMediaProviderId } from "./provider-id.js"; import { normalizeMediaUnderstandingChatType, resolveMediaUnderstandingScope } from "./scope.js"; diff --git a/src/media-understanding/runner.entries.ts b/src/media-understanding/runner.entries.ts index 49ec18a29be..1a4f49dec6b 100644 --- a/src/media-understanding/runner.entries.ts +++ b/src/media-understanding/runner.entries.ts @@ -27,8 +27,7 @@ import { CLI_OUTPUT_MAX_BUFFER, DEFAULT_TIMEOUT_SECONDS, MIN_AUDIO_FILE_BYTES, - resolveDefaultMediaModel, -} from "./defaults.js"; +} from "./defaults.constants.js"; import { MediaUnderstandingSkipError } from "./errors.js"; import { fileExists } from "./fs.js"; import { describeImageWithModel } from "./image-runtime.js"; @@ -632,7 +631,7 @@ export async function runProviderEntry(params: { }); const model = entry.model?.trim() || - resolveDefaultMediaModel({ + (await import("./defaults.js")).resolveDefaultMediaModel({ cfg, providerId, capability: "audio", diff --git a/src/media-understanding/runner.test-utils.ts b/src/media-understanding/runner.test-utils.ts index 44818df0ce8..46722b535e6 100644 --- a/src/media-understanding/runner.test-utils.ts +++ b/src/media-understanding/runner.test-utils.ts @@ -2,7 +2,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { withEnvAsync } from "../test-utils/env.js"; -import { MIN_AUDIO_FILE_BYTES } from "./defaults.js"; +import { MIN_AUDIO_FILE_BYTES } from "./defaults.constants.js"; import { createMediaAttachmentCache, normalizeMediaAttachments } from "./runner.attachments.js"; type MediaFixtureParams = { diff --git a/src/media-understanding/runner.video.test.ts b/src/media-understanding/runner.video.test.ts index 2b5a1ba166d..58647ba9f02 100644 --- a/src/media-understanding/runner.video.test.ts +++ b/src/media-understanding/runner.video.test.ts @@ -5,6 +5,18 @@ import { withEnvAsync } from "../test-utils/env.js"; import { runCapability } from "./runner.js"; import { withVideoFixture } from "./runner.test-utils.js"; +vi.mock("../media/channel-inbound-roots.js", () => ({ + resolveChannelInboundAttachmentRoots: () => undefined, +})); + +vi.mock("../agents/api-key-rotation.js", () => ({ + collectProviderApiKeysForExecution: ({ primaryApiKey }: { primaryApiKey?: string }) => [ + primaryApiKey ?? "test-key", + ], + executeWithApiKeyRotation: async ({ execute }: { execute: (apiKey: string) => Promise }) => + execute("test-key"), +})); + vi.mock("../plugins/capability-provider-runtime.js", async () => { const { createEmptyCapabilityProviderMockModule } = await import("./runner.test-mocks.js"); return createEmptyCapabilityProviderMockModule(); diff --git a/src/media-understanding/video.ts b/src/media-understanding/video.ts index 00773f40ca7..827b635ba03 100644 --- a/src/media-understanding/video.ts +++ b/src/media-understanding/video.ts @@ -1,4 +1,4 @@ -import { DEFAULT_VIDEO_MAX_BASE64_BYTES } from "./defaults.js"; +import { DEFAULT_VIDEO_MAX_BASE64_BYTES } from "./defaults.constants.js"; export function estimateBase64Size(bytes: number): number { return Math.ceil(bytes / 3) * 4; diff --git a/src/plugins/web-search-providers.runtime.test.ts b/src/plugins/web-search-providers.runtime.test.ts index 3001bce7250..6f1d9ea6651 100644 --- a/src/plugins/web-search-providers.runtime.test.ts +++ b/src/plugins/web-search-providers.runtime.test.ts @@ -3,9 +3,11 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite type RegistryModule = typeof import("./registry.js"); type RuntimeModule = typeof import("./runtime.js"); type WebSearchProvidersRuntimeModule = typeof import("./web-search-providers.runtime.js"); -type ManifestRegistryModule = typeof import("./manifest-registry.js"); type PluginAutoEnableModule = typeof import("../config/plugin-auto-enable.js"); type WebSearchProvidersSharedModule = typeof import("./web-search-providers.shared.js"); +type PluginManifestRegistry = import("./manifest-registry.js").PluginManifestRegistry; +type LoadPluginManifestRegistryForPluginRegistry = + typeof import("./plugin-registry.js").loadPluginManifestRegistryForPluginRegistry; const BUNDLED_WEB_SEARCH_PROVIDERS = [ { pluginId: "brave", id: "brave", order: 10 }, @@ -20,13 +22,14 @@ const BUNDLED_WEB_SEARCH_PROVIDERS = [ ] as const; let createEmptyPluginRegistry: RegistryModule["createEmptyPluginRegistry"]; -let loadPluginManifestRegistryMock: ReturnType; +let loadPluginManifestRegistryMock: ReturnType< + typeof vi.fn +>; let setActivePluginRegistry: RuntimeModule["setActivePluginRegistry"]; let resolvePluginWebSearchProviders: WebSearchProvidersRuntimeModule["resolvePluginWebSearchProviders"]; let resolveRuntimeWebSearchProviders: WebSearchProvidersRuntimeModule["resolveRuntimeWebSearchProviders"]; let loadOpenClawPluginsMock: ReturnType; let loaderModule: typeof import("./loader.js"); -let manifestRegistryModule: ManifestRegistryModule; let pluginAutoEnableModule: PluginAutoEnableModule; let applyPluginAutoEnableSpy: ReturnType; let webSearchProvidersSharedModule: WebSearchProvidersSharedModule; @@ -135,7 +138,7 @@ function expectBundledRuntimeProviderKeys( ); } -function createManifestRegistryFixture() { +function createManifestRegistryFixture(): PluginManifestRegistry { return { plugins: [ { @@ -316,8 +319,19 @@ function expectRuntimeProviderResolution( describe("resolvePluginWebSearchProviders", () => { beforeAll(async () => { + loadPluginManifestRegistryMock = vi.fn(); + vi.doMock("./plugin-registry.js", async () => { + const actual = + await vi.importActual("./plugin-registry.js"); + return { + ...actual, + loadPluginManifestRegistryForPluginRegistry: ( + ...args: Parameters + ) => loadPluginManifestRegistryMock(...args), + }; + }); + ({ createEmptyPluginRegistry } = await import("./registry-empty.js")); - manifestRegistryModule = await import("./manifest-registry.js"); loaderModule = await import("./loader.js"); pluginAutoEnableModule = await import("../config/plugin-auto-enable.js"); webSearchProvidersSharedModule = await import("./web-search-providers.shared.js"); @@ -338,15 +352,8 @@ describe("resolvePluginWebSearchProviders", () => { autoEnabledReasons: {}, }) as ReturnType, ); - loadPluginManifestRegistryMock = vi - .spyOn(manifestRegistryModule, "loadPluginManifestRegistry") - .mockReturnValue( - createManifestRegistryFixture() as ManifestRegistryModule["loadPluginManifestRegistry"] extends ( - ...args: unknown[] - ) => infer R - ? R - : never, - ); + loadPluginManifestRegistryMock.mockReset(); + loadPluginManifestRegistryMock.mockReturnValue(createManifestRegistryFixture()); loadOpenClawPluginsMock = vi .spyOn(loaderModule, "loadOpenClawPlugins") .mockImplementation((params) => {