pixi-board-plugin-mivo 0.1.2 → 0.1.4

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.
package/dist/index.js CHANGED
@@ -9,6 +9,10 @@ var DEFAULT_API_BASE = "https://aigc.xindong.com/api/v1";
9
9
  var URL_KEYS = ["signUrl", "sign_url", "url", "uri", "displayUrl", "fileUrl", "downloadUrl", "src", "image"];
10
10
  var ID_KEYS = ["fileId", "file_id", "object_id", "_id"];
11
11
  var TOKEN_REFRESH_SKEW_MS = 6e4;
12
+ var MEDIA_ROUTE_BY_KIND = {
13
+ image: "image",
14
+ video: "video"
15
+ };
12
16
  function createMivoApiClient(options = {}) {
13
17
  const env = options.env ?? process.env;
14
18
  const fetchImpl = options.fetchImpl ?? fetch;
@@ -129,11 +133,11 @@ function createMivoApiClient(options = {}) {
129
133
  const value = await readJsonResponse(response, "Mivo result request");
130
134
  return readMessageResult(value);
131
135
  },
132
- fileUrl(fileId) {
133
- return mivoFileUrl(fileId, apiBase);
136
+ fileUrl(fileId, kind = "image") {
137
+ return mivoFileUrl(fileId, kind, apiBase);
134
138
  },
135
- async downloadFile(fileId, savePath) {
136
- await this.downloadUrl(this.fileUrl(fileId), savePath);
139
+ async downloadFile(fileId, savePath, kind = "image") {
140
+ await this.downloadUrl(this.fileUrl(fileId, kind), savePath);
137
141
  },
138
142
  async downloadUrl(url, savePath) {
139
143
  await mkdir(path.dirname(savePath), { recursive: true });
@@ -151,22 +155,29 @@ function createMivoApiClient(options = {}) {
151
155
  }
152
156
  }
153
157
  function extractImageArtifact(message) {
154
- const images = message.content?.images ?? message.images;
155
- const image = images?.[0];
156
- if (image === void 0 || image === null) return null;
157
- if (typeof image === "string") {
158
- if (/^https?:\/\//i.test(image)) return { url: image };
159
- if (image.startsWith("/file/image/")) return { fileId: image.slice("/file/image/".length) };
160
- return { fileId: image };
161
- }
162
- if (!isRecord(image)) return null;
163
- const url = readStringFromAny(image, URL_KEYS);
164
- const fileId = readStringFromAny(image, ID_KEYS);
158
+ return extractMediaArtifact(message, "image");
159
+ }
160
+ function extractVideoArtifact(message) {
161
+ return extractMediaArtifact(message, "video");
162
+ }
163
+ function extractMediaArtifact(message, kind) {
164
+ const values = kind === "image" ? message.content?.images ?? message.images : message.content?.videos ?? message.videos;
165
+ const media = values?.[0];
166
+ if (media === void 0 || media === null) return null;
167
+ if (typeof media === "string") {
168
+ if (/^https?:\/\//i.test(media)) return { url: media };
169
+ const routePrefix = `/file/${MEDIA_ROUTE_BY_KIND[kind]}/`;
170
+ if (media.startsWith(routePrefix)) return { fileId: media.slice(routePrefix.length) };
171
+ return { fileId: media };
172
+ }
173
+ if (!isRecord(media)) return null;
174
+ const url = readStringFromAny(media, URL_KEYS);
175
+ const fileId = readStringFromAny(media, ID_KEYS);
165
176
  if (!url && !fileId) return null;
166
177
  return { fileId, url };
167
178
  }
168
- function mivoFileUrl(fileId, apiBase = DEFAULT_API_BASE) {
169
- return `${trimTrailingSlash(apiBase)}/file/image/${encodeURIComponent(fileId)}`;
179
+ function mivoFileUrl(fileId, kind = "image", apiBase = DEFAULT_API_BASE) {
180
+ return `${trimTrailingSlash(apiBase)}/file/${MEDIA_ROUTE_BY_KIND[kind]}/${encodeURIComponent(fileId)}`;
170
181
  }
171
182
  function readMessageResultFromSse(text) {
172
183
  const direct = parseMaybeJson(text);
@@ -258,7 +269,16 @@ function isRecord(value) {
258
269
  }
259
270
 
260
271
  // src/index.ts
261
- var RATIOS = ["1:1", "2:3", "3:2", "9:16", "16:9"];
272
+ var GPT_IMAGE_RATIOS = ["1:1", "2:3", "3:2", "9:16", "16:9"];
273
+ var NANO_IMAGE_RATIOS = ["1:1", "2:3", "3:2", "3:4", "4:3", "9:16", "16:9", "21:9", "5:4", "4:5"];
274
+ var SEEDANCE_VIDEO_RATIOS = ["1:1", "3:4", "4:3", "9:16", "16:9", "21:9"];
275
+ var KLING_VIDEO_RATIOS = ["1:1", "9:16", "16:9"];
276
+ var IMAGE_COUNTS = [1, 2, 4];
277
+ var IMAGE_RESOLUTIONS = ["1K", "2K", "4K"];
278
+ var SEEDANCE_RESOLUTIONS = ["480P", "720P", "1080P"];
279
+ var KLING_DURATIONS = [5, 10];
280
+ var KLING_MODES = ["std", "pro"];
281
+ var KLING_SOUNDS = ["off", "on"];
262
282
  var DEFAULT_RATIO = "1:1";
263
283
  var DEFAULT_TIMEOUT_MS = 3e5;
264
284
  var DEFAULT_POLL_INTERVAL_MS = 2e3;
@@ -268,7 +288,7 @@ var anyOutputSchema = {
268
288
  };
269
289
  var mivoPlugin = {
270
290
  name: "pixi-board-plugin-mivo",
271
- version: "0.1.2",
291
+ version: "0.1.4",
272
292
  environmentVariables: [
273
293
  {
274
294
  name: "MIVO_API_KEY",
@@ -287,6 +307,9 @@ var mivoPlugin = {
287
307
  },
288
308
  register(api) {
289
309
  api.registerTool(createMivoCreateGptImageTool());
310
+ api.registerTool(createMivoCreateNanoImageTool());
311
+ api.registerTool(createMivoCreateKlingVideoTool());
312
+ api.registerTool(createMivoCreateSeedanceVideoTool());
290
313
  }
291
314
  };
292
315
  var plugin = mivoPlugin;
@@ -300,56 +323,22 @@ function createMivoCreateGptImageTool(deps = {}) {
300
323
  inputSchema: createGptImageInputSchema,
301
324
  outputSchema: createGptImageOutputSchema,
302
325
  async run(input, ctx) {
303
- const args = readInput(input);
304
- const projectInfo = await ctx.tools.call("canvas.read_project_info", {
305
- projectRoot: args.projectRoot
326
+ const args = readGptImageInput(input);
327
+ return runMivoGeneration(ctx, client, sleep, {
328
+ projectRoot: args.projectRoot,
329
+ prompt: args.prompt,
330
+ name: args.name ?? defaultNodeName("GPT image", args.prompt),
331
+ toolName: "mivo.create_gpt_image",
332
+ chatType: "freeform",
333
+ mediaKind: "image",
334
+ extension: "png",
335
+ timeoutMs: args.timeoutMs,
336
+ pollIntervalMs: args.pollIntervalMs,
337
+ metadata: {
338
+ ratio: args.ratio
339
+ },
340
+ buildPayload: (chatSessionId) => buildGptImageSubmitPayload(args, chatSessionId)
306
341
  });
307
- const projectRoot = projectInfo.projectRoot;
308
- let nodeId = null;
309
- let messageId = null;
310
- try {
311
- const createResult = await ctx.tools.call("canvas.create_nodes", {
312
- projectRoot,
313
- nodes: [
314
- {
315
- kind: "generating",
316
- name: args.name ?? defaultNodeName(args.prompt),
317
- options: {
318
- mivo: {
319
- status: "generating",
320
- provider: "mivo",
321
- tool: "mivo.create_gpt_image",
322
- prompt: args.prompt,
323
- ratio: args.ratio
324
- }
325
- }
326
- }
327
- ]
328
- });
329
- nodeId = readCreatedNodeId(createResult);
330
- const chatSessionId = await client.getChatSessionId("freeform");
331
- messageId = await client.submitMessage(buildSubmitPayload(args, chatSessionId));
332
- const artifact = await pollForImage(client, messageId, args.timeoutMs, args.pollIntervalMs, sleep);
333
- const imagePath = await materializeArtifact(client, artifact, projectRoot, messageId);
334
- const installResult = await ctx.tools.call("canvas.generating_node_install", {
335
- projectRoot,
336
- nodeId,
337
- path: imagePath
338
- });
339
- return {
340
- nodeId,
341
- messageId,
342
- imagePath,
343
- imageUrl: artifact.url ?? (artifact.fileId ? client.fileUrl(artifact.fileId) : void 0),
344
- nodes: installResult.nodes,
345
- assets: installResult.assets ?? []
346
- };
347
- } catch (error) {
348
- if (nodeId) {
349
- await markNodeFailed(ctx, projectRoot, nodeId, error, messageId);
350
- }
351
- throw error;
352
- }
353
342
  }
354
343
  };
355
344
  }
@@ -358,9 +347,41 @@ var createGptImageInputSchema = objectSchema({
358
347
  prompt: stringSchema("Prompt for GPT Image 2"),
359
348
  ratio: {
360
349
  type: "string",
361
- enum: RATIOS,
350
+ enum: GPT_IMAGE_RATIOS,
351
+ description: "Generated image aspect ratio"
352
+ },
353
+ name: stringSchema("Optional display name for the generating node"),
354
+ referenceFileIds: {
355
+ type: "array",
356
+ items: { type: "string" },
357
+ description: "Optional Mivo file ids to use as image references"
358
+ },
359
+ timeoutMs: {
360
+ type: "number",
361
+ description: "Maximum time to wait for the Mivo message result"
362
+ },
363
+ pollIntervalMs: {
364
+ type: "number",
365
+ description: "Delay between Mivo result polling attempts"
366
+ }
367
+ }, ["projectRoot", "prompt"]);
368
+ var createNanoImageInputSchema = objectSchema({
369
+ projectRoot: stringSchema('Canvas project root absolute path, or "active" for the current canvas'),
370
+ prompt: stringSchema("Prompt for Nano image generation"),
371
+ ratio: {
372
+ type: "string",
373
+ enum: NANO_IMAGE_RATIOS,
362
374
  description: "Generated image aspect ratio"
363
375
  },
376
+ resolution: {
377
+ type: "string",
378
+ enum: IMAGE_RESOLUTIONS,
379
+ description: "Nano image resolution"
380
+ },
381
+ n: {
382
+ type: "number",
383
+ description: "Number of images to request. The first returned image is installed on the board."
384
+ },
364
385
  name: stringSchema("Optional display name for the generating node"),
365
386
  referenceFileIds: {
366
387
  type: "array",
@@ -376,11 +397,103 @@ var createGptImageInputSchema = objectSchema({
376
397
  description: "Delay between Mivo result polling attempts"
377
398
  }
378
399
  }, ["projectRoot", "prompt"]);
379
- var createGptImageOutputSchema = objectSchema({
400
+ var createKlingVideoInputSchema = objectSchema({
401
+ projectRoot: stringSchema('Canvas project root absolute path, or "active" for the current canvas'),
402
+ prompt: stringSchema("Prompt for Kling video generation"),
403
+ ratio: {
404
+ type: "string",
405
+ enum: KLING_VIDEO_RATIOS,
406
+ description: "Generated video aspect ratio"
407
+ },
408
+ duration: {
409
+ type: "number",
410
+ description: "Generated video duration in seconds"
411
+ },
412
+ mode: {
413
+ type: "string",
414
+ enum: KLING_MODES,
415
+ description: "Kling generation mode"
416
+ },
417
+ sound: {
418
+ type: "string",
419
+ enum: KLING_SOUNDS,
420
+ description: "Kling sound toggle"
421
+ },
422
+ name: stringSchema("Optional display name for the generating node"),
423
+ referenceImageFileIds: {
424
+ type: "array",
425
+ items: { type: "string" },
426
+ description: "Optional Mivo image file ids to use as references"
427
+ },
428
+ referenceVideoFileIds: {
429
+ type: "array",
430
+ items: { type: "string" },
431
+ description: "Optional Mivo video file ids to use as references"
432
+ },
433
+ timeoutMs: {
434
+ type: "number",
435
+ description: "Maximum time to wait for the Mivo message result"
436
+ },
437
+ pollIntervalMs: {
438
+ type: "number",
439
+ description: "Delay between Mivo result polling attempts"
440
+ }
441
+ }, ["projectRoot", "prompt"]);
442
+ var createSeedanceVideoInputSchema = objectSchema({
443
+ projectRoot: stringSchema('Canvas project root absolute path, or "active" for the current canvas'),
444
+ prompt: stringSchema("Prompt for Seedance video generation"),
445
+ ratio: {
446
+ type: "string",
447
+ enum: SEEDANCE_VIDEO_RATIOS,
448
+ description: "Generated video aspect ratio"
449
+ },
450
+ duration: {
451
+ type: "number",
452
+ description: "Generated video duration in seconds, from 2 to 15"
453
+ },
454
+ resolution: {
455
+ type: "string",
456
+ enum: SEEDANCE_RESOLUTIONS,
457
+ description: "Seedance video resolution"
458
+ },
459
+ audio: {
460
+ type: "boolean",
461
+ description: "Whether to generate audio"
462
+ },
463
+ name: stringSchema("Optional display name for the generating node"),
464
+ referenceImageFileIds: {
465
+ type: "array",
466
+ items: { type: "string" },
467
+ description: "Optional Mivo image file ids to use as references"
468
+ },
469
+ referenceVideoFileIds: {
470
+ type: "array",
471
+ items: { type: "string" },
472
+ description: "Optional Mivo video file ids to use as references"
473
+ },
474
+ referenceAudioFileIds: {
475
+ type: "array",
476
+ items: { type: "string" },
477
+ description: "Optional Mivo audio file ids to use as references"
478
+ },
479
+ timeoutMs: {
480
+ type: "number",
481
+ description: "Maximum time to wait for the Mivo message result"
482
+ },
483
+ pollIntervalMs: {
484
+ type: "number",
485
+ description: "Delay between Mivo result polling attempts"
486
+ }
487
+ }, ["projectRoot", "prompt"]);
488
+ var createMediaOutputSchema = objectSchema({
380
489
  nodeId: { type: "string" },
381
490
  messageId: { type: "string" },
491
+ mediaPath: { type: "string" },
492
+ mediaUrl: { type: "string" },
382
493
  imagePath: { type: "string" },
383
494
  imageUrl: { type: "string" },
495
+ videoPath: { type: "string" },
496
+ videoUrl: { type: "string" },
384
497
  nodes: {
385
498
  type: "array",
386
499
  items: anyOutputSchema
@@ -389,8 +502,104 @@ var createGptImageOutputSchema = objectSchema({
389
502
  type: "array",
390
503
  items: anyOutputSchema
391
504
  }
392
- }, ["nodeId", "messageId", "imagePath", "nodes", "assets"]);
393
- function buildSubmitPayload(input, chatSessionId) {
505
+ }, ["nodeId", "messageId", "mediaPath", "nodes", "assets"]);
506
+ var createGptImageOutputSchema = createMediaOutputSchema;
507
+ var createNanoImageOutputSchema = createMediaOutputSchema;
508
+ var createVideoOutputSchema = createMediaOutputSchema;
509
+ function createMivoCreateNanoImageTool(deps = {}) {
510
+ const client = deps.client ?? createMivoApiClient();
511
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
512
+ return {
513
+ name: "mivo.create_nano_image",
514
+ description: "Create a generating canvas node, submit a Nano image task to Mivo, poll until the image is ready, then replace the generating node with the downloaded media asset.",
515
+ kind: "plugin",
516
+ inputSchema: createNanoImageInputSchema,
517
+ outputSchema: createNanoImageOutputSchema,
518
+ async run(input, ctx) {
519
+ const args = readNanoImageInput(input);
520
+ return runMivoGeneration(ctx, client, sleep, {
521
+ projectRoot: args.projectRoot,
522
+ prompt: args.prompt,
523
+ name: args.name ?? defaultNodeName("Nano image", args.prompt),
524
+ toolName: "mivo.create_nano_image",
525
+ chatType: "freeform",
526
+ mediaKind: "image",
527
+ extension: "png",
528
+ timeoutMs: args.timeoutMs,
529
+ pollIntervalMs: args.pollIntervalMs,
530
+ metadata: {
531
+ ratio: args.ratio,
532
+ resolution: args.resolution
533
+ },
534
+ buildPayload: (chatSessionId) => buildNanoImageSubmitPayload(args, chatSessionId)
535
+ });
536
+ }
537
+ };
538
+ }
539
+ function createMivoCreateKlingVideoTool(deps = {}) {
540
+ const client = deps.client ?? createMivoApiClient();
541
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
542
+ return {
543
+ name: "mivo.create_kling_video",
544
+ description: "Create a generating canvas node, submit a Kling video task to Mivo, poll until the video is ready, then replace the generating node with the downloaded media asset.",
545
+ kind: "plugin",
546
+ inputSchema: createKlingVideoInputSchema,
547
+ outputSchema: createVideoOutputSchema,
548
+ async run(input, ctx) {
549
+ const args = readKlingVideoInput(input);
550
+ return runMivoGeneration(ctx, client, sleep, {
551
+ projectRoot: args.projectRoot,
552
+ prompt: args.prompt,
553
+ name: args.name ?? defaultNodeName("Kling video", args.prompt),
554
+ toolName: "mivo.create_kling_video",
555
+ chatType: "video",
556
+ mediaKind: "video",
557
+ extension: "mp4",
558
+ timeoutMs: args.timeoutMs,
559
+ pollIntervalMs: args.pollIntervalMs,
560
+ metadata: {
561
+ ratio: args.ratio,
562
+ duration: args.duration,
563
+ mode: args.mode,
564
+ sound: args.sound
565
+ },
566
+ buildPayload: (chatSessionId) => buildKlingVideoSubmitPayload(args, chatSessionId)
567
+ });
568
+ }
569
+ };
570
+ }
571
+ function createMivoCreateSeedanceVideoTool(deps = {}) {
572
+ const client = deps.client ?? createMivoApiClient();
573
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
574
+ return {
575
+ name: "mivo.create_seedance_video",
576
+ description: "Create a generating canvas node, submit a Seedance video task to Mivo, poll until the video is ready, then replace the generating node with the downloaded media asset.",
577
+ kind: "plugin",
578
+ inputSchema: createSeedanceVideoInputSchema,
579
+ outputSchema: createVideoOutputSchema,
580
+ async run(input, ctx) {
581
+ const args = readSeedanceVideoInput(input);
582
+ return runMivoGeneration(ctx, client, sleep, {
583
+ projectRoot: args.projectRoot,
584
+ prompt: args.prompt,
585
+ name: args.name ?? defaultNodeName("Seedance video", args.prompt),
586
+ toolName: "mivo.create_seedance_video",
587
+ chatType: "video",
588
+ mediaKind: "video",
589
+ extension: "mp4",
590
+ timeoutMs: args.timeoutMs,
591
+ pollIntervalMs: args.pollIntervalMs,
592
+ metadata: {
593
+ ratio: args.ratio,
594
+ duration: args.duration,
595
+ resolution: args.resolution
596
+ },
597
+ buildPayload: (chatSessionId) => buildSeedanceVideoSubmitPayload(args, chatSessionId)
598
+ });
599
+ }
600
+ };
601
+ }
602
+ function buildGptImageSubmitPayload(input, chatSessionId) {
394
603
  const images = input.referenceFileIds ?? [];
395
604
  return {
396
605
  chatSessionId,
@@ -410,42 +619,167 @@ function buildSubmitPayload(input, chatSessionId) {
410
619
  }
411
620
  };
412
621
  }
413
- async function pollForImage(client, messageId, timeoutMs, pollIntervalMs, sleep) {
622
+ function buildNanoImageSubmitPayload(input, chatSessionId) {
623
+ const images = input.referenceFileIds ?? [];
624
+ return {
625
+ chatSessionId,
626
+ title: "\u4F5C\u56FE",
627
+ modelType: "NANOBANANA",
628
+ action: images.length > 0 ? "modal" : "imagine",
629
+ messageType: "image",
630
+ modelFormat: {
631
+ version: "GEMINI_3_PRO_IMAGE_PREVIEW"
632
+ },
633
+ payload: {
634
+ prompt: input.prompt,
635
+ images,
636
+ n: input.n,
637
+ imgRatio: input.ratio,
638
+ resolution: input.resolution
639
+ }
640
+ };
641
+ }
642
+ function buildKlingVideoSubmitPayload(input, chatSessionId) {
643
+ return {
644
+ chatSessionId,
645
+ title: "\u4F5C\u89C6\u9891",
646
+ modelType: "KLING",
647
+ action: "generate_video",
648
+ messageType: "video",
649
+ modelFormat: {
650
+ version: "kling-v3-omni"
651
+ },
652
+ payload: {
653
+ prompt: input.prompt,
654
+ images: input.referenceImageFileIds ?? [],
655
+ video_clips: input.referenceVideoFileIds ?? [],
656
+ videoRatio: input.ratio,
657
+ duration: input.duration,
658
+ mode: input.mode,
659
+ multi_shot: false,
660
+ sound: input.sound
661
+ }
662
+ };
663
+ }
664
+ function buildSeedanceVideoSubmitPayload(input, chatSessionId) {
665
+ return {
666
+ chatSessionId,
667
+ title: "\u4F5C\u89C6\u9891",
668
+ modelType: "ARK",
669
+ action: "generate_video",
670
+ messageType: "video",
671
+ modelFormat: {
672
+ version: "Seedance_2_0"
673
+ },
674
+ payload: {
675
+ prompt: input.prompt,
676
+ images: input.referenceImageFileIds ?? [],
677
+ video_clips: input.referenceVideoFileIds ?? [],
678
+ audio_clips: input.referenceAudioFileIds ?? [],
679
+ videoRatio: input.ratio,
680
+ duration: input.duration,
681
+ resolution: input.resolution,
682
+ audio: input.audio
683
+ }
684
+ };
685
+ }
686
+ async function runMivoGeneration(ctx, client, sleep, options) {
687
+ const projectInfo = await ctx.tools.call("canvas.read_project_info", {
688
+ projectRoot: options.projectRoot
689
+ });
690
+ const projectRoot = projectInfo.projectRoot;
691
+ let nodeId = null;
692
+ let messageId = null;
693
+ try {
694
+ const createResult = await ctx.tools.call("canvas.create_nodes", {
695
+ projectRoot,
696
+ nodes: [
697
+ {
698
+ kind: "generating",
699
+ name: options.name,
700
+ options: {
701
+ mivo: {
702
+ status: "generating",
703
+ provider: "mivo",
704
+ tool: options.toolName,
705
+ prompt: options.prompt,
706
+ ...options.metadata
707
+ }
708
+ }
709
+ }
710
+ ]
711
+ });
712
+ nodeId = readCreatedNodeId(createResult);
713
+ const chatSessionId = await client.getChatSessionId(options.chatType);
714
+ messageId = await client.submitMessage(options.buildPayload(chatSessionId));
715
+ const artifact = await pollForMedia(
716
+ client,
717
+ messageId,
718
+ options.mediaKind,
719
+ options.timeoutMs,
720
+ options.pollIntervalMs,
721
+ sleep
722
+ );
723
+ const mediaPath = await materializeArtifact(client, artifact, projectRoot, messageId, options.mediaKind, options.extension);
724
+ const installResult = await ctx.tools.call("canvas.generating_node_install", {
725
+ projectRoot,
726
+ nodeId,
727
+ path: mediaPath
728
+ });
729
+ const mediaUrl = artifact.url ?? (artifact.fileId ? client.fileUrl(artifact.fileId, options.mediaKind) : void 0);
730
+ return {
731
+ nodeId,
732
+ messageId,
733
+ mediaPath,
734
+ mediaUrl,
735
+ ...options.mediaKind === "image" ? { imagePath: mediaPath, imageUrl: mediaUrl } : {},
736
+ ...options.mediaKind === "video" ? { videoPath: mediaPath, videoUrl: mediaUrl } : {},
737
+ nodes: installResult.nodes,
738
+ assets: installResult.assets ?? []
739
+ };
740
+ } catch (error) {
741
+ if (nodeId) {
742
+ await markNodeFailed(ctx, projectRoot, nodeId, options.name, error, messageId);
743
+ }
744
+ throw error;
745
+ }
746
+ }
747
+ async function pollForMedia(client, messageId, mediaKind, timeoutMs, pollIntervalMs, sleep) {
414
748
  const start = Date.now();
415
749
  let trySse = true;
416
750
  while (Date.now() - start <= timeoutMs) {
417
751
  const remainingMs = Math.max(1, timeoutMs - (Date.now() - start));
418
752
  const message = trySse ? await readSseOrFallback(client, messageId, remainingMs) : await client.getMessageResult(messageId);
419
753
  trySse = false;
420
- const artifact = extractImageArtifact(message);
754
+ const artifact = mediaKind === "image" ? extractImageArtifact(message) : extractVideoArtifact(message);
421
755
  if (artifact) {
422
756
  return artifact;
423
757
  }
424
758
  const status = normalizeStatus(message.content?.status ?? message.status);
425
759
  const error = message.content?.error ?? message.error;
426
760
  if (error || isFailedStatus(status)) {
427
- throw new Error(`Mivo GPT image generation failed for ${messageId}: ${error ?? status}`);
761
+ throw new Error(`Mivo ${mediaKind} generation failed for ${messageId}: ${error ?? status}`);
428
762
  }
429
763
  if (isCompletedStatus(status)) {
430
- throw new Error(`Mivo GPT image generation completed for ${messageId} but returned no image`);
764
+ throw new Error(`Mivo ${mediaKind} generation completed for ${messageId} but returned no ${mediaKind}`);
431
765
  }
432
766
  await sleep(pollIntervalMs);
433
767
  }
434
- throw new Error(`Mivo GPT image generation timed out for ${messageId} after ${timeoutMs}ms`);
768
+ throw new Error(`Mivo ${mediaKind} generation timed out for ${messageId} after ${timeoutMs}ms`);
435
769
  }
436
- async function materializeArtifact(client, artifact, projectRoot, messageId) {
770
+ async function materializeArtifact(client, artifact, projectRoot, messageId, mediaKind, extension) {
437
771
  const outputDir = path2.join(projectRoot, ".canvas-mcp", "mivo");
438
772
  await mkdir2(outputDir, { recursive: true });
439
- const imagePath = path2.join(outputDir, `${safeFileName(messageId)}.png`);
773
+ const mediaPath = path2.join(outputDir, `${safeFileName(messageId)}.${extension}`);
440
774
  if (artifact.fileId) {
441
- await client.downloadFile(artifact.fileId, imagePath);
442
- return imagePath;
775
+ await client.downloadFile(artifact.fileId, mediaPath, mediaKind);
776
+ return mediaPath;
443
777
  }
444
778
  if (artifact.url) {
445
- await client.downloadUrl(artifact.url, imagePath);
446
- return imagePath;
779
+ await client.downloadUrl(artifact.url, mediaPath);
780
+ return mediaPath;
447
781
  }
448
- throw new Error(`Mivo GPT image generation returned an image artifact without file id or URL for ${messageId}`);
782
+ throw new Error(`Mivo ${mediaKind} generation returned an artifact without file id or URL for ${messageId}`);
449
783
  }
450
784
  async function readSseOrFallback(client, messageId, timeoutMs) {
451
785
  try {
@@ -454,14 +788,14 @@ async function readSseOrFallback(client, messageId, timeoutMs) {
454
788
  return client.getMessageResult(messageId);
455
789
  }
456
790
  }
457
- async function markNodeFailed(ctx, projectRoot, nodeId, error, messageId) {
791
+ async function markNodeFailed(ctx, projectRoot, nodeId, name, error, messageId) {
458
792
  try {
459
793
  await ctx.tools.call("canvas.update_nodes", {
460
794
  projectRoot,
461
795
  updates: [
462
796
  {
463
797
  id: nodeId,
464
- name: "Failed: GPT image",
798
+ name: `Failed: ${name}`,
465
799
  options: {
466
800
  mivo: {
467
801
  status: "failed",
@@ -491,28 +825,81 @@ function readCreatedNodeId(result) {
491
825
  }
492
826
  return nodeId;
493
827
  }
494
- function readInput(input) {
828
+ function readGptImageInput(input) {
495
829
  const record = asRecord2(input);
496
830
  const projectRoot = readString(record, "projectRoot");
497
831
  const prompt = readString(record, "prompt");
498
- const ratio = readOptionalRatio(record.ratio);
832
+ const ratio = readOptionalEnum(record.ratio, GPT_IMAGE_RATIOS, "ratio") ?? DEFAULT_RATIO;
499
833
  const referenceFileIds = readOptionalStringArray(record.referenceFileIds, "referenceFileIds");
500
- const timeoutMs = readOptionalPositiveNumber(record.timeoutMs, "timeoutMs") ?? DEFAULT_TIMEOUT_MS;
501
- const pollIntervalMs = readOptionalPositiveNumber(record.pollIntervalMs, "pollIntervalMs") ?? DEFAULT_POLL_INTERVAL_MS;
502
834
  return {
503
835
  projectRoot,
504
836
  prompt,
505
837
  ratio,
506
838
  name: readOptionalString(record.name, "name"),
507
839
  referenceFileIds,
508
- timeoutMs,
509
- pollIntervalMs
840
+ ...readTiming(record)
841
+ };
842
+ }
843
+ function readNanoImageInput(input) {
844
+ const record = asRecord2(input);
845
+ const projectRoot = readString(record, "projectRoot");
846
+ const prompt = readString(record, "prompt");
847
+ return {
848
+ projectRoot,
849
+ prompt,
850
+ ratio: readOptionalEnum(record.ratio, NANO_IMAGE_RATIOS, "ratio") ?? DEFAULT_RATIO,
851
+ resolution: readOptionalEnum(record.resolution, IMAGE_RESOLUTIONS, "resolution") ?? "2K",
852
+ n: readOptionalEnum(record.n, IMAGE_COUNTS, "n") ?? 1,
853
+ name: readOptionalString(record.name, "name"),
854
+ referenceFileIds: readOptionalStringArray(record.referenceFileIds, "referenceFileIds"),
855
+ ...readTiming(record)
510
856
  };
511
857
  }
512
- function readOptionalRatio(value) {
513
- if (value === void 0) return DEFAULT_RATIO;
514
- if (typeof value !== "string" || !RATIOS.includes(value)) {
515
- throw new Error(`ratio must be one of ${RATIOS.join(", ")}`);
858
+ function readKlingVideoInput(input) {
859
+ const record = asRecord2(input);
860
+ const projectRoot = readString(record, "projectRoot");
861
+ const prompt = readString(record, "prompt");
862
+ return {
863
+ projectRoot,
864
+ prompt,
865
+ ratio: readOptionalEnum(record.ratio, KLING_VIDEO_RATIOS, "ratio") ?? "16:9",
866
+ duration: readOptionalEnum(record.duration, KLING_DURATIONS, "duration") ?? 5,
867
+ mode: readOptionalEnum(record.mode, KLING_MODES, "mode") ?? "std",
868
+ sound: readOptionalEnum(record.sound, KLING_SOUNDS, "sound") ?? "on",
869
+ name: readOptionalString(record.name, "name"),
870
+ referenceImageFileIds: readOptionalStringArray(record.referenceImageFileIds, "referenceImageFileIds"),
871
+ referenceVideoFileIds: readOptionalStringArray(record.referenceVideoFileIds, "referenceVideoFileIds"),
872
+ ...readTiming(record)
873
+ };
874
+ }
875
+ function readSeedanceVideoInput(input) {
876
+ const record = asRecord2(input);
877
+ const projectRoot = readString(record, "projectRoot");
878
+ const prompt = readString(record, "prompt");
879
+ return {
880
+ projectRoot,
881
+ prompt,
882
+ ratio: readOptionalEnum(record.ratio, SEEDANCE_VIDEO_RATIOS, "ratio") ?? "16:9",
883
+ duration: readOptionalIntegerInRange(record.duration, "duration", 2, 15) ?? 5,
884
+ resolution: readOptionalEnum(record.resolution, SEEDANCE_RESOLUTIONS, "resolution") ?? "720P",
885
+ audio: readOptionalBoolean(record.audio, "audio") ?? true,
886
+ name: readOptionalString(record.name, "name"),
887
+ referenceImageFileIds: readOptionalStringArray(record.referenceImageFileIds, "referenceImageFileIds"),
888
+ referenceVideoFileIds: readOptionalStringArray(record.referenceVideoFileIds, "referenceVideoFileIds"),
889
+ referenceAudioFileIds: readOptionalStringArray(record.referenceAudioFileIds, "referenceAudioFileIds"),
890
+ ...readTiming(record)
891
+ };
892
+ }
893
+ function readTiming(record) {
894
+ return {
895
+ timeoutMs: readOptionalPositiveNumber(record.timeoutMs, "timeoutMs") ?? DEFAULT_TIMEOUT_MS,
896
+ pollIntervalMs: readOptionalPositiveNumber(record.pollIntervalMs, "pollIntervalMs") ?? DEFAULT_POLL_INTERVAL_MS
897
+ };
898
+ }
899
+ function readOptionalEnum(value, values, label) {
900
+ if (value === void 0) return void 0;
901
+ if (!values.includes(value)) {
902
+ throw new Error(`${label} must be one of ${values.join(", ")}`);
516
903
  }
517
904
  return value;
518
905
  }
@@ -535,6 +922,20 @@ function readOptionalPositiveNumber(value, label) {
535
922
  }
536
923
  return value;
537
924
  }
925
+ function readOptionalIntegerInRange(value, label, min, max) {
926
+ if (value === void 0) return void 0;
927
+ if (!Number.isInteger(value) || value < min || value > max) {
928
+ throw new Error(`${label} must be an integer from ${min} to ${max}`);
929
+ }
930
+ return value;
931
+ }
932
+ function readOptionalBoolean(value, label) {
933
+ if (value === void 0) return void 0;
934
+ if (typeof value !== "boolean") {
935
+ throw new Error(`${label} must be a boolean`);
936
+ }
937
+ return value;
938
+ }
538
939
  function readOptionalString(value, label) {
539
940
  if (value === void 0) return void 0;
540
941
  if (typeof value !== "string" || value.trim() === "") {
@@ -549,10 +950,10 @@ function readString(record, key) {
549
950
  }
550
951
  return value;
551
952
  }
552
- function defaultNodeName(prompt) {
953
+ function defaultNodeName(prefix, prompt) {
553
954
  const normalized = prompt.replace(/\s+/g, " ").trim();
554
955
  const suffix = normalized.length > 48 ? `${normalized.slice(0, 48)}...` : normalized;
555
- return `GPT image: ${suffix}`;
956
+ return `${prefix}: ${suffix}`;
556
957
  }
557
958
  function safeFileName(value) {
558
959
  return value.replace(/[^a-zA-Z0-9._-]/g, "_") || `mivo-${Date.now()}`;
@@ -579,6 +980,9 @@ function errorMessage(error) {
579
980
  }
580
981
  export {
581
982
  createMivoCreateGptImageTool,
983
+ createMivoCreateKlingVideoTool,
984
+ createMivoCreateNanoImageTool,
985
+ createMivoCreateSeedanceVideoTool,
582
986
  mivoPlugin,
583
987
  plugin
584
988
  };