corent-mcp 0.9.0 → 0.10.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 +36 -3
- package/dist/prompts.js +33 -2
- package/dist/server.js +157 -5
- package/dist/widget-html.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,11 @@ Give any AI agent the ability to generate images, videos, voice, and text throug
|
|
|
7
7
|
| Tool | What it does |
|
|
8
8
|
|---|---|
|
|
9
9
|
| `plan` | Describe a request in plain language; Corent returns the plan (image vs video, tier, settings) + cost estimate, without generating |
|
|
10
|
-
| `create` | Describe what you want + a spend ceiling; Corent decides everything and generates
|
|
10
|
+
| `create` | Describe what you want + a spend ceiling; Corent decides everything and generates one image or one clip (the "zero decisions" path) |
|
|
11
|
+
| `plan_film` | One sentence → a film plan: title, characters, every scene with its dialogue, and the price. Free, kept for 24 hours |
|
|
12
|
+
| `make_film` | Make the planned film, capped by `max_cost_cents`. Returns a `film_id`; about 15 to 30 minutes |
|
|
13
|
+
| `get_film` | The film's stage and, when done, `video_url`: one finished video with sound |
|
|
14
|
+
| `cancel_film` | Stop a film that has not finished. A cancelled film is not billed |
|
|
11
15
|
| `generate_image` | Text → image, synchronous, returns a permanent URL. Takes `reference_image_urls` for character and product consistency |
|
|
12
16
|
| `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
17
|
| `edit_video` | Change something in a finished clip (add, remove, replace); keeps its length and shape |
|
|
@@ -37,10 +41,10 @@ as a ChatGPT app as well as a Claude connector.
|
|
|
37
41
|
|
|
38
42
|
## Prompts (recipes)
|
|
39
43
|
|
|
40
|
-
The server also registers
|
|
44
|
+
The server also registers six MCP prompts. Each is a step list over the
|
|
41
45
|
tools above: plan, confirm the budget with the user, stills with references,
|
|
42
46
|
clips, narration, poll. Hosts that support prompts show them as slash
|
|
43
|
-
commands; the same
|
|
47
|
+
commands; the same six ship as markdown in [`skills/`](skills/) for
|
|
44
48
|
publication as a skills repo.
|
|
45
49
|
|
|
46
50
|
| Prompt | Arguments | What it makes |
|
|
@@ -50,6 +54,7 @@ publication as a skills repo.
|
|
|
50
54
|
| `storyboard-to-clip` | `storyboard`, `style`, `budget_usd` | Numbered storyboard to stills, clips chained through last frames, and narration |
|
|
51
55
|
| `ad-variations` | `concept`, `count`, `product_url` | Many versions of one ad visual, in a single batch |
|
|
52
56
|
| `faceless-explainer` | `topic`, `duration_s`, `budget_usd` | Narrated explainer with no presenter |
|
|
57
|
+
| `make-a-film` | `brief`, `duration_s`, `budget_usd` | One sentence to one finished film with characters, dialogue and sound |
|
|
53
58
|
|
|
54
59
|
Prices the recipes quote before spending: image premium ~7c, video premium
|
|
55
60
|
~52c per clip, pro ~84c per clip, speech ~25-30c per started block of 1,000
|
|
@@ -61,6 +66,34 @@ image-vs-video, the tier, aspect ratio, and duration, the agent never manages
|
|
|
61
66
|
models. `create` enforces a per-call spend ceiling so an autonomous agent can't
|
|
62
67
|
overspend.
|
|
63
68
|
|
|
69
|
+
### Films: one sentence to a finished video
|
|
70
|
+
|
|
71
|
+
Describe a film in one sentence ("a 50 second parody where 5 founders start a
|
|
72
|
+
startup, know nothing, and the AI part is easy because they used Corent") and
|
|
73
|
+
Corent makes the whole thing. It writes the script, creates the characters
|
|
74
|
+
once, shoots every scene with those same characters speaking their lines,
|
|
75
|
+
checks each scene and reshoots a bad one once, then joins everything into one
|
|
76
|
+
video with sound.
|
|
77
|
+
|
|
78
|
+
1. `plan_film` with the `brief` (and optionally `duration_s` 10 to 120,
|
|
79
|
+
`aspect_ratio`, `resolution`, `language`). Free. Returns the title, the
|
|
80
|
+
characters, every scene with its dialogue, `estimated_cost_cents` and a
|
|
81
|
+
range. Show it to the user and get a yes.
|
|
82
|
+
2. `make_film` with `plan_id` and `max_cost_cents` a little above the
|
|
83
|
+
estimate. It returns a `film_id` at once.
|
|
84
|
+
3. `get_film` every 60 seconds: `queued`, `writing`, `casting`, `shooting`,
|
|
85
|
+
`checking`, `editing`, then `completed` with `video_url`. In hosts that
|
|
86
|
+
support MCP Apps the finished film plays inline, like `generate_video`.
|
|
87
|
+
|
|
88
|
+
The price is shown before anything is paid, the charge never goes above
|
|
89
|
+
`max_cost_cents`, and a film that fails or is cancelled (`cancel_film`) is not
|
|
90
|
+
billed. A 50 second 720p film with five characters costs roughly $10 to $20
|
|
91
|
+
and takes about 15 to 30 minutes.
|
|
92
|
+
|
|
93
|
+
`plan` and `create` send a story with several characters, or anything over
|
|
94
|
+
about 30 seconds, to `plan_film`: `create` makes one image or one clip, never
|
|
95
|
+
a film.
|
|
96
|
+
|
|
64
97
|
### When the user names a specific model
|
|
65
98
|
|
|
66
99
|
All four `generate_*` tools also take an optional **`model`**: pin an exact
|
package/dist/prompts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP prompts:
|
|
2
|
+
* MCP prompts: six production recipes built ONLY from the tools this server
|
|
3
3
|
* already exposes. Each is a step list an assistant follows with the user:
|
|
4
4
|
* plan, confirm the budget, stills with references, clips, narration, poll.
|
|
5
5
|
*
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* here now so every tool description stays under 400 characters (ChatGPT and
|
|
8
8
|
* other hosts truncate or reject long ones). The same five recipes ship as
|
|
9
9
|
* markdown in mcp/skills/<name>/SKILL.md for publication as a skills repo.
|
|
10
|
+
* make-a-film is the one that hands the whole job to Corent: plan_film,
|
|
11
|
+
* confirm, make_film, get_film.
|
|
10
12
|
*/
|
|
11
13
|
import { z } from "zod";
|
|
12
14
|
// Honest per-unit prices, in the same words the tool descriptions use. Quote
|
|
@@ -23,12 +25,39 @@ const POLLING = "Video is asynchronous: each generate_video returns a job id. Po
|
|
|
23
25
|
// @Audio 1 in the order given. The one rule every recipe has to repeat:
|
|
24
26
|
export const REAL_FACE_RULE = "The supplier rejects photos of REAL people as references; characters generated inside Corent are accepted, " +
|
|
25
27
|
"so build the person in generate_image first and reference that.";
|
|
26
|
-
const DELIVERY = "
|
|
28
|
+
const DELIVERY = "These tools do not join clips together. Deliver every still, clip and narration URL in order with its cost, " +
|
|
27
29
|
"so the user can assemble them in any editor. list_media can find these again later by prompt keyword.";
|
|
30
|
+
// For the recipes that build a story shot by shot: when the user really wants
|
|
31
|
+
// one finished film, Corent can make the whole thing.
|
|
32
|
+
const FULL_STORY = "0. FULL STORY? If the user wants one finished film with several characters, spoken dialogue and sound, rather " +
|
|
33
|
+
"than separate stills and clips, use the make-a-film recipe instead: plan_film, show the plan and price, then " +
|
|
34
|
+
"make_film. Corent writes, casts, shoots and joins it.";
|
|
35
|
+
// The film recipe's own numbers, in the same words the docs use.
|
|
36
|
+
export const FILM_NOTE = "A film is priced before anything is paid: plan_film is free and returns estimated_cost_cents and a range. " +
|
|
37
|
+
"As a guide, a 50 second 720p film with five characters costs roughly $10 to $20 and takes about 15 to 30 minutes. " +
|
|
38
|
+
"The charge never goes above max_cost_cents, and a film that fails or is cancelled is not billed.";
|
|
28
39
|
function user(text) {
|
|
29
40
|
return { messages: [{ role: "user", content: { type: "text", text } }] };
|
|
30
41
|
}
|
|
31
42
|
export function registerPrompts(server) {
|
|
43
|
+
server.registerPrompt("make-a-film", {
|
|
44
|
+
title: "Make a film",
|
|
45
|
+
description: "One sentence to one finished film with sound: Corent writes the script, creates the characters, shoots every scene with dialogue, checks quality and joins it. Plan and price first, then confirm.",
|
|
46
|
+
argsSchema: {
|
|
47
|
+
brief: z.string().describe("The film in one sentence, in the user's words"),
|
|
48
|
+
duration_s: z.string().optional().describe("Target length in seconds, 10 to 120 (default 30)"),
|
|
49
|
+
budget_usd: z.string().optional().describe("Spend ceiling in US dollars, e.g. 20"),
|
|
50
|
+
},
|
|
51
|
+
}, ({ brief, duration_s, budget_usd }) => user([
|
|
52
|
+
`Make a film. Brief: ${brief}. Length: ${duration_s ?? "30"} seconds. Budget: $${budget_usd ?? "ask the user"}.`,
|
|
53
|
+
"",
|
|
54
|
+
`1. PLAN. Call plan_film with the brief, duration_s ${duration_s ?? "30"}, and aspect_ratio 9:16 if the user wants a vertical video (16:9 otherwise). It is free and spends nothing.`,
|
|
55
|
+
"2. SHOW. Give the user the title and logline, each character in one line, and each scene in one line with its dialogue. Then the total length and the price: estimated_cost_cents as dollars, plus the range. Say the plan is kept for 24 hours.",
|
|
56
|
+
`3. CONFIRM. ${FILM_NOTE} Call get_balance: available_cents must cover the price. Do NOT call make_film until the user says yes. If they want changes, or the price is over their budget, call plan_film again (shorter, fewer characters, or 720p) and show the new plan.`,
|
|
57
|
+
"4. MAKE. Call make_film with plan_id and max_cost_cents a little above estimated_cost_cents (about 10 percent more, rounded up), never above the user's budget. Keep the film_id.",
|
|
58
|
+
"5. POLL. Call get_film every 60 seconds. After each check tell the user the stage in one plain sentence, for example: writing the script, creating the characters, shooting scene 3 of 8, checking quality, editing the film. Do not paste the raw result. If the user changes their mind, cancel_film stops it and they are not billed.",
|
|
59
|
+
"6. DELIVER. When status is completed, give the user video_url, the title, the length and cost_cents as dollars. If quality_notes has anything, sum it up in one plain sentence. If status is failed or cancelled, say they were not billed, give error_message in plain words, and offer to plan it again.",
|
|
60
|
+
].join("\n")));
|
|
32
61
|
server.registerPrompt("product-ugc-reel", {
|
|
33
62
|
title: "Product UGC reel",
|
|
34
63
|
description: "A vertical creator-style reel for one product: 3-5 stills that keep the product consistent, one clip per still, and a single narration track.",
|
|
@@ -59,6 +88,7 @@ export function registerPrompts(server) {
|
|
|
59
88
|
}, ({ character, scenes, budget_usd }) => user([
|
|
60
89
|
`Make a consistent character series. Character: ${character}. Scenes: ${scenes}. Budget: $${budget_usd ?? "ask the user"}.`,
|
|
61
90
|
"",
|
|
91
|
+
FULL_STORY,
|
|
62
92
|
"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.",
|
|
63
93
|
`2. COST. One image per scene at ~7c (premium). ${BUDGET_RULE}`,
|
|
64
94
|
"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).",
|
|
@@ -78,6 +108,7 @@ export function registerPrompts(server) {
|
|
|
78
108
|
`Turn this storyboard into clips. Style: ${style ?? "cinematic"}. Budget: $${budget_usd ?? "ask the user"}.`,
|
|
79
109
|
storyboard,
|
|
80
110
|
"",
|
|
111
|
+
FULL_STORY,
|
|
81
112
|
"1. PLAN. Restate the shots as a table: number, frame, movement, seconds (5 each unless told otherwise), narration line if any.",
|
|
82
113
|
`2. COST. One still (~7c) plus one clip (~52c premium, ~84c pro) per shot, plus ~30c if there is narration. ${BUDGET_RULE}`,
|
|
83
114
|
"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.",
|
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.10.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.
|
|
@@ -84,6 +84,8 @@ function errorCode(err) {
|
|
|
84
84
|
return "invalid_request";
|
|
85
85
|
if (err.status === 409 || detail.includes("estimate"))
|
|
86
86
|
return "estimate_exceeds_ceiling";
|
|
87
|
+
if (err.status === 400 && detail.includes("max_cost_cents"))
|
|
88
|
+
return "estimate_exceeds_ceiling";
|
|
87
89
|
if (err.status === 429)
|
|
88
90
|
return "rate_limited";
|
|
89
91
|
if (err.status === 503)
|
|
@@ -170,6 +172,20 @@ export function createCorentServer(config = {}) {
|
|
|
170
172
|
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
171
173
|
};
|
|
172
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* POST /v1/intent answers task_type "film" for anything one clip cannot
|
|
177
|
+
* deliver (longer than one take, or several characters telling a story), and
|
|
178
|
+
* /v1/intent/execute will not make a single clip for it. Say so in this
|
|
179
|
+
* server's own tool names, so the assistant goes to plan_film.
|
|
180
|
+
*/
|
|
181
|
+
function pointFilmsToPlanFilm(res) {
|
|
182
|
+
if (res && typeof res === "object" && res.plan?.task_type === "film") {
|
|
183
|
+
res.next_step = "plan_film";
|
|
184
|
+
const hint = "This is a film: call plan_film with the same request, show the user the plan and price, then make_film.";
|
|
185
|
+
res.message = typeof res.message === "string" && res.message ? `${res.message} ${hint}` : hint;
|
|
186
|
+
}
|
|
187
|
+
return res;
|
|
188
|
+
}
|
|
173
189
|
function wrap(fn) {
|
|
174
190
|
return async (args) => {
|
|
175
191
|
try {
|
|
@@ -887,7 +903,7 @@ export function createCorentServer(config = {}) {
|
|
|
887
903
|
server.registerTool("plan", {
|
|
888
904
|
title: "Plan (cost preview, no generation)",
|
|
889
905
|
_meta: toolMeta("Planning", "Plan ready"),
|
|
890
|
-
description: "Preview how Corent would handle a plain-language request without generating
|
|
906
|
+
description: "Preview how Corent would handle a plain-language request, without generating: image vs video, tier, shape, duration and an estimated cost. Image and video only; speech is generate_speech. A story with several characters or anything over about 30 seconds is a film: use plan_film. Impossible asks come back with can_fulfill false.",
|
|
891
907
|
inputSchema: {
|
|
892
908
|
intent: z
|
|
893
909
|
.string()
|
|
@@ -896,13 +912,14 @@ export function createCorentServer(config = {}) {
|
|
|
896
912
|
outputSchema: {
|
|
897
913
|
plan: z.object({ can_fulfill: z.boolean().optional() }).passthrough().optional(),
|
|
898
914
|
estimated_cost_cents: z.number().nullable().optional(),
|
|
915
|
+
next_step: z.string().nullable().optional(),
|
|
899
916
|
},
|
|
900
917
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
901
|
-
}, wrap(async ({ intent }) => corent("/v1/intent", { method: "POST", body: JSON.stringify({ intent }) })));
|
|
918
|
+
}, wrap(async ({ intent }) => pointFilmsToPlanFilm(await corent("/v1/intent", { method: "POST", body: JSON.stringify({ intent }) }))));
|
|
902
919
|
server.registerTool("create", {
|
|
903
920
|
title: "Create (plan + generate, budget-capped)",
|
|
904
921
|
_meta: toolMeta("Creating", "Created", true),
|
|
905
|
-
description: "Describe
|
|
922
|
+
description: "Describe one image or one clip in plain language; Corent picks type, tier, shape and duration and generates it. max_cost_cents is a required ceiling: above it nothing is generated. Videos return a job id for get_job. Failed generations are never billed. A story with several characters or anything over about 30 seconds is a film: use plan_film, then make_film.",
|
|
906
923
|
inputSchema: {
|
|
907
924
|
intent: z.string().describe("What you want, in natural language"),
|
|
908
925
|
max_cost_cents: z
|
|
@@ -917,6 +934,7 @@ export function createCorentServer(config = {}) {
|
|
|
917
934
|
actual_cost_cents: z.number().nullable().optional(),
|
|
918
935
|
result: z.object({}).passthrough().nullable().optional(),
|
|
919
936
|
message: z.string().nullable().optional(),
|
|
937
|
+
next_step: z.string().nullable().optional(),
|
|
920
938
|
},
|
|
921
939
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
922
940
|
}, wrap(async ({ intent, max_cost_cents }) => {
|
|
@@ -930,8 +948,142 @@ export function createCorentServer(config = {}) {
|
|
|
930
948
|
if (res && typeof res.message === "string") {
|
|
931
949
|
res.message = res.message.replace("confirm_estimated_cost_cents_up_to", "max_cost_cents");
|
|
932
950
|
}
|
|
933
|
-
return res;
|
|
951
|
+
return pointFilmsToPlanFilm(res);
|
|
952
|
+
}));
|
|
953
|
+
// --- Films (v0.10.0): one sentence in, one finished video with sound out.
|
|
954
|
+
// Corent writes the script, creates the characters once, shoots every scene
|
|
955
|
+
// with them, checks and reshoots, and joins the scenes. plan_film is free and
|
|
956
|
+
// shows the price; make_film spends, capped by max_cost_cents; a failed or
|
|
957
|
+
// cancelled film is not billed. ---
|
|
958
|
+
const FILM_ASPECTS = ["16:9", "9:16", "1:1"];
|
|
959
|
+
const FILM_RESOLUTIONS = ["720p", "1080p"];
|
|
960
|
+
const filmOptions = {
|
|
961
|
+
duration_s: z.number().int().min(10).max(120).optional().describe("Length of the finished film in seconds, 10 to 120 (default 30)."),
|
|
962
|
+
aspect_ratio: z.enum(FILM_ASPECTS).optional().describe("16:9 (default), 9:16 for vertical, or 1:1."),
|
|
963
|
+
resolution: z.enum(FILM_RESOLUTIONS).optional().describe("720p (default) or 1080p; 1080p costs more."),
|
|
964
|
+
language: z.string().optional().describe('Language the characters speak, e.g. "en" or "es". Omit to follow the brief.'),
|
|
965
|
+
customer_id: z.string().min(1).max(128).optional().describe("Your own id for the end customer this film is for."),
|
|
966
|
+
};
|
|
967
|
+
const filmCharacter = z
|
|
968
|
+
.object({
|
|
969
|
+
id: z.string().optional(),
|
|
970
|
+
name: z.string().nullable().optional(),
|
|
971
|
+
description: z.string().nullable().optional(),
|
|
972
|
+
image_url: z.string().nullable().optional(),
|
|
973
|
+
})
|
|
974
|
+
.passthrough();
|
|
975
|
+
const filmScene = z
|
|
976
|
+
.object({
|
|
977
|
+
index: z.number().optional(),
|
|
978
|
+
duration_s: z.number().nullable().optional(),
|
|
979
|
+
setting: z.string().nullable().optional(),
|
|
980
|
+
action: z.string().nullable().optional(),
|
|
981
|
+
camera: z.string().nullable().optional(),
|
|
982
|
+
characters: z.array(z.string()).nullable().optional(),
|
|
983
|
+
dialogue: z.array(z.object({ character_id: z.string().optional(), line: z.string().optional() }).passthrough()).nullable().optional(),
|
|
984
|
+
status: z.string().nullable().optional(),
|
|
985
|
+
video_url: z.string().nullable().optional(),
|
|
986
|
+
last_frame_url: z.string().nullable().optional(),
|
|
987
|
+
reshoots: z.number().nullable().optional(),
|
|
988
|
+
})
|
|
989
|
+
.passthrough();
|
|
990
|
+
const filmOutput = {
|
|
991
|
+
film_id: z.string().optional(),
|
|
992
|
+
status: z.string().optional(),
|
|
993
|
+
progress_percent: z.number().nullable().optional(),
|
|
994
|
+
stage_detail: z.string().nullable().optional(),
|
|
995
|
+
title: z.string().nullable().optional(),
|
|
996
|
+
characters: z.array(filmCharacter).nullable().optional(),
|
|
997
|
+
scenes: z.array(filmScene).nullable().optional(),
|
|
998
|
+
video_url: z.string().nullable().optional(),
|
|
999
|
+
duration_s: z.number().nullable().optional(),
|
|
1000
|
+
estimated_cost_cents: z.number().nullable().optional(),
|
|
1001
|
+
cost_cents: z.number().nullable().optional(),
|
|
1002
|
+
billed: z.boolean().nullable().optional(),
|
|
1003
|
+
quality_notes: z.array(z.string()).nullable().optional(),
|
|
1004
|
+
error: z.string().nullable().optional(),
|
|
1005
|
+
error_message: z.string().nullable().optional(),
|
|
1006
|
+
created_at: z.string().nullable().optional(),
|
|
1007
|
+
completed_at: z.string().nullable().optional(),
|
|
1008
|
+
customer_id: z.string().nullable().optional(),
|
|
1009
|
+
};
|
|
1010
|
+
server.registerTool("plan_film", {
|
|
1011
|
+
title: "Plan a film (script and price, free)",
|
|
1012
|
+
_meta: toolMeta("Writing the film plan", "Film plan ready"),
|
|
1013
|
+
description: "Turn a one-sentence film idea into a plan without spending anything: title, characters, every scene with its dialogue, total length and the price range. The plan lasts 24 hours. Show the user the plan and the price and get a clear yes before make_film. Use it for any story with several characters or anything over about 30 seconds.",
|
|
1014
|
+
inputSchema: {
|
|
1015
|
+
brief: z
|
|
1016
|
+
.string()
|
|
1017
|
+
.min(1)
|
|
1018
|
+
.max(2000)
|
|
1019
|
+
.describe("The film in the user's own words, e.g. 'a 50 second parody where 5 founders start a startup and know nothing'."),
|
|
1020
|
+
...filmOptions,
|
|
1021
|
+
},
|
|
1022
|
+
outputSchema: {
|
|
1023
|
+
plan_id: z.string().optional(),
|
|
1024
|
+
title: z.string().nullable().optional(),
|
|
1025
|
+
logline: z.string().nullable().optional(),
|
|
1026
|
+
style: z.string().nullable().optional(),
|
|
1027
|
+
characters: z.array(filmCharacter).nullable().optional(),
|
|
1028
|
+
scenes: z.array(filmScene).nullable().optional(),
|
|
1029
|
+
total_duration_s: z.number().nullable().optional(),
|
|
1030
|
+
estimated_cost_cents: z.number().nullable().optional(),
|
|
1031
|
+
estimated_cost_range: z.object({ min: z.number().optional(), max: z.number().optional() }).passthrough().nullable().optional(),
|
|
1032
|
+
expires_at: z.string().nullable().optional(),
|
|
1033
|
+
},
|
|
1034
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
1035
|
+
}, wrap(async (args) => corent("/v1/films/plan", { method: "POST", body: JSON.stringify(args) })));
|
|
1036
|
+
server.registerTool("make_film", {
|
|
1037
|
+
title: "Make a film (budget-capped)",
|
|
1038
|
+
_meta: toolMeta("Starting film", "Film started", true),
|
|
1039
|
+
description: "Start the film the user approved: pass plan_id from plan_film (or a brief), and max_cost_cents a little above the estimate. The charge never goes above max_cost_cents, and a failed or cancelled film is not billed. Returns film_id right away; a film takes about 15 to 30 minutes, so check get_film every 60 seconds.",
|
|
1040
|
+
inputSchema: {
|
|
1041
|
+
plan_id: z.string().optional().describe("The plan_id from plan_film. Preferred: it is the plan and price the user saw."),
|
|
1042
|
+
brief: z.string().min(1).max(2000).optional().describe("Only when there is no plan_id: the film idea, planned and made in one go."),
|
|
1043
|
+
...filmOptions,
|
|
1044
|
+
max_cost_cents: z
|
|
1045
|
+
.number()
|
|
1046
|
+
.int()
|
|
1047
|
+
.min(1)
|
|
1048
|
+
.describe("Spend ceiling in cents. Refused up front if the estimate is higher; the charge never goes above it."),
|
|
1049
|
+
webhook_url: z.string().url().optional().describe("Optional https URL that receives the finished film."),
|
|
1050
|
+
webhook_secret: z.string().optional().describe("Optional secret that signs the webhook delivery."),
|
|
1051
|
+
},
|
|
1052
|
+
outputSchema: filmOutput,
|
|
1053
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
1054
|
+
}, wrap(async ({ plan_id, brief, duration_s, aspect_ratio, resolution, language, ...rest }) => {
|
|
1055
|
+
if (!plan_id && !brief) {
|
|
1056
|
+
throw new CorentApiError(422, { message: "Pass plan_id from plan_film, or a brief." });
|
|
1057
|
+
}
|
|
1058
|
+
const options = { duration_s, aspect_ratio, resolution, language };
|
|
1059
|
+
if (plan_id && (brief || Object.values(options).some((v) => v !== undefined))) {
|
|
1060
|
+
throw new CorentApiError(422, {
|
|
1061
|
+
message: "A plan already fixes the story, length, shape and resolution: pass plan_id on its own, or call plan_film again with the new options.",
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
const body = plan_id ? { plan_id, ...rest } : { brief, ...options, ...rest };
|
|
1065
|
+
return corent("/v1/films", {
|
|
1066
|
+
method: "POST",
|
|
1067
|
+
headers: { "Idempotency-Key": idempotencyKey() },
|
|
1068
|
+
body: JSON.stringify(body),
|
|
1069
|
+
});
|
|
934
1070
|
}));
|
|
1071
|
+
server.registerTool("get_film", {
|
|
1072
|
+
title: "Check film progress",
|
|
1073
|
+
_meta: toolMeta("Checking film", "Film checked", true),
|
|
1074
|
+
description: "Progress of a film from make_film: the stage (queued, writing, casting, shooting, checking, editing, completed, failed, cancelled), progress_percent, and each scene. When completed it carries video_url, the finished film with sound, and cost_cents. Read-only and free. Tell the user the stage in one plain sentence.",
|
|
1075
|
+
inputSchema: { film_id: z.string().min(1).describe("The film_id returned by make_film") },
|
|
1076
|
+
outputSchema: filmOutput,
|
|
1077
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1078
|
+
}, wrap(async ({ film_id }) => corent(`/v1/films/${encodeURIComponent(film_id)}`)));
|
|
1079
|
+
server.registerTool("cancel_film", {
|
|
1080
|
+
title: "Cancel a film",
|
|
1081
|
+
_meta: toolMeta("Cancelling film", "Film cancelled"),
|
|
1082
|
+
description: "Stop a film that has not finished. A cancelled film is not billed. Use it as soon as the user says they did not mean to start it or want to change the story. A film that already finished stays completed.",
|
|
1083
|
+
inputSchema: { film_id: z.string().min(1).describe("The film_id to cancel") },
|
|
1084
|
+
outputSchema: filmOutput,
|
|
1085
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
1086
|
+
}, wrap(async ({ film_id }) => corent(`/v1/films/${encodeURIComponent(film_id)}/cancel`, { method: "POST" })));
|
|
935
1087
|
// --- Image tools (v0.8.0): edit what already exists instead of generating
|
|
936
1088
|
// anew. All three are synchronous, flat-priced, and replay-safe under an
|
|
937
1089
|
// Idempotency-Key exactly like generate_image. ---
|