@kolbo/mcp 1.108.0 → 1.110.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/package.json +1 -1
- package/src/apps/index.js +13 -0
- package/src/apps/widgets/generation.js +18 -3
- package/src/index.js +2 -1
- package/src/tools/_shared.js +10 -5
- package/src/tools/flow-contract.json +5 -0
- package/src/tools/flow-schema.js +1 -1
- package/src/tools/generate.js +136 -17
package/package.json
CHANGED
package/src/apps/index.js
CHANGED
|
@@ -358,8 +358,20 @@ async function modelInfoMap(client) {
|
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
/** Resolve one model's { icon, eta, name }; missing → all null. */
|
|
361
|
+
// Engines hidden from /v1/models that still reach a card, shown as the public
|
|
362
|
+
// model the user actually knows. draft_enhance finalizes a Seedance 2.5 Draft;
|
|
363
|
+
// its engine id is internal, so the card showed "Smart Select" / a raw id.
|
|
364
|
+
const DISPLAY_ALIASES = {
|
|
365
|
+
'flux-3-draft-enhance': { base: 'seedance-2-5', name: 'Seedance 2.5 Draft Enhance' },
|
|
366
|
+
'video-draft-enhance': { base: 'seedance-2-5', name: 'Seedance 2.5 Draft Enhance' },
|
|
367
|
+
};
|
|
368
|
+
// Operations whose engine is picked server-side when no model is passed.
|
|
369
|
+
const OPERATION_ENGINES = { draft_enhance: 'flux-3-draft-enhance' };
|
|
370
|
+
|
|
361
371
|
async function modelInfo(client, modelName) {
|
|
362
372
|
if (!modelName) return { icon: null, eta: null, name: null };
|
|
373
|
+
const alias = DISPLAY_ALIASES[String(modelName).toLowerCase()];
|
|
374
|
+
if (alias) return { ...(await modelInfo(client, alias.base)), name: alias.name };
|
|
363
375
|
const catalog = await modelCatalog(client);
|
|
364
376
|
const exact = catalog.byKey.get(String(modelName).toLowerCase());
|
|
365
377
|
if (exact) return exact;
|
|
@@ -827,6 +839,7 @@ module.exports = {
|
|
|
827
839
|
appsEnabled,
|
|
828
840
|
modelIcon,
|
|
829
841
|
modelInfo,
|
|
842
|
+
OPERATION_ENGINES,
|
|
830
843
|
modelInfoMap,
|
|
831
844
|
voiceInfo,
|
|
832
845
|
canonicalModelId,
|
|
@@ -340,7 +340,8 @@ function renderChips(sc) {
|
|
|
340
340
|
: ICONS.mic;
|
|
341
341
|
h += chip(face + ' ' + esc(voice));
|
|
342
342
|
}
|
|
343
|
-
if (s.mode) h += chip(esc(s.mode));
|
|
343
|
+
if (s.mode) h += chip(esc(editLabel(s.mode)));
|
|
344
|
+
if (Array.isArray(s.details)) s.details.forEach(function (d) { if (d) h += chip(esc(String(d))); });
|
|
344
345
|
if (many && !kind) h += chip(sc.count + ' items');
|
|
345
346
|
h += referenceHTML(sc);
|
|
346
347
|
el('chips').innerHTML = h;
|
|
@@ -460,6 +461,17 @@ function capLabel(inner) {
|
|
|
460
461
|
return String(inner).replace(/(^|>)(\\s*)([a-z])/, function (m, gt, sp, ch) { return gt + sp + ch.toUpperCase(); });
|
|
461
462
|
}
|
|
462
463
|
function chip(inner) { return '<span class="k-chip">' + capLabel(inner) + '</span>'; }
|
|
464
|
+
// edit_image / edit_video operation ids -> the words a user reads. Unknown ids
|
|
465
|
+
// fall back to the id with spaces, never the raw snake_case.
|
|
466
|
+
var EDIT_LABELS = {
|
|
467
|
+
upscale: 'Upscale', clarity_upscale: 'Clarity upscale', reframe: 'Reframe', zoom_out: 'Expand',
|
|
468
|
+
removebg: 'Remove background', remove_background: 'Remove background', background_replace: 'Replace background',
|
|
469
|
+
enhance_skin: 'Skin enhance', enhance: 'Photo enhance', inpaint: 'Inpaint', erase: 'Erase', face_swap: 'Face swap',
|
|
470
|
+
camera_angle: 'Camera angle', split: 'Split grid', split_upscale: 'Split + upscale', multi_shot: 'Multi-shot',
|
|
471
|
+
magic_edit: 'Magic edit', generate_audio: 'Add audio', remove_watermark: 'Remove watermark', extend: 'Extend',
|
|
472
|
+
lipsync: 'Lipsync', retake: 'Retake', draft_enhance: 'Finalize draft', draft_quote: 'Draft quote'
|
|
473
|
+
};
|
|
474
|
+
function editLabel(mode) { return EDIT_LABELS[mode] || String(mode).split('_').join(' '); }
|
|
463
475
|
// Same chip with a hover title — used to surface the asset id behind a
|
|
464
476
|
// "2 Visual DNA" / "preset" label without spending chip width on it.
|
|
465
477
|
function chipT(inner, title) {
|
|
@@ -1246,6 +1258,7 @@ function renderActions(sc) {
|
|
|
1246
1258
|
bind('btn-open', function () { window.kolbo.openLink(kolboUrl(state)); });
|
|
1247
1259
|
bind('btn-recreate', function () {
|
|
1248
1260
|
window.kolbo.sendMessage('Recreate this with the same settings' +
|
|
1261
|
+
(state.settings && state.settings.mode ? '\\nOperation: ' + state.settings.mode : '') +
|
|
1249
1262
|
(state.model ? '\\nModel: ' + state.model : '') +
|
|
1250
1263
|
(state.prompt ? '\\nPrompt: ' + state.prompt : '') +
|
|
1251
1264
|
'\\n(from the ' + (TOOL_TITLES[state.tool] || 'generation') + ' widget)');
|
|
@@ -1335,7 +1348,7 @@ function preRefSc(toolName, a) {
|
|
|
1335
1348
|
resolution: a.draft === true ? '480p-draft' : a.draft === false ? String(a.resolution || '').replace(/-draft$/i, '') : a.resolution,
|
|
1336
1349
|
aspect_ratio: a.aspect_ratio,
|
|
1337
1350
|
quality: a.quality,
|
|
1338
|
-
mode: a.mode,
|
|
1351
|
+
mode: a.operation || a.mode,
|
|
1339
1352
|
cinematic: a.cinematic,
|
|
1340
1353
|
visual_dna_ids: a.visual_dna_ids,
|
|
1341
1354
|
moodboard_ids: a.moodboard_ids,
|
|
@@ -1405,6 +1418,7 @@ function liveFromTimedOut(sc) {
|
|
|
1405
1418
|
resolution: originArgs.draft === true ? '480p-draft' : originArgs.draft === false ? String(originArgs.resolution || '').replace(/-draft$/i, '') : originArgs.resolution,
|
|
1406
1419
|
aspect_ratio: originArgs.aspect_ratio,
|
|
1407
1420
|
quality: originArgs.quality,
|
|
1421
|
+
mode: originArgs.operation,
|
|
1408
1422
|
visual_dna_ids: originArgs.visual_dna_ids,
|
|
1409
1423
|
moodboard_id: originArgs.moodboard_id
|
|
1410
1424
|
},
|
|
@@ -1433,7 +1447,8 @@ function completedFromPlain(sc) {
|
|
|
1433
1447
|
is_draft: originArgs.draft,
|
|
1434
1448
|
resolution: originArgs.draft === true ? '480p-draft' : originArgs.draft === false ? String(originArgs.resolution || '').replace(/-draft$/i, '') : originArgs.resolution,
|
|
1435
1449
|
aspect_ratio: originArgs.aspect_ratio,
|
|
1436
|
-
quality: originArgs.quality
|
|
1450
|
+
quality: originArgs.quality,
|
|
1451
|
+
mode: originArgs.operation
|
|
1437
1452
|
},
|
|
1438
1453
|
urls: sc.urls || [],
|
|
1439
1454
|
session_id: sc.session_id || originArgs.session_id,
|
package/src/index.js
CHANGED
|
@@ -141,7 +141,8 @@ function createServer(opts = {}) {
|
|
|
141
141
|
'9. MODEL SELECTION — NAMED MODEL WINS, THEN STRENGTHS SUMMARY: ALWAYS pass a specific `model` on every generation tool — do NOT omit it (omitting falls back to "Smart Select" auto-routing, which hides the choice from the user; use it ONLY if the user explicitly asks you to auto-pick). If the user named a model this turn OR earlier in the conversation (including a compaction "Locked choices" / summary), that name is a FAMILY LOCK: pass it (or its display name) on every follow-up, including when the tool changes (text-to-video → image-to-video). Identifier resolution remaps a t2v id to the family\'s i2v sibling automatically. NEVER substitute a different brand because it is cheaper, faster, or "best balance" (Grok Imagine named → do not fire Seedance). If the named family has no variant for this modality, ASK — do not silently switch. Cheapest-summary routing applies ONLY when no model was named on this task: call `list_models` with the matching `type` and read each model\'s STRENGTHS SUMMARY — the "— …" clause printed after the credit cost — then pick the CHEAPEST model whose summary covers the task. `[NEW]` and `[RECOMMENDED]` badges, a high credit number, and "flagship"/"most intelligent" wording are NOT selection signals — never pick a model because it is newest, biggest or most expensive. Escalate to a premium/frontier model only when the user explicitly asks for maximum quality, or when no cheaper summary covers the requirement. Models printed under "Named-only" (no summary) are opt-in: use them only when the user names them. TEXT/CHAT: `chat_send_message` bills PER TOKEN, so the listed credit number is not the cost — a frontier text model (Claude Fable 5, GPT-5.6 Sol, Pro-class) costs 5-30x a mid-tier one per reply. Default ordinary chat (writing, brainstorming, Q&A, summarising) to a balanced mid-tier model and reserve the frontier tier for hard reasoning or long-form code the user asked for.',
|
|
142
142
|
'10. IMAGE EDITING: for ANY prompt-driven / content edit of an existing image — "make it night", changing scene/lighting/colors, adding/removing/replacing objects, restyling — use `generate_image_edit`. Auto-pick only Nano Banana 2 (`nano-banana-2` / `nano-banana-2-image-editing`) or GPT Image 2 (`gpt-image-2` / `gpt-image-2/edit`) for photoreal photo edits, object removal, keep-subject/remove-others, or crowd cleanup. Do NOT auto-pick Flux 2 / flux-2/edit / Flux Klein — those are generate-from-scratch / style, named-only for editing. Do NOT use `edit_image` for content edits — `edit_image` is ONLY for mechanical enhancements (upscale, expand/outpaint, remove-background, skin retouch). Its `magic_edit` operation is deprecated in favor of `generate_image_edit`. EXPANDING AN IMAGE: to widen/extend/uncrop an image or fit it into a wider frame while KEEPING the existing artwork, use `edit_image` with operation="zoom_out" (outpainting — original pixels preserved; size it with `zoom_out_percentage` or the `expand_left/right/top/bottom` pixel args). The "reframe" operation is NOT this: it re-generates the whole picture at a new aspect ratio and the subject comes back re-imagined. Only pick "reframe" when the user wants the shot re-taken, never when they want their image extended.',
|
|
143
143
|
'11. PRESET CONTRACT: if the user asks for a preset, names a preset, or says to use one of their/Kolbo presets, you MUST call `list_presets` with the matching type before generation, resolve the named or closest matching preset, and pass its exact returned `id` as `preset_id`. Use type="image" for generate_image and type="image_edit" for generate_image_edit. Never silently ignore a preset request, never invent an id, and never claim a preset was applied unless `preset_id` was present in the generation call.',
|
|
144
|
-
'12. DRAFT IS A TIER, NOT A RESOLUTION: when the user says "draft" (Hebrew "טיוטה") for a video — "480 draft", "draft mode", "480p draft" — pass `draft: true` on the generate_* call (or `resolution: "480p-draft"`) with the SAME model, e.g. `seedance-2-5`. Plain `resolution: "480p"` is a regular full-price render and is NEVER Draft. Draft exists only where list_models `supported_resolutions` includes "480p-draft"; if the chosen model lacks it, say so instead of silently rendering regular 480p.'
|
|
144
|
+
'12. DRAFT IS A TIER, NOT A RESOLUTION: when the user says "draft" (Hebrew "טיוטה") for a video — "480 draft", "draft mode", "480p draft" — pass `draft: true` on the generate_* call (or `resolution: "480p-draft"`) with the SAME model, e.g. `seedance-2-5`. Plain `resolution: "480p"` is a regular full-price render and is NEVER Draft. Draft exists only where list_models `supported_resolutions` includes "480p-draft"; if the chosen model lacks it, say so instead of silently rendering regular 480p.',
|
|
145
|
+
'13. VIDEO UPSCALE SETTINGS: when the user asks to upscale a video with specific quality controls (6K/8K, Pro/fast, 10/12-bit, film restoration, AI-footage tuning, custom scale), use `edit_video` operation="upscale" with model "bytedance-upscaler/upscale/video" and pass the matching fields (`resolution` 1080p-8k or `scale_ratio`, `target_fps`, `enhancement_tier`, `enhancement_preset`, `fidelity`, `bit_depth`). Pro costs 10x and caps the source at 30s; 6K/8K cap at 120s/60s; 10/12-bit needs Pro. State the credit cost before running Pro or 6K/8K.'
|
|
145
146
|
].join('\n')
|
|
146
147
|
});
|
|
147
148
|
const progress = require('./progress');
|
package/src/tools/_shared.js
CHANGED
|
@@ -698,7 +698,7 @@ function buildProjectUrl(projectId, opts = {}) {
|
|
|
698
698
|
// ui://kolbo/generation.html widget takes over: live progress, inline result,
|
|
699
699
|
// action buttons. Text-only hosts never enter this path — their blocking
|
|
700
700
|
// behavior and response bytes are UNCHANGED.
|
|
701
|
-
const { UI, uiResult, appsEnabled, modelInfo } = require('../apps');
|
|
701
|
+
const { UI, uiResult, appsEnabled, modelInfo, OPERATION_ENGINES } = require('../apps');
|
|
702
702
|
|
|
703
703
|
/**
|
|
704
704
|
* Chip identity for a model: the CLEAN display name + its icon, resolved from
|
|
@@ -706,8 +706,13 @@ const { UI, uiResult, appsEnabled, modelInfo } = require('../apps');
|
|
|
706
706
|
* user/LLM supplied (an identifier like `google_tts`, `fal-ai/…/omnihuman/v1.5`,
|
|
707
707
|
* or a display name) — the card must never show the raw id.
|
|
708
708
|
*/
|
|
709
|
-
|
|
710
|
-
|
|
709
|
+
// `operation`: an edit whose engine the server picks when no model is passed
|
|
710
|
+
// (draft_enhance) still names that engine on the chip, not "Smart Select".
|
|
711
|
+
// `model` itself stays unset so Recreate never pins a hidden engine id.
|
|
712
|
+
async function modelChipFields(client, model, operation) {
|
|
713
|
+
const lookup = model || (operation && OPERATION_ENGINES[operation]);
|
|
714
|
+
const info = await modelInfo(client, lookup).catch(() => null);
|
|
715
|
+
if (!model && info && info.name) return { model: 'Smart Select', model_name: info.name, model_icon: info.icon || null };
|
|
711
716
|
return {
|
|
712
717
|
model: model || 'Smart Select',
|
|
713
718
|
model_name: (info && info.name) || model || 'Smart Select',
|
|
@@ -898,7 +903,7 @@ function moodboardIds(settings) {
|
|
|
898
903
|
*/
|
|
899
904
|
async function uiGenerating(p) {
|
|
900
905
|
// No ETAs anywhere — just a spinner until the poll flips to completed.
|
|
901
|
-
const chip = await modelChipFields(p.client, p.model);
|
|
906
|
+
const chip = await modelChipFields(p.client, p.model, p.settings && p.settings.mode);
|
|
902
907
|
const settings = await decorateSettings(p.client, p.settings || {});
|
|
903
908
|
const structured = {
|
|
904
909
|
phase: 'generating',
|
|
@@ -1009,7 +1014,7 @@ function preferOwnedUrls(urls) {
|
|
|
1009
1014
|
}
|
|
1010
1015
|
|
|
1011
1016
|
async function uiCompleted(p, textPayload, extraContent) {
|
|
1012
|
-
const chip = await modelChipFields(p.client, p.model);
|
|
1017
|
+
const chip = await modelChipFields(p.client, p.model, p.settings && p.settings.mode);
|
|
1013
1018
|
const settings = p.settings ? await decorateSettings(p.client, p.settings) : undefined;
|
|
1014
1019
|
const structured = {
|
|
1015
1020
|
phase: 'completed',
|
package/src/tools/flow-schema.js
CHANGED
|
@@ -20,7 +20,7 @@ const voice = z.union([z.string().max(256), z.object({
|
|
|
20
20
|
name: z.string().max(500).optional(), type: z.string().max(128).optional(),
|
|
21
21
|
}).strict()]);
|
|
22
22
|
const booleanConfig = new Set('instrumental preservePitch resolved enhancePrompt soundEnabled sound_enabled showLyrics includeAudio loop reverse'.split(' '));
|
|
23
|
-
const numberConfig = new Set('scale frameCount quantity startTime endTime speed timestamp speaking_speed promptInfluence creativity resemblance movement upscaleFactor targetFps extendDuration skinIntensity start end cropX cropY cropWidth cropHeight x y width height fps frameTime seed temperature maxTokens volume'.split(' '));
|
|
23
|
+
const numberConfig = new Set('scale frameCount quantity startTime endTime speed timestamp speaking_speed promptInfluence creativity resemblance movement upscaleFactor targetFps scaleRatio bitDepth extendDuration skinIntensity start end cropX cropY cropWidth cropHeight x y width height fps frameTime seed temperature maxTokens volume'.split(' '));
|
|
24
24
|
const modelConfig = new Set('model t2iModel i2iModel t2vModel i2vModel flModel modelId engineId'.split(' '));
|
|
25
25
|
const configShape = Object.fromEntries(contract.config_fields.map(field => {
|
|
26
26
|
let schema = text;
|
package/src/tools/generate.js
CHANGED
|
@@ -2325,6 +2325,50 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2325
2325
|
}, null, 2));
|
|
2326
2326
|
}
|
|
2327
2327
|
);
|
|
2328
|
+
// Short human chips for the settings an edit actually ran with — the card
|
|
2329
|
+
// shows these next to the operation label (raw ids never reach the card).
|
|
2330
|
+
const editDetails = (op, a) => {
|
|
2331
|
+
const d = [];
|
|
2332
|
+
const has = (v) => v !== undefined && v !== null && v !== '';
|
|
2333
|
+
if (op === 'upscale' || op === 'split_upscale') {
|
|
2334
|
+
if (has(a.scale)) d.push(`${a.scale}×`);
|
|
2335
|
+
if (a.face_enhancement === false) d.push('no face enhance');
|
|
2336
|
+
else if (has(a.face_enhancement_strength)) d.push(`face ${a.face_enhancement_strength}`);
|
|
2337
|
+
}
|
|
2338
|
+
if (op === 'clarity_upscale') {
|
|
2339
|
+
if (has(a.scale)) d.push(`${a.scale}×`);
|
|
2340
|
+
if (has(a.creativity)) d.push(`creativity ${a.creativity}`);
|
|
2341
|
+
if (has(a.resemblance)) d.push(`resemblance ${a.resemblance}`);
|
|
2342
|
+
}
|
|
2343
|
+
if (op === 'enhance_skin') {
|
|
2344
|
+
d.push(a.skin_strength || 'realistic');
|
|
2345
|
+
if (a.fix_lighting) d.push('fix lighting');
|
|
2346
|
+
if (has(a.freckle_intensity) && a.skin_strength === 'freckle') d.push(`freckles ${a.freckle_intensity}`);
|
|
2347
|
+
}
|
|
2348
|
+
if (op === 'camera_angle') {
|
|
2349
|
+
if (a.generate_all_angles) d.push('all angles');
|
|
2350
|
+
else {
|
|
2351
|
+
if (has(a.horizontal_angle)) d.push(`${a.horizontal_angle}° turn`);
|
|
2352
|
+
if (has(a.vertical_angle)) d.push(`${a.vertical_angle}° tilt`);
|
|
2353
|
+
if (has(a.camera_zoom)) d.push(`zoom ${a.camera_zoom}`);
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
if (op === 'zoom_out') {
|
|
2357
|
+
if ([a.expand_left, a.expand_right, a.expand_top, a.expand_bottom].some(has)) d.push('custom expand');
|
|
2358
|
+
else if (has(a.zoom_out_percentage)) d.push(`${a.zoom_out_percentage}%`);
|
|
2359
|
+
}
|
|
2360
|
+
if (op === 'split' && has(a.split_layout)) d.push(a.split_layout);
|
|
2361
|
+
if (op === 'background_replace' && a.background_reference_image) d.push('reference bg');
|
|
2362
|
+
if (has(a.enhancement_model)) d.push(a.enhancement_model);
|
|
2363
|
+
if (has(a.scale_ratio)) d.push(`${a.scale_ratio}×`);
|
|
2364
|
+
if (has(a.enhancement_tier) && a.enhancement_tier !== 'standard') d.push(a.enhancement_tier);
|
|
2365
|
+
if (has(a.enhancement_preset) && a.enhancement_preset !== 'general') d.push(a.enhancement_preset);
|
|
2366
|
+
if (has(a.bit_depth) && Number(a.bit_depth) > 8) d.push(`${a.bit_depth}-bit`);
|
|
2367
|
+
if (has(a.target_fps)) d.push(`${a.target_fps} fps`);
|
|
2368
|
+
if (op === 'extend' && has(a.mode)) d.push(`at ${a.mode}`);
|
|
2369
|
+
return d.length ? d : undefined;
|
|
2370
|
+
};
|
|
2371
|
+
|
|
2328
2372
|
// ─── edit_image ────────────────────────────────────────────
|
|
2329
2373
|
server.tool(
|
|
2330
2374
|
'edit_image',
|
|
@@ -2351,12 +2395,12 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2351
2395
|
'"reframe" — RE-GENERATES the entire picture at a new aspect ratio (Luma Photon). The subject is re-imagined, not preserved — expect a different-looking image. Use ONLY when the user explicitly wants the shot re-taken in another format. If they said "expand", "extend", "widen", "uncrop", "add space", "fill the sides", or "keep it the same but 16:9", they want "zoom_out" instead. Requires `aspect_ratio`.',
|
|
2352
2396
|
'"removebg" — remove the image background, output is transparent PNG.',
|
|
2353
2397
|
'"background_replace" — remove background and replace it with AI-generated content from `prompt`.',
|
|
2354
|
-
'"enhance_skin" —
|
|
2398
|
+
'"enhance_skin" — realistic skin texture on portraits (Enhancor). `skin_strength`: "subtle" (barely visible — avoid unless asked), "realistic" (default, visible pore/texture detail; add `fix_lighting=true` to also even out lighting), "pimple" (removes blemishes), "freckle" (adds freckles — set `freckle_intensity`). Works on visible faces: on a full-body shot or a multi-panel character sheet the face is small and the change is hard to see — run it on a face close-up or crop instead.',
|
|
2355
2399
|
'"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.',
|
|
2356
2400
|
'"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`.',
|
|
2357
2401
|
'"erase" — erase an object defined by `mask_image_url` (white = erase area).',
|
|
2358
2402
|
'"face_swap" — swap the face in `image_url` with the face from `mask_image_url` (required).',
|
|
2359
|
-
'"camera_angle" — generate the image from a different camera angle. Set `
|
|
2403
|
+
'"camera_angle" — generate the image from a different camera angle. Set the view with `horizontal_angle` / `vertical_angle` / `camera_zoom` (preferred — exact), or describe it in `prompt` ("left side", "back", "low angle", "bird\'s eye", "close up", "wide"); with neither, the view barely changes. Set `generate_all_angles=true` for the 12-angle set.',
|
|
2360
2404
|
'"split" — split the image into a 3×3 grid of tiles. "split_upscale" — split into a grid and upscale each tile.',
|
|
2361
2405
|
'"multi_shot" — generate a 3×3 multi-shot grid of scenes (uses `additional_images` as reference shots). Use `resolution` for output quality.',
|
|
2362
2406
|
'"magic_edit" (DEPRECATED) — prompt-driven content edit. Prefer `generate_image_edit` for better results.',
|
|
@@ -2400,7 +2444,45 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2400
2444
|
|
|
2401
2445
|
// ── enhance_skin ───────────────────────────────────────
|
|
2402
2446
|
skin_strength: z.enum(['subtle', 'realistic', 'pimple', 'freckle']).optional()
|
|
2403
|
-
.describe('Skin enhancement preset. Used with operation="enhance_skin". Default: "realistic".'),
|
|
2447
|
+
.describe('Skin enhancement preset. Used with operation="enhance_skin". Default: "realistic". "subtle" changes very little.'),
|
|
2448
|
+
freckle_intensity: z.union([z.literal(0), z.literal(50), z.literal(100)]).optional()
|
|
2449
|
+
.describe('Freckle amount: 0, 50 or 100. Used with operation="enhance_skin" and skin_strength="freckle". Default: 50.'),
|
|
2450
|
+
fix_lighting: z.boolean().optional()
|
|
2451
|
+
.describe('Also correct uneven / flat lighting on the face. Used with operation="enhance_skin" and skin_strength="realistic". Default: false.'),
|
|
2452
|
+
|
|
2453
|
+
// ── camera_angle ───────────────────────────────────────
|
|
2454
|
+
horizontal_angle: z.number().min(0).max(360).optional()
|
|
2455
|
+
.describe('Camera rotation around the subject in degrees (0-360; 0 = unchanged, 90 = right side, 180 = behind). Used with operation="camera_angle" when generate_all_angles is not set.'),
|
|
2456
|
+
vertical_angle: z.number().min(-30).max(90).optional()
|
|
2457
|
+
.describe('Camera tilt in degrees (-30 = from below, 0 = eye level, 90 = straight down). Used with operation="camera_angle".'),
|
|
2458
|
+
camera_zoom: z.number().min(0).max(10).optional()
|
|
2459
|
+
.describe('Camera distance (0 = far, 5 = unchanged, 10 = close-up). Used with operation="camera_angle". Default: 5.'),
|
|
2460
|
+
|
|
2461
|
+
// ── upscale (Topaz) / clarity_upscale ──────────────────
|
|
2462
|
+
face_enhancement: z.boolean().optional()
|
|
2463
|
+
.describe('Topaz face recovery. Used with operation="upscale" on Topaz precision/generative engines. Default: true — set false to keep faces exactly as in the source.'),
|
|
2464
|
+
face_enhancement_strength: z.number().min(0).max(1).optional()
|
|
2465
|
+
.describe('How strongly faces are recovered (0-1). Used with operation="upscale" (Topaz). Default: 0.8.'),
|
|
2466
|
+
face_enhancement_creativity: z.number().min(0).max(1).optional()
|
|
2467
|
+
.describe('How much new facial detail Topaz may invent (0-1; 0 = faithful). Used with operation="upscale". Default: 0.'),
|
|
2468
|
+
subject_detection: z.enum(['All', 'Foreground', 'Background']).optional()
|
|
2469
|
+
.describe('Which part Topaz enhances. Used with operation="upscale". Default: "All".'),
|
|
2470
|
+
crop_to_fill: z.boolean().optional()
|
|
2471
|
+
.describe('Crop to fill the target size instead of keeping the source framing. Used with operation="upscale" (Topaz). Default: false.'),
|
|
2472
|
+
creativity: z.number().optional()
|
|
2473
|
+
.describe('How much new detail is invented. operation="clarity_upscale": 0-1 (default 0.35). operation="upscale" with the Bloom (creative) engine: 1-9.'),
|
|
2474
|
+
resemblance: z.number().min(0).max(1).optional()
|
|
2475
|
+
.describe('How closely the result must match the source (0-1). Used with operation="clarity_upscale". Default: 0.6.'),
|
|
2476
|
+
|
|
2477
|
+
// ── background_replace / split / erase ─────────────────
|
|
2478
|
+
background_reference_image: z.string().optional()
|
|
2479
|
+
.describe('URL or absolute local path of an image to use as the new background. Used with operation="background_replace" (prompt still required).'),
|
|
2480
|
+
negative_prompt: z.string().optional()
|
|
2481
|
+
.describe('What must NOT appear. Used with operation="background_replace" and "clarity_upscale".'),
|
|
2482
|
+
split_layout: z.string().optional()
|
|
2483
|
+
.describe('Grid to split into: "auto" (detect), "2x2", "3x3" or "4x4". Used with operation="split" / "split_upscale". Default: "auto".'),
|
|
2484
|
+
mask_expansion: z.number().min(0).max(100).optional()
|
|
2485
|
+
.describe('Pixels to grow the erase mask so edges blend. Used with operation="erase". Default: 15.'),
|
|
2404
2486
|
|
|
2405
2487
|
// ── inpaint / erase / face_swap / background_replace / zoom_out / camera_angle / magic_edit ──
|
|
2406
2488
|
prompt: z.string().optional()
|
|
@@ -2427,10 +2509,14 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2427
2509
|
session_id: sessionIdField
|
|
2428
2510
|
},
|
|
2429
2511
|
async ({
|
|
2430
|
-
image_url, operation, model, scale, aspect_ratio, skin_strength, prompt,
|
|
2512
|
+
image_url, operation, model, scale, aspect_ratio, skin_strength, freckle_intensity, fix_lighting, prompt,
|
|
2431
2513
|
mask_image_url, additional_images, generate_all_angles, resolution, quality, ai_optimize = false,
|
|
2432
2514
|
zoom_out_percentage, expand_left, expand_right, expand_top, expand_bottom,
|
|
2433
2515
|
enhancement_model, output_format,
|
|
2516
|
+
horizontal_angle, vertical_angle, camera_zoom,
|
|
2517
|
+
face_enhancement, face_enhancement_strength, face_enhancement_creativity,
|
|
2518
|
+
subject_detection, crop_to_fill, creativity, resemblance,
|
|
2519
|
+
background_reference_image, negative_prompt, split_layout, mask_expansion,
|
|
2434
2520
|
project_id, session_id
|
|
2435
2521
|
}) => {
|
|
2436
2522
|
const editModelType = modelTypeForEditOperation('image', operation);
|
|
@@ -2454,23 +2540,37 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2454
2540
|
}
|
|
2455
2541
|
|
|
2456
2542
|
// Local paths → CDN URLs (image_url / mask_image_url / additional_images).
|
|
2457
|
-
[image_url, mask_image_url, additional_images] = await Promise.all([
|
|
2543
|
+
[image_url, mask_image_url, additional_images, background_reference_image] = await Promise.all([
|
|
2458
2544
|
rehostLocal(image_url, 'image', project_id),
|
|
2459
2545
|
rehostLocal(mask_image_url, 'image', project_id),
|
|
2460
2546
|
additional_images ? Promise.all(additional_images.map((s) => rehostLocal(s, 'image', project_id))) : additional_images,
|
|
2547
|
+
rehostLocal(background_reference_image, 'image', project_id),
|
|
2461
2548
|
]);
|
|
2462
2549
|
const gen = await client.post('/v1/edit/image', {
|
|
2463
|
-
image_url, operation, model, scale, aspect_ratio, skin_strength, prompt,
|
|
2550
|
+
image_url, operation, model, scale, aspect_ratio, skin_strength, freckle_intensity, fix_lighting, prompt,
|
|
2464
2551
|
mask_image_url, additional_images, generate_all_angles, resolution, quality, ai_optimize,
|
|
2465
2552
|
zoom_out_percentage, expand_left, expand_right, expand_top, expand_bottom,
|
|
2466
2553
|
enhancement_model, output_format,
|
|
2554
|
+
horizontal_angle, vertical_angle, camera_zoom,
|
|
2555
|
+
face_enhancement, face_enhancement_strength, face_enhancement_creativity,
|
|
2556
|
+
subject_detection, crop_to_fill, creativity, resemblance,
|
|
2557
|
+
background_reference_image, negative_prompt, split_layout, mask_expansion,
|
|
2467
2558
|
project_id, session_id
|
|
2468
2559
|
});
|
|
2560
|
+
const cardSettings = {
|
|
2561
|
+
mode: operation, aspect_ratio, resolution,
|
|
2562
|
+
details: editDetails(operation, {
|
|
2563
|
+
scale, skin_strength, freckle_intensity, fix_lighting, generate_all_angles,
|
|
2564
|
+
horizontal_angle, vertical_angle, camera_zoom, face_enhancement, face_enhancement_strength,
|
|
2565
|
+
creativity, resemblance, zoom_out_percentage, expand_left, expand_right, expand_top, expand_bottom,
|
|
2566
|
+
split_layout, background_reference_image, enhancement_model,
|
|
2567
|
+
}),
|
|
2568
|
+
};
|
|
2469
2569
|
|
|
2470
2570
|
if (returnsImmediately()) return submittedResult({
|
|
2471
2571
|
tool: 'edit_image', kind: 'image', gen, client, model,
|
|
2472
|
-
prompt: prompt ||
|
|
2473
|
-
settings:
|
|
2572
|
+
prompt: prompt || '',
|
|
2573
|
+
settings: cardSettings,
|
|
2474
2574
|
reference_images: [image_url, mask_image_url, ...(additional_images || [])].filter(Boolean)
|
|
2475
2575
|
});
|
|
2476
2576
|
|
|
@@ -2483,8 +2583,8 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2483
2583
|
|
|
2484
2584
|
return uiCompleted({
|
|
2485
2585
|
tool: 'edit_image', kind: 'image', gen, client, model,
|
|
2486
|
-
prompt: prompt ||
|
|
2487
|
-
settings:
|
|
2586
|
+
prompt: prompt || '',
|
|
2587
|
+
settings: cardSettings,
|
|
2488
2588
|
reference_images: [image_url, mask_image_url, ...(additional_images || [])].filter(Boolean),
|
|
2489
2589
|
urls: result.result?.urls || [],
|
|
2490
2590
|
credits_used: creditFields(result).credits_used,
|
|
@@ -2515,7 +2615,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2515
2615
|
'Edit operation:',
|
|
2516
2616
|
'"draft_enhance" — render an owned saved draft at full quality in its original project. Pass `resolution` from the draft model capabilities; the server selects its finalization engine. No prompt or provider task ID is needed.',
|
|
2517
2617
|
'"draft_quote" — check a saved draft’s supported final resolutions, expiry and exact credit cost without starting a render. Use this before draft_enhance.',
|
|
2518
|
-
'"upscale" — boost to 4K/2K resolution (use `scale` for factor, `resolution` for target, `target_fps` for frame rate). On model "blackforestlabs/flux-video-upscale" (Flux Video Upscale): `scale` is 1.5-3x (no named `resolution` target — billed on the OUTPUT tier it lands in), `mode` is "precise" (source-faithful, default) or "creative" (reimagines detail — a pricier tier), and `prompt` optionally guides the creative-mode enhancement.',
|
|
2618
|
+
'"upscale" — boost to 4K/2K resolution (use `scale` for factor, `resolution` for target, `target_fps` for frame rate). On model "blackforestlabs/flux-video-upscale" (Flux Video Upscale): `scale` is 1.5-3x (no named `resolution` target — billed on the OUTPUT tier it lands in), `mode` is "precise" (source-faithful, default) or "creative" (reimagines detail — a pricier tier), and `prompt` optionally guides the creative-mode enhancement. On model "bytedance-upscaler/upscale/video" (Bytedance Upscaler): `resolution` is "1080p" | "2k" | "4k" | "6k" | "8k" (OR `scale_ratio` 1.1-10, output ≤4K), plus `target_fps` (24-120), `enhancement_tier`, `enhancement_preset`, `fidelity`, `bit_depth` — see each field. Price = resolution tier (1080p 1 · 2k 2 · 4k 4 · 6k 8 · 8k 16 credits/s) × every started 30 fps (60 = 2×, 120 = 4×) × tier (fast 0.5×, pro 10×). Source-length caps: 6K 120s, 8K 60s, Pro 30s, otherwise 300s.',
|
|
2519
2619
|
'"reframe" — change aspect ratio (requires `aspect_ratio`; use `grid_position_x`/`grid_position_y` to control where the original sits).',
|
|
2520
2620
|
'"generate_audio" — add AI-generated audio from `prompt`. Optionally split into `sound_effect_prompt` and `background_music_prompt`. Set `original_sound=true` to keep original audio alongside.',
|
|
2521
2621
|
'"remove_watermark" — AI-powered watermark removal.',
|
|
@@ -2535,12 +2635,25 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2535
2635
|
scale: z.number().optional()
|
|
2536
2636
|
.describe('Upscale factor (e.g. 2, 4). Used with operation="upscale". On "blackforestlabs/flux-video-upscale" the valid range is 1.5-3 (default 2).'),
|
|
2537
2637
|
resolution: z.string().optional()
|
|
2538
|
-
.describe('Target resolution (e.g. "4k", "2k", "1080p"). Used with "upscale" and "reframe". Not used by "blackforestlabs/flux-video-upscale" — use `scale` instead.'),
|
|
2638
|
+
.describe('Target resolution (e.g. "4k", "2k", "1080p"; Bytedance Upscaler also "6k", "8k"). Used with "upscale" and "reframe". Not used by "blackforestlabs/flux-video-upscale" — use `scale` instead.'),
|
|
2539
2639
|
target_fps: z.number().optional()
|
|
2540
|
-
.describe('Target frame rate (e.g. 24, 30, 60). Used with operation="upscale".'),
|
|
2640
|
+
.describe('Target frame rate (e.g. 24, 30, 60). Used with operation="upscale". Bytedance Upscaler: 24-120, default 30; price multiplies per started 30 fps.'),
|
|
2541
2641
|
enhancement_model: z.string().optional()
|
|
2542
2642
|
.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.'),
|
|
2543
2643
|
|
|
2644
|
+
// Bytedance Upscaler ("bytedance-upscaler/upscale/video") controls. Validated, priced and
|
|
2645
|
+
// duration-capped server-side (kolbo-api services/globalVideoEditing/bytedanceUpscale.js).
|
|
2646
|
+
enhancement_tier: z.enum(['fast', 'standard', 'pro']).optional()
|
|
2647
|
+
.describe('Bytedance Upscaler quality tier for operation="upscale". "fast" = half price, lighter enhancement; "standard" (default); "pro" = large-model restoration at 10× credits, source ≤30s, required for 10/12-bit.'),
|
|
2648
|
+
enhancement_preset: z.enum(['general', 'ugc', 'short_series', 'aigc', 'old_film']).optional()
|
|
2649
|
+
.describe('Bytedance Upscaler content type for operation="upscale": "general" (default), "ugc" (phone/social shorts), "short_series" (short dramas), "aigc" (AI-generated footage), "old_film" (classic film restoration). Same price for all.'),
|
|
2650
|
+
fidelity: z.enum(['high', 'medium']).optional()
|
|
2651
|
+
.describe('Bytedance Upscaler enhancement strength for operation="upscale": "high" (default, mild and source-faithful) or "medium" (stronger, balanced). Same price.'),
|
|
2652
|
+
bit_depth: z.union([z.literal(8), z.literal(10), z.literal(12)]).optional()
|
|
2653
|
+
.describe('Bytedance Upscaler output bit depth for operation="upscale": 8 (default), 10 or 12. 10/12 require enhancement_tier="pro" and are delivered as HEVC.'),
|
|
2654
|
+
scale_ratio: z.number().min(1.1).max(10).optional()
|
|
2655
|
+
.describe('Bytedance Upscaler custom scale (1.1-10) for operation="upscale", used INSTEAD of `resolution`. Output must stay within 4K; billed on the 1080p/2k/4k tier it lands in. For 6K/8K use `resolution`.'),
|
|
2656
|
+
|
|
2544
2657
|
// ── reframe ────────────────────────────────────────────
|
|
2545
2658
|
aspect_ratio: z.string().optional()
|
|
2546
2659
|
.describe(aspectRatioDescribe() + ' Required for operation="reframe".'),
|
|
@@ -2612,6 +2725,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2612
2725
|
video_url, operation, model, aspect_ratio, scale, prompt,
|
|
2613
2726
|
image_url, audio_url, duration, mode,
|
|
2614
2727
|
target_fps, resolution, enhancement_model,
|
|
2728
|
+
enhancement_tier, enhancement_preset, fidelity, bit_depth, scale_ratio,
|
|
2615
2729
|
grid_position_x, grid_position_y,
|
|
2616
2730
|
sound_effect_prompt, background_music_prompt, original_sound, cfg_strength,
|
|
2617
2731
|
audio_format, segments,
|
|
@@ -2645,6 +2759,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2645
2759
|
video_url, operation, model, aspect_ratio, scale, prompt, enhancement_model,
|
|
2646
2760
|
image_url, audio_url, duration, mode,
|
|
2647
2761
|
target_fps, resolution,
|
|
2762
|
+
enhancement_tier, enhancement_preset, fidelity, bit_depth, scale_ratio,
|
|
2648
2763
|
grid_position_x, grid_position_y,
|
|
2649
2764
|
sound_effect_prompt, background_music_prompt, original_sound, cfg_strength,
|
|
2650
2765
|
audio_format, segments,
|
|
@@ -2662,10 +2777,14 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2662
2777
|
};
|
|
2663
2778
|
|
|
2664
2779
|
if (operation === 'draft_quote') return { content: [{ type: 'text', text: JSON.stringify(gen, null, 2) }] };
|
|
2780
|
+
const cardSettings = {
|
|
2781
|
+
mode: operation, duration, aspect_ratio, resolution,
|
|
2782
|
+
details: editDetails(operation, { scale, enhancement_model, target_fps, mode, enhancement_tier, enhancement_preset, bit_depth, scale_ratio }),
|
|
2783
|
+
};
|
|
2665
2784
|
if (returnsImmediately()) return submittedResult({
|
|
2666
2785
|
tool: 'edit_video', kind: 'video', gen, client, model,
|
|
2667
|
-
prompt: prompt ||
|
|
2668
|
-
settings:
|
|
2786
|
+
prompt: prompt || '',
|
|
2787
|
+
settings: cardSettings,
|
|
2669
2788
|
...editRefs
|
|
2670
2789
|
});
|
|
2671
2790
|
|
|
@@ -2678,8 +2797,8 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2678
2797
|
|
|
2679
2798
|
return uiCompleted({
|
|
2680
2799
|
tool: 'edit_video', kind: 'video', gen, client, model,
|
|
2681
|
-
prompt: prompt ||
|
|
2682
|
-
settings:
|
|
2800
|
+
prompt: prompt || '',
|
|
2801
|
+
settings: cardSettings,
|
|
2683
2802
|
...editRefs,
|
|
2684
2803
|
urls: result.result?.urls || [],
|
|
2685
2804
|
duration: result.result?.duration || null,
|