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 +502 -98
- package/package.json +1 -1
- package/src/index.test.ts +219 -9
- package/src/index.ts +573 -87
- package/src/mivoApi.ts +39 -20
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
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
|
|
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
|
|
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.
|
|
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 =
|
|
304
|
-
|
|
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:
|
|
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
|
|
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", "
|
|
393
|
-
|
|
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
|
-
|
|
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
|
|
761
|
+
throw new Error(`Mivo ${mediaKind} generation failed for ${messageId}: ${error ?? status}`);
|
|
428
762
|
}
|
|
429
763
|
if (isCompletedStatus(status)) {
|
|
430
|
-
throw new Error(`Mivo
|
|
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
|
|
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
|
|
773
|
+
const mediaPath = path2.join(outputDir, `${safeFileName(messageId)}.${extension}`);
|
|
440
774
|
if (artifact.fileId) {
|
|
441
|
-
await client.downloadFile(artifact.fileId,
|
|
442
|
-
return
|
|
775
|
+
await client.downloadFile(artifact.fileId, mediaPath, mediaKind);
|
|
776
|
+
return mediaPath;
|
|
443
777
|
}
|
|
444
778
|
if (artifact.url) {
|
|
445
|
-
await client.downloadUrl(artifact.url,
|
|
446
|
-
return
|
|
779
|
+
await client.downloadUrl(artifact.url, mediaPath);
|
|
780
|
+
return mediaPath;
|
|
447
781
|
}
|
|
448
|
-
throw new Error(`Mivo
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
-
|
|
509
|
-
|
|
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
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
|
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
|
};
|