corent-mcp 0.8.1 → 0.9.0
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/README.md +34 -4
- package/dist/prompts.js +15 -9
- package/dist/server.js +190 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,11 +9,13 @@ Give any AI agent the ability to generate images, videos, voice, and text throug
|
|
|
9
9
|
| `plan` | Describe a request in plain language; Corent returns the plan (image vs video, tier, settings) + cost estimate, without generating |
|
|
10
10
|
| `create` | Describe what you want + a spend ceiling; Corent decides everything and generates it (the "zero decisions" path) |
|
|
11
11
|
| `generate_image` | Text → image, synchronous, returns a permanent URL. Takes `reference_image_urls` for character and product consistency |
|
|
12
|
-
| `generate_video` | Text
|
|
12
|
+
| `generate_video` | Text, image, or references (`@Image 1`, `@Video 1`, `@Audio 1`) → video, async job. `return_last_frame` hands back the final frame to chain shots |
|
|
13
|
+
| `edit_video` | Change something in a finished clip (add, remove, replace); keeps its length and shape |
|
|
14
|
+
| `extend_video` | Continue a finished clip forward or backward |
|
|
13
15
|
| `generate_speech` | Text → spoken audio, synchronous |
|
|
14
16
|
| `generate_text` | Prompt or full conversation → text from a frontier language model. Supports tool calling and JSON mode |
|
|
15
17
|
| `generate_image_batch` | Up to 50 images in one call, async |
|
|
16
|
-
| `generate_video_batch` | Up to 50 clips in one call, async |
|
|
18
|
+
| `generate_video_batch` | Up to 50 clips in one call, async; items take the same reference fields as `generate_video` |
|
|
17
19
|
| `get_batch` | Progress of a submitted batch |
|
|
18
20
|
| `list_models` | The direct-access menu: every model that can be pinned by name (`corent-*`), with quality and live status |
|
|
19
21
|
| `list_tiers` | The tier menu: prices, shapes, video resolutions, and which tiers accept reference images or render sound |
|
|
@@ -44,8 +46,8 @@ publication as a skills repo.
|
|
|
44
46
|
| Prompt | Arguments | What it makes |
|
|
45
47
|
|---|---|---|
|
|
46
48
|
| `product-ugc-reel` | `product_url`, `brief`, `budget_usd` | Vertical creator-style reel for one product |
|
|
47
|
-
| `character-series` | `character`, `scenes`, `budget_usd` | One consistent character across many scenes |
|
|
48
|
-
| `storyboard-to-clip` | `storyboard`, `style`, `budget_usd` | Numbered storyboard to stills, clips and narration |
|
|
49
|
+
| `character-series` | `character`, `scenes`, `budget_usd` | One consistent character across many scenes and clips |
|
|
50
|
+
| `storyboard-to-clip` | `storyboard`, `style`, `budget_usd` | Numbered storyboard to stills, clips chained through last frames, and narration |
|
|
49
51
|
| `ad-variations` | `concept`, `count`, `product_url` | Many versions of one ad visual, in a single batch |
|
|
50
52
|
| `faceless-explainer` | `topic`, `duration_s`, `budget_usd` | Narrated explainer with no presenter |
|
|
51
53
|
|
|
@@ -101,6 +103,34 @@ that actually render it, so a silent model can never quietly serve the ask),
|
|
|
101
103
|
`end_image_url` for the frame to finish on, a `camera` move, `negative_prompt`,
|
|
102
104
|
`seed` and `fps`.
|
|
103
105
|
|
|
106
|
+
### Video with references, edits, extensions and chained shots
|
|
107
|
+
|
|
108
|
+
On `corent-seedance-2.5` `generate_video` also
|
|
109
|
+
takes **`reference_image_urls`** (up to 30), **`reference_video_urls`** (up to
|
|
110
|
+
10) and **`reference_audio_urls`** (up to 10). The prompt points at them as
|
|
111
|
+
`@Image 1`, `@Video 1`, `@Audio 1` in the order given, so "the character in
|
|
112
|
+
@Image 1 walks through the market" keeps that character. `task` says what to
|
|
113
|
+
do with them: `auto` (default), `reference`, `edit` or `extend`.
|
|
114
|
+
`edit_video` and `extend_video` are the two-argument versions of the last two:
|
|
115
|
+
give them a prompt and the clip URL and they set the rest.
|
|
116
|
+
|
|
117
|
+
**`return_last_frame: true`** stores a PNG of the final frame and returns it as
|
|
118
|
+
`last_frame_url` on the finished job; pass it as the next clip's `image_url`
|
|
119
|
+
and the action continues without a cut. That is how a longer scene is built
|
|
120
|
+
from 5 to 10 second clips.
|
|
121
|
+
|
|
122
|
+
The API rules, mirrored here so nothing is spent on a request it would refuse:
|
|
123
|
+
first/last frame (`image_url` / `end_image_url`) and `reference_*` cannot be
|
|
124
|
+
mixed; an edit keeps the source length, so omit `duration_s`; on first/last
|
|
125
|
+
frame, edit and extend the clip keeps the source shape, so omit `aspect_ratio`
|
|
126
|
+
or pass `adaptive`. `list_models` reports `supports_reference_images`,
|
|
127
|
+
`max_reference_images`, `supports_video_edit`, `supports_video_extend` and
|
|
128
|
+
`supports_last_frame` per video model; an incapable model answers 400.
|
|
129
|
+
|
|
130
|
+
The supplier rejects photos of real people as references; characters
|
|
131
|
+
generated inside Corent are accepted, so make the person with
|
|
132
|
+
`generate_image` first and reference that.
|
|
133
|
+
|
|
104
134
|
`generate_speech` takes `stability`, `similarity`, `style`, `speed` and
|
|
105
135
|
`language`. Call `list_voices` first whenever the user wants a particular
|
|
106
136
|
sounding narrator: `voice_id` cannot be guessed.
|
package/dist/prompts.js
CHANGED
|
@@ -17,7 +17,12 @@ export const PRICE_NOTE = "Prices to quote before spending: image premium ~7c ea
|
|
|
17
17
|
const BUDGET_RULE = "Add the numbers up, show the user the total against their budget, and do NOT call a paid tool until they say yes. " +
|
|
18
18
|
"Call get_balance first: available_cents is what can actually be spent.";
|
|
19
19
|
const POLLING = "Video is asynchronous: each generate_video returns a job id. Poll get_job every 10-15 seconds until status is " +
|
|
20
|
-
"completed (a clip usually takes 1-4 minutes); the URL is in videos[0].url
|
|
20
|
+
"completed (a clip usually takes 1-4 minutes); the URL is in videos[0].url, and last_frame_url is on the finished job " +
|
|
21
|
+
"when return_last_frame was true. If the user changes their mind, cancel_job releases the hold.";
|
|
22
|
+
// Video references (Seedance 2.x): the prompt names them @Image 1, @Video 1,
|
|
23
|
+
// @Audio 1 in the order given. The one rule every recipe has to repeat:
|
|
24
|
+
export const REAL_FACE_RULE = "The supplier rejects photos of REAL people as references; characters generated inside Corent are accepted, " +
|
|
25
|
+
"so build the person in generate_image first and reference that.";
|
|
21
26
|
const DELIVERY = "Corent does not stitch clips together. Deliver every still, clip and narration URL in order with its cost, " +
|
|
22
27
|
"so the user can assemble them in any editor. list_media can find these again later by prompt keyword.";
|
|
23
28
|
function user(text) {
|
|
@@ -38,14 +43,14 @@ export function registerPrompts(server) {
|
|
|
38
43
|
"1. PLAN. Write a 4-shot vertical (9:16) shot list: hook, product in use, the benefit, call to action. One line of narration per shot; keep the whole script under 1,000 characters.",
|
|
39
44
|
`2. COST. ${PRICE_NOTE} ${BUDGET_RULE}`,
|
|
40
45
|
"3. STILLS. For each shot call generate_image with reference_image_urls=[product image], tier premium (never air or lite: references need an edit-capable model), aspect_ratio 9:16, style photorealistic. If the label or logo must be pixel-exact, generate the scene, then inpaint_image with a mask that is BLACK over the product and WHITE elsewhere: pixels outside the mask stay byte-for-byte.",
|
|
41
|
-
"4. CLIPS. For each approved still call generate_video with image_url = that still, tier premium (pro if the user wants the best), duration_s 5. Omit aspect_ratio when animating a still. Pass audio false; the narration is a separate track.",
|
|
46
|
+
"4. CLIPS. For each approved still call generate_video with image_url = that still, tier premium (pro if the user wants the best), duration_s 5. Omit aspect_ratio when animating a still. Pass audio false; the narration is a separate track. When the product must stay exact while it moves (a label, a bottle turned in the hand), instead call generate_video with model corent-seedance-2.5, reference_image_urls=[product image] and a prompt that names it as @Image 1; never mix that with image_url. " + REAL_FACE_RULE,
|
|
42
47
|
"5. NARRATION. Call list_voices, let the user pick a voice_id, then ONE generate_speech call with the full script.",
|
|
43
48
|
`6. POLL. ${POLLING}`,
|
|
44
49
|
`7. DELIVER. ${DELIVERY}`,
|
|
45
50
|
].join("\n")));
|
|
46
51
|
server.registerPrompt("character-series", {
|
|
47
52
|
title: "Character series",
|
|
48
|
-
description: "One consistent character across many scenes: an anchor portrait, then every scene generated as an edit of that anchor.",
|
|
53
|
+
description: "One consistent character across many scenes and clips: an anchor portrait, then every scene generated as an edit of that anchor, and every clip referencing it.",
|
|
49
54
|
argsSchema: {
|
|
50
55
|
character: z.string().describe("Who the character is: age, look, clothing, mood"),
|
|
51
56
|
scenes: z.string().describe("The scenes, comma separated, e.g. 'at a cafe, on a train, at the beach'"),
|
|
@@ -57,13 +62,13 @@ export function registerPrompts(server) {
|
|
|
57
62
|
"1. ANCHOR. One generate_image, tier premium, aspect_ratio 3:4: the character alone, neutral pose, plain background, face clearly visible. Show it to the user and iterate (same seed, one change) until they approve. This is the only image the series is built from.",
|
|
58
63
|
`2. COST. One image per scene at ~7c (premium). ${BUDGET_RULE}`,
|
|
59
64
|
"3. SCENES. For each scene call generate_image with reference_image_urls=[anchor URL], tier premium or pro, the scene in the prompt, same style every time. Keep the anchor as the FIRST reference if you add a second (e.g. a product).",
|
|
60
|
-
"4. OPTIONAL CLIPS. generate_video with
|
|
65
|
+
"4. OPTIONAL CLIPS. To keep the character in motion, call generate_video with model corent-seedance-2.5, reference_image_urls=[anchor URL] (add the scene still second if it helps), a prompt that names the character as @Image 1, duration_s 5, aspect_ratio 9:16 or 16:9, and return_last_frame true. Do not pass image_url with references. For the next clip in the same scene pass image_url = the previous job's last_frame_url (then omit references and aspect_ratio) so the action continues without a cut. Quote ~52c (premium) or ~84c (pro) per clip first. " + REAL_FACE_RULE,
|
|
61
66
|
`5. POLL. ${POLLING}`,
|
|
62
67
|
`6. DELIVER. ${DELIVERY} Tell the user the anchor URL: pass it as a reference any time they want this character again.`,
|
|
63
68
|
].join("\n")));
|
|
64
69
|
server.registerPrompt("storyboard-to-clip", {
|
|
65
70
|
title: "Storyboard to clips",
|
|
66
|
-
description: "Turn a numbered storyboard into stills, then one clip per still with a camera move, plus optional narration.",
|
|
71
|
+
description: "Turn a numbered storyboard into stills, then one clip per still with a camera move, chained through last frames, plus optional narration.",
|
|
67
72
|
argsSchema: {
|
|
68
73
|
storyboard: z.string().describe("Numbered shots, one line each: what is in frame and what moves"),
|
|
69
74
|
style: z.string().optional().describe("Look, e.g. cinematic, anime, documentary"),
|
|
@@ -76,10 +81,11 @@ export function registerPrompts(server) {
|
|
|
76
81
|
"1. PLAN. Restate the shots as a table: number, frame, movement, seconds (5 each unless told otherwise), narration line if any.",
|
|
77
82
|
`2. COST. One still (~7c) plus one clip (~52c premium, ~84c pro) per shot, plus ~30c if there is narration. ${BUDGET_RULE}`,
|
|
78
83
|
"3. STILLS. generate_image per shot, aspect_ratio 16:9, style cinematic (or the requested style), tier premium. Use reference_image_urls when a character or object recurs across shots, with the first approved still as the reference.",
|
|
79
|
-
"4. CLIPS. generate_video per approved still: image_url = the still, duration_s 5, a camera value that matches the movement (pan_left, zoom_in, orbit_right, static ...). For an A-to-B shot pass end_image_url = the next shot's still. Omit aspect_ratio when animating a still.",
|
|
80
|
-
"5.
|
|
81
|
-
|
|
82
|
-
`7.
|
|
84
|
+
"4. CLIPS. generate_video per approved still: image_url = the still, duration_s 5, a camera value that matches the movement (pan_left, zoom_in, orbit_right, static ...), and return_last_frame true. For an A-to-B shot pass end_image_url = the next shot's still. Omit aspect_ratio when animating a still.",
|
|
85
|
+
"5. CHAIN. When two shots are one continuous action, start the second from where the first ended: image_url = the first job's last_frame_url (read it from get_job once completed), same prompt style, no still needed. To change something in a finished clip use edit_video; to make it longer use extend_video. " + REAL_FACE_RULE,
|
|
86
|
+
"6. NARRATION. If the storyboard has lines, list_voices then ONE generate_speech with the whole script.",
|
|
87
|
+
`7. POLL. ${POLLING}`,
|
|
88
|
+
`8. DELIVER. ${DELIVERY}`,
|
|
83
89
|
].join("\n")));
|
|
84
90
|
server.registerPrompt("ad-variations", {
|
|
85
91
|
title: "Ad variations",
|
package/dist/server.js
CHANGED
|
@@ -18,7 +18,7 @@ import { z } from "zod";
|
|
|
18
18
|
import { MEDIA_WIDGET_HTML } from "./widget-html.js";
|
|
19
19
|
import { registerPrompts } from "./prompts.js";
|
|
20
20
|
export const DEFAULT_API_URL = "https://api.corent.tech";
|
|
21
|
-
export const SERVER_VERSION = "0.
|
|
21
|
+
export const SERVER_VERSION = "0.9.0";
|
|
22
22
|
// Transport resilience, matching the two SDKs: a 429 or 5xx is retried with
|
|
23
23
|
// backoff, but ONLY on calls that carry an Idempotency-Key (or are GETs), so a
|
|
24
24
|
// retry can never buy a second generation.
|
|
@@ -237,6 +237,12 @@ export function createCorentServer(config = {}) {
|
|
|
237
237
|
// deliverable in it (parity audit 2026-08-30).
|
|
238
238
|
audio: z.array(z.object({ url: z.string() }).passthrough()).optional(),
|
|
239
239
|
progress_percent: z.number().nullable().optional(),
|
|
240
|
+
// Video jobs (2026-09-12): the PNG of the final frame when return_last_frame
|
|
241
|
+
// was asked for (pass it as the next clip's image_url), the task the model
|
|
242
|
+
// ran (auto | reference | edit | extend) and the ACTUAL clip length.
|
|
243
|
+
last_frame_url: z.string().nullable().optional(),
|
|
244
|
+
task: z.string().nullable().optional(),
|
|
245
|
+
duration_s: z.number().nullable().optional(),
|
|
240
246
|
meta: mediaMeta.optional(),
|
|
241
247
|
error: z.string().optional(),
|
|
242
248
|
};
|
|
@@ -332,10 +338,73 @@ export function createCorentServer(config = {}) {
|
|
|
332
338
|
headers: { "Idempotency-Key": idempotencyKey() },
|
|
333
339
|
body: JSON.stringify(args),
|
|
334
340
|
})));
|
|
341
|
+
// --- Video references (Seedance 2.x, 2026-09-12). The prompt points at
|
|
342
|
+
// them as "@Image 1", "@Video 1", "@Audio 1" in the order given. The API
|
|
343
|
+
// enforces three rules; they are mirrored here so a bad combination is
|
|
344
|
+
// refused before it costs a round trip.
|
|
345
|
+
const VIDEO_ASPECTS = ["16:9", "9:16", "1:1", "adaptive"];
|
|
346
|
+
const VIDEO_TASKS = ["auto", "reference", "edit", "extend"];
|
|
347
|
+
const REAL_FACE_RULE = "The supplier rejects photos of REAL people as references; characters generated inside Corent are accepted.";
|
|
348
|
+
const videoReferenceFields = {
|
|
349
|
+
reference_image_urls: z
|
|
350
|
+
.array(z.string().url())
|
|
351
|
+
.min(1)
|
|
352
|
+
.max(30)
|
|
353
|
+
.optional()
|
|
354
|
+
.describe("1 to 30 public https images the clip should keep to (a character, a product, a setting). Refer to " +
|
|
355
|
+
"them in the prompt as @Image 1, @Image 2 in this order. Needs model " +
|
|
356
|
+
"(corent-seedance-2.5); cannot be combined with image_url/end_image_url. " + REAL_FACE_RULE),
|
|
357
|
+
reference_video_urls: z
|
|
358
|
+
.array(z.string().url())
|
|
359
|
+
.min(1)
|
|
360
|
+
.max(10)
|
|
361
|
+
.optional()
|
|
362
|
+
.describe("1 to 10 public https clips, @Video 1, @Video 2 in the prompt: the source for an edit or extend task, " +
|
|
363
|
+
"or motion/style to follow. Cannot be combined with image_url/end_image_url."),
|
|
364
|
+
reference_audio_urls: z
|
|
365
|
+
.array(z.string().url())
|
|
366
|
+
.min(1)
|
|
367
|
+
.max(10)
|
|
368
|
+
.optional()
|
|
369
|
+
.describe("1 to 10 public https audio files, @Audio 1 in the prompt, e.g. a track the clip should cut to."),
|
|
370
|
+
task: z
|
|
371
|
+
.enum(VIDEO_TASKS)
|
|
372
|
+
.optional()
|
|
373
|
+
.describe("What to do with the references (default auto). edit changes something in @Video 1 and keeps its " +
|
|
374
|
+
"length: omit duration_s. extend continues @Video 1. Both need reference_video_urls and " +
|
|
375
|
+
"aspect_ratio omitted or adaptive. The prompt must say the intent (add, remove, replace, change; " +
|
|
376
|
+
"extend, continue)."),
|
|
377
|
+
return_last_frame: z
|
|
378
|
+
.boolean()
|
|
379
|
+
.optional()
|
|
380
|
+
.describe("true also stores a PNG of the final frame and returns it as last_frame_url on the finished job, so the " +
|
|
381
|
+
"next clip can start exactly there (pass it as image_url). Use it to chain shots."),
|
|
382
|
+
output_format: z.enum(["mp4", "mov"]).optional().describe("Delivered file type (default mp4)."),
|
|
383
|
+
};
|
|
384
|
+
function checkVideoRules(args, where = "") {
|
|
385
|
+
const bad = (message) => {
|
|
386
|
+
throw new CorentApiError(422, { message: `${where}${message}` });
|
|
387
|
+
};
|
|
388
|
+
const frames = Boolean(args.image_url || args.end_image_url);
|
|
389
|
+
const refs = ["reference_image_urls", "reference_video_urls", "reference_audio_urls"].some((k) => Array.isArray(args[k]) && args[k].length > 0);
|
|
390
|
+
const task = args.task ?? "auto";
|
|
391
|
+
if (frames && refs) {
|
|
392
|
+
bad("image_url/end_image_url (first/last frame) cannot be combined with reference_*: pick one way to guide the clip.");
|
|
393
|
+
}
|
|
394
|
+
if ((task === "edit" || task === "extend") && !(Array.isArray(args.reference_video_urls) && args.reference_video_urls.length)) {
|
|
395
|
+
bad(`task ${task} needs at least one reference_video_urls entry: the clip to ${task}.`);
|
|
396
|
+
}
|
|
397
|
+
if (task === "edit" && args.duration_s !== undefined) {
|
|
398
|
+
bad("omit duration_s on an edit task: the edited clip keeps the source clip's length.");
|
|
399
|
+
}
|
|
400
|
+
if ((frames || task === "edit" || task === "extend") && args.aspect_ratio !== undefined && args.aspect_ratio !== "adaptive") {
|
|
401
|
+
bad('aspect_ratio must be omitted or "adaptive" on first/last-frame, edit and extend tasks: the clip keeps the source\'s shape.');
|
|
402
|
+
}
|
|
403
|
+
}
|
|
335
404
|
server.registerTool("generate_video", {
|
|
336
405
|
title: "Generate video",
|
|
337
406
|
_meta: toolMeta("Starting video", "Video job started", true),
|
|
338
|
-
description: "Start a video from a
|
|
407
|
+
description: "Start a video from text, a source image, or references (@Image 1, @Video 1, @Audio 1 in the prompt). Asynchronous: returns a job id; poll get_job until completed. Premium ~52c, pro ~84c per clip; failed generations are never billed. References need corent-seedance-2.5; photos of real people are rejected as references.",
|
|
339
408
|
inputSchema: {
|
|
340
409
|
prompt: z.string().describe("What to generate, in plain language"),
|
|
341
410
|
tier: z
|
|
@@ -347,15 +416,19 @@ export function createCorentServer(config = {}) {
|
|
|
347
416
|
.optional()
|
|
348
417
|
.describe("Optional content style hint, same vocabulary as generate_image"),
|
|
349
418
|
aspect_ratio: z
|
|
350
|
-
.enum(
|
|
419
|
+
.enum(VIDEO_ASPECTS)
|
|
351
420
|
.optional()
|
|
352
|
-
.describe("Shape of the clip. OMIT IT when animating a source image: those
|
|
421
|
+
.describe("Shape of the clip. OMIT IT (or pass adaptive) when animating a source image, editing or extending a clip: those keep the source's own shape, so naming another is refused rather than silently ignored. Omitted for text-to-video means the model's own default (landscape)."),
|
|
353
422
|
duration_s: z.number().int().min(1).max(30).optional().describe("Requested duration in seconds"),
|
|
354
423
|
resolution: z
|
|
355
424
|
.enum(["720p", "1080p", "4k"])
|
|
356
425
|
.optional()
|
|
357
426
|
.describe("Pixel resolution (default 720p). Tier-capped: pro allows up to 1080p, max_pro up to 4k; above the tier's cap it is clamped down, never rejected. Higher resolutions cost more (1080p ~2.5x, 4k ~5.5x). GET /v1/tiers lists each tier's menu with prices."),
|
|
358
|
-
image_url: z
|
|
427
|
+
image_url: z
|
|
428
|
+
.string()
|
|
429
|
+
.url()
|
|
430
|
+
.optional()
|
|
431
|
+
.describe("If set, animates this image as the FIRST frame instead of pure text-to-video. Use a previous clip's last_frame_url to continue from it."),
|
|
359
432
|
audio: z
|
|
360
433
|
.boolean()
|
|
361
434
|
.optional()
|
|
@@ -383,15 +456,105 @@ export function createCorentServer(config = {}) {
|
|
|
383
456
|
model: z
|
|
384
457
|
.string()
|
|
385
458
|
.optional()
|
|
386
|
-
.describe('Direct model access: pin an exact model by name, e.g. "corent-seedance-2.
|
|
459
|
+
.describe('Direct model access: pin an exact model by name, e.g. "corent-seedance-2.5" (list_models shows the menu; every name there is spelled corent-*). Bypasses routing -- never substituted; duration and resolution snap to THAT model\'s own menu rather than a tier cap. Mutually exclusive with tier. list_models reports supports_reference_images, supports_video_edit, supports_video_extend and supports_last_frame per video model.'),
|
|
460
|
+
...videoReferenceFields,
|
|
387
461
|
},
|
|
388
462
|
outputSchema: jobOutput,
|
|
389
463
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
390
|
-
}, wrap(async (args) =>
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
464
|
+
}, wrap(async (args) => {
|
|
465
|
+
checkVideoRules(args);
|
|
466
|
+
return corent("/v1/videos/generate", {
|
|
467
|
+
method: "POST",
|
|
468
|
+
headers: { "Idempotency-Key": idempotencyKey() },
|
|
469
|
+
body: JSON.stringify(args),
|
|
470
|
+
});
|
|
471
|
+
}));
|
|
472
|
+
// --- Two conveniences over generate_video for the clip-in, clip-out asks.
|
|
473
|
+
// Same endpoint, same Idempotency-Key, same widget; they only set task and
|
|
474
|
+
// put the source clip in reference_video_urls so the agent cannot get the
|
|
475
|
+
// plumbing wrong. ---
|
|
476
|
+
const editExtendShared = {
|
|
477
|
+
reference_image_urls: z
|
|
478
|
+
.array(z.string().url())
|
|
479
|
+
.min(1)
|
|
480
|
+
.max(30)
|
|
481
|
+
.optional()
|
|
482
|
+
.describe("Optional images the result should match (@Image 1, @Image 2 in the prompt), e.g. a character or product to bring in. " +
|
|
483
|
+
REAL_FACE_RULE),
|
|
484
|
+
reference_audio_urls: z
|
|
485
|
+
.array(z.string().url())
|
|
486
|
+
.min(1)
|
|
487
|
+
.max(10)
|
|
488
|
+
.optional()
|
|
489
|
+
.describe("Optional audio (@Audio 1 in the prompt) for the result to follow."),
|
|
490
|
+
model: z
|
|
491
|
+
.string()
|
|
492
|
+
.optional()
|
|
493
|
+
.describe('Model to run, default "corent-seedance-2.5", the model that can edit or extend a clip today.'),
|
|
494
|
+
resolution: z.enum(["720p", "1080p", "4k"]).optional().describe("Pixel resolution (default 720p); higher costs more."),
|
|
495
|
+
audio: z.boolean().optional().describe("Ask for native sound in the result."),
|
|
496
|
+
seed: z.number().int().min(0).optional().describe("Reproducibility, same contract as generate_video."),
|
|
497
|
+
negative_prompt: z.string().optional().describe("What must NOT appear in the result."),
|
|
498
|
+
return_last_frame: z
|
|
499
|
+
.boolean()
|
|
500
|
+
.optional()
|
|
501
|
+
.describe("true returns last_frame_url on the finished job, a PNG of the final frame to start the next clip from."),
|
|
502
|
+
output_format: z.enum(["mp4", "mov"]).optional().describe("Delivered file type (default mp4)."),
|
|
503
|
+
};
|
|
504
|
+
server.registerTool("edit_video", {
|
|
505
|
+
title: "Edit video",
|
|
506
|
+
_meta: toolMeta("Editing video", "Video edit started", true),
|
|
507
|
+
description: "Edit an existing clip with a prompt: add, remove, replace or change something in @Video 1. Keeps the source clip's length and shape. Asynchronous: returns a job id; poll get_job until completed. Runs on corent-seedance-2.5. Photos of real people are rejected as references; Corent-made characters are fine.",
|
|
508
|
+
inputSchema: {
|
|
509
|
+
prompt: z
|
|
510
|
+
.string()
|
|
511
|
+
.min(1)
|
|
512
|
+
.describe('The change, naming the source as @Video 1 and using an edit verb: "Replace the red car in @Video 1 with a blue bicycle".'),
|
|
513
|
+
video_url: z.string().url().describe("Public https URL of the clip to edit (becomes @Video 1)."),
|
|
514
|
+
...editExtendShared,
|
|
515
|
+
},
|
|
516
|
+
outputSchema: jobOutput,
|
|
517
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
518
|
+
}, wrap(async ({ prompt, video_url, model, ...rest }) => {
|
|
519
|
+
const body = { prompt, task: "edit", reference_video_urls: [video_url], model: model ?? "corent-seedance-2.5", ...rest };
|
|
520
|
+
checkVideoRules(body);
|
|
521
|
+
return corent("/v1/videos/generate", {
|
|
522
|
+
method: "POST",
|
|
523
|
+
headers: { "Idempotency-Key": idempotencyKey() },
|
|
524
|
+
body: JSON.stringify(body),
|
|
525
|
+
});
|
|
526
|
+
}));
|
|
527
|
+
server.registerTool("extend_video", {
|
|
528
|
+
title: "Extend video",
|
|
529
|
+
_meta: toolMeta("Extending video", "Video extension started", true),
|
|
530
|
+
description: "Continue an existing clip (@Video 1) forward or backward, with a prompt for what happens next or before. Keeps the source shape; duration_s is the added length. Asynchronous: returns a job id; poll get_job until completed. Runs on corent-seedance-2.5. Pair with return_last_frame to chain shots.",
|
|
531
|
+
inputSchema: {
|
|
532
|
+
prompt: z.string().min(1).describe("What happens in the new footage. The direction line is added for you."),
|
|
533
|
+
video_url: z.string().url().describe("Public https URL of the clip to continue (becomes @Video 1)."),
|
|
534
|
+
direction: z
|
|
535
|
+
.enum(["forward", "backward"])
|
|
536
|
+
.optional()
|
|
537
|
+
.describe("forward (default) adds footage after the clip; backward adds what happened before it."),
|
|
538
|
+
duration_s: z.number().int().min(1).max(30).optional().describe("Seconds of NEW footage to add."),
|
|
539
|
+
...editExtendShared,
|
|
540
|
+
},
|
|
541
|
+
outputSchema: jobOutput,
|
|
542
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
543
|
+
}, wrap(async ({ prompt, video_url, direction, model, ...rest }) => {
|
|
544
|
+
const body = {
|
|
545
|
+
prompt: `Extend @Video 1 ${direction ?? "forward"}. ${prompt}`,
|
|
546
|
+
task: "extend",
|
|
547
|
+
reference_video_urls: [video_url],
|
|
548
|
+
model: model ?? "corent-seedance-2.5",
|
|
549
|
+
...rest,
|
|
550
|
+
};
|
|
551
|
+
checkVideoRules(body);
|
|
552
|
+
return corent("/v1/videos/generate", {
|
|
553
|
+
method: "POST",
|
|
554
|
+
headers: { "Idempotency-Key": idempotencyKey() },
|
|
555
|
+
body: JSON.stringify(body),
|
|
556
|
+
});
|
|
557
|
+
}));
|
|
395
558
|
server.registerTool("generate_speech", {
|
|
396
559
|
title: "Generate speech (voice)",
|
|
397
560
|
_meta: toolMeta("Generating speech", "Speech ready"),
|
|
@@ -644,22 +807,31 @@ export function createCorentServer(config = {}) {
|
|
|
644
807
|
.array(z.object({
|
|
645
808
|
prompt: z.string(),
|
|
646
809
|
tier: z.enum(TIERS).optional(),
|
|
810
|
+
model: z.string().optional().describe("Pin a model, e.g. corent-seedance-2.5 for reference/edit/extend items."),
|
|
647
811
|
style: z.enum(IMAGE_STYLES).optional(),
|
|
648
|
-
aspect_ratio: z.enum(
|
|
812
|
+
aspect_ratio: z.enum(VIDEO_ASPECTS).optional(),
|
|
649
813
|
duration_s: z.number().int().min(1).max(30).optional(),
|
|
650
814
|
resolution: z.enum(["720p", "1080p", "4k"]).optional(),
|
|
815
|
+
image_url: z.string().url().optional(),
|
|
816
|
+
end_image_url: z.string().url().optional(),
|
|
817
|
+
audio: z.boolean().optional(),
|
|
818
|
+
seed: z.number().int().min(0).optional(),
|
|
819
|
+
...videoReferenceFields,
|
|
651
820
|
}))
|
|
652
821
|
.min(1)
|
|
653
822
|
.max(50)
|
|
654
|
-
.describe("1 to 50 video requests"),
|
|
823
|
+
.describe("1 to 50 video requests; each item takes the same reference fields as generate_video"),
|
|
655
824
|
},
|
|
656
825
|
outputSchema: batchOutput,
|
|
657
826
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
658
|
-
}, wrap(async ({ items }) =>
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
827
|
+
}, wrap(async ({ items }) => {
|
|
828
|
+
items.forEach((item, i) => checkVideoRules(item, `items[${i}]: `));
|
|
829
|
+
return corent("/v1/videos/generate/batch", {
|
|
830
|
+
method: "POST",
|
|
831
|
+
headers: { "Idempotency-Key": idempotencyKey() },
|
|
832
|
+
body: JSON.stringify({ items }),
|
|
833
|
+
});
|
|
834
|
+
}));
|
|
663
835
|
server.registerTool("get_batch", {
|
|
664
836
|
title: "Check batch progress",
|
|
665
837
|
_meta: toolMeta("Checking batch", "Batch checked"),
|
package/package.json
CHANGED