const attempts: FallbackAttempt[] = [];
let lastError: unknown;
let skipWarnEmitted = false; const warnOnFirstSkip = (reason: string) => { // Skip events are common in normal fallback flow, so log the *first* one in // a request at warn level with the reason, and leave the rest at debug. // This gives the operator visible feedback that their primary provider was // passed over without flooding logs on long fallback chains. if (!skipWarnEmitted) {
skipWarnEmitted = true;
log.warn(`video-generation candidate skipped: ${reason}`);
}
};
for (const candidate of candidates) { const provider = getVideoGenerationProvider(candidate.provider, params.cfg); if (!provider) { const error = `No video-generation provider registered for ${candidate.provider}`;
attempts.push({
provider: candidate.provider,
model: candidate.model,
error,
});
lastError = new Error(error); continue;
}
// Guard: skip candidates that cannot satisfy reference-input counts so // we never silently drop audio/image/video refs by falling over to a // provider that ignores them and "succeeds" without the caller's assets. const inputImageCount = params.inputImages?.length ?? 0; const inputVideoCount = params.inputVideos?.length ?? 0; const inputAudioCount = params.inputAudios?.length ?? 0; if (inputAudioCount > 0) { const { capabilities: candCaps } = resolveVideoGenerationModeCapabilities({
provider,
inputImageCount,
inputVideoCount,
}); // Fall back to flat provider.capabilities.maxInputAudios for providers that // set the all-modes default directly rather than nesting it in capabilities.generate etc. const maxAudio = candCaps?.maxInputAudios ?? provider.capabilities.maxInputAudios ?? 0; if (inputAudioCount > maxAudio) { const error =
maxAudio === 0
? `${candidate.provider}/${candidate.model} does not support reference audio inputs; skipping to avoid silent audio drop`
: `${candidate.provider}/${candidate.model} supports at most ${maxAudio} reference audio(s), ${inputAudioCount} requested; skipping`;
attempts.push({ provider: candidate.provider, model: candidate.model, error });
lastError = new Error(error);
warnOnFirstSkip(error);
log.debug(
`video-generation candidate skipped (audio capability): ${candidate.provider}/${candidate.model}`,
); continue;
}
}
// Guard: skip candidates that do not accept the requested providerOptions keys, // or whose declared providerOptions schema does not match the supplied value // types. Same skip-in-fallback rationale as the audio guard above — we never // want to silently forward provider-specific options to the wrong provider, // but we also do not want to block valid fallback candidates that *do* accept // them. Providers opt in by declaring `capabilities.providerOptions` on the // active mode or on the flat provider capabilities. if (
params.providerOptions && typeof params.providerOptions === "object" &&
Object.keys(params.providerOptions).length > 0
) { const { capabilities: optCaps } = resolveVideoGenerationModeCapabilities({
provider,
inputImageCount,
inputVideoCount,
}); const declaredOptions =
optCaps?.providerOptions ?? provider.capabilities.providerOptions ?? undefined; const mismatch = validateProviderOptionsAgainstDeclaration({
providerId: candidate.provider,
model: candidate.model,
providerOptions: params.providerOptions,
declaration: declaredOptions,
}); if (mismatch) {
attempts.push({ provider: candidate.provider, model: candidate.model, error: mismatch });
lastError = new Error(mismatch);
warnOnFirstSkip(mismatch);
log.debug(
`video-generation candidate skipped (providerOptions): ${candidate.provider}/${candidate.model}`,
); continue;
}
}
// Guard: skip candidates whose maxDurationSeconds hard cap is below the requested // duration. Only applies when the provider uses a simple max with no explicit // supported-durations list — when a list exists, runtime normalization snaps to the // nearest valid value so skipping is not appropriate. const requestedDuration = params.durationSeconds; if (typeof requestedDuration === "number" && Number.isFinite(requestedDuration)) { const { capabilities: durCaps } = resolveVideoGenerationModeCapabilities({
provider,
inputImageCount,
inputVideoCount,
}); const supportedDurations = resolveVideoGenerationSupportedDurations({
provider,
model: candidate.model,
inputImageCount,
inputVideoCount,
}); const maxDuration = durCaps?.maxDurationSeconds ?? provider.capabilities.maxDurationSeconds; if (
!supportedDurations && typeof maxDuration === "number" && // Compare the normalized (rounded) duration, not the raw float, since // resolveVideoGenerationOverrides applies Math.round before sending to the provider. // A request for 4.4s against maxDurationSeconds=4 rounds to 4 and is valid.
Math.round(requestedDuration) > maxDuration
) { const error = `${candidate.provider}/${candidate.model} supports at most ${maxDuration}s per video, ${requestedDuration}s requested; skipping`;
attempts.push({ provider: candidate.provider, model: candidate.model, error });
lastError = new Error(error);
warnOnFirstSkip(error);
log.debug(
`video-generation candidate skipped (duration capability): ${candidate.provider}/${candidate.model}`,
); continue;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.