mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 04:30:24 +00:00
Revert "feat: add video generation core infrastructure and extend image generation parameters (#53681)" (#54943)
This reverts commit 4cb8dde894.
This commit is contained in:
@@ -26,7 +26,6 @@ import { createSessionsSpawnTool } from "./tools/sessions-spawn-tool.js";
|
||||
import { createSessionsYieldTool } from "./tools/sessions-yield-tool.js";
|
||||
import { createSubagentsTool } from "./tools/subagents-tool.js";
|
||||
import { createTtsTool } from "./tools/tts-tool.js";
|
||||
import { createVideoGenerateTool } from "./tools/video-generate-tool.js";
|
||||
import { createWebFetchTool, createWebSearchTool } from "./tools/web-tools.js";
|
||||
import { resolveWorkspaceRoot } from "./workspace-dir.js";
|
||||
|
||||
@@ -125,10 +124,6 @@ export function createOpenClawTools(
|
||||
sandbox,
|
||||
fsPolicy: options?.fsPolicy,
|
||||
});
|
||||
const videoGenerateTool = createVideoGenerateTool({
|
||||
config: options?.config,
|
||||
agentDir: options?.agentDir,
|
||||
});
|
||||
const pdfTool = options?.agentDir?.trim()
|
||||
? createPdfTool({
|
||||
config: options?.config,
|
||||
@@ -191,7 +186,6 @@ export function createOpenClawTools(
|
||||
config: options?.config,
|
||||
}),
|
||||
...(imageGenerateTool ? [imageGenerateTool] : []),
|
||||
...(videoGenerateTool ? [videoGenerateTool] : []),
|
||||
createGatewayTool({
|
||||
agentSessionKey: options?.agentSessionKey,
|
||||
config: options?.config,
|
||||
|
||||
@@ -143,7 +143,6 @@ const TRUSTED_TOOL_RESULT_MEDIA = new Set([
|
||||
"gateway",
|
||||
"image",
|
||||
"image_generate",
|
||||
"video_generate",
|
||||
"memory_get",
|
||||
"memory_search",
|
||||
"message",
|
||||
|
||||
@@ -256,7 +256,6 @@ export function buildAgentSystemPrompt(params: {
|
||||
"Show a /status-equivalent status card (usage + time + Reasoning/Verbose/Elevated); use for model-use questions (📊 session_status); optional per-session model override",
|
||||
image: "Analyze an image with the configured image model",
|
||||
image_generate: "Generate images with the configured image-generation model",
|
||||
video_generate: "Generate short videos with the configured video-generation model",
|
||||
};
|
||||
|
||||
const toolOrder = [
|
||||
@@ -285,7 +284,6 @@ export function buildAgentSystemPrompt(params: {
|
||||
"session_status",
|
||||
"image",
|
||||
"image_generate",
|
||||
"video_generate",
|
||||
];
|
||||
|
||||
const rawToolNames = (params.toolNames ?? []).map((tool) => tool.trim());
|
||||
|
||||
@@ -241,14 +241,6 @@ const CORE_TOOL_DEFINITIONS: CoreToolDefinition[] = [
|
||||
profiles: ["coding"],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "video_generate",
|
||||
label: "video_generate",
|
||||
description: "Video generation",
|
||||
sectionId: "media",
|
||||
profiles: ["coding"],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "tts",
|
||||
label: "tts",
|
||||
|
||||
@@ -142,26 +142,6 @@ export function readNumberParam(
|
||||
return integer ? Math.trunc(value) : value;
|
||||
}
|
||||
|
||||
export function readBooleanParam(
|
||||
params: Record<string, unknown>,
|
||||
key: string,
|
||||
): boolean | undefined {
|
||||
const raw = readParamRaw(params, key);
|
||||
if (typeof raw === "boolean") {
|
||||
return raw;
|
||||
}
|
||||
if (typeof raw === "string") {
|
||||
const trimmed = raw.trim().toLowerCase();
|
||||
if (trimmed === "true" || trimmed === "1") {
|
||||
return true;
|
||||
}
|
||||
if (trimmed === "false" || trimmed === "0") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function readStringArrayParam(
|
||||
params: Record<string, unknown>,
|
||||
key: string,
|
||||
|
||||
@@ -16,7 +16,7 @@ import { saveMediaBuffer } from "../../media/store.js";
|
||||
import { loadWebMedia } from "../../media/web-media.js";
|
||||
import { getProviderEnvVars } from "../../secrets/provider-env-vars.js";
|
||||
import { resolveUserPath } from "../../utils.js";
|
||||
import { ToolInputError, readBooleanParam, readNumberParam, readStringParam } from "./common.js";
|
||||
import { ToolInputError, readNumberParam, readStringParam } from "./common.js";
|
||||
import { decodeDataUrl } from "./image-tool.helpers.js";
|
||||
import {
|
||||
applyImageGenerationModelConfigDefaults,
|
||||
@@ -106,18 +106,6 @@ const ImageGenerateToolSchema = Type.Object({
|
||||
maximum: MAX_COUNT,
|
||||
}),
|
||||
),
|
||||
seed: Type.Optional(Type.Number({ description: "Random seed for reproducibility." })),
|
||||
watermark: Type.Optional(Type.Boolean({ description: "Add watermark to image." })),
|
||||
guidanceScale: Type.Optional(
|
||||
Type.Number({
|
||||
description: "Controls prompt adherence strength (1.0-20.0). Higher means stricter.",
|
||||
minimum: 1,
|
||||
maximum: 20,
|
||||
}),
|
||||
),
|
||||
optimizePrompt: Type.Optional(
|
||||
Type.Boolean({ description: "Enable automatic prompt optimization for better results." }),
|
||||
),
|
||||
});
|
||||
|
||||
function getImageGenerationProviderAuthEnvVars(providerId: string): string[] {
|
||||
@@ -591,11 +579,6 @@ export function createImageGenerateTool(options?: {
|
||||
resolution,
|
||||
});
|
||||
|
||||
const seed = readNumberParam(params, "seed", { integer: true });
|
||||
const watermark = readBooleanParam(params, "watermark");
|
||||
const guidanceScale = readNumberParam(params, "guidanceScale");
|
||||
const optimizePrompt = readBooleanParam(params, "optimizePrompt");
|
||||
|
||||
const result = await generateImage({
|
||||
cfg: effectiveCfg,
|
||||
prompt,
|
||||
@@ -606,10 +589,6 @@ export function createImageGenerateTool(options?: {
|
||||
resolution,
|
||||
count,
|
||||
inputImages,
|
||||
seed,
|
||||
watermark,
|
||||
guidanceScale,
|
||||
optimizePrompt,
|
||||
});
|
||||
|
||||
const savedImages = await Promise.all(
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import { saveMediaBuffer } from "../../media/store.js";
|
||||
import {
|
||||
generateVideo,
|
||||
listRuntimeVideoGenerationProviders,
|
||||
} from "../../video-generation/runtime.js";
|
||||
import type { VideoGenerationProvider } from "../../video-generation/types.js";
|
||||
import { ToolInputError, readBooleanParam, readNumberParam, readStringParam } from "./common.js";
|
||||
import type { AnyAgentTool } from "./tool-runtime.helpers.js";
|
||||
|
||||
const SUPPORTED_ASPECT_RATIOS = new Set(["1:1", "3:4", "4:3", "9:16", "16:9", "21:9"]);
|
||||
|
||||
const VideoGenerateToolSchema = Type.Object({
|
||||
action: Type.Optional(
|
||||
Type.String({
|
||||
description:
|
||||
'Optional action: "generate" (default) or "list" to inspect available providers/models.',
|
||||
}),
|
||||
),
|
||||
prompt: Type.Optional(Type.String({ description: "Video generation prompt." })),
|
||||
model: Type.Optional(
|
||||
Type.String({
|
||||
description: "Optional provider/model override, e.g. byteplus/seedance-1-5-pro-251215.",
|
||||
}),
|
||||
),
|
||||
duration: Type.Optional(
|
||||
Type.Number({
|
||||
description: "Duration in seconds (typically 4-12).",
|
||||
minimum: 1,
|
||||
maximum: 60,
|
||||
}),
|
||||
),
|
||||
aspectRatio: Type.Optional(
|
||||
Type.String({
|
||||
description: "Optional aspect ratio: 1:1, 3:4, 4:3, 9:16, 16:9, or 21:9.",
|
||||
}),
|
||||
),
|
||||
resolution: Type.Optional(
|
||||
Type.String({
|
||||
description: "Optional resolution: 480p, 720p, or 1080p.",
|
||||
}),
|
||||
),
|
||||
seed: Type.Optional(Type.Number({ description: "Random seed for reproducibility." })),
|
||||
watermark: Type.Optional(Type.Boolean({ description: "Add watermark to video." })),
|
||||
firstFrameImageUrl: Type.Optional(
|
||||
Type.String({
|
||||
description: "URL of the first frame reference image for image-to-video (I2V) generation.",
|
||||
}),
|
||||
),
|
||||
lastFrameImageUrl: Type.Optional(
|
||||
Type.String({
|
||||
description: "URL of the last frame reference image for I2V generation.",
|
||||
}),
|
||||
),
|
||||
camerafixed: Type.Optional(
|
||||
Type.Boolean({ description: "Fix camera position during generation." }),
|
||||
),
|
||||
draft: Type.Optional(
|
||||
Type.Boolean({ description: "Draft mode for faster generation (forces 480p)." }),
|
||||
),
|
||||
});
|
||||
|
||||
function resolveAction(args: Record<string, unknown>): "generate" | "list" {
|
||||
const raw = readStringParam(args, "action");
|
||||
if (!raw) {
|
||||
return "generate";
|
||||
}
|
||||
const normalized = raw.trim().toLowerCase();
|
||||
if (normalized === "generate" || normalized === "list") {
|
||||
return normalized;
|
||||
}
|
||||
throw new ToolInputError('action must be "generate" or "list"');
|
||||
}
|
||||
|
||||
function buildListResponse(cfg?: OpenClawConfig): string {
|
||||
const providers = listRuntimeVideoGenerationProviders({ config: cfg });
|
||||
if (providers.length === 0) {
|
||||
return "No video generation providers available. Install a plugin that supports video generation.";
|
||||
}
|
||||
const lines: string[] = ["Available video generation providers:"];
|
||||
for (const provider of providers) {
|
||||
const models = provider.models ?? (provider.defaultModel ? [provider.defaultModel] : []);
|
||||
const capLines: string[] = [];
|
||||
if (provider.capabilities.aspectRatios?.length) {
|
||||
capLines.push(`aspect ratios: ${provider.capabilities.aspectRatios.join(", ")}`);
|
||||
}
|
||||
if (provider.capabilities.resolutions?.length) {
|
||||
capLines.push(`resolutions: ${provider.capabilities.resolutions.join(", ")}`);
|
||||
}
|
||||
if (provider.capabilities.maxDurationSeconds) {
|
||||
capLines.push(
|
||||
`duration: ${provider.capabilities.minDurationSeconds ?? 1}-${provider.capabilities.maxDurationSeconds}s`,
|
||||
);
|
||||
}
|
||||
lines.push(
|
||||
`- ${provider.id}${provider.label ? ` (${provider.label})` : ""}: ${models.join(", ")}${capLines.length > 0 ? ` [${capLines.join("; ")}]` : ""}`,
|
||||
);
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function validateAspectRatio(
|
||||
requested: string | undefined,
|
||||
provider: VideoGenerationProvider | undefined,
|
||||
): string | undefined {
|
||||
if (!requested) {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = requested.trim();
|
||||
if (!SUPPORTED_ASPECT_RATIOS.has(normalized)) {
|
||||
throw new ToolInputError(
|
||||
`Unsupported aspect ratio "${normalized}". Supported: ${[...SUPPORTED_ASPECT_RATIOS].join(", ")}`,
|
||||
);
|
||||
}
|
||||
if (
|
||||
provider?.capabilities.aspectRatios?.length &&
|
||||
!provider.capabilities.aspectRatios.includes(normalized)
|
||||
) {
|
||||
throw new ToolInputError(
|
||||
`Provider does not support aspect ratio "${normalized}". Supported: ${provider.capabilities.aspectRatios.join(", ")}`,
|
||||
);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function createVideoGenerateTool(params: {
|
||||
config?: OpenClawConfig;
|
||||
agentDir?: string;
|
||||
}): AnyAgentTool | null {
|
||||
const providers = listRuntimeVideoGenerationProviders({ config: params.config });
|
||||
if (providers.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
label: "Video Generation",
|
||||
name: "video_generate",
|
||||
description:
|
||||
'Generate a short video from a text prompt. Returns a saved video file path. Use action="list" to see available providers, models, and capabilities. Generated videos are delivered automatically from the tool result as MEDIA paths.',
|
||||
parameters: VideoGenerateToolSchema,
|
||||
execute: async (_toolCallId, args) => {
|
||||
const rawArgs = args as Record<string, unknown>;
|
||||
const cfg = params.config ?? loadConfig();
|
||||
const action = resolveAction(rawArgs);
|
||||
|
||||
if (action === "list") {
|
||||
return {
|
||||
content: [{ type: "text", text: buildListResponse(cfg) }],
|
||||
details: {},
|
||||
};
|
||||
}
|
||||
|
||||
const prompt = readStringParam(rawArgs, "prompt");
|
||||
if (!prompt?.trim()) {
|
||||
throw new ToolInputError("prompt is required for video generation");
|
||||
}
|
||||
|
||||
const modelOverride = readStringParam(rawArgs, "model");
|
||||
const duration = readNumberParam(rawArgs, "duration", { integer: true });
|
||||
const aspectRatio = readStringParam(rawArgs, "aspectRatio");
|
||||
const resolution = readStringParam(rawArgs, "resolution");
|
||||
const seed = readNumberParam(rawArgs, "seed", { integer: true });
|
||||
const watermark = readBooleanParam(rawArgs, "watermark");
|
||||
const firstFrameImageUrl = readStringParam(rawArgs, "firstFrameImageUrl");
|
||||
const lastFrameImageUrl = readStringParam(rawArgs, "lastFrameImageUrl");
|
||||
const camerafixed = readBooleanParam(rawArgs, "camerafixed");
|
||||
const draft = readBooleanParam(rawArgs, "draft");
|
||||
|
||||
const currentProviders = listRuntimeVideoGenerationProviders({ config: cfg });
|
||||
const parsedOverride = modelOverride?.includes("/")
|
||||
? { provider: modelOverride.split("/")[0] }
|
||||
: null;
|
||||
const validationProvider = parsedOverride
|
||||
? (currentProviders.find((p) => p.id === parsedOverride.provider) ?? currentProviders[0])
|
||||
: currentProviders[0];
|
||||
const validatedAspectRatio = validateAspectRatio(aspectRatio, validationProvider);
|
||||
|
||||
const providerOptions: Record<string, unknown> = {};
|
||||
if (camerafixed != null) {
|
||||
providerOptions.camerafixed = camerafixed;
|
||||
}
|
||||
if (draft != null) {
|
||||
providerOptions.draft = draft;
|
||||
}
|
||||
|
||||
const result = await generateVideo({
|
||||
cfg,
|
||||
prompt: prompt.trim(),
|
||||
agentDir: params.agentDir,
|
||||
modelOverride,
|
||||
durationSeconds: duration,
|
||||
aspectRatio: validatedAspectRatio,
|
||||
resolution: resolution?.trim(),
|
||||
seed,
|
||||
watermark,
|
||||
firstFrameImageUrl,
|
||||
lastFrameImageUrl,
|
||||
providerOptions: Object.keys(providerOptions).length > 0 ? providerOptions : undefined,
|
||||
});
|
||||
|
||||
const VIDEO_MAX_BYTES = 200 * 1024 * 1024; // 200MB for generated videos
|
||||
const savedVideos = await Promise.all(
|
||||
result.videos.map((video) =>
|
||||
saveMediaBuffer(
|
||||
video.buffer,
|
||||
video.mimeType,
|
||||
"tool-video-generation",
|
||||
VIDEO_MAX_BYTES,
|
||||
video.fileName,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const lines = [
|
||||
`Generated ${savedVideos.length} video${savedVideos.length === 1 ? "" : "s"} with ${result.provider}/${result.model}.`,
|
||||
];
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: lines.join("\n") }],
|
||||
details: {
|
||||
provider: result.provider,
|
||||
model: result.model,
|
||||
count: savedVideos.length,
|
||||
media: {
|
||||
mediaUrls: savedVideos.map((video) => video.path),
|
||||
},
|
||||
paths: savedVideos.map((video) => video.path),
|
||||
...(duration ? { duration } : {}),
|
||||
...(validatedAspectRatio ? { aspectRatio: validatedAspectRatio } : {}),
|
||||
...(resolution ? { resolution } : {}),
|
||||
attempts: result.attempts,
|
||||
metadata: result.metadata,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -102,7 +102,6 @@ const createRegistry = (channels: PluginRegistry["channels"]): PluginRegistry =>
|
||||
speechProviders: [],
|
||||
mediaUnderstandingProviders: [],
|
||||
imageGenerationProviders: [],
|
||||
videoGenerationProviders: [],
|
||||
webSearchProviders: [],
|
||||
gatewayHandlers: {},
|
||||
httpRoutes: [],
|
||||
|
||||
@@ -394,7 +394,6 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
|
||||
@@ -62,7 +62,6 @@ const createRegistry = (diagnostics: PluginDiagnostic[]): PluginRegistry => ({
|
||||
speechProviders: [],
|
||||
mediaUnderstandingProviders: [],
|
||||
imageGenerationProviders: [],
|
||||
videoGenerationProviders: [],
|
||||
webSearchProviders: [],
|
||||
gatewayHandlers: {},
|
||||
httpRoutes: [],
|
||||
|
||||
@@ -163,7 +163,6 @@ const createStubPluginRegistry = (): PluginRegistry => ({
|
||||
],
|
||||
mediaUnderstandingProviders: [],
|
||||
imageGenerationProviders: [],
|
||||
videoGenerationProviders: [],
|
||||
webSearchProviders: [],
|
||||
gatewayHandlers: {},
|
||||
httpRoutes: [],
|
||||
|
||||
@@ -30,11 +30,6 @@ export type GenerateImageParams = {
|
||||
aspectRatio?: string;
|
||||
resolution?: ImageGenerationResolution;
|
||||
inputImages?: ImageGenerationSourceImage[];
|
||||
seed?: number;
|
||||
watermark?: boolean;
|
||||
guidanceScale?: number;
|
||||
optimizePrompt?: boolean;
|
||||
providerOptions?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type GenerateImageRuntimeResult = {
|
||||
@@ -158,11 +153,6 @@ export async function generateImage(
|
||||
aspectRatio: params.aspectRatio,
|
||||
resolution: params.resolution,
|
||||
inputImages: params.inputImages,
|
||||
seed: params.seed,
|
||||
watermark: params.watermark,
|
||||
guidanceScale: params.guidanceScale,
|
||||
optimizePrompt: params.optimizePrompt,
|
||||
providerOptions: params.providerOptions,
|
||||
});
|
||||
if (!Array.isArray(result.images) || result.images.length === 0) {
|
||||
throw new Error("Image generation provider returned no images.");
|
||||
|
||||
@@ -31,12 +31,6 @@ export type ImageGenerationRequest = {
|
||||
aspectRatio?: string;
|
||||
resolution?: ImageGenerationResolution;
|
||||
inputImages?: ImageGenerationSourceImage[];
|
||||
seed?: number;
|
||||
watermark?: boolean;
|
||||
guidanceScale?: number;
|
||||
optimizePrompt?: boolean;
|
||||
/** Provider-specific options (e.g. sequential generation). */
|
||||
providerOptions?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type ImageGenerationResult = {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Public video-generation helpers and types for provider plugins.
|
||||
|
||||
export type {
|
||||
GeneratedVideoAsset,
|
||||
VideoGenerationProvider,
|
||||
VideoGenerationProviderCapabilities,
|
||||
VideoGenerationRequest,
|
||||
VideoGenerationResult,
|
||||
VideoGenerationSourceImage,
|
||||
} from "../video-generation/types.js";
|
||||
@@ -5,7 +5,6 @@ import type {
|
||||
OpenClawPluginApi,
|
||||
ProviderPlugin,
|
||||
SpeechProviderPlugin,
|
||||
VideoGenerationProviderPlugin,
|
||||
WebSearchProviderPlugin,
|
||||
} from "./types.js";
|
||||
|
||||
@@ -15,7 +14,6 @@ export type CapturedPluginRegistration = {
|
||||
speechProviders: SpeechProviderPlugin[];
|
||||
mediaUnderstandingProviders: MediaUnderstandingProviderPlugin[];
|
||||
imageGenerationProviders: ImageGenerationProviderPlugin[];
|
||||
videoGenerationProviders: VideoGenerationProviderPlugin[];
|
||||
webSearchProviders: WebSearchProviderPlugin[];
|
||||
tools: AnyAgentTool[];
|
||||
};
|
||||
@@ -25,7 +23,6 @@ export function createCapturedPluginRegistration(): CapturedPluginRegistration {
|
||||
const speechProviders: SpeechProviderPlugin[] = [];
|
||||
const mediaUnderstandingProviders: MediaUnderstandingProviderPlugin[] = [];
|
||||
const imageGenerationProviders: ImageGenerationProviderPlugin[] = [];
|
||||
const videoGenerationProviders: VideoGenerationProviderPlugin[] = [];
|
||||
const webSearchProviders: WebSearchProviderPlugin[] = [];
|
||||
const tools: AnyAgentTool[] = [];
|
||||
|
||||
@@ -34,7 +31,6 @@ export function createCapturedPluginRegistration(): CapturedPluginRegistration {
|
||||
speechProviders,
|
||||
mediaUnderstandingProviders,
|
||||
imageGenerationProviders,
|
||||
videoGenerationProviders,
|
||||
webSearchProviders,
|
||||
tools,
|
||||
api: {
|
||||
@@ -50,9 +46,6 @@ export function createCapturedPluginRegistration(): CapturedPluginRegistration {
|
||||
registerImageGenerationProvider(provider: ImageGenerationProviderPlugin) {
|
||||
imageGenerationProviders.push(provider);
|
||||
},
|
||||
registerVideoGenerationProvider(provider: VideoGenerationProviderPlugin) {
|
||||
videoGenerationProviders.push(provider);
|
||||
},
|
||||
registerWebSearchProvider(provider: WebSearchProviderPlugin) {
|
||||
webSearchProviders.push(provider);
|
||||
},
|
||||
|
||||
@@ -203,7 +203,6 @@ describe("plugin contract registry", () => {
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: ["exa"],
|
||||
toolNames: [],
|
||||
});
|
||||
@@ -212,7 +211,6 @@ describe("plugin contract registry", () => {
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: ["firecrawl"],
|
||||
toolNames: ["firecrawl_search", "firecrawl_scrape"],
|
||||
});
|
||||
@@ -221,7 +219,6 @@ describe("plugin contract registry", () => {
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: ["tavily"],
|
||||
toolNames: ["tavily_search", "tavily_extract"],
|
||||
});
|
||||
@@ -233,7 +230,6 @@ describe("plugin contract registry", () => {
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: ["fal"],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
});
|
||||
expect(findRegistrationForPlugin("google")).toMatchObject({
|
||||
@@ -241,7 +237,6 @@ describe("plugin contract registry", () => {
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: ["google"],
|
||||
imageGenerationProviderIds: ["google"],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: ["gemini"],
|
||||
});
|
||||
expect(findRegistrationForPlugin("openai")).toMatchObject({
|
||||
@@ -249,21 +244,18 @@ describe("plugin contract registry", () => {
|
||||
speechProviderIds: ["openai"],
|
||||
mediaUnderstandingProviderIds: ["openai", "openai-codex"],
|
||||
imageGenerationProviderIds: ["openai"],
|
||||
videoGenerationProviderIds: [],
|
||||
});
|
||||
expect(findRegistrationForPlugin("elevenlabs")).toMatchObject({
|
||||
providerIds: [],
|
||||
speechProviderIds: ["elevenlabs"],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
});
|
||||
expect(findRegistrationForPlugin("microsoft")).toMatchObject({
|
||||
providerIds: [],
|
||||
speechProviderIds: ["microsoft"],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@ type PluginRegistrationContractEntry = {
|
||||
speechProviderIds: string[];
|
||||
mediaUnderstandingProviderIds: string[];
|
||||
imageGenerationProviderIds: string[];
|
||||
videoGenerationProviderIds: string[];
|
||||
webSearchProviderIds: string[];
|
||||
toolNames: string[];
|
||||
};
|
||||
@@ -420,10 +419,6 @@ function upsertPluginRegistrationContractEntry(
|
||||
existing.imageGenerationProviderIds,
|
||||
next.imageGenerationProviderIds,
|
||||
);
|
||||
existing.videoGenerationProviderIds = mergeIds(
|
||||
existing.videoGenerationProviderIds,
|
||||
next.videoGenerationProviderIds,
|
||||
);
|
||||
existing.webSearchProviderIds = mergeIds(
|
||||
existing.webSearchProviderIds,
|
||||
next.webSearchProviderIds,
|
||||
@@ -448,7 +443,6 @@ function mergeProviderContractRegistrations(
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
toolNames: [],
|
||||
});
|
||||
@@ -470,9 +464,6 @@ function loadPluginRegistrationContractRegistry(): PluginRegistrationContractEnt
|
||||
imageGenerationProviderIds: captured.imageGenerationProviders.map(
|
||||
(provider) => provider.id,
|
||||
),
|
||||
videoGenerationProviderIds: captured.videoGenerationProviders.map(
|
||||
(provider) => provider.id,
|
||||
),
|
||||
webSearchProviderIds: captured.webSearchProviders.map((provider) => provider.id),
|
||||
toolNames: captured.tools.map((tool) => tool.name),
|
||||
});
|
||||
|
||||
@@ -20,7 +20,6 @@ function createPluginRecord(id: string, name: string): PluginRecord {
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
|
||||
@@ -20,7 +20,6 @@ export function createMockPluginRegistry(
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
@@ -46,7 +45,6 @@ export function createMockPluginRegistry(
|
||||
speechProviders: [],
|
||||
mediaUnderstandingProviders: [],
|
||||
imageGenerationProviders: [],
|
||||
videoGenerationProviders: [],
|
||||
webSearchProviders: [],
|
||||
httpRoutes: [],
|
||||
gatewayHandlers: {},
|
||||
|
||||
@@ -351,7 +351,6 @@ function createPluginRecord(params: {
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
|
||||
@@ -12,7 +12,6 @@ export function createEmptyPluginRegistry(): PluginRegistry {
|
||||
speechProviders: [],
|
||||
mediaUnderstandingProviders: [],
|
||||
imageGenerationProviders: [],
|
||||
videoGenerationProviders: [],
|
||||
webSearchProviders: [],
|
||||
gatewayHandlers: {},
|
||||
httpRoutes: [],
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
} from "./types.js";
|
||||
import type {
|
||||
ImageGenerationProviderPlugin,
|
||||
VideoGenerationProviderPlugin,
|
||||
OpenClawPluginApi,
|
||||
OpenClawPluginChannelRegistration,
|
||||
OpenClawPluginCliRegistrar,
|
||||
@@ -124,8 +123,6 @@ export type PluginMediaUnderstandingProviderRegistration =
|
||||
PluginOwnedProviderRegistration<MediaUnderstandingProviderPlugin>;
|
||||
export type PluginImageGenerationProviderRegistration =
|
||||
PluginOwnedProviderRegistration<ImageGenerationProviderPlugin>;
|
||||
export type PluginVideoGenerationProviderRegistration =
|
||||
PluginOwnedProviderRegistration<VideoGenerationProviderPlugin>;
|
||||
export type PluginWebSearchProviderRegistration =
|
||||
PluginOwnedProviderRegistration<WebSearchProviderPlugin>;
|
||||
|
||||
@@ -185,7 +182,6 @@ export type PluginRecord = {
|
||||
speechProviderIds: string[];
|
||||
mediaUnderstandingProviderIds: string[];
|
||||
imageGenerationProviderIds: string[];
|
||||
videoGenerationProviderIds: string[];
|
||||
webSearchProviderIds: string[];
|
||||
gatewayMethods: string[];
|
||||
cliCommands: string[];
|
||||
@@ -209,7 +205,6 @@ export type PluginRegistry = {
|
||||
speechProviders: PluginSpeechProviderRegistration[];
|
||||
mediaUnderstandingProviders: PluginMediaUnderstandingProviderRegistration[];
|
||||
imageGenerationProviders: PluginImageGenerationProviderRegistration[];
|
||||
videoGenerationProviders: PluginVideoGenerationProviderRegistration[];
|
||||
webSearchProviders: PluginWebSearchProviderRegistration[];
|
||||
gatewayHandlers: GatewayRequestHandlers;
|
||||
httpRoutes: PluginHttpRouteRegistration[];
|
||||
@@ -649,19 +644,6 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
});
|
||||
};
|
||||
|
||||
const registerVideoGenerationProvider = (
|
||||
record: PluginRecord,
|
||||
provider: VideoGenerationProviderPlugin,
|
||||
) => {
|
||||
registerUniqueProviderLike({
|
||||
record,
|
||||
provider,
|
||||
kindLabel: "video-generation provider",
|
||||
registrations: registry.videoGenerationProviders,
|
||||
ownedIds: record.videoGenerationProviderIds,
|
||||
});
|
||||
};
|
||||
|
||||
const registerWebSearchProvider = (record: PluginRecord, provider: WebSearchProviderPlugin) => {
|
||||
registerUniqueProviderLike({
|
||||
record,
|
||||
@@ -936,10 +918,6 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
registrationMode === "full"
|
||||
? (provider) => registerImageGenerationProvider(record, provider)
|
||||
: () => {},
|
||||
registerVideoGenerationProvider:
|
||||
registrationMode === "full"
|
||||
? (provider) => registerVideoGenerationProvider(record, provider)
|
||||
: () => {},
|
||||
registerWebSearchProvider:
|
||||
registrationMode === "full"
|
||||
? (provider) => registerWebSearchProvider(record, provider)
|
||||
|
||||
@@ -47,7 +47,6 @@ export function createPluginRecord(
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
@@ -111,7 +110,6 @@ export function createPluginLoadResult(
|
||||
speechProviders: [],
|
||||
mediaUnderstandingProviders: [],
|
||||
imageGenerationProviders: [],
|
||||
videoGenerationProviders: [],
|
||||
webSearchProviders: [],
|
||||
tools: [],
|
||||
hooks: [],
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
createCompatibilityNotice,
|
||||
createCustomHook,
|
||||
createPluginLoadResult,
|
||||
createPluginRecord,
|
||||
createTypedHook,
|
||||
HOOK_ONLY_MESSAGE,
|
||||
LEGACY_BEFORE_AGENT_START_MESSAGE,
|
||||
} from "./status.test-helpers.js";
|
||||
|
||||
const loadConfigMock = vi.fn();
|
||||
const loadOpenClawPluginsMock = vi.fn();
|
||||
@@ -27,32 +36,22 @@ vi.mock("../agents/workspace.js", () => ({
|
||||
resolveDefaultAgentWorkspaceDir: () => "/default-workspace",
|
||||
}));
|
||||
|
||||
function setPluginLoadResult(overrides: Partial<ReturnType<typeof createPluginLoadResult>>) {
|
||||
loadOpenClawPluginsMock.mockReturnValue(
|
||||
createPluginLoadResult({
|
||||
plugins: [],
|
||||
...overrides,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
describe("buildPluginStatusReport", () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
loadConfigMock.mockReset();
|
||||
loadOpenClawPluginsMock.mockReset();
|
||||
loadConfigMock.mockReturnValue({});
|
||||
loadOpenClawPluginsMock.mockReturnValue({
|
||||
plugins: [],
|
||||
diagnostics: [],
|
||||
channels: [],
|
||||
providers: [],
|
||||
speechProviders: [],
|
||||
mediaUnderstandingProviders: [],
|
||||
imageGenerationProviders: [],
|
||||
videoGenerationProviders: [],
|
||||
webSearchProviders: [],
|
||||
tools: [],
|
||||
hooks: [],
|
||||
typedHooks: [],
|
||||
channelSetups: [],
|
||||
httpRoutes: [],
|
||||
gatewayHandlers: {},
|
||||
cliRegistrars: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
});
|
||||
setPluginLoadResult({ plugins: [] });
|
||||
({
|
||||
buildAllPluginInspectReports,
|
||||
buildPluginCompatibilityNotices,
|
||||
@@ -83,52 +82,17 @@ describe("buildPluginStatusReport", () => {
|
||||
});
|
||||
|
||||
it("normalizes bundled plugin versions to the core base release", () => {
|
||||
loadOpenClawPluginsMock.mockReturnValue({
|
||||
setPluginLoadResult({
|
||||
plugins: [
|
||||
{
|
||||
createPluginRecord({
|
||||
id: "whatsapp",
|
||||
name: "WhatsApp",
|
||||
description: "Bundled channel plugin",
|
||||
version: "2026.3.22",
|
||||
source: "/tmp/whatsapp/index.ts",
|
||||
origin: "bundled",
|
||||
enabled: true,
|
||||
status: "loaded",
|
||||
toolNames: [],
|
||||
hookNames: [],
|
||||
channelIds: ["whatsapp"],
|
||||
providerIds: [],
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
httpRoutes: 0,
|
||||
hookCount: 0,
|
||||
configSchema: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
diagnostics: [],
|
||||
channels: [],
|
||||
providers: [],
|
||||
speechProviders: [],
|
||||
mediaUnderstandingProviders: [],
|
||||
imageGenerationProviders: [],
|
||||
videoGenerationProviders: [],
|
||||
webSearchProviders: [],
|
||||
tools: [],
|
||||
hooks: [],
|
||||
typedHooks: [],
|
||||
channelSetups: [],
|
||||
httpRoutes: [],
|
||||
gatewayHandlers: {},
|
||||
cliRegistrars: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
});
|
||||
|
||||
const report = buildPluginStatusReport({
|
||||
@@ -155,58 +119,21 @@ describe("buildPluginStatusReport", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
loadOpenClawPluginsMock.mockReturnValue({
|
||||
setPluginLoadResult({
|
||||
plugins: [
|
||||
{
|
||||
createPluginRecord({
|
||||
id: "google",
|
||||
name: "Google",
|
||||
description: "Google provider plugin",
|
||||
source: "/tmp/google/index.ts",
|
||||
origin: "bundled",
|
||||
enabled: true,
|
||||
status: "loaded",
|
||||
toolNames: [],
|
||||
hookNames: [],
|
||||
channelIds: [],
|
||||
providerIds: ["google"],
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: ["google"],
|
||||
imageGenerationProviderIds: ["google"],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: ["google"],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
httpRoutes: 0,
|
||||
hookCount: 0,
|
||||
configSchema: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
diagnostics: [{ level: "warn", pluginId: "google", message: "watch this surface" }],
|
||||
channels: [],
|
||||
channelSetups: [],
|
||||
providers: [],
|
||||
speechProviders: [],
|
||||
mediaUnderstandingProviders: [],
|
||||
imageGenerationProviders: [],
|
||||
videoGenerationProviders: [],
|
||||
webSearchProviders: [],
|
||||
tools: [],
|
||||
hooks: [],
|
||||
typedHooks: [
|
||||
{
|
||||
pluginId: "google",
|
||||
hookName: "before_agent_start",
|
||||
handler: () => undefined,
|
||||
source: "/tmp/google/index.ts",
|
||||
},
|
||||
],
|
||||
httpRoutes: [],
|
||||
gatewayHandlers: {},
|
||||
cliRegistrars: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
typedHooks: [createTypedHook({ pluginId: "google", hookName: "before_agent_start" })],
|
||||
});
|
||||
|
||||
const inspect = buildPluginInspectReport({ id: "google" });
|
||||
@@ -222,13 +149,7 @@ describe("buildPluginStatusReport", () => {
|
||||
]);
|
||||
expect(inspect?.usesLegacyBeforeAgentStart).toBe(true);
|
||||
expect(inspect?.compatibility).toEqual([
|
||||
{
|
||||
pluginId: "google",
|
||||
code: "legacy-before-agent-start",
|
||||
severity: "warn",
|
||||
message:
|
||||
"still uses legacy before_agent_start; keep regression coverage on this plugin, and prefer before_model_resolve/before_prompt_build for new work.",
|
||||
},
|
||||
createCompatibilityNotice({ pluginId: "google", code: "legacy-before-agent-start" }),
|
||||
]);
|
||||
expect(inspect?.policy).toEqual({
|
||||
allowPromptInjection: false,
|
||||
@@ -242,94 +163,25 @@ describe("buildPluginStatusReport", () => {
|
||||
});
|
||||
|
||||
it("builds inspect reports for every loaded plugin", () => {
|
||||
loadOpenClawPluginsMock.mockReturnValue({
|
||||
setPluginLoadResult({
|
||||
plugins: [
|
||||
{
|
||||
createPluginRecord({
|
||||
id: "lca",
|
||||
name: "LCA",
|
||||
description: "Legacy hook plugin",
|
||||
source: "/tmp/lca/index.ts",
|
||||
origin: "workspace",
|
||||
enabled: true,
|
||||
status: "loaded",
|
||||
toolNames: [],
|
||||
hookNames: [],
|
||||
channelIds: [],
|
||||
providerIds: [],
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
httpRoutes: 0,
|
||||
hookCount: 1,
|
||||
configSchema: false,
|
||||
},
|
||||
{
|
||||
}),
|
||||
createPluginRecord({
|
||||
id: "microsoft",
|
||||
name: "Microsoft",
|
||||
description: "Hybrid capability plugin",
|
||||
source: "/tmp/microsoft/index.ts",
|
||||
origin: "bundled",
|
||||
enabled: true,
|
||||
status: "loaded",
|
||||
toolNames: [],
|
||||
hookNames: [],
|
||||
channelIds: [],
|
||||
providerIds: ["microsoft"],
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: ["microsoft"],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
httpRoutes: 0,
|
||||
hookCount: 0,
|
||||
configSchema: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
diagnostics: [],
|
||||
channels: [],
|
||||
channelSetups: [],
|
||||
providers: [],
|
||||
speechProviders: [],
|
||||
mediaUnderstandingProviders: [],
|
||||
imageGenerationProviders: [],
|
||||
videoGenerationProviders: [],
|
||||
webSearchProviders: [],
|
||||
tools: [],
|
||||
hooks: [
|
||||
{
|
||||
pluginId: "lca",
|
||||
events: ["message"],
|
||||
entry: {
|
||||
hook: {
|
||||
name: "legacy",
|
||||
handler: () => undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
typedHooks: [
|
||||
{
|
||||
pluginId: "lca",
|
||||
hookName: "before_agent_start",
|
||||
handler: () => undefined,
|
||||
source: "/tmp/lca/index.ts",
|
||||
},
|
||||
],
|
||||
httpRoutes: [],
|
||||
gatewayHandlers: {},
|
||||
cliRegistrars: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
hooks: [createCustomHook({ pluginId: "lca", events: ["message"] })],
|
||||
typedHooks: [createTypedHook({ pluginId: "lca", hookName: "before_agent_start" })],
|
||||
});
|
||||
|
||||
const inspect = buildAllPluginInspectReports();
|
||||
@@ -344,221 +196,58 @@ describe("buildPluginStatusReport", () => {
|
||||
});
|
||||
|
||||
it("builds compatibility warnings for legacy compatibility paths", () => {
|
||||
loadOpenClawPluginsMock.mockReturnValue({
|
||||
setPluginLoadResult({
|
||||
plugins: [
|
||||
{
|
||||
createPluginRecord({
|
||||
id: "lca",
|
||||
name: "LCA",
|
||||
description: "Legacy hook plugin",
|
||||
source: "/tmp/lca/index.ts",
|
||||
origin: "workspace",
|
||||
enabled: true,
|
||||
status: "loaded",
|
||||
toolNames: [],
|
||||
hookNames: [],
|
||||
channelIds: [],
|
||||
providerIds: [],
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
httpRoutes: 0,
|
||||
hookCount: 1,
|
||||
configSchema: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
diagnostics: [],
|
||||
channels: [],
|
||||
channelSetups: [],
|
||||
providers: [],
|
||||
speechProviders: [],
|
||||
mediaUnderstandingProviders: [],
|
||||
imageGenerationProviders: [],
|
||||
videoGenerationProviders: [],
|
||||
webSearchProviders: [],
|
||||
tools: [],
|
||||
hooks: [],
|
||||
typedHooks: [
|
||||
{
|
||||
pluginId: "lca",
|
||||
hookName: "before_agent_start",
|
||||
handler: () => undefined,
|
||||
source: "/tmp/lca/index.ts",
|
||||
},
|
||||
],
|
||||
httpRoutes: [],
|
||||
gatewayHandlers: {},
|
||||
cliRegistrars: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
typedHooks: [createTypedHook({ pluginId: "lca", hookName: "before_agent_start" })],
|
||||
});
|
||||
|
||||
expect(buildPluginCompatibilityWarnings()).toEqual([
|
||||
"lca still uses legacy before_agent_start; keep regression coverage on this plugin, and prefer before_model_resolve/before_prompt_build for new work.",
|
||||
"lca is hook-only. This remains a supported compatibility path, but it has not migrated to explicit capability registration yet.",
|
||||
`lca ${LEGACY_BEFORE_AGENT_START_MESSAGE}`,
|
||||
`lca ${HOOK_ONLY_MESSAGE}`,
|
||||
]);
|
||||
});
|
||||
|
||||
it("builds structured compatibility notices with deterministic ordering", () => {
|
||||
loadOpenClawPluginsMock.mockReturnValue({
|
||||
setPluginLoadResult({
|
||||
plugins: [
|
||||
{
|
||||
createPluginRecord({
|
||||
id: "hook-only",
|
||||
name: "Hook Only",
|
||||
description: "",
|
||||
source: "/tmp/hook-only/index.ts",
|
||||
origin: "workspace",
|
||||
enabled: true,
|
||||
status: "loaded",
|
||||
toolNames: [],
|
||||
hookNames: [],
|
||||
channelIds: [],
|
||||
providerIds: [],
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
httpRoutes: 0,
|
||||
hookCount: 1,
|
||||
configSchema: false,
|
||||
},
|
||||
{
|
||||
}),
|
||||
createPluginRecord({
|
||||
id: "legacy-only",
|
||||
name: "Legacy Only",
|
||||
description: "",
|
||||
source: "/tmp/legacy-only/index.ts",
|
||||
origin: "workspace",
|
||||
enabled: true,
|
||||
status: "loaded",
|
||||
toolNames: [],
|
||||
hookNames: [],
|
||||
channelIds: [],
|
||||
providerIds: ["legacy-only"],
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
httpRoutes: 0,
|
||||
hookCount: 1,
|
||||
configSchema: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
diagnostics: [],
|
||||
channels: [],
|
||||
channelSetups: [],
|
||||
providers: [],
|
||||
speechProviders: [],
|
||||
mediaUnderstandingProviders: [],
|
||||
imageGenerationProviders: [],
|
||||
videoGenerationProviders: [],
|
||||
webSearchProviders: [],
|
||||
tools: [],
|
||||
hooks: [
|
||||
{
|
||||
pluginId: "hook-only",
|
||||
events: ["message"],
|
||||
entry: {
|
||||
hook: {
|
||||
name: "legacy",
|
||||
handler: () => undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
typedHooks: [
|
||||
{
|
||||
pluginId: "legacy-only",
|
||||
hookName: "before_agent_start",
|
||||
handler: () => undefined,
|
||||
source: "/tmp/legacy-only/index.ts",
|
||||
},
|
||||
],
|
||||
httpRoutes: [],
|
||||
gatewayHandlers: {},
|
||||
cliRegistrars: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
hooks: [createCustomHook({ pluginId: "hook-only", events: ["message"] })],
|
||||
typedHooks: [createTypedHook({ pluginId: "legacy-only", hookName: "before_agent_start" })],
|
||||
});
|
||||
|
||||
expect(buildPluginCompatibilityNotices()).toEqual([
|
||||
{
|
||||
pluginId: "hook-only",
|
||||
code: "hook-only",
|
||||
severity: "info",
|
||||
message:
|
||||
"is hook-only. This remains a supported compatibility path, but it has not migrated to explicit capability registration yet.",
|
||||
},
|
||||
{
|
||||
pluginId: "legacy-only",
|
||||
code: "legacy-before-agent-start",
|
||||
severity: "warn",
|
||||
message:
|
||||
"still uses legacy before_agent_start; keep regression coverage on this plugin, and prefer before_model_resolve/before_prompt_build for new work.",
|
||||
},
|
||||
createCompatibilityNotice({ pluginId: "hook-only", code: "hook-only" }),
|
||||
createCompatibilityNotice({ pluginId: "legacy-only", code: "legacy-before-agent-start" }),
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns no compatibility warnings for modern capability plugins", () => {
|
||||
loadOpenClawPluginsMock.mockReturnValue({
|
||||
setPluginLoadResult({
|
||||
plugins: [
|
||||
{
|
||||
createPluginRecord({
|
||||
id: "modern",
|
||||
name: "Modern",
|
||||
description: "",
|
||||
source: "/tmp/modern/index.ts",
|
||||
origin: "workspace",
|
||||
enabled: true,
|
||||
status: "loaded",
|
||||
toolNames: [],
|
||||
hookNames: [],
|
||||
channelIds: [],
|
||||
providerIds: ["modern"],
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
httpRoutes: 0,
|
||||
hookCount: 0,
|
||||
configSchema: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
diagnostics: [],
|
||||
channels: [],
|
||||
channelSetups: [],
|
||||
providers: [],
|
||||
speechProviders: [],
|
||||
mediaUnderstandingProviders: [],
|
||||
imageGenerationProviders: [],
|
||||
videoGenerationProviders: [],
|
||||
webSearchProviders: [],
|
||||
tools: [],
|
||||
hooks: [],
|
||||
typedHooks: [],
|
||||
httpRoutes: [],
|
||||
gatewayHandlers: {},
|
||||
cliRegistrars: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
});
|
||||
|
||||
expect(buildPluginCompatibilityNotices()).toEqual([]);
|
||||
@@ -566,55 +255,19 @@ describe("buildPluginStatusReport", () => {
|
||||
});
|
||||
|
||||
it("populates bundleCapabilities from plugin record", () => {
|
||||
loadOpenClawPluginsMock.mockReturnValue({
|
||||
setPluginLoadResult({
|
||||
plugins: [
|
||||
{
|
||||
createPluginRecord({
|
||||
id: "claude-bundle",
|
||||
name: "Claude Bundle",
|
||||
description: "A bundle plugin with skills and commands",
|
||||
source: "/tmp/claude-bundle/.claude-plugin/plugin.json",
|
||||
origin: "workspace",
|
||||
enabled: true,
|
||||
status: "loaded",
|
||||
format: "bundle",
|
||||
bundleFormat: "claude",
|
||||
bundleCapabilities: ["skills", "commands", "agents", "settings"],
|
||||
rootDir: "/tmp/claude-bundle",
|
||||
toolNames: [],
|
||||
hookNames: [],
|
||||
channelIds: [],
|
||||
providerIds: [],
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
httpRoutes: 0,
|
||||
hookCount: 0,
|
||||
configSchema: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
diagnostics: [],
|
||||
channels: [],
|
||||
channelSetups: [],
|
||||
providers: [],
|
||||
speechProviders: [],
|
||||
mediaUnderstandingProviders: [],
|
||||
imageGenerationProviders: [],
|
||||
videoGenerationProviders: [],
|
||||
webSearchProviders: [],
|
||||
tools: [],
|
||||
hooks: [],
|
||||
typedHooks: [],
|
||||
httpRoutes: [],
|
||||
gatewayHandlers: {},
|
||||
cliRegistrars: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
});
|
||||
|
||||
const inspect = buildPluginInspectReport({ id: "claude-bundle" });
|
||||
@@ -626,51 +279,15 @@ describe("buildPluginStatusReport", () => {
|
||||
});
|
||||
|
||||
it("returns empty bundleCapabilities and mcpServers for non-bundle plugins", () => {
|
||||
loadOpenClawPluginsMock.mockReturnValue({
|
||||
setPluginLoadResult({
|
||||
plugins: [
|
||||
{
|
||||
createPluginRecord({
|
||||
id: "plain-plugin",
|
||||
name: "Plain Plugin",
|
||||
description: "A regular plugin",
|
||||
source: "/tmp/plain-plugin/index.ts",
|
||||
origin: "workspace",
|
||||
enabled: true,
|
||||
status: "loaded",
|
||||
toolNames: [],
|
||||
hookNames: [],
|
||||
channelIds: [],
|
||||
providerIds: ["plain"],
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
videoGenerationProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
httpRoutes: 0,
|
||||
hookCount: 0,
|
||||
configSchema: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
diagnostics: [],
|
||||
channels: [],
|
||||
channelSetups: [],
|
||||
providers: [],
|
||||
speechProviders: [],
|
||||
mediaUnderstandingProviders: [],
|
||||
imageGenerationProviders: [],
|
||||
videoGenerationProviders: [],
|
||||
webSearchProviders: [],
|
||||
tools: [],
|
||||
hooks: [],
|
||||
typedHooks: [],
|
||||
httpRoutes: [],
|
||||
gatewayHandlers: {},
|
||||
cliRegistrars: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
});
|
||||
|
||||
const inspect = buildPluginInspectReport({ id: "plain-plugin" });
|
||||
@@ -681,27 +298,18 @@ describe("buildPluginStatusReport", () => {
|
||||
});
|
||||
|
||||
it("formats and summarizes compatibility notices", () => {
|
||||
const notice = {
|
||||
const notice = createCompatibilityNotice({
|
||||
pluginId: "legacy-plugin",
|
||||
code: "legacy-before-agent-start" as const,
|
||||
severity: "warn" as const,
|
||||
message:
|
||||
"still uses legacy before_agent_start; keep regression coverage on this plugin, and prefer before_model_resolve/before_prompt_build for new work.",
|
||||
};
|
||||
code: "legacy-before-agent-start",
|
||||
});
|
||||
|
||||
expect(formatPluginCompatibilityNotice(notice)).toBe(
|
||||
"legacy-plugin still uses legacy before_agent_start; keep regression coverage on this plugin, and prefer before_model_resolve/before_prompt_build for new work.",
|
||||
`legacy-plugin ${LEGACY_BEFORE_AGENT_START_MESSAGE}`,
|
||||
);
|
||||
expect(
|
||||
summarizePluginCompatibility([
|
||||
notice,
|
||||
{
|
||||
pluginId: "legacy-plugin",
|
||||
code: "hook-only",
|
||||
severity: "info",
|
||||
message:
|
||||
"is hook-only. This remains a supported compatibility path, but it has not migrated to explicit capability registration yet.",
|
||||
},
|
||||
createCompatibilityNotice({ pluginId: "legacy-plugin", code: "hook-only" }),
|
||||
]),
|
||||
).toEqual({
|
||||
noticeCount: 2,
|
||||
|
||||
@@ -21,7 +21,6 @@ export type PluginCapabilityKind =
|
||||
| "speech"
|
||||
| "media-understanding"
|
||||
| "image-generation"
|
||||
| "video-generation"
|
||||
| "web-search"
|
||||
| "channel";
|
||||
|
||||
@@ -166,7 +165,6 @@ function buildCapabilityEntries(plugin: PluginRegistry["plugins"][number]) {
|
||||
{ kind: "speech" as const, ids: plugin.speechProviderIds },
|
||||
{ kind: "media-understanding" as const, ids: plugin.mediaUnderstandingProviderIds },
|
||||
{ kind: "image-generation" as const, ids: plugin.imageGenerationProviderIds },
|
||||
{ kind: "video-generation" as const, ids: plugin.videoGenerationProviderIds },
|
||||
{ kind: "web-search" as const, ids: plugin.webSearchProviderIds },
|
||||
{ kind: "channel" as const, ids: plugin.channelIds },
|
||||
].filter((entry) => entry.ids.length > 0);
|
||||
|
||||
@@ -40,7 +40,6 @@ import type {
|
||||
SpeechTelephonySynthesisResult,
|
||||
SpeechVoiceOption,
|
||||
} from "../tts/provider-types.js";
|
||||
import type { VideoGenerationProvider } from "../video-generation/types.js";
|
||||
import type { WizardPrompter } from "../wizard/prompts.js";
|
||||
import type { SecretInputMode } from "./provider-auth-types.js";
|
||||
import type { createVpsAwareOAuthHandlers } from "./provider-oauth-flow.js";
|
||||
@@ -951,7 +950,6 @@ export type PluginSpeechProviderEntry = SpeechProviderPlugin & {
|
||||
|
||||
export type MediaUnderstandingProviderPlugin = MediaUnderstandingProvider;
|
||||
export type ImageGenerationProviderPlugin = ImageGenerationProvider;
|
||||
export type VideoGenerationProviderPlugin = VideoGenerationProvider;
|
||||
|
||||
export type OpenClawPluginGatewayMethod = {
|
||||
method: string;
|
||||
@@ -1354,8 +1352,6 @@ export type OpenClawPluginApi = {
|
||||
registerMediaUnderstandingProvider: (provider: MediaUnderstandingProviderPlugin) => void;
|
||||
/** Register an image generation provider (image generation capability). */
|
||||
registerImageGenerationProvider: (provider: ImageGenerationProviderPlugin) => void;
|
||||
/** Register a video generation provider (video generation capability). */
|
||||
registerVideoGenerationProvider: (provider: VideoGenerationProviderPlugin) => void;
|
||||
/** Register a web search provider (web search capability). */
|
||||
registerWebSearchProvider: (provider: WebSearchProviderPlugin) => void;
|
||||
registerInteractiveHandler: (registration: PluginInteractiveHandlerRegistration) => void;
|
||||
|
||||
@@ -29,7 +29,6 @@ export const createTestRegistry = (channels: TestChannelRegistration[] = []): Pl
|
||||
speechProviders: [],
|
||||
mediaUnderstandingProviders: [],
|
||||
imageGenerationProviders: [],
|
||||
videoGenerationProviders: [],
|
||||
webSearchProviders: [],
|
||||
gatewayHandlers: {},
|
||||
httpRoutes: [],
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import { normalizeProviderId } from "../agents/model-selection.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { isBlockedObjectKey } from "../infra/prototype-keys.js";
|
||||
import { loadOpenClawPlugins } from "../plugins/loader.js";
|
||||
import { getActivePluginRegistry, getActivePluginRegistryKey } from "../plugins/runtime.js";
|
||||
import type { VideoGenerationProviderPlugin } from "../plugins/types.js";
|
||||
|
||||
const BUILTIN_VIDEO_GENERATION_PROVIDERS: readonly VideoGenerationProviderPlugin[] = [];
|
||||
const UNSAFE_PROVIDER_IDS = new Set(["__proto__", "constructor", "prototype"]);
|
||||
|
||||
function normalizeVideoGenerationProviderId(id: string | undefined): string | undefined {
|
||||
const normalized = normalizeProviderId(id ?? "");
|
||||
if (!normalized || isBlockedObjectKey(normalized)) {
|
||||
return undefined;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function isSafeVideoGenerationProviderId(id: string | undefined): id is string {
|
||||
return Boolean(id && !UNSAFE_PROVIDER_IDS.has(id));
|
||||
}
|
||||
|
||||
function resolvePluginVideoGenerationProviders(
|
||||
cfg?: OpenClawConfig,
|
||||
): VideoGenerationProviderPlugin[] {
|
||||
const active = getActivePluginRegistry();
|
||||
const registry =
|
||||
(active?.videoGenerationProviders?.length ?? 0) > 0 || getActivePluginRegistryKey() || !cfg
|
||||
? active
|
||||
: loadOpenClawPlugins({ config: cfg });
|
||||
return registry?.videoGenerationProviders?.map((entry) => entry.provider) ?? [];
|
||||
}
|
||||
|
||||
function buildProviderMaps(cfg?: OpenClawConfig): {
|
||||
canonical: Map<string, VideoGenerationProviderPlugin>;
|
||||
aliases: Map<string, VideoGenerationProviderPlugin>;
|
||||
} {
|
||||
const canonical = new Map<string, VideoGenerationProviderPlugin>();
|
||||
const aliases = new Map<string, VideoGenerationProviderPlugin>();
|
||||
const register = (provider: VideoGenerationProviderPlugin) => {
|
||||
const id = normalizeVideoGenerationProviderId(provider.id);
|
||||
if (!isSafeVideoGenerationProviderId(id)) {
|
||||
return;
|
||||
}
|
||||
canonical.set(id, provider);
|
||||
aliases.set(id, provider);
|
||||
for (const alias of provider.aliases ?? []) {
|
||||
const normalizedAlias = normalizeVideoGenerationProviderId(alias);
|
||||
if (isSafeVideoGenerationProviderId(normalizedAlias)) {
|
||||
aliases.set(normalizedAlias, provider);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (const provider of BUILTIN_VIDEO_GENERATION_PROVIDERS) {
|
||||
register(provider);
|
||||
}
|
||||
for (const provider of resolvePluginVideoGenerationProviders(cfg)) {
|
||||
register(provider);
|
||||
}
|
||||
|
||||
return { canonical, aliases };
|
||||
}
|
||||
|
||||
export function listVideoGenerationProviders(
|
||||
cfg?: OpenClawConfig,
|
||||
): VideoGenerationProviderPlugin[] {
|
||||
return [...buildProviderMaps(cfg).canonical.values()];
|
||||
}
|
||||
|
||||
export function getVideoGenerationProvider(
|
||||
providerId: string | undefined,
|
||||
cfg?: OpenClawConfig,
|
||||
): VideoGenerationProviderPlugin | undefined {
|
||||
const normalized = normalizeVideoGenerationProviderId(providerId);
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
return buildProviderMaps(cfg).aliases.get(normalized);
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
import type { AuthProfileStore } from "../agents/auth-profiles.js";
|
||||
import { describeFailoverError, isFailoverError } from "../agents/failover-error.js";
|
||||
import type { FallbackAttempt } from "../agents/model-fallback.types.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { getVideoGenerationProvider, listVideoGenerationProviders } from "./provider-registry.js";
|
||||
import type { GeneratedVideoAsset, VideoGenerationResult } from "./types.js";
|
||||
|
||||
const log = createSubsystemLogger("video-generation");
|
||||
|
||||
export type GenerateVideoParams = {
|
||||
cfg: OpenClawConfig;
|
||||
prompt: string;
|
||||
agentDir?: string;
|
||||
authStore?: AuthProfileStore;
|
||||
modelOverride?: string;
|
||||
durationSeconds?: number;
|
||||
aspectRatio?: string;
|
||||
resolution?: string;
|
||||
seed?: number;
|
||||
watermark?: boolean;
|
||||
firstFrameImageUrl?: string;
|
||||
lastFrameImageUrl?: string;
|
||||
providerOptions?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type GenerateVideoRuntimeResult = {
|
||||
videos: GeneratedVideoAsset[];
|
||||
provider: string;
|
||||
model: string;
|
||||
attempts: FallbackAttempt[];
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
function parseModelRef(raw: string | undefined): { provider: string; model: string } | null {
|
||||
const trimmed = raw?.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
const slashIndex = trimmed.indexOf("/");
|
||||
if (slashIndex <= 0 || slashIndex === trimmed.length - 1) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
provider: trimmed.slice(0, slashIndex).trim(),
|
||||
model: trimmed.slice(slashIndex + 1).trim(),
|
||||
};
|
||||
}
|
||||
|
||||
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 = parseModelRef(raw);
|
||||
if (!parsed) {
|
||||
return;
|
||||
}
|
||||
const key = `${parsed.provider}/${parsed.model}`;
|
||||
if (seen.has(key)) {
|
||||
return;
|
||||
}
|
||||
seen.add(key);
|
||||
candidates.push(parsed);
|
||||
};
|
||||
|
||||
add(params.modelOverride);
|
||||
|
||||
// Fall back to first registered provider's default model
|
||||
if (candidates.length === 0) {
|
||||
const providers = listVideoGenerationProviders(params.cfg);
|
||||
for (const provider of providers) {
|
||||
if (provider.defaultModel) {
|
||||
add(`${provider.id}/${provider.defaultModel}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
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(
|
||||
"No video-generation model configured. Install a provider plugin that supports video generation (e.g. @openclaw/byteplus-provider).",
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
durationSeconds: params.durationSeconds,
|
||||
aspectRatio: params.aspectRatio,
|
||||
resolution: params.resolution,
|
||||
seed: params.seed,
|
||||
watermark: params.watermark,
|
||||
firstFrameImage: params.firstFrameImageUrl
|
||||
? { url: params.firstFrameImageUrl, role: "first_frame" }
|
||||
: undefined,
|
||||
lastFrameImage: params.lastFrameImageUrl
|
||||
? { url: params.lastFrameImageUrl, role: "last_frame" }
|
||||
: undefined,
|
||||
providerOptions: params.providerOptions,
|
||||
});
|
||||
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 });
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import type { AuthProfileStore } from "../agents/auth-profiles.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
|
||||
export type GeneratedVideoAsset = {
|
||||
buffer: Buffer;
|
||||
mimeType: string;
|
||||
fileName?: string;
|
||||
durationSeconds?: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type VideoGenerationSourceImage = {
|
||||
url?: string;
|
||||
buffer?: Buffer;
|
||||
mimeType?: string;
|
||||
role: "first_frame" | "last_frame";
|
||||
};
|
||||
|
||||
export type VideoGenerationRequest = {
|
||||
provider: string;
|
||||
model: string;
|
||||
prompt: string;
|
||||
cfg: OpenClawConfig;
|
||||
agentDir?: string;
|
||||
authStore?: AuthProfileStore;
|
||||
timeoutMs?: number;
|
||||
durationSeconds?: number;
|
||||
aspectRatio?: string;
|
||||
resolution?: string;
|
||||
seed?: number;
|
||||
watermark?: boolean;
|
||||
firstFrameImage?: VideoGenerationSourceImage;
|
||||
lastFrameImage?: VideoGenerationSourceImage;
|
||||
/** Provider-specific options (e.g. camerafixed, draft). */
|
||||
providerOptions?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type VideoGenerationResult = {
|
||||
videos: GeneratedVideoAsset[];
|
||||
model?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type VideoGenerationProviderCapabilities = {
|
||||
supportsDuration?: boolean;
|
||||
supportsAspectRatio?: boolean;
|
||||
supportsResolution?: boolean;
|
||||
maxDurationSeconds?: number;
|
||||
minDurationSeconds?: number;
|
||||
aspectRatios?: string[];
|
||||
resolutions?: string[];
|
||||
};
|
||||
|
||||
export type VideoGenerationProvider = {
|
||||
id: string;
|
||||
aliases?: string[];
|
||||
label?: string;
|
||||
defaultModel?: string;
|
||||
models?: string[];
|
||||
capabilities: VideoGenerationProviderCapabilities;
|
||||
generateVideo: (req: VideoGenerationRequest) => Promise<VideoGenerationResult>;
|
||||
};
|
||||
Reference in New Issue
Block a user