perf(test): trim media runner import cost

This commit is contained in:
Peter Steinberger
2026-04-29 16:59:36 +01:00
parent e8b82d1cf9
commit 58db3d2d22
8 changed files with 81 additions and 57 deletions

View File

@@ -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<MediaUnderstandingCapability, number> = {
image: 10 * MB,
audio: 20 * MB,
video: 50 * MB,
};
export const DEFAULT_TIMEOUT_SECONDS: Record<MediaUnderstandingCapability, number> = {
image: 60,
audio: 60,
video: 120,
};
export const DEFAULT_PROMPT: Record<MediaUnderstandingCapability, string> = {
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;

View File

@@ -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<MediaUnderstandingCapability, number> = {
image: 10 * MB,
audio: 20 * MB,
video: 50 * MB,
};
export const DEFAULT_TIMEOUT_SECONDS: Record<MediaUnderstandingCapability, number> = {
image: 60,
audio: 60,
video: 120,
};
export const DEFAULT_PROMPT: Record<MediaUnderstandingCapability, string> = {
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<string, MediaUnderstandingProvider> | null = null;
const configRegistryCache = new Map<string, Map<string, MediaUnderstandingProvider>>();
@@ -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;

View File

@@ -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";

View File

@@ -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",

View File

@@ -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 = {

View File

@@ -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 <T>({ execute }: { execute: (apiKey: string) => Promise<T> }) =>
execute("test-key"),
}));
vi.mock("../plugins/capability-provider-runtime.js", async () => {
const { createEmptyCapabilityProviderMockModule } = await import("./runner.test-mocks.js");
return createEmptyCapabilityProviderMockModule();

View File

@@ -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;

View File

@@ -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<typeof vi.fn>;
let loadPluginManifestRegistryMock: ReturnType<
typeof vi.fn<LoadPluginManifestRegistryForPluginRegistry>
>;
let setActivePluginRegistry: RuntimeModule["setActivePluginRegistry"];
let resolvePluginWebSearchProviders: WebSearchProvidersRuntimeModule["resolvePluginWebSearchProviders"];
let resolveRuntimeWebSearchProviders: WebSearchProvidersRuntimeModule["resolveRuntimeWebSearchProviders"];
let loadOpenClawPluginsMock: ReturnType<typeof vi.fn>;
let loaderModule: typeof import("./loader.js");
let manifestRegistryModule: ManifestRegistryModule;
let pluginAutoEnableModule: PluginAutoEnableModule;
let applyPluginAutoEnableSpy: ReturnType<typeof vi.fn>;
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<LoadPluginManifestRegistryForPluginRegistry>();
vi.doMock("./plugin-registry.js", async () => {
const actual =
await vi.importActual<typeof import("./plugin-registry.js")>("./plugin-registry.js");
return {
...actual,
loadPluginManifestRegistryForPluginRegistry: (
...args: Parameters<LoadPluginManifestRegistryForPluginRegistry>
) => 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<PluginAutoEnableModule["applyPluginAutoEnable"]>,
);
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) => {