mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-11 01:01:13 +00:00
190 lines
5.9 KiB
TypeScript
190 lines
5.9 KiB
TypeScript
import {
|
|
createSubsystemLogger,
|
|
describeFailoverError,
|
|
getProviderEnvVars,
|
|
getVideoGenerationProvider,
|
|
isFailoverError,
|
|
listVideoGenerationProviders,
|
|
parseVideoGenerationModelRef,
|
|
resolveAgentModelFallbackValues,
|
|
resolveAgentModelPrimaryValue,
|
|
type AuthProfileStore,
|
|
type FallbackAttempt,
|
|
type GeneratedVideoAsset,
|
|
type OpenClawConfig,
|
|
type VideoGenerationResolution,
|
|
type VideoGenerationResult,
|
|
type VideoGenerationSourceAsset,
|
|
} from "../api.js";
|
|
|
|
const log = createSubsystemLogger("video-generation");
|
|
|
|
export type GenerateVideoParams = {
|
|
cfg: OpenClawConfig;
|
|
prompt: string;
|
|
agentDir?: string;
|
|
authStore?: AuthProfileStore;
|
|
modelOverride?: string;
|
|
size?: string;
|
|
aspectRatio?: string;
|
|
resolution?: VideoGenerationResolution;
|
|
durationSeconds?: number;
|
|
audio?: boolean;
|
|
watermark?: boolean;
|
|
inputImages?: VideoGenerationSourceAsset[];
|
|
inputVideos?: VideoGenerationSourceAsset[];
|
|
};
|
|
|
|
export type GenerateVideoRuntimeResult = {
|
|
videos: GeneratedVideoAsset[];
|
|
provider: string;
|
|
model: string;
|
|
attempts: FallbackAttempt[];
|
|
metadata?: Record<string, unknown>;
|
|
};
|
|
|
|
function resolveVideoGenerationCandidates(params: {
|
|
cfg: OpenClawConfig;
|
|
modelOverride?: string;
|
|
}): Array<{ provider: string; model: string }> {
|
|
const candidates: Array<{ provider: string; model: string }> = [];
|
|
const seen = new Set<string>();
|
|
const add = (raw: string | undefined) => {
|
|
const parsed = parseVideoGenerationModelRef(raw);
|
|
if (!parsed) {
|
|
return;
|
|
}
|
|
const key = `${parsed.provider}/${parsed.model}`;
|
|
if (seen.has(key)) {
|
|
return;
|
|
}
|
|
seen.add(key);
|
|
candidates.push(parsed);
|
|
};
|
|
|
|
add(params.modelOverride);
|
|
add(resolveAgentModelPrimaryValue(params.cfg.agents?.defaults?.videoGenerationModel));
|
|
for (const fallback of resolveAgentModelFallbackValues(
|
|
params.cfg.agents?.defaults?.videoGenerationModel,
|
|
)) {
|
|
add(fallback);
|
|
}
|
|
return candidates;
|
|
}
|
|
|
|
function throwVideoGenerationFailure(params: {
|
|
attempts: FallbackAttempt[];
|
|
lastError: unknown;
|
|
}): never {
|
|
if (params.attempts.length <= 1 && params.lastError) {
|
|
throw params.lastError;
|
|
}
|
|
const summary =
|
|
params.attempts.length > 0
|
|
? params.attempts
|
|
.map((attempt) => `${attempt.provider}/${attempt.model}: ${attempt.error}`)
|
|
.join(" | ")
|
|
: "unknown";
|
|
throw new Error(`All video generation models failed (${params.attempts.length}): ${summary}`, {
|
|
cause: params.lastError instanceof Error ? params.lastError : undefined,
|
|
});
|
|
}
|
|
|
|
function buildNoVideoGenerationModelConfiguredMessage(cfg: OpenClawConfig): string {
|
|
const providers = listVideoGenerationProviders(cfg);
|
|
const sampleModel =
|
|
providers.find((provider) => provider.defaultModel) ??
|
|
({ id: "qwen", defaultModel: "wan2.6-t2v" } as const);
|
|
const authHints = providers
|
|
.flatMap((provider) => {
|
|
const envVars = getProviderEnvVars(provider.id);
|
|
if (envVars.length === 0) {
|
|
return [];
|
|
}
|
|
return [`${provider.id}: ${envVars.join(" / ")}`];
|
|
})
|
|
.slice(0, 3);
|
|
return [
|
|
`No video-generation model configured. Set agents.defaults.videoGenerationModel.primary to a provider/model like "${sampleModel.id}/${sampleModel.defaultModel}".`,
|
|
authHints.length > 0
|
|
? `If you want a specific provider, also configure that provider's auth/API key first (${authHints.join("; ")}).`
|
|
: "If you want a specific provider, also configure that provider's auth/API key first.",
|
|
].join(" ");
|
|
}
|
|
|
|
export function listRuntimeVideoGenerationProviders(params?: { config?: OpenClawConfig }) {
|
|
return listVideoGenerationProviders(params?.config);
|
|
}
|
|
|
|
export async function generateVideo(
|
|
params: GenerateVideoParams,
|
|
): Promise<GenerateVideoRuntimeResult> {
|
|
const candidates = resolveVideoGenerationCandidates({
|
|
cfg: params.cfg,
|
|
modelOverride: params.modelOverride,
|
|
});
|
|
if (candidates.length === 0) {
|
|
throw new Error(buildNoVideoGenerationModelConfiguredMessage(params.cfg));
|
|
}
|
|
|
|
const attempts: FallbackAttempt[] = [];
|
|
let lastError: unknown;
|
|
|
|
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;
|
|
}
|
|
|
|
try {
|
|
const result: VideoGenerationResult = await provider.generateVideo({
|
|
provider: candidate.provider,
|
|
model: candidate.model,
|
|
prompt: params.prompt,
|
|
cfg: params.cfg,
|
|
agentDir: params.agentDir,
|
|
authStore: params.authStore,
|
|
size: params.size,
|
|
aspectRatio: params.aspectRatio,
|
|
resolution: params.resolution,
|
|
durationSeconds: params.durationSeconds,
|
|
audio: params.audio,
|
|
watermark: params.watermark,
|
|
inputImages: params.inputImages,
|
|
inputVideos: params.inputVideos,
|
|
});
|
|
if (!Array.isArray(result.videos) || result.videos.length === 0) {
|
|
throw new Error("Video generation provider returned no videos.");
|
|
}
|
|
return {
|
|
videos: result.videos,
|
|
provider: candidate.provider,
|
|
model: result.model ?? candidate.model,
|
|
attempts,
|
|
metadata: result.metadata,
|
|
};
|
|
} catch (err) {
|
|
lastError = err;
|
|
const described = isFailoverError(err) ? describeFailoverError(err) : undefined;
|
|
attempts.push({
|
|
provider: candidate.provider,
|
|
model: candidate.model,
|
|
error: described?.message ?? (err instanceof Error ? err.message : String(err)),
|
|
reason: described?.reason,
|
|
status: described?.status,
|
|
code: described?.code,
|
|
});
|
|
log.debug(`video-generation candidate failed: ${candidate.provider}/${candidate.model}`);
|
|
}
|
|
}
|
|
|
|
throwVideoGenerationFailure({ attempts, lastError });
|
|
}
|