@kolbo/mcp 1.77.1 → 1.77.3
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/package.json
CHANGED
|
@@ -110,6 +110,7 @@ These elevate rich cinematic / reference-anchored sequences. For a short, tight,
|
|
|
110
110
|
## Dialogue & expression
|
|
111
111
|
|
|
112
112
|
- Dialogue goes in quotes and may be in ANY language (Hebrew included). For silent tension, deliver it as expression, not speech: `He does not speak. His expression clearly says: "…"`.
|
|
113
|
+
- **Seedance PERFORMS quoted dialogue natively** — synced voices, lip movement, and room tone come out of the video model itself. Never route scene dialogue through TTS (`generate_speech`) or `generate_lipsync`; write each line in quotes inside its shot beat (`DANIEL says: "…"`) and generate once.
|
|
113
114
|
|
|
114
115
|
## Content tone
|
|
115
116
|
|
|
@@ -9,7 +9,7 @@ Load this file when the user wants a **Seedance 2.5** video (they said "2.5" / "
|
|
|
9
9
|
|
|
10
10
|
**Kolbo MCP routing:** `generate_video` or `generate_elements` (refs / Visual DNA / first-last). Run `list_models({ type: "text_to_video" })` and pick the Seedance 2.5 variant by name.
|
|
11
11
|
|
|
12
|
-
**Audio:** Seedance 2.5 still emits real synced audio. `list_models` may show `sound_generation_type: none` because there is no in-app toggle (`sound_baked_in: true`). Do not tell the user the model is silent.
|
|
12
|
+
**Audio:** Seedance 2.5 still emits real synced audio. `list_models` may show `sound_generation_type: none` because there is no in-app toggle (`sound_baked_in: true`). Do not tell the user the model is silent. Quoted dialogue in the prompt is PERFORMED — synced voices, lip movement, room tone — so scene dialogue never goes through `generate_speech` or `generate_lipsync`; write the lines in quotes inside their shot beats.
|
|
13
13
|
|
|
14
14
|
## What's NEW in 2.5 (verified — never hedge)
|
|
15
15
|
|
|
@@ -668,15 +668,21 @@ function renderAudio(sc, urls) {
|
|
|
668
668
|
var titleBase = track.title || sc.title || (TOOL_TITLES[sc.tool] || 'Audio');
|
|
669
669
|
var title = titleBase + (urls.length > 1 ? ' — Track ' + (i + 1) : '');
|
|
670
670
|
var duration = track.duration != null ? track.duration : sc.duration;
|
|
671
|
-
|
|
671
|
+
// The voice's own portrait is the artwork for speech — a generic note glyph
|
|
672
|
+
// told the user nothing about the one thing that defines the take.
|
|
673
|
+
var artwork = track.thumbnail_url || sc.thumbnail_url || sc.voice_thumbnail;
|
|
674
|
+
var placeholder = sc.tool === 'generate_speech' ? ICONS.mic : ICONS.audio;
|
|
672
675
|
return '<div class="k-audio-row k-generated-audio">' +
|
|
673
|
-
(artwork ? '<img class="k-audio-art" src="' + esc(artwork) + '" alt="" loading="lazy">' :
|
|
674
|
-
'<div class="k-audio-art k-audio-placeholder">' +
|
|
676
|
+
(artwork ? '<img class="k-audio-art" src="' + esc(artwork) + '" alt="" loading="lazy" onerror="this.style.display=\\'none\\'">' :
|
|
677
|
+
'<div class="k-audio-art k-audio-placeholder">' + placeholder + '</div>') +
|
|
675
678
|
'<div class="k-audio-meta"><div class="k-audio-title">' + esc(title) + '</div>' +
|
|
676
|
-
//
|
|
677
|
-
//
|
|
678
|
-
|
|
679
|
-
|
|
679
|
+
// Voice FIRST where there is one — on a speech row, who is speaking is the
|
|
680
|
+
// thing that defines the take; the engine is secondary. Resolved names
|
|
681
|
+
// only: a per-track model field is the raw id, and every track in one
|
|
682
|
+
// generation came from the same model anyway.
|
|
683
|
+
'<div class="k-audio-sub">' +
|
|
684
|
+
[voiceLabel(sc), modelLabel(sc) || track.model, duration ? fmtDur(duration) : '']
|
|
685
|
+
.filter(Boolean).map(esc).join(' · ') + '</div></div>' +
|
|
680
686
|
'<button class="k-btn k-audio-download" data-audio-download="' + esc(u) +
|
|
681
687
|
'" aria-label="Download ' + esc(title) + '">' + ICONS.download + ' Download</button>' +
|
|
682
688
|
'<audio class="k-audio-player" src="' + esc(u) + '" controls preload="none" aria-label="Play ' +
|
package/src/tools/_shared.js
CHANGED
|
@@ -611,6 +611,7 @@ async function uiGenerating(p) {
|
|
|
611
611
|
? { failed_submissions: p.failed_submissions } : {}),
|
|
612
612
|
...(p.warning ? { _warning: p.warning } : {}),
|
|
613
613
|
_widget_note: 'A live Kolbo widget is rendering this generation for the user (progress + final result + action buttons). Tell the user it is generating and the card above will update — do NOT poll in a loop. If you need the output URLs (e.g. for a follow-up edit or a report), call get_generation_status ONCE with wait=true — it blocks until done. Tracking several generations? Pass ALL their ids in generation_ids in that one call.',
|
|
614
|
+
_paid_note: 'This generation is RUNNING and the user is paying for it. If you now realize the tool, model, or parameters were wrong, call cancel_generation with this generation_id FIRST, then start the replacement — never leave a wrong generation running alongside its retry (the user gets two cards and two charges).',
|
|
614
615
|
}, null, 2);
|
|
615
616
|
return uiResult(UI.generation, text, structured);
|
|
616
617
|
}
|
|
@@ -659,6 +660,14 @@ async function uiCompleted(p, textPayload, extraContent) {
|
|
|
659
660
|
// above which assume everything finished together. Only set when the
|
|
660
661
|
// caller actually has this shape; every existing caller is unaffected.
|
|
661
662
|
...(Array.isArray(p.items) ? { items: p.items } : {}),
|
|
663
|
+
// The voice, by name and portrait. uiGenerating has carried this since the
|
|
664
|
+
// chips were introduced; uiCompleted never did, so it silently dropped a
|
|
665
|
+
// resolved voice its caller had already looked up — every FINISHED speech
|
|
666
|
+
// card fell back to `settings.voice`, printing a raw ElevenLabs id where the
|
|
667
|
+
// name belongs and rendering the generic note placeholder instead of the
|
|
668
|
+
// voice's face. Exactly the "never show a raw id on a card" rule, broken on
|
|
669
|
+
// the one path the user actually ends up looking at.
|
|
670
|
+
...(p.voice ? { voice_name: p.voice.name, voice_thumbnail: p.voice.thumbnail } : {}),
|
|
662
671
|
// The RAW generation state, when the caller has one. `phase` above is
|
|
663
672
|
// hardcoded 'completed' — it means "this tool call finished", not "the
|
|
664
673
|
// generation finished" — so a status check on a still-running job looked
|
package/src/tools/generate.js
CHANGED
|
@@ -541,7 +541,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
541
541
|
// retired textToVideoGeneration path and was stale.
|
|
542
542
|
server.tool(
|
|
543
543
|
'generate_video',
|
|
544
|
-
'Generate a video from a text prompt using Kolbo AI. For SEVERAL different videos, pass all their prompts in `prompts` in ONE call (one combined widget) — never a series of separate calls. For animating an existing still image into motion, use generate_video_from_image instead. For a coordinated multi-scene video campaign, use generate_creative_director with workflow_type="video". Supports reference images (for style/composition guidance) and Visual DNA for character consistency. Returns the final video URL when complete.',
|
|
544
|
+
'Generate a video from a text prompt using Kolbo AI. For SEVERAL different videos, pass all their prompts in `prompts` in ONE call (one combined widget) — never a series of separate calls. For animating an existing still image into motion, use generate_video_from_image instead. For a coordinated multi-scene video campaign, use generate_creative_director with workflow_type="video". Supports reference images (for style/composition guidance) and Visual DNA for character consistency. ROUTE BEFORE CALLING: when reference images anchor IDENTITY (specific characters, a specific product, a location that must match) — especially 2+ of them — that is generate_elements, not this tool; reference_images here are loose style/composition hints. Decide the right tool FIRST: a mis-routed call still starts a PAID generation, and switching tools afterwards without cancel_generation leaves the user paying for both. Returns the final video URL when complete.',
|
|
545
545
|
{
|
|
546
546
|
prompt: z.string().optional().describe('Text description of the video to generate. Required unless `prompts` is provided.'),
|
|
547
547
|
prompts: promptsField('videos'),
|
|
@@ -855,11 +855,19 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
855
855
|
if (poll.timedOut) return poll.timedOut;
|
|
856
856
|
const result = poll.result;
|
|
857
857
|
|
|
858
|
+
// What ACTUALLY ran, not what was asked for. The status result reports the
|
|
859
|
+
// engine's own choice (`model: "google_tts"`, `voice: "he-IL-Chirp3-HD-Kore"`)
|
|
860
|
+
// and for an omitted model that is the ONLY place the real answer appears —
|
|
861
|
+
// the card was labelling those "Smart Select", which is not even a text-to-
|
|
862
|
+
// speech option, and naming the voice the caller typed rather than the one
|
|
863
|
+
// that spoke. Same resolution addDisplayNames does for the polling path.
|
|
864
|
+
const ranVoice = (await voiceInfo(client, result.result?.voice).catch(() => null)) || voiceRecord;
|
|
858
865
|
return uiCompleted({
|
|
859
|
-
tool: 'generate_speech', kind: 'audio', gen, client,
|
|
860
|
-
|
|
866
|
+
tool: 'generate_speech', kind: 'audio', gen, client, prompt: text,
|
|
867
|
+
model: result.result?.model || model,
|
|
868
|
+
voice: ranVoice,
|
|
861
869
|
settings: {
|
|
862
|
-
voice: voice || 'Rachel',
|
|
870
|
+
voice: (ranVoice && ranVoice.name) || voice || 'Rachel',
|
|
863
871
|
style: selected_style || emotion || style_instructions_preset_id || style_instructions,
|
|
864
872
|
speaking_speed,
|
|
865
873
|
language,
|
|
@@ -1181,10 +1189,10 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1181
1189
|
{
|
|
1182
1190
|
prompt: z.string().describe('Locked Intro prompt (Seedance/Elements): Total line, [GLOBAL LOOK], [CAST] with @ExactDNAName for every visual_dna_ids entry, [LOCATION], then SHOT N. Not SCENE CONTEXT/OPTICS/ACTION packs. Never substitute "the left man" or "Zohar\'s" for @Name.'),
|
|
1183
1191
|
model: z.string().optional().describe('Model identifier. If the user already named a family (Grok / Kling / Veo / Seedance / …), pass THAT family — never default to Seedance because Elements often uses it. Use list_models type="elements" for exact ids and elements_max_* caps. Do NOT omit (omitting = Smart Select).'),
|
|
1184
|
-
reference_images: z.array(z.string()).optional().describe('Array of image references (product shots, character references, etc.). Accepts a public URL (forwarded as-is,
|
|
1185
|
-
reference_videos: z.array(z.string()).optional().describe('Array of reference videos for models that accept video inputs. Accepts a public URL (forwarded as-is,
|
|
1186
|
-
reference_audio_urls: z.array(z.string()).optional().describe('Array of reference audio tracks for models that accept audio inputs. Accepts a public URL (forwarded as-is,
|
|
1187
|
-
audio_url: z.string().optional().describe('A single reference audio track — legacy form of reference_audio_urls. Accepts a public URL (forwarded as-is,
|
|
1192
|
+
reference_images: z.array(z.string()).optional().describe('Array of image references (product shots, character references, etc.). Accepts a public URL (forwarded as-is; if the API rejects an external URL as untrusted, it is auto-rehosted into the media library and retried once) OR an absolute local path, which is uploaded for you. **Cap: pass at most `elements_max_images` URLs from list_models for the chosen model — exceeding it is a deterministic 400.**'),
|
|
1193
|
+
reference_videos: z.array(z.string()).optional().describe('Array of reference videos for models that accept video inputs. Accepts a public URL (forwarded as-is; if the API rejects an external URL as untrusted, it is auto-rehosted into the media library and retried once) OR an absolute local path, which is uploaded for you. **Cap: pass at most `elements_max_videos` URLs from list_models — if the cap is 0 the model rejects videos.**'),
|
|
1194
|
+
reference_audio_urls: z.array(z.string()).optional().describe('Array of reference audio tracks for models that accept audio inputs. Accepts a public URL (forwarded as-is; if the API rejects an external URL as untrusted, it is auto-rehosted into the media library and retried once) OR an absolute local path, which is uploaded for you. **Cap: pass at most `elements_max_audio` URLs from list_models.** `audio_url` remains supported as the legacy single-track form.'),
|
|
1195
|
+
audio_url: z.string().optional().describe('A single reference audio track — legacy form of reference_audio_urls. Accepts a public URL (forwarded as-is; if the API rejects an external URL as untrusted, it is auto-rehosted into the media library and retried once) OR an absolute local path, which is uploaded for you. **Audio constraints: `elements_max_audio` from list_models gates whether audio is accepted at all; audio duration must fall within `min_audio_duration`-`max_audio_duration`; format must be in `supported_audio_formats` (if specified).**'),
|
|
1188
1196
|
files: z.array(z.string()).optional().describe('Untyped catch-all for mixed media — images, videos AND audio, each a URL or an absolute local path. The kind is detected from the file extension and the item is routed to the matching reference list, so a local .mp4 is sent as a video and a local .mp3 as audio. Prefer the typed lists (reference_images / reference_videos / reference_audio_urls) when you already know the kind; they accept local paths too. URLs given here are forwarded as URLs, never re-uploaded. **Caps still apply per kind: `elements_max_images` / `elements_max_videos` / `elements_max_audio` from list_models. Local uploads are capped at 200MB each.**'),
|
|
1189
1197
|
duration: z.number().optional().describe('Output duration in seconds. Must be in `supported_durations` from list_models, OR within `min_output_duration`-`max_output_duration`. Default: 5'),
|
|
1190
1198
|
aspect_ratio: z.string().optional().describe('Aspect ratio (e.g., "16:9", "9:16", "1:1"). Must be in `supported_aspect_ratios` from list_models. Default: "16:9"'),
|
|
@@ -1250,10 +1258,10 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1250
1258
|
session_name, project_id, session_id
|
|
1251
1259
|
};
|
|
1252
1260
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1261
|
+
const startElements = async () => {
|
|
1262
|
+
if (!locals.length) {
|
|
1263
|
+
return client.post('/v1/generate/elements', body);
|
|
1264
|
+
}
|
|
1257
1265
|
const resolved = await Promise.all(locals.map(({ src, kind }) =>
|
|
1258
1266
|
resolveToBuffer(src, kind, { maxBytes: ELEMENTS_MAX_UPLOAD_BYTES })));
|
|
1259
1267
|
const form = new FormData();
|
|
@@ -1277,7 +1285,36 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1277
1285
|
for (const f of resolved) {
|
|
1278
1286
|
form.append('files', f.buffer, { filename: f.filename, contentType: f.contentType });
|
|
1279
1287
|
}
|
|
1280
|
-
|
|
1288
|
+
return client.postMultipart('/v1/generate/elements', form);
|
|
1289
|
+
};
|
|
1290
|
+
|
|
1291
|
+
let startResponse;
|
|
1292
|
+
try {
|
|
1293
|
+
startResponse = await startElements();
|
|
1294
|
+
} catch (err) {
|
|
1295
|
+
// The elements trust gate only accepts remote references that are BOTH
|
|
1296
|
+
// Kolbo-hosted AND registered to this caller's library/project — any
|
|
1297
|
+
// other public URL 400s with this code, even though every schema here
|
|
1298
|
+
// says "public URL, forwarded as-is". Agents used to recover by hand
|
|
1299
|
+
// (upload_media, then resend). Do that detour for them: rehost every
|
|
1300
|
+
// remote reference into the caller's library and retry ONCE. The gate
|
|
1301
|
+
// fires before any charge, so the failed first attempt costs nothing.
|
|
1302
|
+
if (err?.code !== 'UNTRUSTED_REFERENCE_MEDIA_URL') throw err;
|
|
1303
|
+
for (const kind of ['image', 'video', 'audio']) {
|
|
1304
|
+
media[kind] = await Promise.all(media[kind].map(async (src) => {
|
|
1305
|
+
if (!isUrlSource(src)) return src;
|
|
1306
|
+
const file = await resolveToBuffer(src, kind, { maxBytes: ELEMENTS_MAX_UPLOAD_BYTES });
|
|
1307
|
+
const form = new FormData();
|
|
1308
|
+
form.append('file', file.buffer, { filename: file.filename, contentType: file.contentType });
|
|
1309
|
+
if (project_id) form.append('project_id', project_id);
|
|
1310
|
+
const uploaded = await client.postMultipart('/v1/media/upload', form);
|
|
1311
|
+
return uploaded?.media?.url || uploaded?.url || src;
|
|
1312
|
+
}));
|
|
1313
|
+
}
|
|
1314
|
+
body.reference_images = some(urlsOf('image'));
|
|
1315
|
+
body.reference_videos = some(urlsOf('video'));
|
|
1316
|
+
body.reference_audio_urls = some(urlsOf('audio'));
|
|
1317
|
+
startResponse = await startElements();
|
|
1281
1318
|
}
|
|
1282
1319
|
|
|
1283
1320
|
if (ui()) return uiGenerating({
|
|
@@ -1576,10 +1613,18 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1576
1613
|
highlighted: z.object({ font: z.string().optional(), weight: z.number().int().min(100).max(900).optional(), color: z.string().optional() }).optional().describe('Highlighted word tier styling.'),
|
|
1577
1614
|
}).optional(),
|
|
1578
1615
|
}).optional().describe('VEED Subtitles only: style overrides. Any omitted field keeps the preset default. Best supported by Basic presets.'),
|
|
1616
|
+
enhancement_model: z.string().optional()
|
|
1617
|
+
.describe('Topaz Slow Motion only (model "topaz/interpolate/video"): retiming engine — "Apollo" (smooth motion, default), "Chronos" (complex motion and occlusion), or "Aion" (highest quality for extreme slow motion, and the most expensive).'),
|
|
1618
|
+
target_fps: z.number().optional()
|
|
1619
|
+
.describe('Topaz Slow Motion only: frames per second of the OUTPUT (16-120, default 60). Higher rates generate more frames and cost proportionally more.'),
|
|
1620
|
+
slowdown_factor: z.number().optional()
|
|
1621
|
+
.describe('Topaz Slow Motion only: how many times longer the output runs (1-8, default 1). 4 turns a 5s clip into 20s of slow motion. Billing is on the OUTPUT length, so an 8x pass costs 8x a 1x pass.'),
|
|
1622
|
+
output_format: z.string().optional()
|
|
1623
|
+
.describe('Topaz HDR only (model "topaz/sdr-to-hdr/video"): "mp4" for 10-bit H.265 HDR10 (default) or "prores" for 10-bit ProRes 422 HQ. Both are HDR masters — the in-app player shows a tone-mapped SDR preview and the HDR file is the download.'),
|
|
1579
1624
|
project_id: projectIdField,
|
|
1580
1625
|
session_id: sessionIdField
|
|
1581
1626
|
},
|
|
1582
|
-
async ({ source_video, prompt, model, aspect_ratio, duration, enhance_prompt = false, visual_dna_ids, resolution, sound_enabled, reference_images, reference_videos, elements, preset, source_language, translation_language, srt_content, srt_file_url, vocabulary, customization, project_id, session_id }) => {
|
|
1627
|
+
async ({ source_video, prompt, model, aspect_ratio, duration, enhance_prompt = false, visual_dna_ids, resolution, sound_enabled, reference_images, reference_videos, elements, preset, source_language, translation_language, srt_content, srt_file_url, vocabulary, customization, enhancement_model, target_fps, slowdown_factor, output_format, project_id, session_id }) => {
|
|
1583
1628
|
model = await canonicalModelId(client, model, 'video_to_video'); // lenient id resolution ("z-image" → "z-image/turbo")
|
|
1584
1629
|
if (!source_video) throw new Error('source_video is required');
|
|
1585
1630
|
|
|
@@ -1589,7 +1634,9 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1589
1634
|
startResponse = await client.post('/v1/generate/video-from-video', {
|
|
1590
1635
|
video_url: source_video, prompt, model, aspect_ratio, duration, enhance_prompt, visual_dna_ids, resolution, sound_enabled,
|
|
1591
1636
|
reference_images, reference_videos, elements, preset, source_language, translation_language,
|
|
1592
|
-
srt_content, srt_file_url, vocabulary, customization,
|
|
1637
|
+
srt_content, srt_file_url, vocabulary, customization,
|
|
1638
|
+
enhancement_model, target_fps, slowdown_factor, output_format,
|
|
1639
|
+
project_id, session_id
|
|
1593
1640
|
});
|
|
1594
1641
|
} else {
|
|
1595
1642
|
const resolved = await resolveToBuffer(source_video, 'video');
|
|
@@ -1613,6 +1660,10 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1613
1660
|
if (reference_images) form.append('reference_images', JSON.stringify(reference_images));
|
|
1614
1661
|
if (reference_videos) form.append('reference_videos', JSON.stringify(reference_videos));
|
|
1615
1662
|
if (elements) form.append('elements', JSON.stringify(elements));
|
|
1663
|
+
if (enhancement_model) form.append('enhancement_model', enhancement_model);
|
|
1664
|
+
if (target_fps !== undefined) form.append('target_fps', String(target_fps));
|
|
1665
|
+
if (slowdown_factor !== undefined) form.append('slowdown_factor', String(slowdown_factor));
|
|
1666
|
+
if (output_format) form.append('output_format', output_format);
|
|
1616
1667
|
if (project_id) form.append('project_id', project_id);
|
|
1617
1668
|
if (session_id) form.append('session_id', session_id);
|
|
1618
1669
|
startResponse = await client.postMultipart('/v1/generate/video-from-video', form);
|
|
@@ -1822,7 +1873,8 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1822
1873
|
'camera_angle',
|
|
1823
1874
|
'split', 'split_upscale',
|
|
1824
1875
|
'multi_shot',
|
|
1825
|
-
'magic_edit'
|
|
1876
|
+
'magic_edit',
|
|
1877
|
+
'enhance'
|
|
1826
1878
|
]).describe([
|
|
1827
1879
|
'Edit operation:',
|
|
1828
1880
|
'"upscale" — increase resolution by 2×, 3×, or 4× (use `scale`). "clarity_upscale" — AI-powered clarity upscale with detail enhancement (use `resolution`).',
|
|
@@ -1831,6 +1883,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1831
1883
|
'"removebg" — remove the image background, output is transparent PNG.',
|
|
1832
1884
|
'"background_replace" — remove background and replace it with AI-generated content from `prompt`.',
|
|
1833
1885
|
'"enhance_skin" — portrait skin retouching (use `skin_strength`: "subtle" | "realistic" | "pimple" | "freckle").',
|
|
1886
|
+
'"enhance" — Topaz photo correction at the SOURCE resolution (no resizing). Pick the tool with `model`: "topaz/adjust/image" (exposure, white balance, or colorizing a black-and-white photo), "topaz/sharpen/image" (lens / motion / portrait / wildlife blur, or Super Focus for severely blurred shots), "topaz/denoise/image" (high-ISO and night noise), "topaz/restore/image" (old or damaged photos, dust and scratches). Choose the specific engine with `enhancement_model` — call list_models type="graphics_enhance" to see each model\'s engines. To make an image BIGGER use "upscale" instead.',
|
|
1834
1887
|
'"inpaint" — paint over a masked area using `mask_image_url` (B&W mask, white = fill area) and optional `prompt`. Add reference images via `additional_images`.',
|
|
1835
1888
|
'"erase" — erase an object defined by `mask_image_url` (white = erase area).',
|
|
1836
1889
|
'"face_swap" — swap the face in `image_url` with the face from `mask_image_url` (required).',
|
|
@@ -1845,7 +1898,13 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1845
1898
|
|
|
1846
1899
|
// ── upscale ────────────────────────────────────────────
|
|
1847
1900
|
scale: z.number().optional()
|
|
1848
|
-
.describe('Upscale factor:
|
|
1901
|
+
.describe('Upscale factor: 1, 2, 4 or 8. Used with operation="upscale". Default: 2.'),
|
|
1902
|
+
|
|
1903
|
+
enhancement_model: z.string().optional()
|
|
1904
|
+
.describe('Topaz engine. With operation="upscale" on model "topaz/upscale/image" it selects the engine family: "Standard V2" / "High Fidelity V3" / "CGI" / "Text Refine" (faithful), "Wonder 3.5" (rebuilds natural detail), "Bloom 2" (reinvents detail — most expensive), "Transparent" (keeps the alpha channel). With operation="enhance" it selects the correction engine for the chosen model. Omit for the model default. Call list_models to see the engines a model offers.'),
|
|
1905
|
+
|
|
1906
|
+
output_format: z.string().optional()
|
|
1907
|
+
.describe('Output image format: "jpeg" or "png". Used with Topaz "upscale" and "enhance". Defaults to jpeg (the transparent upscaler always returns png).'),
|
|
1849
1908
|
|
|
1850
1909
|
resolution: z.string().optional()
|
|
1851
1910
|
.describe('Target output resolution (e.g. "4k", "2k", "1080p"). Used with "clarity_upscale", "split_upscale", "multi_shot".'),
|
|
@@ -1902,6 +1961,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1902
1961
|
image_url, operation, model, scale, aspect_ratio, skin_strength, prompt,
|
|
1903
1962
|
mask_image_url, additional_images, generate_all_angles, resolution, quality, ai_optimize = false,
|
|
1904
1963
|
zoom_out_percentage, expand_left, expand_right, expand_top, expand_bottom,
|
|
1964
|
+
enhancement_model, output_format,
|
|
1905
1965
|
project_id, session_id
|
|
1906
1966
|
}) => {
|
|
1907
1967
|
// No `type` argument: these are operation-routed tools (upscale / reframe /
|
|
@@ -1911,6 +1971,9 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1911
1971
|
|
|
1912
1972
|
// Basic validation
|
|
1913
1973
|
if (operation === 'reframe' && !aspect_ratio) throw new Error('aspect_ratio is required for reframe');
|
|
1974
|
+
if (operation === 'enhance' && !model) {
|
|
1975
|
+
throw new Error('model is required for enhance — pick one of: topaz/adjust/image, topaz/sharpen/image, topaz/denoise/image, topaz/restore/image');
|
|
1976
|
+
}
|
|
1914
1977
|
if (operation === 'background_replace' && !prompt) throw new Error('prompt is required for background_replace');
|
|
1915
1978
|
if (operation === 'face_swap' && !mask_image_url && !(additional_images && additional_images.length > 0)) {
|
|
1916
1979
|
throw new Error('mask_image_url (face reference) is required for face_swap');
|
|
@@ -1920,6 +1983,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1920
1983
|
image_url, operation, model, scale, aspect_ratio, skin_strength, prompt,
|
|
1921
1984
|
mask_image_url, additional_images, generate_all_angles, resolution, quality, ai_optimize,
|
|
1922
1985
|
zoom_out_percentage, expand_left, expand_right, expand_top, expand_bottom,
|
|
1986
|
+
enhancement_model, output_format,
|
|
1923
1987
|
project_id, session_id
|
|
1924
1988
|
});
|
|
1925
1989
|
|
|
@@ -1992,6 +2056,8 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1992
2056
|
.describe('Target resolution (e.g. "4k", "2k", "1080p"). Used with "upscale" and "reframe".'),
|
|
1993
2057
|
target_fps: z.number().optional()
|
|
1994
2058
|
.describe('Target frame rate (e.g. 24, 30, 60). Used with operation="upscale".'),
|
|
2059
|
+
enhancement_model: z.string().optional()
|
|
2060
|
+
.describe('Topaz enhancement engine for operation="upscale" on model "topaz/upscale/video". Families: Precision (faithful — "Proteus", "Artemis High Quality", "Iris", "Dione TV", "Gaia CG", "Gaia 2"), Denoise ("Nyx", "Nyx Fast"), Generative (rebuilds detail that is not in the source — "Starlight Fast 2", "Starlight HQ", "Starlight Precise 2.6"), Creative ("Astra 2", always renders 4K). Generative and Creative engines cost several times the Precision rate. Omit for the default.'),
|
|
1995
2061
|
|
|
1996
2062
|
// ── reframe ────────────────────────────────────────────
|
|
1997
2063
|
aspect_ratio: z.string().optional()
|
|
@@ -2055,7 +2121,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2055
2121
|
async ({
|
|
2056
2122
|
video_url, operation, model, aspect_ratio, scale, prompt,
|
|
2057
2123
|
image_url, audio_url, duration, mode,
|
|
2058
|
-
target_fps, resolution,
|
|
2124
|
+
target_fps, resolution, enhancement_model,
|
|
2059
2125
|
grid_position_x, grid_position_y,
|
|
2060
2126
|
sound_effect_prompt, background_music_prompt, original_sound, cfg_strength,
|
|
2061
2127
|
refine_edges, subject_is_person,
|
|
@@ -2078,7 +2144,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2078
2144
|
if (operation === 'extend' && !duration) throw new Error('duration is required for extend');
|
|
2079
2145
|
|
|
2080
2146
|
const gen = await client.post('/v1/edit/video', {
|
|
2081
|
-
video_url, operation, model, aspect_ratio, scale, prompt,
|
|
2147
|
+
video_url, operation, model, aspect_ratio, scale, prompt, enhancement_model,
|
|
2082
2148
|
image_url, audio_url, duration, mode,
|
|
2083
2149
|
target_fps, resolution,
|
|
2084
2150
|
grid_position_x, grid_position_y,
|