@kolbo/mcp 1.82.5 → 1.83.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/skill/SKILL.md +2 -0
- package/src/apps/index.js +55 -0
- package/src/apps/theme.js +2 -0
- package/src/apps/widgets/plans.js +14 -104
- package/src/tools/editModelCatalog.js +64 -0
- package/src/tools/generate.js +33 -14
- package/src/tools/models.js +13 -1
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -105,10 +105,12 @@ Each `references/models/*.md` mirrors the matching skill prompt in `kolbo-api/sr
|
|
|
105
105
|
|------|-------------|
|
|
106
106
|
| `generate_image` | Single image from a text prompt. Supports Visual DNA, moodboards, image presets (custom instructions live here), reference images, web-search grounding. Named sheets/styles: `list_presets({ type: "image", search: "headless" })` then `preset_id`. |
|
|
107
107
|
| `generate_image_edit` | Edit/transform an existing image. Pass `source_images` + edit prompt. Image-editing presets are supported through `preset_id` from `list_presets({ type: "image_edit" })`. |
|
|
108
|
+
| `edit_image` | Operation-routed image tools such as upscale, reframe, outpaint, background work, inpaint and enhance. Before choosing `model`, call `list_models` with the operation family (`image_upscale`, `image_reframe`, `image_zoom_out`, `background_remove`, `graphics_enhance`, etc.) and pass a concrete returned identifier. `multi_shot` and `split` use pinned processing, so omit `model` for those. |
|
|
108
109
|
| `generate_creative_director` | **2–8 related images or videos as one coherent set.** Use INSTEAD of multiple `generate_image` calls for any related multi-output. |
|
|
109
110
|
| `generate_video` | Text-to-video. Accepts `visual_dna_ids` and `sound_enabled`; `generate_elements` is still the primary reference-driven route for a DNA-anchored film. |
|
|
110
111
|
| `generate_video_from_image` | Animate a still. Prompt describes motion, not subject. |
|
|
111
112
|
| `generate_video_from_video` | Restyle/transform an existing video. Keeps original motion. |
|
|
113
|
+
| `edit_video` | Operation-routed video tools such as upscale, reframe, audio generation, watermark/background removal, face swap, extend, inpaint and retake. Discover the real engines through the operation family (`video_upscale`, `video_reframe`, `video_to_sound`, `video_extend`, etc.), compare caps/cost/`params`, and pass a concrete identifier — never a `kolbo_gateway_*` navigation row. |
|
|
112
114
|
| `generate_elements` | Reference-driven video. **Primary route for DNA → video.** Prompt = Seedance Locked Intro (`Total` + `[GLOBAL LOOK]` / `[CAST]` / `[LOCATION]` + `SHOT N`). Every DNA in `visual_dna_ids` must also be `@Name` in that prompt. |
|
|
113
115
|
| `generate_first_last_frame` | Keyframe interpolation between two frames. |
|
|
114
116
|
| `generate_lipsync` | Lipsync an existing waveform onto a face. **Not the route for dialogue in a film you are generating** — write the line in the Seedance prompt instead. |
|
package/src/apps/index.js
CHANGED
|
@@ -97,6 +97,11 @@ const WIDGET_CSP = {
|
|
|
97
97
|
connectDomains: [
|
|
98
98
|
'https://api.kolbo.ai',
|
|
99
99
|
],
|
|
100
|
+
// Nested iframes. Empty/omitted → frame-src 'none' and the live pricing
|
|
101
|
+
// embed inside the upgrade card is a blank box.
|
|
102
|
+
frameDomains: [
|
|
103
|
+
'https://app.kolbo.ai',
|
|
104
|
+
],
|
|
100
105
|
};
|
|
101
106
|
|
|
102
107
|
/** Register all Kolbo widget resources on an McpServer. */
|
|
@@ -557,6 +562,30 @@ async function canonicalModelId(client, input, type) {
|
|
|
557
562
|
const ids = new Set((narrowed.length ? narrowed : prefixed).map((i) => i.id));
|
|
558
563
|
if (ids.size === 1) return [...ids][0];
|
|
559
564
|
|
|
565
|
+
// The general catalog is cached for widget performance, while list_models
|
|
566
|
+
// is intentionally live. On a just-published model, refresh only the typed
|
|
567
|
+
// family before reporting an unknown identifier so a model discovered one
|
|
568
|
+
// moment ago is immediately usable.
|
|
569
|
+
if (types.length && typeof client.get === 'function') {
|
|
570
|
+
try {
|
|
571
|
+
const freshRows = [];
|
|
572
|
+
for (const expectedType of types) {
|
|
573
|
+
const response = await client.get(`/v1/models?type=${encodeURIComponent(expectedType)}`);
|
|
574
|
+
freshRows.push(...(response?.models || response?.data?.models || []));
|
|
575
|
+
}
|
|
576
|
+
const freshMatches = freshRows.filter((row) => {
|
|
577
|
+
const id = row?.identifier;
|
|
578
|
+
const name = row?.name;
|
|
579
|
+
return (id && (id.toLowerCase() === key || normId(id) === want))
|
|
580
|
+
|| (name && (name.toLowerCase() === key || normId(name) === want));
|
|
581
|
+
});
|
|
582
|
+
const freshIds = [...new Set(freshMatches.map((row) => row.identifier).filter(Boolean))];
|
|
583
|
+
if (freshIds.length === 1) return freshIds[0];
|
|
584
|
+
} catch (_) {
|
|
585
|
+
// Keep the existing actionable near-miss error when the refresh fails.
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
560
589
|
// 4. unknown — name the near misses instead of dead-ending at the API.
|
|
561
590
|
const stem = normId(key.split(/[\s._/-]+/).filter(Boolean)[0] || key);
|
|
562
591
|
const near = [...new Set(
|
|
@@ -572,6 +601,31 @@ async function canonicalModelId(client, input, type) {
|
|
|
572
601
|
);
|
|
573
602
|
}
|
|
574
603
|
|
|
604
|
+
/**
|
|
605
|
+
* Reject a published model that belongs to a different operation family.
|
|
606
|
+
* Hidden/unpublished identifiers still fail open so existing pinned engines
|
|
607
|
+
* remain usable; the API remains authoritative for those.
|
|
608
|
+
*/
|
|
609
|
+
async function assertModelSupportsType(client, modelId, type) {
|
|
610
|
+
if (!modelId || !type) return modelId;
|
|
611
|
+
let all;
|
|
612
|
+
try {
|
|
613
|
+
all = (await modelCatalog(client)).all;
|
|
614
|
+
} catch (_) {
|
|
615
|
+
return modelId;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const row = (all || []).find((item) => item.id === modelId);
|
|
619
|
+
if (!row) return modelId;
|
|
620
|
+
const expected = (Array.isArray(type) ? type : [type]).filter(Boolean);
|
|
621
|
+
if (!expected.length || row.types.some((value) => expected.includes(value))) return modelId;
|
|
622
|
+
|
|
623
|
+
throw new Error(
|
|
624
|
+
`Model "${modelId}" cannot be used for this operation (expected type: ${expected.join(' or ')}). `
|
|
625
|
+
+ `Call list_models with type="${expected[0]}" and pass a concrete identifier it returns.`
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
575
629
|
/* ------------------------------------------------------------------ */
|
|
576
630
|
/* Declaration-level widget metadata */
|
|
577
631
|
/* ------------------------------------------------------------------ */
|
|
@@ -663,6 +717,7 @@ module.exports = {
|
|
|
663
717
|
modelInfoMap,
|
|
664
718
|
voiceInfo,
|
|
665
719
|
canonicalModelId,
|
|
720
|
+
assertModelSupportsType,
|
|
666
721
|
normalizeAspectRatio,
|
|
667
722
|
closestAspectRatio,
|
|
668
723
|
resolveCatalogAspectRatio,
|
package/src/apps/theme.js
CHANGED
|
@@ -183,6 +183,8 @@ html.k-peek-fs .k-peek img, html.k-peek-fs .k-peek video { max-height: calc(100v
|
|
|
183
183
|
.k-pack-head { margin: 14px 0 6px; font-size: 11px; font-weight: 700; letter-spacing: 0.04em;
|
|
184
184
|
text-transform: uppercase; color: var(--text-faint); }
|
|
185
185
|
.k-pack-row { margin-bottom: 6px; }
|
|
186
|
+
.k-pricing-frame { display: block; width: 100%; height: 560px; border: 0; border-radius: 12px;
|
|
187
|
+
background: #0f0f0f; }
|
|
186
188
|
/* Visual DNA chips: the character's face, so you can see WHICH DNA is locked in. */
|
|
187
189
|
.k-dna-face { width: 18px; height: 18px; border-radius: 999px; object-fit: cover; margin-left: -3px; background: var(--border-strong); }
|
|
188
190
|
.k-dna-stack .k-dna-stack-item { display: inline-flex; }
|
|
@@ -6,11 +6,11 @@ const { widgetPage } = require('../html');
|
|
|
6
6
|
* Plans / upgrade widget — shown by `show_plans`, and by any generation the
|
|
7
7
|
* server refused for credits.
|
|
8
8
|
*
|
|
9
|
-
* The
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* The live pricing UI lives on app.kolbo.ai. This card iframes
|
|
10
|
+
* `/pricing/embed` (same PlanList / PricingCard as the site) so a price or
|
|
11
|
+
* perk change does not require an MCP republish. Buy/Subscribe on that page
|
|
12
|
+
* open https://app.kolbo.ai/pricing in a new tab — Stripe and the user's
|
|
13
|
+
* session must not run inside a third-party host iframe.
|
|
14
14
|
*
|
|
15
15
|
* structuredContent contract:
|
|
16
16
|
* {
|
|
@@ -19,8 +19,8 @@ const { widgetPage } = require('../html');
|
|
|
19
19
|
* balance, required, shortfall, // when reason === 'insufficient_credits'
|
|
20
20
|
* current_plan: { key, name },
|
|
21
21
|
* plans: [{ key, name, interval, credits, price, original_price,
|
|
22
|
-
* discount_percent, promo_text, currency }],
|
|
23
|
-
* credit_packs: [
|
|
22
|
+
* discount_percent, promo_text, currency, top_up_discount }],
|
|
23
|
+
* credit_packs: [{ …, subscriber_price, is_subscriber }],
|
|
24
24
|
* pricing_url
|
|
25
25
|
* }
|
|
26
26
|
*/
|
|
@@ -35,10 +35,6 @@ const BODY = `
|
|
|
35
35
|
</div>
|
|
36
36
|
<div class="k-body">
|
|
37
37
|
<div class="k-error" id="notice" style="display:none"></div>
|
|
38
|
-
<div class="k-plan-toggle" id="toggle" style="display:none">
|
|
39
|
-
<button type="button" class="k-toggle-btn" data-interval="month">Monthly</button>
|
|
40
|
-
<button type="button" class="k-toggle-btn" data-interval="year">Annual</button>
|
|
41
|
-
</div>
|
|
42
38
|
<div id="stage"></div>
|
|
43
39
|
<div class="k-actions" id="actions"></div>
|
|
44
40
|
</div>
|
|
@@ -50,7 +46,7 @@ const BODY = `
|
|
|
50
46
|
|
|
51
47
|
const SCRIPT = `
|
|
52
48
|
var state = null;
|
|
53
|
-
var
|
|
49
|
+
var PRICING_EMBED = 'https://app.kolbo.ai/pricing/embed';
|
|
54
50
|
|
|
55
51
|
el('logo').innerHTML = KOLBO_LOGO + '<span>Kolbo</span>';
|
|
56
52
|
el('kolbo-link').onclick = function (e) { e.preventDefault(); window.kolbo.openLink('https://app.kolbo.ai'); };
|
|
@@ -58,17 +54,6 @@ el('kolbo-link').onclick = function (e) { e.preventDefault(); window.kolbo.openL
|
|
|
58
54
|
function pricingUrl() {
|
|
59
55
|
return (state && state.pricing_url) || 'https://app.kolbo.ai/pricing';
|
|
60
56
|
}
|
|
61
|
-
function money(amount, currency) {
|
|
62
|
-
if (amount == null) return '';
|
|
63
|
-
var sym = String(currency || 'usd').toLowerCase() === 'usd' ? '$' : '';
|
|
64
|
-
var n = Math.round(Number(amount) * 100) / 100;
|
|
65
|
-
return sym + n + (sym ? '' : ' ' + String(currency || '').toUpperCase());
|
|
66
|
-
}
|
|
67
|
-
function perMonthNote(plan) {
|
|
68
|
-
if (plan.interval !== 'year' || plan.price == null) return '';
|
|
69
|
-
var monthly = Math.round((Number(plan.price) / 12) * 100) / 100;
|
|
70
|
-
return money(monthly, plan.currency) + '/mo, billed annually';
|
|
71
|
-
}
|
|
72
57
|
|
|
73
58
|
function boot(sc) {
|
|
74
59
|
if (!sc) return;
|
|
@@ -90,86 +75,12 @@ function boot(sc) {
|
|
|
90
75
|
: 'That generation needs more credits than you have left.');
|
|
91
76
|
}
|
|
92
77
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (
|
|
97
|
-
el('toggle').style.display = '';
|
|
98
|
-
if (!intervals[interval]) interval = intervals.year ? 'year' : 'month';
|
|
99
|
-
} else {
|
|
100
|
-
interval = intervals.year ? 'year' : 'month';
|
|
101
|
-
}
|
|
102
|
-
wireToggle();
|
|
103
|
-
render();
|
|
104
|
-
window.kolbo.notifySize();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function wireToggle() {
|
|
108
|
-
Array.prototype.forEach.call(document.querySelectorAll('.k-toggle-btn'), function (b) {
|
|
109
|
-
b.onclick = function () { interval = b.getAttribute('data-interval'); render(); window.kolbo.notifySize(); };
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function render() {
|
|
114
|
-
Array.prototype.forEach.call(document.querySelectorAll('.k-toggle-btn'), function (b) {
|
|
115
|
-
b.classList.toggle('active', b.getAttribute('data-interval') === interval);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
var plans = (state.plans || []).filter(function (p) { return p.interval === interval; });
|
|
119
|
-
// Cheapest first so the ladder reads left-to-right. A zero-price plan is not
|
|
120
|
-
// an upgrade path, so it never takes a card slot.
|
|
121
|
-
plans = plans.filter(function (p) { return Number(p.price) > 0; });
|
|
122
|
-
plans.sort(function (a, b) { return (a.price || 0) - (b.price || 0); });
|
|
123
|
-
|
|
124
|
-
var current = state.current_plan && state.current_plan.key;
|
|
125
|
-
var html = plans.length
|
|
126
|
-
? '<div class="k-plan-grid">' + plans.map(function (p) { return planCard(p, current); }).join('') + '</div>'
|
|
127
|
-
: '<div class="k-empty">Plan details are on the pricing page</div>';
|
|
128
|
-
|
|
129
|
-
var packs = (state.credit_packs || []).filter(function (p) { return Number(p.price) > 0; });
|
|
130
|
-
if (packs.length) {
|
|
131
|
-
packs.sort(function (a, b) { return (a.price || 0) - (b.price || 0); });
|
|
132
|
-
html += '<div class="k-pack-head">One-time credit packs</div>' +
|
|
133
|
-
packs.slice(0, 4).map(function (p) {
|
|
134
|
-
return '<div class="k-audio-row k-pack-row"><div class="k-audio-meta">' +
|
|
135
|
-
'<div class="k-audio-title">' + esc(p.name || '') + '</div>' +
|
|
136
|
-
(p.credits != null ? '<div class="k-audio-sub">' + esc(String(p.credits)) + ' credits</div>' : '') +
|
|
137
|
-
'</div><span class="k-chip">' + esc(money(p.price, p.currency)) + '</span></div>';
|
|
138
|
-
}).join('');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
el('stage').innerHTML = html;
|
|
142
|
-
Array.prototype.forEach.call(el('stage').querySelectorAll('[data-buy]'), function (b) {
|
|
143
|
-
b.onclick = function () { window.kolbo.openLink(pricingUrl()); };
|
|
144
|
-
});
|
|
78
|
+
el('stage').innerHTML = '<iframe class="k-pricing-frame" src="' + PRICING_EMBED +
|
|
79
|
+
'" title="Kolbo plans" referrerpolicy="no-referrer-when-downgrade"></iframe>';
|
|
80
|
+
var frame = el('stage').querySelector('iframe');
|
|
81
|
+
if (frame) frame.onload = function () { window.kolbo.notifySize(); };
|
|
145
82
|
renderActions();
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
function planCard(p, currentKey) {
|
|
149
|
-
var isCurrent = currentKey && p.key === currentKey;
|
|
150
|
-
var badges = '';
|
|
151
|
-
if (p.discount_percent) badges += '<span class="k-plan-badge">' + esc(String(p.discount_percent)) + '% OFF</span>';
|
|
152
|
-
if (isCurrent) badges += '<span class="k-plan-badge current">Current</span>';
|
|
153
|
-
|
|
154
|
-
var price = '<span class="k-plan-price">' + esc(money(p.price, p.currency)) + '</span>';
|
|
155
|
-
if (p.original_price && p.original_price > p.price) {
|
|
156
|
-
price = '<span class="k-plan-was">' + esc(money(p.original_price, p.currency)) + '</span> ' + price;
|
|
157
|
-
}
|
|
158
|
-
var note = perMonthNote(p);
|
|
159
|
-
|
|
160
|
-
return '<div class="k-plan' + (isCurrent ? ' current' : '') + '">' +
|
|
161
|
-
'<div class="k-plan-top"><span class="k-plan-name">' + esc(p.name || p.key || '') + '</span>' + badges + '</div>' +
|
|
162
|
-
(p.credits != null
|
|
163
|
-
? '<div class="k-plan-credits">' + ICONS.sparkle + ' ' + esc(String(p.credits)) + ' credits' +
|
|
164
|
-
(p.interval === 'month' ? '/mo' : p.interval === 'year' ? '/yr' : '') + '</div>'
|
|
165
|
-
: '') +
|
|
166
|
-
'<div class="k-plan-pricing">' + price + '</div>' +
|
|
167
|
-
(note ? '<div class="k-plan-note">' + esc(note) + '</div>' : '') +
|
|
168
|
-
(p.promo_text ? '<div class="k-plan-note">' + esc(p.promo_text) + '</div>' : '') +
|
|
169
|
-
(isCurrent
|
|
170
|
-
? '<button class="k-btn" disabled>Your plan</button>'
|
|
171
|
-
: '<button class="k-btn primary" data-buy="' + esc(p.key || '') + '">Get ' + esc(p.name || 'plan') + '</button>') +
|
|
172
|
-
'</div>';
|
|
83
|
+
window.kolbo.notifySize();
|
|
173
84
|
}
|
|
174
85
|
|
|
175
86
|
function renderActions() {
|
|
@@ -184,8 +95,7 @@ window.kolbo.onToolResult(function (result) {
|
|
|
184
95
|
});
|
|
185
96
|
window.kolbo.onToolInput(function () {
|
|
186
97
|
if (state) return;
|
|
187
|
-
el('stage').innerHTML = '<div class="k-
|
|
188
|
-
+ '<div class="k-skel square" style="min-height:120px"></div></div>';
|
|
98
|
+
el('stage').innerHTML = '<div class="k-skel square" style="min-height:240px"></div>';
|
|
189
99
|
window.kolbo.notifySize();
|
|
190
100
|
});
|
|
191
101
|
window.kolbo.ready(function (ctx) {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Operation-specific model catalogs for edit_image / edit_video.
|
|
3
|
+
*
|
|
4
|
+
* The `kolbo_gateway_*` rows in video_to_video are navigation aliases used by
|
|
5
|
+
* Kolbo's web picker. They are not provider engines. MCP callers must discover
|
|
6
|
+
* and submit a concrete model from the operation's real DB type instead.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const VIDEO_EDIT_MODEL_TYPES = Object.freeze({
|
|
10
|
+
upscale: 'video_upscale',
|
|
11
|
+
reframe: 'video_reframe',
|
|
12
|
+
generate_audio: 'video_to_sound',
|
|
13
|
+
remove_watermark: 'video_watermark_removal',
|
|
14
|
+
face_swap: 'video_face_swap',
|
|
15
|
+
extend: 'video_extend',
|
|
16
|
+
magic_edit: 'video_to_video',
|
|
17
|
+
lipsync: 'lipsync-video',
|
|
18
|
+
remove_background: 'video_background_removal',
|
|
19
|
+
inpaint: 'video_inpaint',
|
|
20
|
+
retake: 'video_retake',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const IMAGE_EDIT_MODEL_TYPES = Object.freeze({
|
|
24
|
+
upscale: 'image_upscale',
|
|
25
|
+
clarity_upscale: 'image_upscale',
|
|
26
|
+
reframe: 'image_reframe',
|
|
27
|
+
zoom_out: 'image_zoom_out',
|
|
28
|
+
inpaint: 'inpaint',
|
|
29
|
+
erase: 'erase',
|
|
30
|
+
face_swap: 'face_swap',
|
|
31
|
+
background_remove: 'background_remove',
|
|
32
|
+
removebg: 'background_remove',
|
|
33
|
+
background_replace: 'background_replace',
|
|
34
|
+
magic_edit: 'image_editing',
|
|
35
|
+
camera_angle: 'image_editing',
|
|
36
|
+
enhance_skin: 'skin_enhancer',
|
|
37
|
+
enhance: 'graphics_enhance',
|
|
38
|
+
// The API intentionally pins multi_shot to its dedicated engine.
|
|
39
|
+
multi_shot: null,
|
|
40
|
+
split_upscale: 'image_upscale',
|
|
41
|
+
split: null,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function modelTypeForEditOperation(kind, operation) {
|
|
45
|
+
const map = kind === 'image' ? IMAGE_EDIT_MODEL_TYPES : VIDEO_EDIT_MODEL_TYPES;
|
|
46
|
+
return map[operation] || null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function assertExecutableEditModel(model, kind, operation) {
|
|
50
|
+
if (!model || !/^kolbo_gateway_/i.test(String(model))) return;
|
|
51
|
+
const type = modelTypeForEditOperation(kind, operation);
|
|
52
|
+
throw new Error(
|
|
53
|
+
`"${model}" is a Kolbo navigation alias, not an executable AI model. ` +
|
|
54
|
+
`Call list_models with type="${type || (kind === 'image' ? 'image_editing' : 'video_to_video')}" ` +
|
|
55
|
+
`and pass one of the concrete model identifiers it returns.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
VIDEO_EDIT_MODEL_TYPES,
|
|
61
|
+
IMAGE_EDIT_MODEL_TYPES,
|
|
62
|
+
modelTypeForEditOperation,
|
|
63
|
+
assertExecutableEditModel,
|
|
64
|
+
};
|
package/src/tools/generate.js
CHANGED
|
@@ -8,7 +8,8 @@ const FormData = require('form-data');
|
|
|
8
8
|
const { pollUntilDone, waitWindowMs } = require('../polling');
|
|
9
9
|
const { resolveToBuffer, pollOrTimedOut, creditFields, projectIdField, sessionIdField, inlineImageBlocks, linkFields, uiGenerating, uiCompleted, appsEnabled } = require('./_shared');
|
|
10
10
|
const { ownedUrl } = require('./owned-url');
|
|
11
|
-
const { UI, uiResult, canonicalModelId, modelInfo, voiceInfo, resolveCatalogAspectRatio } = require('../apps');
|
|
11
|
+
const { UI, uiResult, canonicalModelId, assertModelSupportsType, modelInfo, voiceInfo, resolveCatalogAspectRatio } = require('../apps');
|
|
12
|
+
const { modelTypeForEditOperation, assertExecutableEditModel } = require('./editModelCatalog');
|
|
12
13
|
|
|
13
14
|
// ─── Cinematic Dimensions schema (shared by generate_image + generate_image_edit) ───
|
|
14
15
|
// Kolbo's "Cinema mode": eight independent photographic dimensions, each an OPTIONAL
|
|
@@ -1979,7 +1980,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1979
1980
|
].join(' ')),
|
|
1980
1981
|
|
|
1981
1982
|
model: z.string().optional()
|
|
1982
|
-
.describe('
|
|
1983
|
+
.describe('Concrete model identifier for this operation. Dynamically discover it with list_models using the operation-specific type: upscale/clarity_upscale/split_upscale → "image_upscale"; reframe → "image_reframe"; zoom_out → "image_zoom_out"; inpaint → "inpaint"; erase → "erase"; face_swap → "face_swap"; removebg → "background_remove"; background_replace → "background_replace"; enhance_skin → "skin_enhancer"; enhance → "graphics_enhance"; magic_edit/camera_angle → "image_editing". multi_shot and split use pinned/non-selectable processing, so omit model for those operations. Omit elsewhere to use the platform default. Never pass a kolbo_gateway_* navigation alias.'),
|
|
1983
1984
|
|
|
1984
1985
|
// ── upscale ────────────────────────────────────────────
|
|
1985
1986
|
scale: z.number().optional()
|
|
@@ -2049,11 +2050,15 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2049
2050
|
enhancement_model, output_format,
|
|
2050
2051
|
project_id, session_id
|
|
2051
2052
|
}) => {
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2053
|
+
const editModelType = modelTypeForEditOperation('image', operation);
|
|
2054
|
+
if ((operation === 'multi_shot' || operation === 'split') && model) {
|
|
2055
|
+
throw new Error(`model is not configurable for ${operation}; omit it to use the operation's pinned processing path`);
|
|
2056
|
+
}
|
|
2057
|
+
assertExecutableEditModel(model, 'image', operation);
|
|
2058
|
+
model = await canonicalModelId(client, model, editModelType || undefined);
|
|
2059
|
+
assertExecutableEditModel(model, 'image', operation);
|
|
2060
|
+
await assertModelSupportsType(client, model, editModelType || undefined);
|
|
2061
|
+
aspect_ratio = await resolveCatalogAspectRatio(client, model, aspect_ratio, editModelType || undefined);
|
|
2057
2062
|
|
|
2058
2063
|
// Basic validation
|
|
2059
2064
|
if (operation === 'reframe' && !aspect_ratio) throw new Error('aspect_ratio is required for reframe');
|
|
@@ -2133,7 +2138,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2133
2138
|
].join(' ')),
|
|
2134
2139
|
|
|
2135
2140
|
model: z.string().optional()
|
|
2136
|
-
.describe('
|
|
2141
|
+
.describe('Concrete model identifier for this operation. Dynamically discover it with list_models using the operation-specific type: upscale → "video_upscale"; reframe → "video_reframe"; generate_audio → "video_to_sound"; remove_watermark → "video_watermark_removal"; face_swap → "video_face_swap"; extend → "video_extend"; magic_edit → "video_to_video"; lipsync → "lipsync-video"; remove_background → "video_background_removal"; inpaint → "video_inpaint"; retake → "video_retake". Compare the returned credit, supported resolutions/aspect ratios, duration limits, resolution multipliers, and params, then pass a concrete identifier. Omit to use the platform default. Never pass a kolbo_gateway_* navigation alias.'),
|
|
2137
2142
|
|
|
2138
2143
|
// ── upscale ────────────────────────────────────────────
|
|
2139
2144
|
scale: z.number().optional()
|
|
@@ -2164,6 +2169,14 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2164
2169
|
.describe('When true, keeps the original video audio and mixes in the generated audio. Used with "generate_audio". Default: false.'),
|
|
2165
2170
|
cfg_strength: z.number().optional()
|
|
2166
2171
|
.describe('Guidance strength for audio generation (higher = follows prompt more strictly). Used with "generate_audio".'),
|
|
2172
|
+
audio_format: z.enum(['wav', 'mp3', 'aac', 'flac']).optional()
|
|
2173
|
+
.describe('Separate generated-audio format for Sonilo sound-effects models. Read output_audio_formats/default_output_audio_format from list_models; default is "aac".'),
|
|
2174
|
+
segments: z.array(z.object({
|
|
2175
|
+
start: z.number().nonnegative(),
|
|
2176
|
+
end: z.number().positive(),
|
|
2177
|
+
prompt: z.string()
|
|
2178
|
+
})).optional()
|
|
2179
|
+
.describe('Optional contiguous Sonilo sound-design ranges. The first start must be 0, each end must equal the next start, and the final end must not exceed the video duration. Omit to let Sonilo detect scenes automatically.'),
|
|
2167
2180
|
|
|
2168
2181
|
// ── face_swap ──────────────────────────────────────────
|
|
2169
2182
|
image_url: z.string().optional()
|
|
@@ -2210,21 +2223,26 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2210
2223
|
target_fps, resolution, enhancement_model,
|
|
2211
2224
|
grid_position_x, grid_position_y,
|
|
2212
2225
|
sound_effect_prompt, background_music_prompt, original_sound, cfg_strength,
|
|
2226
|
+
audio_format, segments,
|
|
2213
2227
|
refine_edges, subject_is_person,
|
|
2214
2228
|
text_prompt, context,
|
|
2215
2229
|
mask_video_url, object_prompt, video_strength,
|
|
2216
2230
|
start_time,
|
|
2217
2231
|
project_id, session_id
|
|
2218
2232
|
}) => {
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
model
|
|
2223
|
-
|
|
2233
|
+
const editModelType = modelTypeForEditOperation('video', operation);
|
|
2234
|
+
assertExecutableEditModel(model, 'video', operation);
|
|
2235
|
+
model = await canonicalModelId(client, model, editModelType || undefined);
|
|
2236
|
+
assertExecutableEditModel(model, 'video', operation);
|
|
2237
|
+
await assertModelSupportsType(client, model, editModelType || undefined);
|
|
2238
|
+
aspect_ratio = await resolveCatalogAspectRatio(client, model, aspect_ratio, editModelType || undefined);
|
|
2224
2239
|
|
|
2225
2240
|
// Validation
|
|
2226
2241
|
if (operation === 'magic_edit' && !prompt) throw new Error('prompt is required for magic_edit');
|
|
2227
|
-
if (operation === 'generate_audio'&& !prompt
|
|
2242
|
+
if (operation === 'generate_audio' && !prompt && !sound_effect_prompt && !background_music_prompt
|
|
2243
|
+
&& !(typeof model === 'string' && model.includes('sonilo'))) {
|
|
2244
|
+
throw new Error('prompt is required for generate_audio unless dedicated sound/music prompts are provided or the selected model supports automatic captioning');
|
|
2245
|
+
}
|
|
2228
2246
|
if (operation === 'reframe' && !aspect_ratio)throw new Error('aspect_ratio is required for reframe');
|
|
2229
2247
|
if (operation === 'face_swap' && !image_url) throw new Error('image_url (reference face) is required for face_swap');
|
|
2230
2248
|
if (operation === 'lipsync' && !audio_url && !text_prompt) throw new Error('audio_url or text_prompt is required for lipsync');
|
|
@@ -2236,6 +2254,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2236
2254
|
target_fps, resolution,
|
|
2237
2255
|
grid_position_x, grid_position_y,
|
|
2238
2256
|
sound_effect_prompt, background_music_prompt, original_sound, cfg_strength,
|
|
2257
|
+
audio_format, segments,
|
|
2239
2258
|
refine_edges, subject_is_person,
|
|
2240
2259
|
text_prompt, context,
|
|
2241
2260
|
mask_video_url, object_prompt, video_strength,
|
package/src/tools/models.js
CHANGED
|
@@ -15,6 +15,18 @@ const TYPE_GROUPS = {
|
|
|
15
15
|
text_to_speech: 'Voice',
|
|
16
16
|
image_editing: 'Image Editing',
|
|
17
17
|
video_to_video: 'Video to Video',
|
|
18
|
+
image_upscale: 'Image Upscale',
|
|
19
|
+
image_reframe: 'Image Reframe',
|
|
20
|
+
image_zoom_out: 'Image Expand',
|
|
21
|
+
video_upscale: 'Video Upscale',
|
|
22
|
+
video_reframe: 'Video Reframe',
|
|
23
|
+
video_background_removal: 'Video Background Removal',
|
|
24
|
+
video_to_sound: 'Video Audio Generation',
|
|
25
|
+
video_face_swap: 'Video Face Swap',
|
|
26
|
+
video_watermark_removal: 'Video Watermark Removal',
|
|
27
|
+
video_extend: 'Video Extend',
|
|
28
|
+
video_inpaint: 'Video Inpaint',
|
|
29
|
+
video_retake: 'Video Retake',
|
|
18
30
|
elements: 'Elements',
|
|
19
31
|
};
|
|
20
32
|
|
|
@@ -105,7 +117,7 @@ function registerModelTools(server, client, options = {}) {
|
|
|
105
117
|
'list_models',
|
|
106
118
|
'List available AI models on Kolbo. Filter by `type` to narrow to a generation type, and pass `format: "json"` to enumerate the catalog with exact identifiers — `format: "json"` + `type` returns the full raw model documents (every constraint field, for programmatic comparison / cap validation before submitting a generation); `format: "json"` alone returns a compact index of EVERY model and its identifier. Default `format: "text"` returns the human-readable summary. NEVER guess a model identifier: call this tool. ⚠️ COST: for any model whose type is video / firstlast / elements / motion_graphic / cast, `credit` is a PER-SECOND rate, not a per-clip price — multiply by the requested `duration` before quoting cost to the user (e.g. `credit: 9` at `duration: 8` is 72 credits, not 9). This is the universal rule, not a per-model exception. The one carve-out is a model with `flat_credit_by_resolution` set — those charge the flat rate regardless of duration. Every other model type (image, audio, 3D, per-token text) already bills flat per generation as `credit` states.',
|
|
107
119
|
{
|
|
108
|
-
type: z.string().optional().describe('Filter by DB type name: "text_to_img", "image_editing", "text_to_video", "img_to_video", "draw_to_video", "video_to_video", "elements", "firstlastgenerations", "lipsync-image", "lipsync-video", "music_gen", "text_to_speech", "text_to_sound", "stt", "text". Legacy aliases also accepted: "image", "image_edit", "video", "video_from_image", "video_from_video", "music", "speech", "sound", "chat", "lipsync"
|
|
120
|
+
type: z.string().optional().describe('Filter by DB type name. Generation: "text_to_img", "image_editing", "text_to_video", "img_to_video", "draw_to_video", "video_to_video", "elements", "firstlastgenerations", "lipsync-image", "lipsync-video", "music_gen", "text_to_speech", "text_to_sound", "stt", "text". Image-edit engines: "image_upscale", "image_reframe", "image_zoom_out", "inpaint", "erase", "face_swap", "background_remove", "background_replace", "skin_enhancer", "graphics_enhance". Video-edit engines: "video_upscale", "video_reframe", "video_background_removal", "video_to_sound", "video_face_swap", "video_watermark_removal", "video_extend", "video_inpaint", "video_retake". For edit_image/edit_video, query the operation-specific type and pass a CONCRETE returned identifier; never submit a kolbo_gateway_* row, because those are web-navigation aliases rather than AI engines. Legacy aliases also accepted: "image", "image_edit", "video", "video_from_image", "video_from_video", "music", "speech", "sound", "chat", "lipsync", "three_d", "first_last_frame", "transcription". Omit for all models.'),
|
|
109
121
|
format: z.enum(['text', 'json']).optional().describe('Output format. "text" (default) returns a human-readable summary with the most-used caps. "json" is the source of truth for identifiers and caps: with `type` it returns the raw model documents from the API (identifier, credit, supported_durations, supported_resolutions, supported_aspect_ratios, max_reference_images, max_visual_dna, max_video_duration, …) for EVERY model of that type; without `type` it returns a compact index of every model in the catalog and its exact identifier. Use it whenever you need an identifier you have not seen listed, or must verify a cap before passing a value that might exceed a model-specific limit.'),
|
|
110
122
|
display_catalog: z.boolean().optional().describe('Set true when the USER explicitly asked to see/browse the available models — the visual catalog opens expanded. Leave unset for internal lookups (verifying a model name, checking caps before a generation): the catalog stays collapsed to a single row the user can tap to browse.')
|
|
111
123
|
},
|