* Build the VideoGenerationProvider that registers BlockRun video models * with OpenClaw's native video generation UI. * Delegates to the local proxy (which handles x402 payment + polling).
()
| 1255 | * Delegates to the local proxy (which handles x402 payment + polling). |
| 1256 | */ |
| 1257 | function buildVideoGenerationProvider(): VideoGenerationProviderPlugin { |
| 1258 | return { |
| 1259 | id: "blockrun", |
| 1260 | label: "BlockRun", |
| 1261 | defaultModel: "xai/grok-imagine-video", |
| 1262 | models: [ |
| 1263 | "xai/grok-imagine-video", |
| 1264 | "bytedance/seedance-1.5-pro", |
| 1265 | "bytedance/seedance-2.0-fast", |
| 1266 | "bytedance/seedance-2.0", |
| 1267 | ], |
| 1268 | capabilities: { |
| 1269 | maxVideos: 1, |
| 1270 | maxInputImages: 1, |
| 1271 | maxDurationSeconds: 10, |
| 1272 | supportedDurationSeconds: [5, 8, 10], |
| 1273 | supportsAudio: false, |
| 1274 | imageToVideo: { |
| 1275 | enabled: true, |
| 1276 | maxInputImages: 1, |
| 1277 | maxDurationSeconds: 10, |
| 1278 | supportedDurationSeconds: [5, 8, 10], |
| 1279 | }, |
| 1280 | }, |
| 1281 | isConfigured: () => existsSync(WALLET_FILE), |
| 1282 | generateVideo: async (req: VideoGenerationRequest) => { |
| 1283 | const port = getProxyPort(); |
| 1284 | const imageUrl = req.inputImages?.[0]?.url; |
| 1285 | const body = JSON.stringify({ |
| 1286 | model: req.model, |
| 1287 | prompt: req.prompt, |
| 1288 | ...(imageUrl ? { image_url: imageUrl } : {}), |
| 1289 | ...(req.durationSeconds ? { duration_seconds: req.durationSeconds } : {}), |
| 1290 | }); |
| 1291 | // Video generation: ClawRouter proxy internally polls upstream for up to 5 min. |
| 1292 | // Client timeout must exceed that — use 330s (5.5 min) to give the proxy headroom |
| 1293 | // to return the final downloaded URL after the last poll. |
| 1294 | const timeoutMs = req.timeoutMs ?? 330_000; |
| 1295 | const resp = await fetch(`http://127.0.0.1:${port}/v1/videos/generations`, { |
| 1296 | method: "POST", |
| 1297 | headers: { "content-type": "application/json" }, |
| 1298 | body, |
| 1299 | signal: AbortSignal.timeout(timeoutMs), |
| 1300 | }); |
| 1301 | if (!resp.ok) { |
| 1302 | const errText = await resp.text().catch(() => ""); |
| 1303 | throw new Error(`BlockRun video generation failed (${resp.status}): ${errText}`); |
| 1304 | } |
| 1305 | const result = (await resp.json()) as { |
| 1306 | data?: Array<{ url?: string; duration_seconds?: number }>; |
| 1307 | model?: string; |
| 1308 | }; |
| 1309 | const videos = await Promise.all( |
| 1310 | (result.data ?? []).map(async (clip) => { |
| 1311 | // URL format: http://localhost:PORT/videos/FILENAME |
| 1312 | const filename = clip.url?.split("/videos/").pop(); |
| 1313 | if (!filename) throw new Error(`Unexpected video URL format: ${clip.url}`); |
| 1314 | const filePath = join(VIDEO_DIR, filename); |
no test coverage detected