Download src/media-generation/runtime-shared.test.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 16 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/media-generation/runtime-shared.test.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/media-generation/runtime-shared.test.ts
-
curl -L -o runtime-shared.test.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/media-generation/runtime-shared.test.ts
16 kB
| // Covers shared media-generation runtime polling and timeout helpers. | |
| import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion"; | |
| import { describe, expect, it } from "vitest"; | |
| import type { OpenClawConfig } from "../config/types.js"; | |
| import { | |
| normalizeDurationToClosestMax, | |
| resolveCapabilityModelCandidates, | |
| resolveClosestAspectRatio, | |
| resolveClosestResolution, | |
| resolveClosestSize, | |
| resolveMediaProviderRequestTimeoutMs, | |
| resolveReferenceImageCapabilityError, | |
| runMediaGenerationCandidates, | |
| throwCapabilityGenerationFailure, | |
| } from "./runtime-shared.js"; | |
| function parseModelRef(raw?: string) { | |
| const trimmed = raw?.trim(); | |
| if (!trimmed) { | |
| return null; | |
| } | |
| const slash = trimmed.indexOf("/"); | |
| if (slash <= 0 || slash === trimmed.length - 1) { | |
| return null; | |
| } | |
| return { | |
| provider: trimmed.slice(0, slash), | |
| model: trimmed.slice(slash + 1), | |
| }; | |
| } | |
| describe("media-generation runtime shared candidates", () => { | |
| it.each([ | |
| [0, undefined, undefined], | |
| [1, { enabled: false }, "provider/model does not support reference-image edit inputs"], | |
| [ | |
| 2, | |
| { enabled: true, maxInputImages: 1 }, | |
| "provider/model supports at most 1 reference image, 2 requested", | |
| ], | |
| [11, { enabled: true }, "provider/model supports at most 10 reference images, 11 requested"], | |
| ] as const)( | |
| "validates finite reference-image capability for %s inputs", | |
| (inputImageCount, edit, error) => { | |
| expect( | |
| resolveReferenceImageCapabilityError({ | |
| candidateRef: "provider/model", | |
| inputImageCount, | |
| edit, | |
| }), | |
| ).toBe(error); | |
| }, | |
| ); | |
| it("appends auth-backed provider defaults after explicit refs by default", () => { | |
| const cfg = { | |
| agents: { | |
| defaults: { | |
| model: { | |
| primary: "openai/gpt-5.4", | |
| }, | |
| }, | |
| }, | |
| } as OpenClawConfig; | |
| const candidates = resolveCapabilityModelCandidates({ | |
| cfg, | |
| modelConfig: { | |
| primary: "google/gemini-3.1-flash-image-preview", | |
| fallbacks: ["fal/fal-ai/flux/dev"], | |
| }, | |
| parseModelRef, | |
| listProviders: () => [ | |
| { | |
| id: "google", | |
| defaultModel: "gemini-3.1-flash-image-preview", | |
| isConfigured: () => true, | |
| }, | |
| { | |
| id: "openai", | |
| defaultModel: "gpt-image-1", | |
| isConfigured: () => true, | |
| }, | |
| { | |
| id: "minimax", | |
| defaultModel: "image-01", | |
| isConfigured: () => true, | |
| }, | |
| ], | |
| }); | |
| expect(candidates).toEqual([ | |
| { provider: "google", model: "gemini-3.1-flash-image-preview" }, | |
| { provider: "fal", model: "fal-ai/flux/dev" }, | |
| { provider: "openai", model: "gpt-image-1" }, | |
| { provider: "minimax", model: "image-01" }, | |
| ]); | |
| }); | |
| it("auto-detects auth-backed provider defaults when no explicit media model is configured", () => { | |
| const candidates = resolveCapabilityModelCandidates({ | |
| cfg: {} as OpenClawConfig, | |
| modelConfig: undefined, | |
| parseModelRef, | |
| listProviders: () => [ | |
| { | |
| id: "openai", | |
| defaultModel: "gpt-image-1", | |
| isConfigured: () => true, | |
| }, | |
| { | |
| id: "fal", | |
| defaultModel: "fal-ai/flux/dev", | |
| isConfigured: () => true, | |
| }, | |
| ], | |
| }); | |
| expect(candidates).toEqual([ | |
| { provider: "openai", model: "gpt-image-1" }, | |
| { provider: "fal", model: "fal-ai/flux/dev" }, | |
| ]); | |
| }); | |
| it("auto-detects config-only providers that do not implement custom readiness", () => { | |
| const candidates = resolveCapabilityModelCandidates({ | |
| cfg: { | |
| models: { | |
| providers: { | |
| "media-config-only": { | |
| apiKey: "config-only-media-key", | |
| baseUrl: "https://media.example.test/v1", | |
| models: [], | |
| }, | |
| }, | |
| }, | |
| } as OpenClawConfig, | |
| modelConfig: undefined, | |
| parseModelRef, | |
| listProviders: () => [ | |
| { | |
| id: "media-config-only", | |
| defaultModel: "configured-video", | |
| }, | |
| ], | |
| }); | |
| expect(candidates).toEqual([{ provider: "media-config-only", model: "configured-video" }]); | |
| }); | |
| it("preserves an owner readiness veto even when generic config contains an API key", () => { | |
| const candidates = resolveCapabilityModelCandidates({ | |
| cfg: { | |
| models: { | |
| providers: { | |
| "media-config-only": { | |
| apiKey: "config-only-media-key", | |
| baseUrl: "https://media.example.test/v1", | |
| models: [], | |
| }, | |
| }, | |
| }, | |
| } as OpenClawConfig, | |
| modelConfig: undefined, | |
| parseModelRef, | |
| listProviders: () => [ | |
| { | |
| id: "media-config-only", | |
| defaultModel: "configured-video", | |
| isConfigured: () => false, | |
| }, | |
| ], | |
| }); | |
| expect(candidates).toEqual([]); | |
| }); | |
| it("orders auto-detected provider defaults by canonical aliases", () => { | |
| const candidates = resolveCapabilityModelCandidates({ | |
| cfg: { | |
| agents: { | |
| defaults: { | |
| model: { | |
| primary: "media-alias/gpt-5.5", | |
| }, | |
| }, | |
| }, | |
| } as OpenClawConfig, | |
| modelConfig: undefined, | |
| parseModelRef, | |
| listProviders: () => [ | |
| { | |
| id: "fal", | |
| defaultModel: "fal-ai/flux/dev", | |
| isConfigured: () => true, | |
| }, | |
| { | |
| id: "openai", | |
| aliases: ["media-alias"], | |
| defaultModel: "gpt-image-2", | |
| isConfigured: () => true, | |
| }, | |
| ], | |
| }); | |
| expect(candidates).toEqual([ | |
| { provider: "openai", model: "gpt-image-2" }, | |
| { provider: "fal", model: "fal-ai/flux/dev" }, | |
| ]); | |
| }); | |
| it("keeps implicit provider expansion enabled when the retired opt-out is present", () => { | |
| let listProviderCalls = 0; | |
| const candidates = resolveCapabilityModelCandidates({ | |
| cfg: { | |
| agents: { | |
| defaults: { | |
| mediaGenerationAutoProviderFallback: false, | |
| }, | |
| }, | |
| } as OpenClawConfig, | |
| modelConfig: { | |
| primary: "google/gemini-3.1-flash-image-preview", | |
| }, | |
| parseModelRef, | |
| listProviders: () => { | |
| listProviderCalls += 1; | |
| return [ | |
| { | |
| id: "openai", | |
| defaultModel: "gpt-image-1", | |
| isConfigured: () => true, | |
| }, | |
| ]; | |
| }, | |
| }); | |
| expect(candidates).toEqual([ | |
| { provider: "google", model: "gemini-3.1-flash-image-preview" }, | |
| { provider: "openai", model: "gpt-image-1" }, | |
| ]); | |
| expect(listProviderCalls).toBe(1); | |
| }); | |
| it("treats an explicit model override as exact-only", () => { | |
| const candidates = resolveCapabilityModelCandidates({ | |
| cfg: { | |
| agents: { | |
| defaults: { | |
| mediaGenerationAutoProviderFallback: false, | |
| }, | |
| }, | |
| } as OpenClawConfig, | |
| modelConfig: { | |
| primary: "google/gemini-3.1-flash-image-preview", | |
| fallbacks: ["fal/fal-ai/flux/dev"], | |
| }, | |
| modelOverride: "openai/gpt-image-2", | |
| parseModelRef, | |
| listProviders: () => [ | |
| { | |
| id: "google", | |
| defaultModel: "gemini-3.1-flash-image-preview", | |
| isConfigured: () => true, | |
| }, | |
| ], | |
| }); | |
| expect(candidates).toEqual([{ provider: "openai", model: "gpt-image-2" }]); | |
| }); | |
| it("resolves slash-containing provider model IDs from registered provider models", () => { | |
| const candidates = resolveCapabilityModelCandidates({ | |
| cfg: {} as OpenClawConfig, | |
| modelConfig: { | |
| primary: "openai/gpt-image-2", | |
| }, | |
| modelOverride: "fal-ai/flux/dev", | |
| parseModelRef, | |
| listProviders: () => [ | |
| { | |
| id: "fal", | |
| defaultModel: "fal-ai/flux/dev", | |
| models: ["fal-ai/flux/dev", "fal-ai/flux/dev/image-to-image"], | |
| isConfigured: () => true, | |
| }, | |
| ], | |
| }); | |
| expect(candidates).toEqual([{ provider: "fal", model: "fal-ai/flux/dev" }]); | |
| }); | |
| it("prefers explicit provider refs over colliding slash-containing model IDs", () => { | |
| const candidates = resolveCapabilityModelCandidates({ | |
| cfg: {} as OpenClawConfig, | |
| modelConfig: { | |
| primary: "google/lyria-3-pro-preview", | |
| }, | |
| parseModelRef, | |
| listProviders: () => [ | |
| { | |
| id: "google", | |
| defaultModel: "lyria-3-clip-preview", | |
| models: ["lyria-3-clip-preview", "lyria-3-pro-preview"], | |
| isConfigured: () => true, | |
| }, | |
| { | |
| id: "openrouter", | |
| defaultModel: "google/lyria-3-clip-preview", | |
| models: ["google/lyria-3-clip-preview", "google/lyria-3-pro-preview"], | |
| isConfigured: () => true, | |
| }, | |
| ], | |
| }); | |
| expect(candidates[0]).toEqual({ provider: "google", model: "lyria-3-pro-preview" }); | |
| }); | |
| }); | |
| describe("media-generation candidate lifecycle", () => { | |
| it("preserves missing, skipped, and failed attempts before the first usable result", async () => { | |
| const calls: string[] = []; | |
| const result = await runMediaGenerationCandidates({ | |
| candidates: ["missing", "skipped", "failed", "success", "unused"].map((provider) => ({ | |
| provider, | |
| model: "model", | |
| })), | |
| capability: "image", | |
| includeSkipFailureDetails: true, | |
| getProvider(id) { | |
| calls.push(`lookup:${id}`); | |
| return id === "missing" ? undefined : { id }; | |
| }, | |
| prepareCandidate(candidate) { | |
| if (candidate.provider === "skipped") { | |
| return "reference inputs unsupported"; | |
| } | |
| return async (attempts) => { | |
| calls.push(`generate:${candidate.provider}`); | |
| if (candidate.provider === "failed") { | |
| throw new Error("generation failed"); | |
| } | |
| return { model: "selected-model", attempts }; | |
| }; | |
| }, | |
| }); | |
| expect(result.model).toBe("selected-model"); | |
| expect(result.attempts).toStrictEqual([ | |
| { | |
| provider: "missing", | |
| model: "model", | |
| error: "No image-generation provider registered for missing", | |
| }, | |
| { | |
| provider: "skipped", | |
| model: "model", | |
| error: "reference inputs unsupported", | |
| reason: undefined, | |
| status: undefined, | |
| code: undefined, | |
| }, | |
| { | |
| provider: "failed", | |
| model: "model", | |
| error: "generation failed", | |
| reason: undefined, | |
| status: undefined, | |
| code: undefined, | |
| }, | |
| ]); | |
| expect(calls).toEqual([ | |
| "lookup:missing", | |
| "lookup:skipped", | |
| "lookup:failed", | |
| "generate:failed", | |
| "lookup:success", | |
| "generate:success", | |
| ]); | |
| }); | |
| it.each(["lookup", "prepare", "async prepare"])( | |
| "propagates %s failures without submitting a fallback", | |
| async (stage) => { | |
| const error = new Error("provider registry unavailable"); | |
| const lookedUp: string[] = []; | |
| let executions = 0; | |
| const result = runMediaGenerationCandidates({ | |
| candidates: [ | |
| { provider: "primary", model: "model" }, | |
| { provider: "fallback", model: "model" }, | |
| ], | |
| capability: "video", | |
| getProvider(id) { | |
| lookedUp.push(id); | |
| if (stage === "lookup") { | |
| throw error; | |
| } | |
| return { id }; | |
| }, | |
| prepareCandidate() { | |
| if (stage === "prepare") { | |
| throw error; | |
| } | |
| if (stage === "async prepare") { | |
| return Promise.reject(error); | |
| } | |
| return async () => { | |
| executions += 1; | |
| return "unexpected generation"; | |
| }; | |
| }, | |
| }); | |
| await expect(result).rejects.toBe(error); | |
| expect(lookedUp).toEqual(["primary"]); | |
| expect(executions).toBe(0); | |
| }, | |
| ); | |
| }); | |
| describe("media-generation runtime shared normalization", () => { | |
| it("caps media provider timeouts to the timer-safe range", () => { | |
| expect( | |
| resolveMediaProviderRequestTimeoutMs({ | |
| timeoutMs: Number.MAX_SAFE_INTEGER, | |
| providerDefaultTimeoutMs: 30_000, | |
| }), | |
| ).toBe(MAX_TIMER_TIMEOUT_MS); | |
| expect( | |
| resolveMediaProviderRequestTimeoutMs({ | |
| timeoutMs: 0, | |
| providerDefaultTimeoutMs: 45_000, | |
| }), | |
| ).toBe(45_000); | |
| }); | |
| it("rejects unsafe size dimensions before deriving ratios", () => { | |
| expect( | |
| resolveClosestSize({ | |
| requestedSize: "9007199254740993x3", | |
| supportedSizes: ["1024x1024", "1536x1024"], | |
| }), | |
| ).toBeUndefined(); | |
| }); | |
| it("maps unsupported sizes to the closest supported size", () => { | |
| expect( | |
| resolveClosestSize({ | |
| requestedSize: "1792x1024", | |
| supportedSizes: ["1024x1024", "1024x1536", "1536x1024"], | |
| }), | |
| ).toBe("1536x1024"); | |
| }); | |
| it("maps unsupported aspect ratios to the closest supported aspect ratio", () => { | |
| expect( | |
| resolveClosestAspectRatio({ | |
| requestedAspectRatio: "17:10", | |
| supportedAspectRatios: ["1:1", "4:3", "16:9"], | |
| }), | |
| ).toBe("16:9"); | |
| }); | |
| it("maps unsupported resolutions to the closest supported resolution", () => { | |
| expect( | |
| resolveClosestResolution({ | |
| requestedResolution: "2K", | |
| supportedResolutions: ["1K", "4K"], | |
| }), | |
| ).toBe("1K"); | |
| }); | |
| it("maps video-style resolutions by numeric distance", () => { | |
| expect( | |
| resolveClosestResolution({ | |
| requestedResolution: "480P", | |
| supportedResolutions: ["360P", "540P", "720P"], | |
| order: ["360P", "480P", "540P", "720P"], | |
| }), | |
| ).toBe("540P"); | |
| }); | |
| it("does not map across image and video resolution units", () => { | |
| expect( | |
| resolveClosestResolution({ | |
| requestedResolution: "4K", | |
| supportedResolutions: ["768P", "1080P"], | |
| order: ["360P", "480P", "540P", "720P", "768P", "1080P"], | |
| }), | |
| ).toBeUndefined(); | |
| }); | |
| it("clamps durations to the closest supported max", () => { | |
| expect(normalizeDurationToClosestMax(12, 8)).toBe(8); | |
| expect(normalizeDurationToClosestMax(6, 8)).toBe(6); | |
| }); | |
| }); | |
| describe("media-generation runtime shared failure summaries", () => { | |
| it("collapses abort cascades behind the non-abort failure", () => { | |
| expect(() => | |
| throwCapabilityGenerationFailure({ | |
| capabilityLabel: "music generation", | |
| attempts: [ | |
| { | |
| provider: "google", | |
| model: "lyria-3-clip-preview", | |
| error: "Manually set deadline 1s is too short. Minimum allowed deadline is 10s.", | |
| }, | |
| { | |
| provider: "minimax", | |
| model: "music-2.6", | |
| error: "This operation was aborted", | |
| }, | |
| { | |
| provider: "minimax-portal", | |
| model: "music-2.6", | |
| error: "This operation was aborted", | |
| }, | |
| ], | |
| lastError: new Error("This operation was aborted"), | |
| }), | |
| ).toThrow( | |
| "All music generation models failed (3): google/lyria-3-clip-preview: Manually set deadline 1s is too short. Minimum allowed deadline is 10s. | 2 fallback(s) aborted after the request was cancelled or timed out: minimax/music-2.6, minimax-portal/music-2.6", | |
| ); | |
| }); | |
| it("summarizes all-aborted attempts once", () => { | |
| expect(() => | |
| throwCapabilityGenerationFailure({ | |
| capabilityLabel: "music generation", | |
| attempts: [ | |
| { | |
| provider: "minimax", | |
| model: "music-2.6", | |
| error: "This operation was aborted", | |
| }, | |
| { | |
| provider: "minimax-portal", | |
| model: "music-2.6", | |
| error: "This operation was aborted", | |
| }, | |
| ], | |
| lastError: new Error("This operation was aborted"), | |
| }), | |
| ).toThrow( | |
| "All music generation models failed (2): 2 fallback(s) aborted after the request was cancelled or timed out: minimax/music-2.6, minimax-portal/music-2.6", | |
| ); | |
| }); | |
| }); | |