cerevox 4.0.0-alpha.9 → 4.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -54,6 +54,7 @@ const node_fs_1 = require("node:fs");
54
54
  const mp3_duration_1 = __importDefault(require("mp3-duration"));
55
55
  const image_size_1 = __importDefault(require("image-size"));
56
56
  const seed_1 = require("../../utils/seed");
57
+ const uuid_1 = require("uuid");
57
58
  function createErrorResponse(error, operation, details) {
58
59
  const errorMessage = error instanceof Error ? error.message : String(error);
59
60
  console.error(`[${operation}] Error:`, error);
@@ -322,6 +323,24 @@ async function listFiles(dir) {
322
323
  const entries = await (0, promises_1.readdir)(dir, { withFileTypes: true });
323
324
  return entries.filter(e => e.isFile()).map(e => (0, node_path_1.resolve)(dir, e.name));
324
325
  }
326
+ function checkModelEnabled(model) {
327
+ // 检查是否是许可的模型
328
+ if (!process.env.ENABLED_MODELS) {
329
+ return true;
330
+ }
331
+ const enabledModels = process.env.ENABLED_MODELS.split(/\s*,\s*/g);
332
+ if (!enabledModels.includes(model)) {
333
+ throw new Error(`Type ${model} is not enabled. Use any model in ${JSON.stringify(enabledModels)} instead.`);
334
+ }
335
+ return true;
336
+ }
337
+ function checkSkillEnabled(skillName) {
338
+ if (!process.env.ENABLED_SKILLS) {
339
+ return true;
340
+ }
341
+ const enabledSkills = process.env.ENABLED_SKILLS.split(/\s*,\s*/g);
342
+ return enabledSkills.includes(skillName);
343
+ }
325
344
  // Create an MCP server
326
345
  const server = new mcp_js_1.McpServer({
327
346
  name: 'Cerevox Server',
@@ -332,18 +351,18 @@ const cerevox = new index_1.default({
332
351
  logLevel: 'error',
333
352
  });
334
353
  let session = null;
335
- let projectLocalDir = process.env.ZEROCUT_PROJECT_CWD || process.cwd() || '.';
336
- let checkStoryboardFlag = false;
337
- let checkAudioVideoDurationFlag = false;
338
- // let checkStoryboardSubtitlesFlag = false;
354
+ let projectLocalDir = process.env.ZEROCUT_PROJECT_CWD ||
355
+ `${(0, node_path_1.resolve)(process.env.ZEROCUT_WORKSPACE_DIR, (0, uuid_1.v4)())}` ||
356
+ '.';
339
357
  let closeSessionTimerId = null;
358
+ const CLIENT_TYPE = process.env.CLIENT_TYPE || 'trae';
340
359
  // 注册 ZeroCut 指导规范 Prompt
341
360
  server.registerPrompt('zerocut-guideline', {
342
361
  title: 'ZeroCut 短视频创作指导规范',
343
362
  description: '专业的短视频创作 Agent 指导规范,包含完整的工作流程、工具说明和质量建议',
344
363
  }, async () => {
345
364
  try {
346
- const promptPath = (0, node_path_1.resolve)(__dirname, './prompts/zerocut-core-web.md');
365
+ const promptPath = (0, node_path_1.resolve)(__dirname, './prompts/zerocut-core.md');
347
366
  const promptContent = await (0, promises_1.readFile)(promptPath, 'utf-8');
348
367
  return {
349
368
  messages: [
@@ -364,25 +383,23 @@ server.registerPrompt('zerocut-guideline', {
364
383
  });
365
384
  server.registerTool('project-open', {
366
385
  title: 'Open Project',
367
- description: 'Launch a new Cerevox session with a Chromium browser instance and open a new project context. Supports smart file filtering to optimize upload performance.',
386
+ description: 'Launch a new Cerevox session with a Chromium browser instance and open a new project context. Supports smart file filtering to optimize resource-sync performance.',
368
387
  inputSchema: {
369
- localDir: zod_1.z
388
+ projectName: zod_1.z
370
389
  .string()
371
- .optional()
372
- .default('.')
373
- .describe('The path of the file to upload.'),
390
+ .describe('项目名,命名规则如同命名文件,可以用空格,但不得使用特殊字符(如/、:等),如果你知晓当前目录,必须用当前目录的最后一级目录名作为项目名,否则根据用户需求自定义项目名'),
374
391
  tosFiles: zod_1.z
375
392
  .array(zod_1.z.string())
376
393
  .optional()
377
394
  .default([])
378
395
  .describe('对象存储系统中的持久化文件,不通过本地直接下载到项目目录,可选参数'),
379
- uploadAllFiles: zod_1.z
396
+ syncAllFiles: zod_1.z
380
397
  .boolean()
381
398
  .optional()
382
399
  .default(false)
383
- .describe('Whether to upload all files without filtering. If true, skips the smart filtering logic.'),
400
+ .describe('Whether to sync all resources without filtering. If true, skips the smart filtering logic.'),
384
401
  },
385
- }, async ({ localDir, uploadAllFiles, tosFiles }, context) => {
402
+ }, async ({ projectName, syncAllFiles, tosFiles }, context) => {
386
403
  try {
387
404
  if (closeSessionTimerId) {
388
405
  clearTimeout(closeSessionTimerId);
@@ -390,12 +407,9 @@ server.registerTool('project-open', {
390
407
  }
391
408
  // 检查是否已有活跃session
392
409
  if (session) {
393
- console.warn('Session already exists, closing previous session');
394
- // try {
395
- // await session.close();
396
- // } catch (closeError) {
397
- // console.warn('Failed to close previous session:', closeError);
398
- // }
410
+ if (session.projectName !== projectName) {
411
+ return createErrorResponse('project-open', `Another project is already open: ${session.projectName},Ask user to close it first.`);
412
+ }
399
413
  const result = {
400
414
  success: true,
401
415
  sessionId: session.id,
@@ -426,10 +440,20 @@ server.registerTool('project-open', {
426
440
  if (!session) {
427
441
  throw new Error('Failed to create Cerevox session');
428
442
  }
443
+ // 保存项目名到session
444
+ session.projectName = projectName;
429
445
  console.log('Initializing project...');
430
446
  const workDir = await initProject(session);
431
- projectLocalDir = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), localDir || '.');
447
+ if (!process.env.ZEROCUT_PROJECT_CWD &&
448
+ !process.env.ZEROCUT_WORKSPACE_DIR) {
449
+ throw new Error('ZEROCUT_WORKSPACE_DIR environment variable is required');
450
+ }
451
+ projectLocalDir = process.env.ZEROCUT_PROJECT_CWD
452
+ ? (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD, '.')
453
+ : (0, node_path_1.resolve)(process.env.ZEROCUT_WORKSPACE_DIR, projectName);
432
454
  const syncDir = (0, node_path_1.resolve)(projectLocalDir, 'materials');
455
+ // 保证项目目录存在
456
+ await (0, promises_1.mkdir)(projectLocalDir, { recursive: true });
433
457
  try {
434
458
  await (0, promises_1.mkdir)(syncDir, { recursive: true });
435
459
  }
@@ -441,39 +465,37 @@ server.registerTool('project-open', {
441
465
  // 文件过滤逻辑
442
466
  let filesToUpload = [];
443
467
  let skippedFiles = [];
444
- if (localDir) {
445
- try {
446
- materials = await listFiles(syncDir);
447
- }
448
- catch (listError) {
449
- console.warn('Failed to list materials:', listError);
450
- materials = [];
451
- }
452
- if (uploadAllFiles) {
453
- // 如果 uploadAllFiles 为 true,跳过智能过滤,上传所有文件
454
- filesToUpload = materials;
455
- skippedFiles = [];
456
- }
457
- else {
458
- // 智能文件过滤逻辑
459
- const filterResult = await filterMaterialsForUpload(materials, projectLocalDir);
460
- filesToUpload = filterResult.filesToUpload;
461
- skippedFiles = filterResult.skippedFiles;
462
- }
468
+ try {
469
+ materials = await listFiles(syncDir);
470
+ }
471
+ catch (listError) {
472
+ console.warn('Failed to list materials:', listError);
473
+ materials = [];
474
+ }
475
+ if (syncAllFiles) {
476
+ // 如果 syncAllFiles 为 true,跳过智能过滤,上传所有文件
477
+ filesToUpload = materials;
478
+ skippedFiles = [];
479
+ }
480
+ else {
481
+ // 智能文件过滤逻辑
482
+ const filterResult = await filterMaterialsForUpload(materials, projectLocalDir);
483
+ filesToUpload = filterResult.filesToUpload;
484
+ skippedFiles = filterResult.skippedFiles;
463
485
  }
464
486
  const files = session.files;
465
487
  let progress = 0;
466
- const uploadErrors = [];
488
+ const syncErrors = [];
467
489
  const totalFiles = filesToUpload.length + tosFiles.length;
468
490
  for (const material of filesToUpload) {
469
491
  try {
470
492
  await files.upload(material, `${workDir}/materials/${(0, node_path_1.basename)(material)}`);
471
493
  await sendProgress(context, ++progress, totalFiles, material);
472
494
  }
473
- catch (uploadError) {
474
- const errorMsg = `Failed to upload ${material}: ${uploadError}`;
495
+ catch (syncError) {
496
+ const errorMsg = `Failed to sync ${material}: ${syncError}`;
475
497
  console.error(errorMsg);
476
- uploadErrors.push(errorMsg);
498
+ syncErrors.push(errorMsg);
477
499
  }
478
500
  }
479
501
  for (const tosFile of tosFiles) {
@@ -482,10 +504,10 @@ server.registerTool('project-open', {
482
504
  await session.terminal.run(`wget -O ${workDir}/materials/${(0, node_path_1.basename)(url.pathname)} ${tosFile}`);
483
505
  await sendProgress(context, ++progress, totalFiles, tosFile);
484
506
  }
485
- catch (uploadError) {
486
- const errorMsg = `Failed to upload ${tosFile}: ${uploadError}`;
507
+ catch (syncError) {
508
+ const errorMsg = `Failed to sync ${tosFile}: ${syncError}`;
487
509
  console.error(errorMsg);
488
- uploadErrors.push(errorMsg);
510
+ syncErrors.push(errorMsg);
489
511
  }
490
512
  }
491
513
  const result = {
@@ -493,11 +515,12 @@ server.registerTool('project-open', {
493
515
  nextActionSuggest: '检查规则上下文是否已召回,若未召回,调用 retrieve_rules 工具召回规则上下文',
494
516
  sessionId: session.id,
495
517
  workDir,
518
+ projectName,
496
519
  projectLocalDir,
497
520
  materials,
498
- uploadedFiles: filesToUpload.map(file => (0, node_path_1.basename)(file)),
521
+ syncedFiles: filesToUpload.map(file => (0, node_path_1.basename)(file)),
499
522
  skippedFiles: skippedFiles.map(file => (0, node_path_1.basename)(file)),
500
- uploadErrors: uploadErrors.length > 0 ? uploadErrors : undefined,
523
+ syncErrors: syncErrors.length > 0 ? syncErrors : undefined,
501
524
  };
502
525
  return {
503
526
  content: [
@@ -523,7 +546,7 @@ server.registerTool('project-close', {
523
546
  .min(0)
524
547
  .max(20)
525
548
  .default(5)
526
- .describe('Close the session after the specified number of minutes. Default is 5 minutes. 除非用户要求立即关闭会话,将该参数设置为0,否则应默认设为5'),
549
+ .describe('Close the session after the specified number of minutes. Default is 5 minutes. 当用户主动要求关闭会话时,将该参数设置为0,否则应默认设为5'),
527
550
  },
528
551
  }, async ({ inMinutes }) => {
529
552
  try {
@@ -580,9 +603,9 @@ server.registerTool('retrieve-rules-context', {
580
603
  }
581
604
  else {
582
605
  // 当 projectRulesFile 不存在时,设置 checkStoryboardFlag 为 false
583
- checkStoryboardFlag = false;
606
+ // checkStoryboardFlag = false;
584
607
  // 当 projectRulesFile 不存在时,设置 checkAudioVideoDurationFlag 为 false
585
- checkAudioVideoDurationFlag = false;
608
+ // checkAudioVideoDurationFlag = false;
586
609
  }
587
610
  try {
588
611
  const ai = currentSession.ai;
@@ -595,7 +618,8 @@ server.registerTool('retrieve-rules-context', {
595
618
  await (0, promises_1.writeFile)(projectRulesFile, promptContent);
596
619
  }
597
620
  if (!(0, node_fs_1.existsSync)(skillsIndexFile)) {
598
- const skills = rules.filter((rule) => rule.name.startsWith('skill-'));
621
+ const skills = rules.filter((rule) => rule.name.startsWith('skill-') &&
622
+ checkSkillEnabled(rule.name.slice(6)));
599
623
  const skillsList = skills.map((skill) => `## ${skill.name}
600
624
  ${skill.trigger}
601
625
  `);
@@ -638,8 +662,8 @@ server.registerTool('upload-custom-material', {
638
662
  try {
639
663
  // 验证session状态
640
664
  const currentSession = await validateSession('upload-custom-material');
641
- // 构建本地文件路径,使用 ZEROCUT_PROJECT_CWD 环境变量
642
- const validatedPath = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir, 'materials', localFileName.trim());
665
+ // 构建本地文件路径,使用 projectLocalDir
666
+ const validatedPath = (0, node_path_1.resolve)(projectLocalDir, 'materials', localFileName.trim());
643
667
  // 验证本地文件存在性
644
668
  if (!(0, node_fs_1.existsSync)(validatedPath)) {
645
669
  throw new Error(`File not found: ${validatedPath}`);
@@ -713,7 +737,7 @@ server.registerTool('upload-custom-material', {
713
737
  }
714
738
  });
715
739
  server.registerTool('wait-for-task-finish', {
716
- title: 'Wait Workflow or VideoTask Done;只有正在运行Coze工作流或者有异步生成视频任务时才需要执行这个工具;⚠️ 如果执行这个工具未失败只是超时,你应立即再次重新调用,以继续等待直到任务完成或失败',
740
+ title: 'Wait Workflow or VideoTask Done;只有正在运行Coze工作流或者有异步生成视频任务时才需要执行这个工具;⚠️ 如果执行这个工具未失败只是超时,你应立即再次重新调用,以继续等待直到任务完成或失败;‼️ 有的任务执行时间需要很长,所以如果只是超时不是失败,重试多少次都是正常的,请耐心等待即可。',
717
741
  description: 'Wait for a workflow to complete.',
718
742
  inputSchema: {
719
743
  taskUrl: zod_1.z
@@ -769,6 +793,32 @@ server.registerTool('wait-for-task-finish', {
769
793
  catch (error) {
770
794
  console.warn(`Failed to update media_logs.json for ${validatedFileName}:`, error);
771
795
  }
796
+ if (res.data?.scenes) {
797
+ const { scenes, video_type, voice_type, voiceover_tone, bgm_prompt, video_model, aspect_ratio, } = res.data;
798
+ const seed = (0, seed_1.getRandomSeed)();
799
+ const orientation = aspect_ratio === '16:9' ? 'landscape' : 'portrait';
800
+ const storyboard = {
801
+ orientation,
802
+ video_type,
803
+ outline_sheet: 'outline_sheet.png',
804
+ bgm_prompt,
805
+ voice_type,
806
+ scenes: scenes.map((scene) => {
807
+ let video_prompt = scene.video_prompt;
808
+ if (voiceover_tone && video_prompt.includes('画外音')) {
809
+ video_prompt = video_prompt.replace(/画外音[::]/g, `画外音(${voiceover_tone}):`);
810
+ }
811
+ return {
812
+ ...scene,
813
+ video_prompt,
814
+ use_video_model: video_model,
815
+ seed,
816
+ };
817
+ }),
818
+ };
819
+ const saveLocalPath = (0, node_path_1.resolve)(projectLocalDir, 'storyboard.json');
820
+ await (0, promises_1.writeFile)(saveLocalPath, JSON.stringify(storyboard, null, 2));
821
+ }
772
822
  return {
773
823
  content: [
774
824
  {
@@ -841,6 +891,7 @@ server.registerTool('generate-character-image', {
841
891
  try {
842
892
  // 验证session状态
843
893
  const currentSession = await validateSession('generate-character-image');
894
+ checkModelEnabled(type);
844
895
  const validatedFileName = validateFileName(saveToFileName);
845
896
  // 根据 isTurnaround 参数生成不同的提示词和尺寸
846
897
  let prompt;
@@ -934,7 +985,7 @@ ${roleDescriptionPrompt}
934
985
  ? referenceImage
935
986
  : `./materials/${referenceImage}`;
936
987
  // 需要得到当前项目的绝对路径
937
- const imageFilePath = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir, imagePath);
988
+ const imageFilePath = (0, node_path_1.resolve)(projectLocalDir, imagePath);
938
989
  // 读取图片文件内容
939
990
  const imageBuffer = await (0, promises_1.readFile)(imageFilePath);
940
991
  const fileName = (0, node_path_1.basename)(imagePath);
@@ -1007,7 +1058,7 @@ ${roleDescriptionPrompt}
1007
1058
  });
1008
1059
  server.registerTool('generate-image', {
1009
1060
  title: 'Generate Image',
1010
- description: `生成图片,支持批量生成1-9张图,若用户要求生成关联图片或组图,请一次生成,不要分几次生成`,
1061
+ description: `生成图片`,
1011
1062
  inputSchema: {
1012
1063
  type: zod_1.z
1013
1064
  .enum([
@@ -1028,11 +1079,6 @@ server.registerTool('generate-image', {
1028
1079
  .min(1)
1029
1080
  .optional()
1030
1081
  .describe('分镜索引,从1开始的下标,如果非分镜对应素材,则可不传,分镜素材必传'),
1031
- storyBoardFile: zod_1.z
1032
- .string()
1033
- .optional()
1034
- .default('storyboard.json')
1035
- .describe('故事板文件路径'),
1036
1082
  size: zod_1.z
1037
1083
  .enum([
1038
1084
  '1024x1024',
@@ -1117,12 +1163,14 @@ server.registerTool('generate-image', {
1117
1163
  \`\`\`
1118
1164
  `),
1119
1165
  },
1120
- }, async ({ type = 'seedream', prompt, sceneIndex, storyBoardFile = 'storyboard.json', size = '720x1280', imageCount = 1, saveToFileNames, watermark, referenceImages, }, context) => {
1166
+ }, async ({ type = 'seedream', prompt, sceneIndex, size = '720x1280', imageCount = 1, saveToFileNames, watermark, referenceImages, }, context) => {
1121
1167
  try {
1168
+ const storyBoardFile = 'storyboard.json';
1122
1169
  // 验证session状态
1123
1170
  const currentSession = await validateSession('generate-image');
1124
- const storyBoardPath = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir, storyBoardFile);
1125
- const outlineSheetImagePath = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir, 'materials', 'outline_sheet.png');
1171
+ checkModelEnabled(type);
1172
+ const storyBoardPath = (0, node_path_1.resolve)(projectLocalDir, storyBoardFile);
1173
+ const outlineSheetImagePath = (0, node_path_1.resolve)(projectLocalDir, 'materials', 'outline_sheet.png');
1126
1174
  const hasOutlineSheet = (0, node_fs_1.existsSync)(outlineSheetImagePath);
1127
1175
  // 校验 prompt 与 storyboard.json 中分镜设定的一致性
1128
1176
  if (sceneIndex) {
@@ -1228,7 +1276,7 @@ server.registerTool('generate-image', {
1228
1276
  ? refImage.image
1229
1277
  : `./materials/${refImage.image}`;
1230
1278
  // 需要得到当前项目的绝对路径
1231
- const imageFilePath = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir, imagePath);
1279
+ const imageFilePath = (0, node_path_1.resolve)(projectLocalDir, imagePath);
1232
1280
  try {
1233
1281
  // 直接读取本地文件
1234
1282
  if (!(0, node_fs_1.existsSync)(imageFilePath)) {
@@ -1289,17 +1337,13 @@ ${processedPrompt}`.trim();
1289
1337
  }
1290
1338
  }
1291
1339
  const ai = currentSession.ai;
1292
- const { taskUrl } = await ai.generateImage({
1340
+ let progress = 0;
1341
+ const res = await ai.generateImage({
1293
1342
  type,
1294
1343
  prompt: processedPrompt,
1295
1344
  size,
1296
1345
  watermark,
1297
1346
  image: imageBase64Array,
1298
- async: true,
1299
- });
1300
- let progress = 0;
1301
- const res = await ai.waitForTaskComplete({
1302
- taskUrl,
1303
1347
  onProgress: async (metaData) => {
1304
1348
  try {
1305
1349
  await sendProgress(context, ++progress, undefined, JSON.stringify(metaData));
@@ -1312,7 +1356,21 @@ ${processedPrompt}`.trim();
1312
1356
  if (!res) {
1313
1357
  throw new Error('Failed to generate image: no response from AI service');
1314
1358
  }
1315
- if (res.urls && res.urls.length > 0) {
1359
+ if (res.taskUrl) {
1360
+ return {
1361
+ content: [
1362
+ {
1363
+ type: 'text',
1364
+ text: JSON.stringify({
1365
+ success: true,
1366
+ message: '该任务正在运行中,它是异步任务,且执行时间较长,你应立即调用工具 wait-for-task-finish (saveFileName=outline_sheet.png) 来等待任务结束,如 wait-for-task-finish 工具调用超时,你应立即再次重新调用直到任务结束。',
1367
+ taskUrl: res.taskUrl,
1368
+ }),
1369
+ },
1370
+ ],
1371
+ };
1372
+ }
1373
+ else if (res.urls && res.urls.length > 0) {
1316
1374
  console.log('Image generated successfully, saving to materials...');
1317
1375
  let uris = [];
1318
1376
  if (res.urls.length === 1 && res.urls[0]) {
@@ -1380,7 +1438,7 @@ server.registerTool('edit-image', {
1380
1438
  type: zod_1.z
1381
1439
  .enum(['banana-pro', 'banana', 'seedream', 'seedream-pro'])
1382
1440
  .optional()
1383
- .default('seedream')
1441
+ .default('seedream-pro')
1384
1442
  .describe('The type of image model to use.'),
1385
1443
  sourceImageFileName: zod_1.z.string().describe('The source image file name.'),
1386
1444
  saveToFileName: zod_1.z
@@ -1437,7 +1495,7 @@ server.registerTool('edit-image', {
1437
1495
  ? sourceImageFileName
1438
1496
  : `./materials/${sourceImageFileName}`;
1439
1497
  // 需要得到当前项目的绝对路径
1440
- const imageFilePath = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir, imagePath);
1498
+ const imageFilePath = (0, node_path_1.resolve)(projectLocalDir, imagePath);
1441
1499
  if (!(0, node_fs_1.existsSync)(imageFilePath)) {
1442
1500
  return createErrorResponse(`Reference image not found: ${imageFilePath}`, 'edit-image');
1443
1501
  }
@@ -1464,6 +1522,7 @@ server.registerTool('edit-image', {
1464
1522
  if (!res) {
1465
1523
  throw new Error('Failed to generate image: no response from AI service');
1466
1524
  }
1525
+ res.url = res.urls?.[0];
1467
1526
  if (res.url) {
1468
1527
  console.log('Image generated successfully, saving to materials...');
1469
1528
  const uri = await saveMaterial(currentSession, res.url, validatedFileName);
@@ -1505,9 +1564,9 @@ server.registerTool('edit-image', {
1505
1564
  return createErrorResponse(error, 'edit-image');
1506
1565
  }
1507
1566
  });
1508
- server.registerTool('generate-short-video-outlines', {
1509
- title: 'Generate Short Video Outlines',
1510
- description: `根据用户描述生成短视频的大纲;执行本工具会自动创建两个文件,一个是storyboard.json,一个是outline_sheet.png,前者是分镜设置,后者是分镜宫格图`,
1567
+ server.registerTool('generate-video-outlines', {
1568
+ title: 'Generate Video Outlines',
1569
+ description: `根据用户描述生成短视频的大纲;执行本工具会自动创建两个文件,一个是storyboard.json,一个是outline_sheet.png,前者是分镜设置,后者是分镜宫格图,其中outline_sheet.png生成于materials目录下,storyboard.json生成于materials的上级目录。`,
1511
1570
  inputSchema: {
1512
1571
  prompt: zod_1.z
1513
1572
  .string()
@@ -1534,6 +1593,7 @@ server.registerTool('generate-short-video-outlines', {
1534
1593
  'vidu-turbo',
1535
1594
  'vidu-pro',
1536
1595
  'kling',
1596
+ 'wan',
1537
1597
  'pixv',
1538
1598
  'veo3.1',
1539
1599
  'veo3.1-pro',
@@ -1543,10 +1603,11 @@ server.registerTool('generate-short-video-outlines', {
1543
1603
  .default('pro')
1544
1604
  .describe('除非用户主动提出使用其他模型,否则一律用pro模型'),
1545
1605
  },
1546
- }, async ({ prompt, voiceType, language, images, orientation, model }) => {
1606
+ }, async ({ prompt, voiceType, language, images, orientation, model }, context) => {
1547
1607
  try {
1548
1608
  // 验证session状态
1549
- const currentSession = await validateSession('generate-short-video-outlines');
1609
+ const currentSession = await validateSession('generate-video-outlines');
1610
+ let progress = 0;
1550
1611
  const ai = currentSession.ai;
1551
1612
  const res = await ai.generateShortVideoOutlines({
1552
1613
  prompt,
@@ -1555,6 +1616,15 @@ server.registerTool('generate-short-video-outlines', {
1555
1616
  images,
1556
1617
  videoModel: model,
1557
1618
  aspectRatio: orientation === 'portrait' ? '9:16' : '16:9',
1619
+ onProgress: async (metaData) => {
1620
+ try {
1621
+ await sendProgress(context, ++progress, undefined, JSON.stringify(metaData));
1622
+ }
1623
+ catch (progressError) {
1624
+ console.warn('Failed to send progress update:', progressError);
1625
+ }
1626
+ },
1627
+ waitForFinish: CLIENT_TYPE !== '5ire',
1558
1628
  });
1559
1629
  if (!res) {
1560
1630
  throw new Error('Failed to generate short video outlines: no response from AI service');
@@ -1566,7 +1636,7 @@ server.registerTool('generate-short-video-outlines', {
1566
1636
  type: 'text',
1567
1637
  text: JSON.stringify({
1568
1638
  success: true,
1569
- message: '该任务正在运行中,它是异步任务,且执行时间较长,你应立即调用工具 wait-for-task-finish 来等待任务结束,如 wait-for-task-finish 工具调用超时,你应立即再次重新调用直到任务结束。',
1639
+ message: '该任务正在运行中,它是异步任务,且执行时间较长,你应立即调用工具 wait-for-task-finish (saveFileName=outline_sheet.png) 来等待任务结束,如 wait-for-task-finish 工具调用超时,你应立即再次重新调用直到任务结束。',
1570
1640
  taskUrl: res.taskUrl,
1571
1641
  }),
1572
1642
  },
@@ -1576,7 +1646,7 @@ server.registerTool('generate-short-video-outlines', {
1576
1646
  else if (res.url) {
1577
1647
  const url = res.url;
1578
1648
  await saveMaterial(currentSession, url, 'outline_sheet.png');
1579
- const { scenes, video_type, voice_type, voiceover_tone, bgm_prompt } = res.data || {};
1649
+ const { scenes, video_type, voice_type, voiceover_tone, bgm_prompt, video_model, } = res.data || {};
1580
1650
  const seed = (0, seed_1.getRandomSeed)();
1581
1651
  const storyboard = {
1582
1652
  orientation,
@@ -1592,7 +1662,7 @@ server.registerTool('generate-short-video-outlines', {
1592
1662
  return {
1593
1663
  ...scene,
1594
1664
  video_prompt,
1595
- use_video_model: model,
1665
+ use_video_model: video_model,
1596
1666
  seed,
1597
1667
  };
1598
1668
  }),
@@ -1627,7 +1697,7 @@ server.registerTool('generate-short-video-outlines', {
1627
1697
  };
1628
1698
  }
1629
1699
  catch (error) {
1630
- return createErrorResponse(error, 'generate-short-video-outlines');
1700
+ return createErrorResponse(error, 'generate-video-outlines');
1631
1701
  }
1632
1702
  });
1633
1703
  server.registerTool('generate-music-or-mv', {
@@ -1944,17 +2014,14 @@ server.registerTool('generate-video', {
1944
2014
  title: 'Generate Video',
1945
2015
  description: `图生视频和首尾帧生视频工具`,
1946
2016
  inputSchema: {
1947
- prompt: zod_1.z.string().describe('The prompt to generate. '),
2017
+ prompt: zod_1.z
2018
+ .string()
2019
+ .describe('The prompt to generate. 尽量忠于用户的原始需求,除非用户明确要求协助优化,否则不要擅自发挥!'),
1948
2020
  sceneIndex: zod_1.z
1949
2021
  .number()
1950
2022
  .min(1)
1951
2023
  .optional()
1952
2024
  .describe('分镜索引,从1开始的下标,如果非分镜对应素材,则可不传,分镜素材必传'),
1953
- storyBoardFile: zod_1.z
1954
- .string()
1955
- .optional()
1956
- .default('storyboard.json')
1957
- .describe('故事板文件路径'),
1958
2025
  type: zod_1.z
1959
2026
  .enum([
1960
2027
  'pro',
@@ -2015,10 +2082,11 @@ server.registerTool('generate-video', {
2015
2082
  .default(false)
2016
2083
  .describe('Whether to optimize the prompt.'),
2017
2084
  },
2018
- }, async ({ prompt, sceneIndex, storyBoardFile = 'storyboard.json', saveToFileName, start_frame, end_frame, duration, resolution, type = 'vidu', optimizePrompt, saveLastFrameAs, mute = true, seed, }, context) => {
2085
+ }, async ({ prompt, sceneIndex, saveToFileName, start_frame, end_frame, duration, resolution, type = 'vidu', optimizePrompt, saveLastFrameAs, mute = true, seed, }, context) => {
2019
2086
  try {
2020
2087
  // 验证session状态
2021
2088
  const currentSession = await validateSession('generate-video');
2089
+ checkModelEnabled(type);
2022
2090
  const isZeroModel = type.startsWith('zero');
2023
2091
  if (!start_frame && !isZeroModel) {
2024
2092
  return createErrorResponse('start_frame 不能为空', 'generate-video');
@@ -2036,10 +2104,11 @@ server.registerTool('generate-video', {
2036
2104
  console.warn(`zero 模型的视频仅支持 1080p 分辨率,用户指定的分辨率为 %s,已自动将 ${resolution} 转换为 1080p`, resolution);
2037
2105
  resolution = '1080p';
2038
2106
  }
2107
+ const storyBoardFile = 'storyboard.json';
2039
2108
  // 校验 prompt 与 storyboard.json 中分镜设定的一致性
2040
2109
  if (sceneIndex) {
2041
2110
  try {
2042
- const storyBoardPath = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir, storyBoardFile);
2111
+ const storyBoardPath = (0, node_path_1.resolve)(projectLocalDir, storyBoardFile);
2043
2112
  if ((0, node_fs_1.existsSync)(storyBoardPath)) {
2044
2113
  const storyBoardContent = await (0, promises_1.readFile)(storyBoardPath, 'utf8');
2045
2114
  // 检查 storyBoard JSON 语法合法性
@@ -2062,7 +2131,7 @@ server.registerTool('generate-video', {
2062
2131
  }
2063
2132
  if (scene.video_duration != null &&
2064
2133
  duration !== scene.video_duration) {
2065
- return createErrorResponse(`视频时长必须严格遵照storyboard的设定,用户指定的时长为 ${duration} 秒,而 storyboard 中建议的时长为 ${scene.video_duration} 秒。`, 'generate-video');
2134
+ return createErrorResponse(`视频时长必须严格遵照storyboard的设定,storyboard 中设定的时长为 ${scene.video_duration} 秒。`, 'generate-video');
2066
2135
  }
2067
2136
  if (storyBoard.voice_type &&
2068
2137
  storyBoard.voice_type !== 'slient') {
@@ -2208,7 +2277,7 @@ server.registerTool('generate-video', {
2208
2277
  },
2209
2278
  };
2210
2279
  const analysisPayload = {
2211
- model: 'Doubao-Seed-1.6-flash',
2280
+ model: 'Doubao-Seed-1.8',
2212
2281
  messages: [
2213
2282
  {
2214
2283
  role: 'system',
@@ -2343,7 +2412,7 @@ server.registerTool('generate-video', {
2343
2412
  console.warn('Failed to send progress update:', progressError);
2344
2413
  }
2345
2414
  },
2346
- waitForFinish: type !== 'zero',
2415
+ waitForFinish: type !== 'zero' && CLIENT_TYPE !== '5ire',
2347
2416
  mute,
2348
2417
  seed,
2349
2418
  });
@@ -2426,44 +2495,83 @@ server.registerTool('edit-video', {
2426
2495
  title: 'Edit Video',
2427
2496
  description: `Edit video using Coze workflow,可以做以下事情:
2428
2497
 
2429
- - 替换视频内容,type 为 replace
2498
+ - 编辑视频(增加、修改、删除视频中的内容),type 为 edit
2499
+ - 参考视频动作和特效,type 也为 edit
2430
2500
  - 视频对口型,type 为 lipsync
2431
- - 视频动作模仿,type 为 imitate
2501
+ - 视频延长 1~7 秒,type 为 extend
2502
+ - 视频超清,type 为 upscale
2503
+
2504
+ ‼️ 故障排查
2505
+ 如果出错,依次检查以下事项:
2506
+
2507
+ 1. 视频是否太长(超过了8秒)或太大(超过了100M)
2508
+ 2. 视频像素过小(小于128x128)或宽高比太悬殊(大于1:4)
2509
+ 3. 视频格式是否是 mp4
2432
2510
  `,
2433
2511
  inputSchema: {
2434
2512
  type: zod_1.z
2435
- .enum(['replace', 'lipsync', 'imitate'])
2513
+ .enum(['edit', 'lipsync', 'extend', 'upscale'])
2514
+ .default('edit')
2436
2515
  .describe('The editing type'),
2437
2516
  video: zod_1.z.string().describe(`The video to edit
2438
2517
 
2439
- - type 为 replace 时,video 为要替换内容的视频
2518
+ - type 为 edit 时,video 为要编辑的视频
2440
2519
  - type 为 lipsync 时,video 为要对口型的视频
2441
- - type 为 imitate 时,video 为模仿动作参考视频
2520
+ - type 为 extend 时,video 为要延长的视频
2521
+ - type 为 upscale 时,video 为要转为超清的视频
2522
+ `),
2523
+ duration: zod_1.z
2524
+ .number()
2525
+ .min(0)
2526
+ .max(7)
2527
+ .optional()
2528
+ .describe(`The duration of the edited video in seconds. 0 表示与原视频相同。编辑视频时如用户未指定,默认为 0。延长视频时,最小1,最大7,默认值5。
2529
+
2530
+ - type 为 lipsync 或 upscale 时,忽略 duration 参数
2531
+ `),
2532
+ resolution: zod_1.z.enum(['720p', '1080p', '2K', '4K']).optional()
2533
+ .describe(`延长视频和超清视频时的分辨率(可选)
2534
+
2535
+ - type 为 edit 或 upscale 时,忽略 resolution 参数
2536
+ - type 为 extend 时,不传为 720p
2537
+ - type 为 upscale 时,不传为 1080p
2442
2538
  `),
2443
2539
  prompt: zod_1.z.string().optional()
2444
- .describe(`The editing prompt, 如实转述用户需求即可,**不**添加其他任何信息如视频规格等,本工具会自己优化
2540
+ .describe(`The editing prompt, 如实转述用户需求即可
2541
+
2542
+ 要求:
2543
+ 1)用**极简的话语**准确描述,不添加其他任何补充信息,本工具会自己优化
2544
+ 2)video一律用“视频1”指代
2445
2545
 
2446
- - type 为 replace 时,prompt 为要替换的内容
2546
+ - type 为 edit 时,prompt 为编辑指令
2447
2547
  - type 为 lipsync 时,prompt 为空
2448
- - type 为 imitate 时,prompt 为要模仿动作的内容,和 referenceImageUrl 二选一
2548
+ - type 为 extend 时,prompt 为延长提示词或空
2549
+ - type 为 upscale 时,prompt 为空
2449
2550
  `),
2450
- referenceImage: zod_1.z.string().optional()
2451
- .describe(`The reference image File for editing
2452
- - type 为 replace 时,referenceImage 为参考图片
2453
- - type 为 lipsync 时,referenceImage 为空
2454
- - type 为 imitate 时,referenceImage 为要模仿动作的画面,和 prompt 二选一
2551
+ referenceImages: zod_1.z.array(zod_1.z.string()).optional()
2552
+ .describe(`The reference image Files for editing
2553
+ - type 为 edit 时,referenceImages 为参考图片(1-7张)
2554
+ - type 为 lipsync 时,referenceImages 为空
2555
+ - type 为 extend 时,referenceImages 为空或单张图,表示延长到尾帧的参考图像
2556
+ - type 为 upscale 时,referenceImages 为空
2455
2557
  `),
2456
2558
  saveToFileName: zod_1.z
2457
2559
  .string()
2458
2560
  .describe(`The file name to save the edited video to. 应该是mp4文件`),
2459
2561
  },
2460
- }, async ({ type, video, prompt, referenceImage, saveToFileName }, context) => {
2562
+ }, async ({ type, video, duration = 0, resolution, prompt, referenceImages, saveToFileName, }, context) => {
2461
2563
  try {
2462
2564
  const currentSession = await validateSession('edit-video');
2463
2565
  const ai = currentSession.ai;
2464
2566
  const validatedFileName = validateFileName(saveToFileName);
2465
2567
  let res = {};
2466
2568
  let progress = 0;
2569
+ if (type === 'extend' && (resolution === '2K' || resolution === '4K')) {
2570
+ throw new Error('Extend type only supports 720p resolution or 1080p resolution');
2571
+ }
2572
+ if (type === 'upscale' && resolution === '720p') {
2573
+ throw new Error('720p resolution is not supported for upscale type');
2574
+ }
2467
2575
  const onProgress = async (metaData) => {
2468
2576
  console.log('Replace video progress:', metaData);
2469
2577
  try {
@@ -2473,20 +2581,25 @@ server.registerTool('edit-video', {
2473
2581
  console.warn('Failed to send progress update:', progressError);
2474
2582
  }
2475
2583
  };
2476
- let referenceImageUrl = undefined;
2477
- if (referenceImage) {
2478
- referenceImageUrl = getMaterialUri(currentSession, referenceImage);
2584
+ let referenceImageUrls = undefined;
2585
+ if (referenceImages) {
2586
+ referenceImageUrls = referenceImages.map(fileName => getMaterialUri(currentSession, fileName));
2479
2587
  }
2480
2588
  const videoUrl = getMaterialUri(currentSession, video);
2481
- if (type === 'replace') {
2589
+ if (type === 'edit') {
2482
2590
  if (!prompt) {
2483
- throw new Error('prompt is required for replace type');
2591
+ throw new Error('prompt is required for edit type');
2484
2592
  }
2485
- res = await ai.editVideo({
2486
- videoUrl,
2593
+ res = await ai.referencesToVideo({
2487
2594
  prompt,
2488
- referenceImageUrl,
2595
+ duration,
2596
+ type: 'vidu-pro', //'vidu', //'pixv', // 'lite', // 'sora2',
2597
+ reference_images: referenceImageUrls,
2598
+ videos: [videoUrl],
2489
2599
  onProgress,
2600
+ waitForFinish: CLIENT_TYPE !== '5ire',
2601
+ // aspect_ratio: '9:16',
2602
+ // mute: true,
2490
2603
  });
2491
2604
  }
2492
2605
  else if (type === 'lipsync') {
@@ -2495,29 +2608,31 @@ server.registerTool('edit-video', {
2495
2608
  videoUrl,
2496
2609
  });
2497
2610
  res = await ai.lipSync({
2498
- type: 'pixv',
2499
2611
  videoUrl,
2500
2612
  audioUrl,
2501
2613
  audioInMs: 0,
2502
2614
  pad_audio: false,
2503
2615
  onProgress,
2616
+ waitForFinish: CLIENT_TYPE !== '5ire',
2504
2617
  });
2505
2618
  }
2506
- else if (type === 'imitate') {
2507
- if (!prompt && !referenceImageUrl) {
2508
- throw new Error('prompt or referenceImageUrl is required for imitate type');
2509
- }
2510
- if (prompt) {
2511
- referenceImageUrl = (await ai.generateImage({
2512
- prompt,
2513
- type: 'banana',
2514
- image: referenceImageUrl ? [referenceImageUrl] : undefined,
2515
- })).url;
2516
- }
2517
- res = await ai.actionImitation({
2518
- videoUrl,
2519
- imageUrl: referenceImageUrl,
2619
+ else if (type === 'extend') {
2620
+ res = await ai.extendVideo({
2621
+ video_url: videoUrl,
2622
+ resolution: resolution || '720p',
2623
+ prompt,
2624
+ duration: duration || 5,
2625
+ end_frame: referenceImageUrls?.[0],
2626
+ onProgress,
2627
+ waitForFinish: CLIENT_TYPE !== '5ire',
2628
+ });
2629
+ }
2630
+ else if (type === 'upscale') {
2631
+ res = await ai.upscaleVideo({
2632
+ video_url: videoUrl,
2633
+ resolution,
2520
2634
  onProgress,
2635
+ waitForFinish: CLIENT_TYPE !== '5ire',
2521
2636
  });
2522
2637
  }
2523
2638
  if (res.url) {
@@ -2726,11 +2841,15 @@ server.registerTool('audio-video-sync', {
2726
2841
  .describe('The volume of video audio. 0.0 to 2.0.'),
2727
2842
  loopAudio: zod_1.z.boolean().optional().default(true),
2728
2843
  addSubtitles: zod_1.z.boolean().optional().default(false),
2844
+ subtitlesContext: zod_1.z
2845
+ .string()
2846
+ .optional()
2847
+ .describe('字幕的参考上下文(非必需),用于提升字幕准确性'),
2729
2848
  saveToFileName: zod_1.z
2730
2849
  .string()
2731
2850
  .describe('The filename to save the audio-video-synced video. 应该是mp4文件'),
2732
2851
  },
2733
- }, async ({ videos, audio, audioInMs, audioFadeOutMs, audioVolume, videoAudioVolume, saveToFileName, loopAudio, addSubtitles, }, context) => {
2852
+ }, async ({ videos, audio, audioInMs, audioFadeOutMs, audioVolume, videoAudioVolume, saveToFileName, loopAudio, addSubtitles, subtitlesContext, }, context) => {
2734
2853
  try {
2735
2854
  // 验证session状态
2736
2855
  const currentSession = await validateSession('audio-video-sync');
@@ -2764,6 +2883,7 @@ server.registerTool('audio-video-sync', {
2764
2883
  videoAudioVolume,
2765
2884
  loopAudio,
2766
2885
  subtitles: addSubtitles,
2886
+ subtitlesContext,
2767
2887
  });
2768
2888
  if (result.url) {
2769
2889
  console.log('Audio sync completed successfully');
@@ -2811,12 +2931,7 @@ server.registerTool('generate-video-by-ref', {
2811
2931
  inputSchema: {
2812
2932
  prompt: zod_1.z
2813
2933
  .string()
2814
- .describe('The prompt to generate video with or without reference images. '),
2815
- rewritePrompt: zod_1.z
2816
- .boolean()
2817
- .optional()
2818
- .default(true)
2819
- .describe('Whether to rewrite the prompt.'),
2934
+ .describe('The prompt to generate video with or without reference images. 尽量忠于用户的原始需求,除非用户明确要求协助优化,否则不要擅自发挥!'),
2820
2935
  referenceImages: zod_1.z
2821
2936
  .array(zod_1.z.object({
2822
2937
  name: zod_1.z
@@ -2830,6 +2945,16 @@ server.registerTool('generate-video-by-ref', {
2830
2945
  .optional()
2831
2946
  .default([])
2832
2947
  .describe('Array of reference image objects with name, url and type. Can be empty for text-only generation.'),
2948
+ referenceVideo: zod_1.z
2949
+ .object({
2950
+ name: zod_1.z
2951
+ .string()
2952
+ .describe('Reference video file name in materials directory'),
2953
+ fileName: zod_1.z.string().describe('Reference video file name'),
2954
+ type: zod_1.z.enum(['video']).describe('Type of reference: video'),
2955
+ })
2956
+ .optional()
2957
+ .describe('Reference video file name in materials directory, 用于参考视频动作和特效,只有vidu-pro模型支持'),
2833
2958
  duration: zod_1.z
2834
2959
  .number()
2835
2960
  .min(0)
@@ -2839,9 +2964,11 @@ server.registerTool('generate-video-by-ref', {
2839
2964
  .describe('The duration of the video in seconds.可以传0,此时会根据视频提示词内容自动确定时长'),
2840
2965
  aspectRatio: zod_1.z
2841
2966
  .enum(['16:9', '9:16'])
2967
+ .default('16:9')
2842
2968
  .describe('The aspect ratio of the video.'),
2843
2969
  resolution: zod_1.z
2844
2970
  .enum(['720p', '1080p'])
2971
+ .default('720p')
2845
2972
  .describe('The resolution of the video.'),
2846
2973
  type: zod_1.z
2847
2974
  .enum([
@@ -2851,6 +2978,7 @@ server.registerTool('generate-video-by-ref', {
2851
2978
  'veo3.1',
2852
2979
  'veo3.1-pro',
2853
2980
  'vidu',
2981
+ 'vidu-pro',
2854
2982
  'vidu-uc',
2855
2983
  'pixv',
2856
2984
  ])
@@ -2873,23 +3001,23 @@ server.registerTool('generate-video-by-ref', {
2873
3001
  .min(1)
2874
3002
  .optional()
2875
3003
  .describe('分镜索引,从1开始的下标,如果非分镜对应素材,则可不传,分镜素材必传'),
2876
- storyBoardFile: zod_1.z
2877
- .string()
2878
- .optional()
2879
- .default('storyboard.json')
2880
- .describe('故事板文件路径'),
2881
3004
  optimizePrompt: zod_1.z
2882
3005
  .boolean()
2883
3006
  .optional()
2884
3007
  .default(false)
2885
3008
  .describe('Whether to optimize the prompt.'),
2886
3009
  },
2887
- }, async ({ prompt, rewritePrompt, referenceImages, duration, aspectRatio, resolution, type = 'vidu', mute, saveToFileName, sceneIndex, storyBoardFile, optimizePrompt, seed, }, context) => {
3010
+ }, async ({ prompt, referenceImages, duration, aspectRatio, resolution, type = 'vidu', mute, saveToFileName, sceneIndex, optimizePrompt, seed, referenceVideo, }, context) => {
2888
3011
  try {
3012
+ const storyBoardFile = 'storyboard.json';
2889
3013
  // 验证session状态
2890
3014
  const currentSession = await validateSession('generate-video-by-ref');
2891
- const storyBoardPath = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir, storyBoardFile);
2892
- const outlineSheetImagePath = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir, 'materials', 'outline_sheet.png');
3015
+ checkModelEnabled(type);
3016
+ const storyBoardPath = (0, node_path_1.resolve)(projectLocalDir, storyBoardFile);
3017
+ if (type !== 'vidu-pro' && referenceVideo) {
3018
+ return createErrorResponse('只有vidu-pro模型支持参考视频', 'generate-video-by-ref');
3019
+ }
3020
+ const outlineSheetImagePath = (0, node_path_1.resolve)(projectLocalDir, 'materials', 'outline_sheet.png');
2893
3021
  const hasOutlineSheet = (0, node_fs_1.existsSync)(outlineSheetImagePath);
2894
3022
  // 校验 prompt 与 storyboard.json 中分镜设定的一致性(如果提供了 sceneIndex)
2895
3023
  if (sceneIndex) {
@@ -3036,7 +3164,6 @@ server.registerTool('generate-video-by-ref', {
3036
3164
  console.log(`Generating video ${referenceImages.length > 0 ? `with ${referenceImages.length} reference image(s)` : 'without reference images'} using ${type} model...`);
3037
3165
  // 处理参考图:转换为URL而不是base64
3038
3166
  const referenceImageUrls = [];
3039
- let promptPrefix = '';
3040
3167
  for (const imageRef of referenceImages) {
3041
3168
  // 使用 getMaterialUri 获取图片URL
3042
3169
  const imageUrl = getMaterialUri(currentSession, imageRef.fileName);
@@ -3046,14 +3173,17 @@ server.registerTool('generate-video-by-ref', {
3046
3173
  url: imageUrl,
3047
3174
  });
3048
3175
  console.log(`Added reference image URL: ${imageUrl} (name: ${imageRef.name}, type: ${imageRef.type})`);
3049
- if (rewritePrompt) {
3050
- promptPrefix += `参考“${imageRef.name}”(图${referenceImageUrls.length})${imageRef.type === 'subject' ? '主体形象' : '背景'}\n`;
3051
- }
3052
3176
  }
3053
- if (promptPrefix) {
3054
- promptPrefix += '\n';
3055
- }
3056
- const finalPrompt = `${promptPrefix}${prompt}`;
3177
+ const finalPrompt = `${prompt}`;
3178
+ const videos = referenceVideo
3179
+ ? [
3180
+ {
3181
+ type: 'video',
3182
+ name: referenceVideo.name,
3183
+ url: getMaterialUri(currentSession, referenceVideo.fileName),
3184
+ },
3185
+ ]
3186
+ : undefined;
3057
3187
  // 调用 referencesToVideo 函数
3058
3188
  const result = await currentSession.ai.referencesToVideo({
3059
3189
  prompt: finalPrompt,
@@ -3064,10 +3194,12 @@ server.registerTool('generate-video-by-ref', {
3064
3194
  type,
3065
3195
  mute,
3066
3196
  seed,
3197
+ videos,
3067
3198
  onProgress: metaData => {
3068
3199
  console.log('Video generation progress:', metaData);
3069
3200
  sendProgress(context, metaData.progress || 0, 100, 'Generating video...');
3070
3201
  },
3202
+ waitForFinish: CLIENT_TYPE !== '5ire',
3071
3203
  });
3072
3204
  if (result.error) {
3073
3205
  return createErrorResponse(result.error, 'generate-video-by-ref');
@@ -3126,122 +3258,6 @@ server.registerTool('generate-video-by-ref', {
3126
3258
  return createErrorResponse(error.message, 'generate-video-by-ref');
3127
3259
  }
3128
3260
  });
3129
- server.registerTool('extend-video-duration', {
3130
- title: 'Extend Video Duration',
3131
- description: 'Extend any MP4 video in materials directory by 1-7 seconds using AI video extension technology.',
3132
- inputSchema: {
3133
- videoFileName: zod_1.z
3134
- .string()
3135
- .describe('The MP4 video file name in materials directory to extend.'),
3136
- duration: zod_1.z
3137
- .number()
3138
- .min(1)
3139
- .max(7)
3140
- .default(3)
3141
- .describe('Duration to extend the video in seconds (1-7).'),
3142
- resolution: zod_1.z
3143
- .enum(['720p', '1080p'])
3144
- .default('720p')
3145
- .describe('The resolution of the video.'),
3146
- prompt: zod_1.z
3147
- .string()
3148
- .optional()
3149
- .describe('Optional prompt to guide the video extension. If not provided, will use a default prompt.'),
3150
- type: zod_1.z
3151
- .enum(['turbo', 'pro'])
3152
- .default('turbo')
3153
- .describe('Model type for video extension. turbo is faster, pro is higher quality.'),
3154
- endFrame: zod_1.z
3155
- .string()
3156
- .optional()
3157
- .describe('Optional end frame image file name in materials directory to guide the video extension.'),
3158
- saveToFileName: zod_1.z
3159
- .string()
3160
- .describe('The filename to save the extended video. 应该是mp4文件'),
3161
- },
3162
- }, async ({ videoFileName, duration, resolution, prompt, type = 'turbo', endFrame, saveToFileName, }, context) => {
3163
- try {
3164
- await validateSession('extend-video');
3165
- validateFileName(videoFileName);
3166
- validateFileName(saveToFileName);
3167
- if (!session) {
3168
- return createErrorResponse(new Error('No active session. Please open a project first.'), 'extend-video');
3169
- }
3170
- // 检查视频文件为MP4格式
3171
- const fileExtension = node_path_1.default.extname(videoFileName).toLowerCase();
3172
- if (fileExtension !== '.mp4') {
3173
- return createErrorResponse(new Error(`Only MP4 files are supported. Found: ${fileExtension}`), 'extend-video');
3174
- }
3175
- const videoUri = getMaterialUri(session, videoFileName);
3176
- // 处理结束帧参数
3177
- let endFrameUri;
3178
- if (endFrame) {
3179
- validateFileName(endFrame);
3180
- endFrameUri = getMaterialUri(session, endFrame);
3181
- }
3182
- // 设置默认提示词,用中文
3183
- const defaultPrompt = `延长这视频自然平滑,保持相同的风格、照明和运动模式。保持视觉一致性,确保无缝过渡。`;
3184
- const finalPrompt = prompt || defaultPrompt;
3185
- let progress = 0;
3186
- const result = await session.ai.extendVideo({
3187
- type: type,
3188
- video_url: videoUri,
3189
- prompt: finalPrompt,
3190
- duration,
3191
- resolution,
3192
- end_frame: endFrameUri,
3193
- onProgress: async (metaData) => {
3194
- sendProgress(context, ++progress, undefined, `Extension progress: ${Math.round(progress * 100)}%`);
3195
- },
3196
- });
3197
- // 检查结果
3198
- if (!result || result.error) {
3199
- throw new Error(result?.error || 'Video extension failed');
3200
- }
3201
- // 检查返回的视频URL
3202
- if (!result.url) {
3203
- throw new Error('Failed to get extended video URL');
3204
- }
3205
- // 保存延长后的视频
3206
- await saveMaterial(session, result.url, saveToFileName);
3207
- // 更新媒体日志
3208
- await updateMediaLogs(session, saveToFileName, {
3209
- originalVideo: videoFileName,
3210
- extensionDuration: duration,
3211
- prompt: finalPrompt,
3212
- modelType: type,
3213
- endFrame: endFrame,
3214
- extendedVideoUrl: result.url,
3215
- ...result,
3216
- }, 'video');
3217
- return {
3218
- content: [
3219
- {
3220
- type: 'text',
3221
- text: JSON.stringify({
3222
- success: true,
3223
- message: 'Video extended successfully',
3224
- originalVideo: videoFileName,
3225
- extendedVideo: saveToFileName,
3226
- extensionDuration: duration,
3227
- prompt: finalPrompt,
3228
- modelType: type,
3229
- extendedVideoUrl: result.url,
3230
- details: {
3231
- duration: result.duration || 'Unknown',
3232
- resolution: result.resolution || 'Unknown',
3233
- aspectRatio: result.ratio || 'Unknown',
3234
- },
3235
- }, null, 2),
3236
- },
3237
- ],
3238
- };
3239
- }
3240
- catch (error) {
3241
- console.error('Error extending video:', error);
3242
- return createErrorResponse(error, 'extend-video');
3243
- }
3244
- });
3245
3261
  server.registerTool('use-template', {
3246
3262
  title: 'Use Template',
3247
3263
  description: 'Find a template that matches the user request, and use it to generate a new material.',
@@ -3269,7 +3285,7 @@ server.registerTool('use-template', {
3269
3285
  }));
3270
3286
  const validatedFileName = validateFileName(saveToFileName);
3271
3287
  let completion = await ai.getCompletions({
3272
- model: 'Doubao-Seed-1.6',
3288
+ model: 'Doubao-Seed-1.8',
3273
3289
  messages: [
3274
3290
  {
3275
3291
  role: 'system',
@@ -3315,7 +3331,7 @@ ${user_request}
3315
3331
  ${JSON.stringify(materialUrls)}`;
3316
3332
  // console.log(prompt);
3317
3333
  completion = await ai.getCompletions({
3318
- model: 'Doubao-Seed-1.6',
3334
+ model: 'Doubao-Seed-1.8',
3319
3335
  messages: [
3320
3336
  {
3321
3337
  role: 'system',