@kolbo/mcp 1.81.4 → 1.81.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/apps/bridge.js +4 -0
- package/src/apps/html.js +2 -1
- package/src/apps/index.js +1 -0
- package/src/apps/theme.js +2 -1
- package/src/apps/widgets/catalog.js +1 -1
- package/src/apps/widgets/generation.js +72 -17
- package/src/apps/widgets/list.js +6 -6
- package/src/apps/widgets/mediaGrid.js +3 -6
- package/src/tools/_shared.js +60 -11
- package/src/tools/generate.js +43 -11
- package/src/tools/projects.js +41 -9
package/README.md
CHANGED
|
@@ -58,12 +58,12 @@ Or add the config by hand — this block is identical for every MCP client and c
|
|
|
58
58
|
|
|
59
59
|
Restart your app, then ask it to generate something. The first time, a Kolbo login opens in your browser — click **Allow** (no API key to create). _Prefer an API key? Create one at [app.kolbo.ai/developer](https://app.kolbo.ai/developer) and add `"env": { "KOLBO_API_KEY": "kolbo_live_..." }` to the block above._
|
|
60
60
|
|
|
61
|
-
### Browser-only (claude.ai / ChatGPT / Codex): connector +
|
|
61
|
+
### Browser-only (claude.ai / ChatGPT / Codex): connector + skill
|
|
62
62
|
|
|
63
63
|
1. Add the custom connector **`https://api.kolbo.ai/mcp`** under Settings → Connectors, then Connect → log in → Allow.
|
|
64
|
-
2. Download
|
|
64
|
+
2. [Download the Skill](https://kolbo.ai/skills/kolbo.zip) and upload it (Claude.ai: Settings → Features → Skills; ChatGPT/Codex: Settings → Skills). Codex CLI: unzip into `~/.codex/skills/`.
|
|
65
65
|
|
|
66
|
-
The
|
|
66
|
+
The Skill is the canonical routing layer. Without it the tools still work, but the model will not load Seedance / Visual DNA / filmmaking rules.
|
|
67
67
|
|
|
68
68
|
### Optional upgrade: add the Kolbo skill for slash-commands + smart routing
|
|
69
69
|
|
package/package.json
CHANGED
package/src/apps/bridge.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* .onThemeChange(fn) — fn(hostContext) on host-context-changed
|
|
23
23
|
* .callTool(name, args) — Promise<CallToolResult>
|
|
24
24
|
* .sendMessage(text) — append a user chat message (returns Promise)
|
|
25
|
+
* .insertText(text) — paste into the host composer, do not send
|
|
25
26
|
* .openLink(url) — open external URL
|
|
26
27
|
* .copyText(text) — copy via the host clipboard (ui/copy-text)
|
|
27
28
|
* .notifySize() — report content size to host
|
|
@@ -183,6 +184,9 @@ const BRIDGE_JS = `
|
|
|
183
184
|
sendMessage: function (text) {
|
|
184
185
|
return request('ui/message', { role: 'user', content: [{ type: 'text', text: text }] });
|
|
185
186
|
},
|
|
187
|
+
insertText: function (text) {
|
|
188
|
+
return request('ui/insert-text', { text: text });
|
|
189
|
+
},
|
|
186
190
|
openLink: function (url) { return request('ui/open-link', { url: url }); },
|
|
187
191
|
copyText: function (text) { return request('ui/copy-text', { text: text }); },
|
|
188
192
|
// Hand a piece of this widget's media to the host's composer. Dragging it
|
package/src/apps/html.js
CHANGED
|
@@ -136,7 +136,8 @@ function modelChipHTML(name, iconUrl) {
|
|
|
136
136
|
return '<span class="k-chip brand">' + inner + esc(name) + '</span>';
|
|
137
137
|
}
|
|
138
138
|
function monogram(name) {
|
|
139
|
-
|
|
139
|
+
var ch = String(name || '').replace(/^[@#]+/, '').trim().charAt(0) || '?';
|
|
140
|
+
return '<span class="k-mono-icon">' + esc(ch.toUpperCase()) + '</span>';
|
|
140
141
|
}
|
|
141
142
|
// In-widget preview overlay — every card (generation chips, media grid, list
|
|
142
143
|
// rows) uses this so a DNA / reference / library thumb opens a popup in Kolbo
|
package/src/apps/index.js
CHANGED
package/src/apps/theme.js
CHANGED
|
@@ -340,7 +340,8 @@ html.k-fullscreen .k-actions { flex: none; padding-top: 8px; }
|
|
|
340
340
|
background: var(--brand-soft); }
|
|
341
341
|
.k-audio-meta { flex: 1; min-width: 0; }
|
|
342
342
|
.k-audio-title { font-size: 12.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
343
|
-
.k-audio-sub { font-size: 11px; color: var(--text-faint);
|
|
343
|
+
.k-audio-sub { font-size: 11px; color: var(--text-faint);
|
|
344
|
+
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
344
345
|
.k-generated-audio {
|
|
345
346
|
display: grid; grid-template-columns: 40px minmax(0, 1fr) auto;
|
|
346
347
|
align-items: center; gap: 7px 10px;
|
|
@@ -67,7 +67,7 @@ function boot(sc) {
|
|
|
67
67
|
Array.prototype.forEach.call(document.querySelectorAll('[data-g]'), function (row) {
|
|
68
68
|
row.onclick = function () {
|
|
69
69
|
var m = state.groups[+row.getAttribute('data-g')].models[+row.getAttribute('data-m')];
|
|
70
|
-
window.kolbo.
|
|
70
|
+
window.kolbo.insertText(m.identifier || m.name || '');
|
|
71
71
|
};
|
|
72
72
|
});
|
|
73
73
|
window.kolbo.notifySize();
|
|
@@ -75,6 +75,34 @@ var TOOL_TITLES = {
|
|
|
75
75
|
generate_creative_director: 'Creative Director', edit_image: 'Image Edit', edit_video: 'Video Edit',
|
|
76
76
|
get_generation_status: 'Generations'
|
|
77
77
|
};
|
|
78
|
+
var OPEN_ROUTES = {
|
|
79
|
+
generate_image: { path: '/image-tools', tool: 'text-to-image' },
|
|
80
|
+
generate_image_edit: { path: '/image-tools', tool: 'image-editing' },
|
|
81
|
+
edit_image: { path: '/image-tools', tool: 'image-editing' },
|
|
82
|
+
generate_video: { path: '/video-tools', tool: 'text-to-video' },
|
|
83
|
+
generate_video_from_image: { path: '/video-tools', tool: 'image-to-video' },
|
|
84
|
+
generate_elements: { path: '/video-tools', tool: 'image-to-video', mode: 'elements' },
|
|
85
|
+
generate_first_last_frame: { path: '/video-tools', tool: 'image-to-video', mode: 'first-last' },
|
|
86
|
+
generate_video_from_video: { path: '/video-tools', tool: 'video-to-video' },
|
|
87
|
+
generate_lipsync: { path: '/video-tools', tool: 'lipsync' },
|
|
88
|
+
generate_music: { path: '/audio-tools', tool: 'music-generator' },
|
|
89
|
+
generate_speech: { path: '/audio-tools', tool: 'text-to-speech' },
|
|
90
|
+
generate_sound: { path: '/audio-tools', tool: 'text-to-sound' },
|
|
91
|
+
transcribe_audio: { path: '/audio-tools', tool: 'speech-to-text' },
|
|
92
|
+
generate_creative_director: { path: '/creative-director' }
|
|
93
|
+
};
|
|
94
|
+
function kolboUrl(sc) {
|
|
95
|
+
if (sc && typeof sc.open_url === 'string' && sc.open_url) return sc.open_url;
|
|
96
|
+
var sid = sc && (sc.session_id || sc.sessionId);
|
|
97
|
+
var route = OPEN_ROUTES[(sc && sc.tool) || originTool];
|
|
98
|
+
if (!route || !sid) return 'https://app.kolbo.ai';
|
|
99
|
+
var url = 'https://app.kolbo.ai' + route.path + '?session=' + encodeURIComponent(sid);
|
|
100
|
+
if (route.tool) url += '&tool=' + route.tool;
|
|
101
|
+
if (route.mode) url += '&mode=' + route.mode;
|
|
102
|
+
var pid = sc && (sc.project_id || sc.projectId);
|
|
103
|
+
if (pid) url += '&project=' + encodeURIComponent(pid);
|
|
104
|
+
return url;
|
|
105
|
+
}
|
|
78
106
|
|
|
79
107
|
// Long text is clamped by CSS (.k-prompt 2 lines / .k-caption 1 line). Expand
|
|
80
108
|
// lives on a separate button so the text itself stays selectable.
|
|
@@ -488,20 +516,20 @@ function stopNow(sc, spec) {
|
|
|
488
516
|
var refund = 0;
|
|
489
517
|
sts.forEach(function (s) { if (s.credits_refunded) refund += s.credits_refunded; });
|
|
490
518
|
return {
|
|
491
|
-
cancelled: sts.some(function (s) { return s.cancelled
|
|
519
|
+
cancelled: sts.some(function (s) { return s.cancelled === true; }),
|
|
492
520
|
credits_refunded: refund || undefined
|
|
493
521
|
};
|
|
494
522
|
})
|
|
495
523
|
: window.kolbo.callTool(spec.tool, spec.args).then(function (r) { return structured(r) || {}; });
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
524
|
+
call.then(function (st) {
|
|
525
|
+
if (!st || st.cancelled !== true) {
|
|
526
|
+
// Empty host ack used to land here as {} and the card painted
|
|
527
|
+
// cancelled while the job kept running. Only an explicit cancel counts.
|
|
528
|
+
cancelRequested = false;
|
|
529
|
+
renderStopButton(sc);
|
|
530
|
+
return schedulePoll(sc);
|
|
531
|
+
}
|
|
532
|
+
renderCancelled(st.credits_refunded);
|
|
505
533
|
}).catch(function () {
|
|
506
534
|
cancelRequested = false;
|
|
507
535
|
renderStopButton(sc);
|
|
@@ -608,7 +636,12 @@ function poll(sc) {
|
|
|
608
636
|
var done = Object.assign({}, sc, r, {
|
|
609
637
|
phase: 'completed',
|
|
610
638
|
urls: r.urls || st.urls || [],
|
|
611
|
-
credits_used: st.credits_used != null ? st.credits_used : sc.credits_used
|
|
639
|
+
credits_used: st.credits_used != null ? st.credits_used : sc.credits_used,
|
|
640
|
+
// Status structuredContent used to set open_url:undefined and wipe the
|
|
641
|
+
// session deep-link the generating card already had.
|
|
642
|
+
open_url: (sc && sc.open_url) || r.open_url,
|
|
643
|
+
session_id: (sc && (sc.session_id || sc.sessionId)) || r.session_id || st.session_id,
|
|
644
|
+
project_id: (sc && (sc.project_id || sc.projectId)) || r.project_id || st.project_id
|
|
612
645
|
});
|
|
613
646
|
state = done;
|
|
614
647
|
el('credits').textContent = done.credits_used != null ? fmtCredits(done.credits_used) : '';
|
|
@@ -652,6 +685,11 @@ function handleBatchStatus(sc, st) {
|
|
|
652
685
|
if (!st.all_done) return schedulePoll(sc);
|
|
653
686
|
|
|
654
687
|
var scenes = [], failedCount = 0, credits = 0, haveCredits = false, allUrls = [];
|
|
688
|
+
// get_generation_status resolves model_name/model_icon into EACH result
|
|
689
|
+
// (addDisplayNames in generate.js) — sc is only the submit-time guess
|
|
690
|
+
// (usually "Smart Select"). Every id in one batch ran the same model, so the
|
|
691
|
+
// first resolved one is enough to replace the guess on the finished card.
|
|
692
|
+
var resolved = {};
|
|
655
693
|
gens.forEach(function (g, i) {
|
|
656
694
|
var r = g.result || g;
|
|
657
695
|
var urls = (r && r.urls) || [];
|
|
@@ -659,6 +697,16 @@ function handleBatchStatus(sc, st) {
|
|
|
659
697
|
allUrls = allUrls.concat(urls);
|
|
660
698
|
var c = g.credits_used != null ? g.credits_used : (r.credits_used != null ? r.credits_used : null);
|
|
661
699
|
if (c != null) { credits += c; haveCredits = true; }
|
|
700
|
+
if (!resolved.model_name && r.model_name) {
|
|
701
|
+
resolved.model = r.model;
|
|
702
|
+
resolved.model_name = r.model_name;
|
|
703
|
+
resolved.model_icon = r.model_icon;
|
|
704
|
+
}
|
|
705
|
+
// Same for the references actually used (reference_details from the
|
|
706
|
+
// server) — every id in the batch shares one reference set.
|
|
707
|
+
if (!resolved.reference_images && r.reference_images && r.reference_images.length) {
|
|
708
|
+
resolved.reference_images = r.reference_images;
|
|
709
|
+
}
|
|
662
710
|
scenes.push({
|
|
663
711
|
scene_number: i + 1,
|
|
664
712
|
title: (sc.prompts && sc.prompts[i]) || '',
|
|
@@ -668,7 +716,7 @@ function handleBatchStatus(sc, st) {
|
|
|
668
716
|
});
|
|
669
717
|
if (!scenes.length) return renderError('All ' + gens.length + ' generations failed');
|
|
670
718
|
|
|
671
|
-
var done = Object.assign({}, sc, {
|
|
719
|
+
var done = Object.assign({}, sc, resolved, {
|
|
672
720
|
phase: 'completed', kind: 'scenes', batch: true, scenes: scenes, urls: [],
|
|
673
721
|
credits_used: haveCredits ? credits : sc.credits_used
|
|
674
722
|
});
|
|
@@ -902,10 +950,14 @@ function renderBatchGrid(sc) {
|
|
|
902
950
|
dlBtnHTML(it.url) + '</div>';
|
|
903
951
|
}).join('') + '</div>';
|
|
904
952
|
wireDlButtons(el('stage'));
|
|
953
|
+
// In-widget popup, not the host round-trip focusMedia() uses — a batch grid
|
|
954
|
+
// tile has no visible full-size image otherwise, so a host that never resolves
|
|
955
|
+
// requestDisplayMode() (or drops window.open after the async round trip eats
|
|
956
|
+
// the click's user-activation window) leaves the click doing nothing at all.
|
|
905
957
|
Array.prototype.forEach.call(el('stage').querySelectorAll('[data-focus]'), function (cell) {
|
|
906
958
|
var it = items[+cell.getAttribute('data-focus')];
|
|
907
959
|
if (it.type !== 'image') return; // <video controls> owns its own clicks
|
|
908
|
-
cell.onclick = function () {
|
|
960
|
+
cell.onclick = function () { openPeek(it.url, 'image', it.label); };
|
|
909
961
|
});
|
|
910
962
|
renderActions(sc);
|
|
911
963
|
window.kolbo.notifySize();
|
|
@@ -949,7 +1001,7 @@ function renderStatusGrid(sc) {
|
|
|
949
1001
|
Array.prototype.forEach.call(el('stage').querySelectorAll('[data-focus]'), function (cell) {
|
|
950
1002
|
var it = items[+cell.getAttribute('data-focus')];
|
|
951
1003
|
if (it.kind !== 'image') return; // <video controls> owns its own clicks
|
|
952
|
-
cell.onclick = function () {
|
|
1004
|
+
cell.onclick = function () { openPeek(it.url, 'image', it.title); };
|
|
953
1005
|
});
|
|
954
1006
|
renderActions(sc);
|
|
955
1007
|
window.kolbo.notifySize();
|
|
@@ -1030,7 +1082,7 @@ function renderTrackingIssue(msg) {
|
|
|
1030
1082
|
(state && state.generation_id ? '\\nGeneration ID: ' + state.generation_id : ''));
|
|
1031
1083
|
};
|
|
1032
1084
|
el('tracking-open').onclick = function () {
|
|
1033
|
-
window.kolbo.openLink((state
|
|
1085
|
+
window.kolbo.openLink(kolboUrl(state));
|
|
1034
1086
|
};
|
|
1035
1087
|
window.kolbo.notifySize();
|
|
1036
1088
|
}
|
|
@@ -1100,7 +1152,7 @@ function renderActions(sc) {
|
|
|
1100
1152
|
el('actions').innerHTML = a.join('');
|
|
1101
1153
|
|
|
1102
1154
|
bind('btn-download', function () { window.kolbo.openLink(downloadUrl(currentUrl())); });
|
|
1103
|
-
bind('btn-open', function () { window.kolbo.openLink(state
|
|
1155
|
+
bind('btn-open', function () { window.kolbo.openLink(kolboUrl(state)); });
|
|
1104
1156
|
bind('btn-recreate', function () {
|
|
1105
1157
|
window.kolbo.sendMessage('Recreate this with the same settings' +
|
|
1106
1158
|
(state.model ? '\\nModel: ' + state.model : '') +
|
|
@@ -1195,7 +1247,10 @@ function completedFromPlain(sc) {
|
|
|
1195
1247
|
aspect_ratio: originArgs.aspect_ratio,
|
|
1196
1248
|
quality: originArgs.quality
|
|
1197
1249
|
},
|
|
1198
|
-
urls: sc.urls || []
|
|
1250
|
+
urls: sc.urls || [],
|
|
1251
|
+
session_id: sc.session_id || originArgs.session_id,
|
|
1252
|
+
project_id: sc.project_id || originArgs.project_id,
|
|
1253
|
+
open_url: sc.open_url
|
|
1199
1254
|
});
|
|
1200
1255
|
}
|
|
1201
1256
|
|
package/src/apps/widgets/list.js
CHANGED
|
@@ -20,7 +20,7 @@ const { widgetPage } = require('../html');
|
|
|
20
20
|
* badge, // small pill, e.g. role/status ("owner", "default", "shared")
|
|
21
21
|
* meta, // small trailing text, e.g. a date or count
|
|
22
22
|
* open_url, // optional — renders an "Open" button (openLink)
|
|
23
|
-
* use_hint //
|
|
23
|
+
* use_hint // unused — a click pastes `id` into the composer
|
|
24
24
|
* }],
|
|
25
25
|
* total
|
|
26
26
|
* }
|
|
@@ -72,7 +72,7 @@ function apply(result) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
function itemHTML(item, i) {
|
|
75
|
-
var clickable = !!item.
|
|
75
|
+
var clickable = !!item.id;
|
|
76
76
|
var avatar = item.thumbnail
|
|
77
77
|
? '<img class="k-audio-art k-peek-hit" src="' + esc(item.thumbnail) + '" alt="" loading="lazy"'
|
|
78
78
|
+ peekAttrs(item.thumbnail, 'image', item.title)
|
|
@@ -98,10 +98,10 @@ function wire() {
|
|
|
98
98
|
});
|
|
99
99
|
Array.prototype.forEach.call(document.querySelectorAll('.k-audio-row'), function (row) {
|
|
100
100
|
var item = state.items[+row.getAttribute('data-i')];
|
|
101
|
-
if (!item || !item.
|
|
102
|
-
row.onclick = function () {
|
|
103
|
-
|
|
104
|
-
window.kolbo.
|
|
101
|
+
if (!item || !item.id) return;
|
|
102
|
+
row.onclick = function (e) {
|
|
103
|
+
if (e.target && e.target.closest && e.target.closest('[data-open],[data-peek]')) return;
|
|
104
|
+
window.kolbo.insertText(String(item.id));
|
|
105
105
|
};
|
|
106
106
|
});
|
|
107
107
|
}
|
|
@@ -14,7 +14,7 @@ const { widgetPage } = require('../html');
|
|
|
14
14
|
* id, title, subtitle, thumbnail, media_type: 'image'|'video'|'audio'|'3d',
|
|
15
15
|
* url, // full asset / playback URL
|
|
16
16
|
* preview_audio, // audio preview URL (voices, music)
|
|
17
|
-
* use_hint //
|
|
17
|
+
* use_hint // unused — Use pastes `id` into the composer
|
|
18
18
|
* }],
|
|
19
19
|
* total, has_more,
|
|
20
20
|
* import_tool_hint // e.g. 'import via import_stock_asset' — shown on Use
|
|
@@ -154,11 +154,8 @@ function wire() {
|
|
|
154
154
|
|
|
155
155
|
function useItem(i) {
|
|
156
156
|
var item = state.items[i];
|
|
157
|
-
if (!item) return;
|
|
158
|
-
|
|
159
|
-
? item.use_hint.replace('{URL}', item.url || '').replace('{ID}', item.id || '').replace('{TITLE}', item.title || '')
|
|
160
|
-
: 'Use this asset: "' + (item.title || item.id) + '"\\nURL: ' + (item.url || '') + (item.id ? '\\nID: ' + item.id : '');
|
|
161
|
-
window.kolbo.sendMessage(msg);
|
|
157
|
+
if (!item || !item.id) return;
|
|
158
|
+
window.kolbo.insertText(String(item.id));
|
|
162
159
|
}
|
|
163
160
|
|
|
164
161
|
window.kolbo.onToolResult(function (result) {
|
package/src/tools/_shared.js
CHANGED
|
@@ -458,8 +458,8 @@ const OPEN_URL_ROUTES = {
|
|
|
458
458
|
edit_image: { path: '/image-tools', tool: 'image-editing' },
|
|
459
459
|
generate_video: { path: '/video-tools', tool: 'text-to-video' },
|
|
460
460
|
generate_video_from_image: { path: '/video-tools', tool: 'image-to-video' },
|
|
461
|
-
generate_elements: { path: '/video-tools', tool: 'image-to-video' },
|
|
462
|
-
generate_first_last_frame: { path: '/video-tools', tool: 'image-to-video' },
|
|
461
|
+
generate_elements: { path: '/video-tools', tool: 'image-to-video', mode: 'elements' },
|
|
462
|
+
generate_first_last_frame: { path: '/video-tools', tool: 'image-to-video', mode: 'first-last' },
|
|
463
463
|
generate_video_from_video: { path: '/video-tools', tool: 'video-to-video' },
|
|
464
464
|
generate_lipsync: { path: '/video-tools', tool: 'lipsync' },
|
|
465
465
|
generate_music: { path: '/audio-tools', tool: 'music-generator' },
|
|
@@ -468,6 +468,35 @@ const OPEN_URL_ROUTES = {
|
|
|
468
468
|
transcribe_audio: { path: '/audio-tools', tool: 'speech-to-text' },
|
|
469
469
|
generate_creative_director: { path: '/creative-director' },
|
|
470
470
|
};
|
|
471
|
+
// SDK tracking `type` on get_generation_status — same session model as the
|
|
472
|
+
// generate_* tool that created the job, so "Open in Kolbo" still deep-links
|
|
473
|
+
// after a status poll overwrites the live card.
|
|
474
|
+
const TYPE_TO_TOOL = {
|
|
475
|
+
image: 'generate_image',
|
|
476
|
+
image_edit: 'generate_image_edit',
|
|
477
|
+
global_image_edit: 'edit_image',
|
|
478
|
+
video: 'generate_video',
|
|
479
|
+
video_from_image: 'generate_video_from_image',
|
|
480
|
+
elements: 'generate_elements',
|
|
481
|
+
first_last_frame: 'generate_first_last_frame',
|
|
482
|
+
video_from_video: 'generate_video_from_video',
|
|
483
|
+
lipsync: 'generate_lipsync',
|
|
484
|
+
music: 'generate_music',
|
|
485
|
+
speech: 'generate_speech',
|
|
486
|
+
sound: 'generate_sound',
|
|
487
|
+
transcription: 'transcribe_audio',
|
|
488
|
+
creative_director: 'generate_creative_director',
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
function sessionOf(gen) {
|
|
492
|
+
if (!gen || typeof gen !== 'object') return undefined;
|
|
493
|
+
return gen.session_id || gen.sessionId || undefined;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function projectOf(gen) {
|
|
497
|
+
if (!gen || typeof gen !== 'object') return undefined;
|
|
498
|
+
return gen.project_id || gen.projectId || undefined;
|
|
499
|
+
}
|
|
471
500
|
|
|
472
501
|
/**
|
|
473
502
|
* Build the "Open in Kolbo" deep link for a generation's actual session.
|
|
@@ -476,14 +505,30 @@ const OPEN_URL_ROUTES = {
|
|
|
476
505
|
* kolbo-api, shorts render, 3D).
|
|
477
506
|
*/
|
|
478
507
|
function buildOpenUrl(tool, gen) {
|
|
479
|
-
const
|
|
480
|
-
if (!
|
|
481
|
-
|
|
508
|
+
const sid = sessionOf(gen);
|
|
509
|
+
if (!sid) return undefined;
|
|
510
|
+
const key = OPEN_URL_ROUTES[tool] ? tool : (TYPE_TO_TOOL[gen && gen.type] || TYPE_TO_TOOL[tool]);
|
|
511
|
+
const route = OPEN_URL_ROUTES[key];
|
|
512
|
+
if (!route) return undefined;
|
|
513
|
+
let url = `${APP_BASE_URL}${route.path}?session=${encodeURIComponent(sid)}`;
|
|
482
514
|
if (route.tool) url += `&tool=${route.tool}`;
|
|
483
|
-
if (
|
|
515
|
+
if (route.mode) url += `&mode=${route.mode}`;
|
|
516
|
+
const pid = projectOf(gen);
|
|
517
|
+
if (pid) url += `&project=${encodeURIComponent(pid)}`;
|
|
484
518
|
return url;
|
|
485
519
|
}
|
|
486
520
|
|
|
521
|
+
function linkFields(tool, gen) {
|
|
522
|
+
const sid = sessionOf(gen);
|
|
523
|
+
const pid = projectOf(gen);
|
|
524
|
+
const href = buildOpenUrl(tool, gen);
|
|
525
|
+
return {
|
|
526
|
+
...(sid ? { session_id: String(sid) } : {}),
|
|
527
|
+
...(pid ? { project_id: String(pid) } : {}),
|
|
528
|
+
...(href ? { open_url: href } : {}),
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
487
532
|
/**
|
|
488
533
|
* Build the "Open in Kolbo" deep link for a PROJECT. Lands on the Media hub
|
|
489
534
|
* (all-assets view) pre-filtered to this project, where the user sees every
|
|
@@ -671,8 +716,11 @@ function mediaRefs(p) {
|
|
|
671
716
|
? p.reference_images.filter(Boolean)
|
|
672
717
|
: (p.reference_image ? [p.reference_image] : []);
|
|
673
718
|
return {
|
|
674
|
-
|
|
675
|
-
|
|
719
|
+
// Omitted when empty: a live card MERGES status payloads over its own
|
|
720
|
+
// state, so a present-but-empty reference_images (the single-id status
|
|
721
|
+
// path has no input refs of its own) wiped the submit-time thumbnails
|
|
722
|
+
// off the finished card.
|
|
723
|
+
...(images.length ? { reference_images: images, reference_image: p.reference_image || images[0] } : {}),
|
|
676
724
|
...(Array.isArray(p.reference_videos) && p.reference_videos.length
|
|
677
725
|
? { reference_videos: p.reference_videos.filter(Boolean) } : {}),
|
|
678
726
|
...(Array.isArray(p.reference_audio) && p.reference_audio.length
|
|
@@ -723,7 +771,7 @@ async function uiGenerating(p) {
|
|
|
723
771
|
// `reference_image` is retained for older widget builds. New widgets render
|
|
724
772
|
// every browser-loadable image supplied to the generation.
|
|
725
773
|
...mediaRefs(p),
|
|
726
|
-
|
|
774
|
+
...linkFields(p.tool, p.gen),
|
|
727
775
|
};
|
|
728
776
|
// Batch mode (prompts[] fan-out): ONE widget tracks every id in the set.
|
|
729
777
|
if (Array.isArray(p.generation_ids) && p.generation_ids.length > 1) {
|
|
@@ -741,7 +789,7 @@ async function uiGenerating(p) {
|
|
|
741
789
|
...(p.failed_submissions && p.failed_submissions.length
|
|
742
790
|
? { failed_submissions: p.failed_submissions } : {}),
|
|
743
791
|
...(p.warning ? { _warning: p.warning } : {}),
|
|
744
|
-
_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
|
|
792
|
+
_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. CREDIT GUARD: END YOUR TURN now — do not keep Thinking, writing skills/files, or planning while the card spins (that burns coding credits). Do NOT poll in a loop. If you need the output URLs for a follow-up step, call get_generation_status ONCE with wait=true as the only next tool — it blocks until done. Tracking several generations? Pass ALL their ids in generation_ids in that one call.',
|
|
745
793
|
_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).',
|
|
746
794
|
}, null, 2);
|
|
747
795
|
return uiResult(UI.generation, text, structured);
|
|
@@ -819,7 +867,7 @@ async function uiCompleted(p, textPayload, extraContent) {
|
|
|
819
867
|
// resolved as completed-with-no-media and painted a red failure card.
|
|
820
868
|
...(p.state ? { state: p.state } : {}),
|
|
821
869
|
credits_used: p.credits_used,
|
|
822
|
-
|
|
870
|
+
...linkFields(p.tool, p.gen),
|
|
823
871
|
};
|
|
824
872
|
const out = uiResult(UI.generation, textPayload, structured);
|
|
825
873
|
if (Array.isArray(extraContent) && extraContent.length) {
|
|
@@ -913,6 +961,7 @@ module.exports = {
|
|
|
913
961
|
projectScopeReadField,
|
|
914
962
|
inlineImageBlocks,
|
|
915
963
|
buildOpenUrl,
|
|
964
|
+
linkFields,
|
|
916
965
|
buildProjectUrl,
|
|
917
966
|
uiGenerating,
|
|
918
967
|
uiCompleted,
|
package/src/tools/generate.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
const { z } = require('zod');
|
|
7
7
|
const FormData = require('form-data');
|
|
8
8
|
const { pollUntilDone, waitWindowMs } = require('../polling');
|
|
9
|
-
const { resolveToBuffer, pollOrTimedOut, creditFields, projectIdField, sessionIdField, inlineImageBlocks,
|
|
9
|
+
const { resolveToBuffer, pollOrTimedOut, creditFields, projectIdField, sessionIdField, inlineImageBlocks, linkFields, uiGenerating, uiCompleted, appsEnabled } = require('./_shared');
|
|
10
10
|
const { ownedUrl } = require('./owned-url');
|
|
11
11
|
const { UI, uiResult, canonicalModelId, modelInfo, voiceInfo, resolveCatalogAspectRatio } = require('../apps');
|
|
12
12
|
|
|
@@ -282,7 +282,10 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
282
282
|
return uiCompleted({
|
|
283
283
|
tool: 'generate_image', kind: 'image', gen, client, model, prompt,
|
|
284
284
|
count: num_images, settings: imageSettings(shared),
|
|
285
|
-
|
|
285
|
+
// The status result reports what the server ACTUALLY conditioned on
|
|
286
|
+
// (reference_details: DNA stills, merged sources, auto-routing) — the
|
|
287
|
+
// input arg is only what the caller happened to name.
|
|
288
|
+
reference_images: result.result.reference_images || reference_images,
|
|
286
289
|
urls: result.result.urls,
|
|
287
290
|
credits_used: creditFields(result).credits_used,
|
|
288
291
|
}, JSON.stringify({
|
|
@@ -371,7 +374,8 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
371
374
|
return uiCompleted({
|
|
372
375
|
tool: 'generate_image_edit', kind: 'image', gen, client, model, prompt,
|
|
373
376
|
count: num_images, settings: imageSettings(shared),
|
|
374
|
-
reference_images:
|
|
377
|
+
reference_images: result.result.reference_images
|
|
378
|
+
|| [...(source_images || []), ...(reference_images || [])],
|
|
375
379
|
urls: result.result.urls,
|
|
376
380
|
credits_used: creditFields(result).credits_used,
|
|
377
381
|
}, JSON.stringify({
|
|
@@ -1086,10 +1090,21 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1086
1090
|
// The model that ACTUALLY ran. A single-id check resolves one
|
|
1087
1091
|
// generation, so 'Generations' here overwrote the real model name on
|
|
1088
1092
|
// the finished card the moment the live widget merged this payload in.
|
|
1089
|
-
model: res.model || 'Generations',
|
|
1093
|
+
model: res.model || 'Generations',
|
|
1094
|
+
gen: {
|
|
1095
|
+
generation_id: single.generation_id,
|
|
1096
|
+
session_id: single.session_id,
|
|
1097
|
+
project_id: single.project_id,
|
|
1098
|
+
type: single.type,
|
|
1099
|
+
},
|
|
1090
1100
|
state: single.state,
|
|
1091
1101
|
urls: done ? urls : undefined,
|
|
1092
1102
|
thumbnail_url: res.thumbnail_url,
|
|
1103
|
+
// The refs the server actually conditioned on (reference_details) —
|
|
1104
|
+
// the live card merges this payload over its submit-time state, so
|
|
1105
|
+
// the finished card shows every reference, including server-side
|
|
1106
|
+
// additions (DNA stills, merged sources) the tool call never named.
|
|
1107
|
+
reference_images: res.reference_images,
|
|
1093
1108
|
prompt: res.prompt_used || res.prompt || undefined,
|
|
1094
1109
|
credits_used: creditFields(single).credits_used,
|
|
1095
1110
|
items: [{
|
|
@@ -1130,10 +1145,27 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1130
1145
|
// moodboards.js / visual_dna.js / listResult(). uiResult always embeds
|
|
1131
1146
|
// both text and structuredContent, so hosts without a UI-app renderer
|
|
1132
1147
|
// are unaffected — only the fallback quality changes.
|
|
1148
|
+
// A live batch card (prompts[] fan-out) merges this payload over its
|
|
1149
|
+
// submit-time state, so a hardcoded 'Generations' model here painted
|
|
1150
|
+
// over the real model chip on every finished batch. When all results
|
|
1151
|
+
// ran the same model (the batch case), name it; only a genuinely mixed
|
|
1152
|
+
// multi-id check falls back to the generic label. Same for the
|
|
1153
|
+
// references actually used — one shared set per batch.
|
|
1154
|
+
const modelsRan = [...new Set(results.map(r => r.result && r.result.model).filter(Boolean))];
|
|
1155
|
+
const actualRefs = results
|
|
1156
|
+
.map(r => r.result && r.result.reference_images)
|
|
1157
|
+
.find(refs => Array.isArray(refs) && refs.length);
|
|
1133
1158
|
return uiCompleted({
|
|
1134
1159
|
tool: 'get_generation_status', kind: 'status', client,
|
|
1135
|
-
model:
|
|
1160
|
+
model: modelsRan.length === 1 ? modelsRan[0] : 'Generations',
|
|
1161
|
+
gen: {
|
|
1162
|
+
generation_id: ids[0],
|
|
1163
|
+
session_id: results[0] && results[0].session_id,
|
|
1164
|
+
project_id: results[0] && results[0].project_id,
|
|
1165
|
+
type: results[0] && results[0].type,
|
|
1166
|
+
},
|
|
1136
1167
|
settings: {},
|
|
1168
|
+
reference_images: actualRefs,
|
|
1137
1169
|
items: results.map(r => {
|
|
1138
1170
|
const res = r.result || {};
|
|
1139
1171
|
return {
|
|
@@ -1786,7 +1818,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1786
1818
|
poll_tool: 'get_generation_status',
|
|
1787
1819
|
status_args: { generation_id: startResponse.generation_id, wait: true },
|
|
1788
1820
|
audio_url: isUrl ? source : undefined,
|
|
1789
|
-
|
|
1821
|
+
...linkFields('transcribe_audio', startResponse),
|
|
1790
1822
|
});
|
|
1791
1823
|
}
|
|
1792
1824
|
|
|
@@ -2056,7 +2088,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2056
2088
|
'inpaint', 'retake'
|
|
2057
2089
|
]).describe([
|
|
2058
2090
|
'Edit operation:',
|
|
2059
|
-
'"upscale" — boost to 4K/2K resolution (use `scale` for factor, `resolution` for target, `target_fps` for frame rate).',
|
|
2091
|
+
'"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.',
|
|
2060
2092
|
'"reframe" — change aspect ratio (requires `aspect_ratio`; use `grid_position_x`/`grid_position_y` to control where the original sits).',
|
|
2061
2093
|
'"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.',
|
|
2062
2094
|
'"remove_watermark" — AI-powered watermark removal.',
|
|
@@ -2074,9 +2106,9 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2074
2106
|
|
|
2075
2107
|
// ── upscale ────────────────────────────────────────────
|
|
2076
2108
|
scale: z.number().optional()
|
|
2077
|
-
.describe('Upscale factor (e.g. 2, 4). Used with operation="upscale".'),
|
|
2109
|
+
.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).'),
|
|
2078
2110
|
resolution: z.string().optional()
|
|
2079
|
-
.describe('Target resolution (e.g. "4k", "2k", "1080p"). Used with "upscale" and "reframe".'),
|
|
2111
|
+
.describe('Target resolution (e.g. "4k", "2k", "1080p"). Used with "upscale" and "reframe". Not used by "blackforestlabs/flux-video-upscale" — use `scale` instead.'),
|
|
2080
2112
|
target_fps: z.number().optional()
|
|
2081
2113
|
.describe('Target frame rate (e.g. 24, 30, 60). Used with operation="upscale".'),
|
|
2082
2114
|
enhancement_model: z.string().optional()
|
|
@@ -2092,7 +2124,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2092
2124
|
|
|
2093
2125
|
// ── generate_audio ─────────────────────────────────────
|
|
2094
2126
|
prompt: z.string().optional()
|
|
2095
|
-
.describe('Text prompt. Required for "magic_edit". Used with "generate_audio", "extend", "inpaint", "retake",
|
|
2127
|
+
.describe('Text prompt. Required for "magic_edit". Used with "generate_audio", "extend", "inpaint", "retake", optionally "lipsync" (for text-to-speech instead of audio_url), and optionally "upscale" on model "blackforestlabs/flux-video-upscale" (guides creative-mode detail enhancement).'),
|
|
2096
2128
|
sound_effect_prompt: z.string().optional()
|
|
2097
2129
|
.describe('Separate prompt for sound effects layer. Used with operation="generate_audio" when you want to specify SFX and music independently.'),
|
|
2098
2130
|
background_music_prompt: z.string().optional()
|
|
@@ -2110,7 +2142,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2110
2142
|
duration: z.number().optional()
|
|
2111
2143
|
.describe('Seconds of content to generate. Used with "extend" (required) and "retake" (optional). Typical range: 1–20.'),
|
|
2112
2144
|
mode: z.string().optional()
|
|
2113
|
-
.describe('Extension direction: "start" or "end". Used with "extend". For "retake": replacement mode (default: "replace_audio_and_video"). Default: "end".'),
|
|
2145
|
+
.describe('Extension direction: "start" or "end". Used with "extend". For "retake": replacement mode (default: "replace_audio_and_video"). For "upscale" on model "blackforestlabs/flux-video-upscale": "precise" (source-faithful, default) or "creative" (reimagines detail — pricier). Default: "end".'),
|
|
2114
2146
|
context: z.string().optional()
|
|
2115
2147
|
.describe('Additional context to guide what gets generated in the extended segment. Used with "extend".'),
|
|
2116
2148
|
|
package/src/tools/projects.js
CHANGED
|
@@ -4,9 +4,15 @@
|
|
|
4
4
|
* new OPTIONAL args only. Full rules: ../index.js top-of-file and CLAUDE.md. */
|
|
5
5
|
|
|
6
6
|
const { z } = require('zod');
|
|
7
|
-
const { buildProjectUrl } = require('./_shared');
|
|
7
|
+
const { buildProjectUrl, compactList } = require('./_shared');
|
|
8
8
|
const { listResult } = require('../apps');
|
|
9
9
|
|
|
10
|
+
function blurb(text) {
|
|
11
|
+
const s = String(text || '').replace(/\s+/g, ' ').trim();
|
|
12
|
+
if (s.length <= 96) return s;
|
|
13
|
+
return s.slice(0, 96).replace(/\s+\S*$/, '') + '…';
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
function registerProjectTools(server, client) {
|
|
11
17
|
// ─── list_projects ─────────────────────────────────────────
|
|
12
18
|
server.tool(
|
|
@@ -465,11 +471,33 @@ function registerProjectTools(server, client) {
|
|
|
465
471
|
const result = await client.get(`/v1/projects/${encodeURIComponent(project_id)}/assets`);
|
|
466
472
|
const dnas = result.visual_dnas || [];
|
|
467
473
|
const moodboards = result.moodboards || [];
|
|
468
|
-
const text =
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
474
|
+
const text = compactList(
|
|
475
|
+
[
|
|
476
|
+
...dnas.map((d) => ({
|
|
477
|
+
id: d.id,
|
|
478
|
+
name: d.name,
|
|
479
|
+
dna_type: d.dna_type,
|
|
480
|
+
thumbnail: d.thumbnail_url || d.thumbnail,
|
|
481
|
+
note: blurb(d.note),
|
|
482
|
+
description: blurb(d.description),
|
|
483
|
+
})),
|
|
484
|
+
...moodboards.map((m) => ({
|
|
485
|
+
id: m.id,
|
|
486
|
+
name: m.name,
|
|
487
|
+
dna_type: 'moodboard',
|
|
488
|
+
thumbnail: m.thumbnail_url || m.thumbnail,
|
|
489
|
+
note: blurb(m.note),
|
|
490
|
+
description: blurb(m.summary),
|
|
491
|
+
})),
|
|
492
|
+
],
|
|
493
|
+
{
|
|
494
|
+
fields: ['id', 'name', 'dna_type', 'thumbnail', 'note', 'description'],
|
|
495
|
+
cap: 80,
|
|
496
|
+
total: dnas.length + moodboards.length,
|
|
497
|
+
extra: { visual_dna_count: dnas.length, moodboard_count: moodboards.length },
|
|
498
|
+
note: 'Edit identity text with update_project_asset (description) or update_visual_dna. `note` is project-scoped purpose. Link missing assets with link_project_asset — do not delete+recreate.',
|
|
499
|
+
},
|
|
500
|
+
);
|
|
473
501
|
return listResult(text, {
|
|
474
502
|
widget: 'list',
|
|
475
503
|
title: 'Project cast',
|
|
@@ -477,13 +505,17 @@ function registerProjectTools(server, client) {
|
|
|
477
505
|
...dnas.map((d) => ({
|
|
478
506
|
id: d.id,
|
|
479
507
|
title: '@' + (d.name || d.id),
|
|
480
|
-
subtitle:
|
|
508
|
+
subtitle: blurb(d.note || d.description),
|
|
509
|
+
thumbnail: d.thumbnail_url || d.thumbnail,
|
|
510
|
+
badge: d.dna_type,
|
|
481
511
|
})),
|
|
482
512
|
...moodboards.map((m) => ({
|
|
483
513
|
id: m.id,
|
|
484
514
|
title: '#' + (m.name || m.id),
|
|
485
|
-
subtitle:
|
|
486
|
-
|
|
515
|
+
subtitle: blurb(m.note || m.summary),
|
|
516
|
+
thumbnail: m.thumbnail_url || m.thumbnail,
|
|
517
|
+
badge: 'moodboard',
|
|
518
|
+
})),
|
|
487
519
|
],
|
|
488
520
|
total: dnas.length + moodboards.length
|
|
489
521
|
});
|