@kolbo/mcp 1.87.7 → 1.87.9
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/bridge.js +69 -0
- package/src/apps/index.js +9 -7
- package/src/apps/widgets/list.js +31 -1
- package/src/apps/widgets/mediaGrid.js +25 -5
- package/src/tools/color_palettes.js +3 -3
- package/src/tools/docs.js +3 -1
- package/src/tools/moodboards.js +8 -6
- package/src/tools/music_library.js +9 -4
- package/src/tools/projects.js +6 -2
- package/src/tools/stock_library.js +8 -3
- package/src/tools/visual_dna.js +7 -2
- package/src/tools/voices.js +4 -1
package/package.json
CHANGED
package/src/apps/bridge.js
CHANGED
|
@@ -210,6 +210,75 @@ const BRIDGE_JS = `
|
|
|
210
210
|
},
|
|
211
211
|
hostContext: function () { return hostContext; }
|
|
212
212
|
};
|
|
213
|
+
|
|
214
|
+
// ChatGPT (OpenAI Apps SDK) host. It mounts the same iframe but speaks no
|
|
215
|
+
// JSON-RPC: tool input / output / theme arrive as window.openai globals plus
|
|
216
|
+
// an 'openai:set_globals' DOM event, and actions are methods on that object.
|
|
217
|
+
// Without this adapter the card rendered, waited for a tool-input that never
|
|
218
|
+
// came, and sat on "Preparing" with no prompt, no settings and no result —
|
|
219
|
+
// even after the tool had returned. Everything here maps onto the same
|
|
220
|
+
// callbacks the JSON-RPC path drives, so widgets need no host-specific code.
|
|
221
|
+
var oa = window.openai && typeof window.openai === 'object' ? window.openai : null;
|
|
222
|
+
if (oa) {
|
|
223
|
+
var oaInputSeen = false, oaOutputSeen = false;
|
|
224
|
+
var oaName = function () {
|
|
225
|
+
var meta = oa.toolResponseMetadata || {};
|
|
226
|
+
return oa.toolName || meta['kolbo/tool'] || (oa.toolOutput && oa.toolOutput.tool) || undefined;
|
|
227
|
+
};
|
|
228
|
+
var oaSync = function () {
|
|
229
|
+
var name = oaName();
|
|
230
|
+
// Same shape claude.ai hands ready(): toolInfo.tool.name is what widgets read.
|
|
231
|
+
hostContext = { theme: oa.theme, displayMode: oa.displayMode, toolInfo: { name: name, tool: { name: name }, arguments: oa.toolInput || {} } };
|
|
232
|
+
if (!initialized) {
|
|
233
|
+
initialized = true;
|
|
234
|
+
queue = [];
|
|
235
|
+
readyFns.forEach(function (f) { try { f(hostContext); } catch (e) {} });
|
|
236
|
+
} else {
|
|
237
|
+
themeFns.forEach(function (f) { try { f(hostContext); } catch (e) {} });
|
|
238
|
+
}
|
|
239
|
+
if (oa.toolInput && !oaInputSeen) {
|
|
240
|
+
oaInputSeen = true;
|
|
241
|
+
toolInputFns.forEach(function (f) { try { f(oa.toolInput, { name: oaName(), arguments: oa.toolInput }); } catch (e) {} });
|
|
242
|
+
}
|
|
243
|
+
if (oa.toolOutput && !oaOutputSeen) {
|
|
244
|
+
oaOutputSeen = true;
|
|
245
|
+
var res = { structuredContent: oa.toolOutput, _meta: oa.toolResponseMetadata || {} };
|
|
246
|
+
toolResultFns.forEach(function (f) { try { f(res); } catch (e) {} });
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
var oaCall = function (method) { return typeof oa[method] === 'function'; };
|
|
250
|
+
window.kolbo.callTool = function (name, args) {
|
|
251
|
+
if (!oaCall('callTool')) return Promise.reject(new Error('host cannot call tools'));
|
|
252
|
+
return Promise.resolve(oa.callTool(name, args || {})).then(function (r) {
|
|
253
|
+
// The SDK resolves with the CallToolResult (content + structuredContent);
|
|
254
|
+
// some builds unwrap to the structured payload — normalise either.
|
|
255
|
+
if (r && (r.structuredContent || r.content)) return r;
|
|
256
|
+
return { structuredContent: r, content: [] };
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
window.kolbo.sendMessage = function (text) {
|
|
260
|
+
return oaCall('sendFollowUpMessage') ? Promise.resolve(oa.sendFollowUpMessage({ prompt: text })) : Promise.reject(new Error('unsupported'));
|
|
261
|
+
};
|
|
262
|
+
window.kolbo.insertText = function () { return Promise.reject(new Error('unsupported')); };
|
|
263
|
+
window.kolbo.openLink = function (url) {
|
|
264
|
+
return oaCall('openExternal') ? Promise.resolve(oa.openExternal({ href: url })) : Promise.resolve(window.open(url, '_blank'));
|
|
265
|
+
};
|
|
266
|
+
window.kolbo.copyText = function (t) {
|
|
267
|
+
return navigator.clipboard && navigator.clipboard.writeText ? navigator.clipboard.writeText(t) : Promise.reject(new Error('unsupported'));
|
|
268
|
+
};
|
|
269
|
+
window.kolbo.attachMedia = function () { return Promise.reject(new Error('unsupported')); };
|
|
270
|
+
window.kolbo.updateModelContext = function () { return Promise.resolve(); };
|
|
271
|
+
window.kolbo.requestDisplayMode = function (mode) {
|
|
272
|
+
if (!oaCall('requestDisplayMode')) return Promise.resolve({ mode: 'inline' });
|
|
273
|
+
return Promise.resolve(oa.requestDisplayMode({ mode: mode })).then(function (r) { return { mode: (r && r.mode) || mode }; });
|
|
274
|
+
};
|
|
275
|
+
window.addEventListener('openai:set_globals', oaSync);
|
|
276
|
+
// Deferred: the widget's own script (which registers ready/onToolInput/
|
|
277
|
+
// onToolResult) runs AFTER this bridge block in the same document, and a
|
|
278
|
+
// synchronous sync here fired into empty listener lists and marked the
|
|
279
|
+
// input/output as seen.
|
|
280
|
+
setTimeout(oaSync, 0);
|
|
281
|
+
}
|
|
213
282
|
})();
|
|
214
283
|
`;
|
|
215
284
|
|
package/src/apps/index.js
CHANGED
|
@@ -71,12 +71,12 @@ const WIDGET_CSP = {
|
|
|
71
71
|
// Public hosts owned by Kolbo.
|
|
72
72
|
'https://api.kolbo.ai',
|
|
73
73
|
'https://app.kolbo.ai',
|
|
74
|
-
'https://cdn.kolbo.ai',
|
|
75
|
-
// Preset thumbnails and library media live on the media-* hosts (prod
|
|
76
|
-
// presets still reference media-dev); without these every preset tile
|
|
77
|
-
// rendered as a black box. All three are on kolbo-api's download allowlist.
|
|
78
|
-
'https://media.kolbo.ai',
|
|
79
|
-
'https://media-staging.kolbo.ai',
|
|
74
|
+
'https://cdn.kolbo.ai',
|
|
75
|
+
// Preset thumbnails and library media live on the media-* hosts (prod
|
|
76
|
+
// presets still reference media-dev); without these every preset tile
|
|
77
|
+
// rendered as a black box. All three are on kolbo-api's download allowlist.
|
|
78
|
+
'https://media.kolbo.ai',
|
|
79
|
+
'https://media-staging.kolbo.ai',
|
|
80
80
|
'https://media-dev.kolbo.ai',
|
|
81
81
|
...KOLBO_MEDIA_DOMAINS,
|
|
82
82
|
'https://kolbo-general-media.fra1.digitaloceanspaces.com',
|
|
@@ -201,7 +201,9 @@ function uiResult(uri, text, structured) {
|
|
|
201
201
|
return {
|
|
202
202
|
content: [{ type: 'text', text }],
|
|
203
203
|
structuredContent: structured,
|
|
204
|
-
|
|
204
|
+
// 'kolbo/tool' reaches ChatGPT widgets as toolResponseMetadata — that host
|
|
205
|
+
// never tells the iframe which tool ran, and the card title depends on it.
|
|
206
|
+
_meta: { ...uiMeta(uri), ...(structured && structured.tool ? { 'kolbo/tool': structured.tool } : {}) },
|
|
205
207
|
};
|
|
206
208
|
}
|
|
207
209
|
|
package/src/apps/widgets/list.js
CHANGED
|
@@ -47,6 +47,8 @@ var state = null;
|
|
|
47
47
|
function boot(sc) {
|
|
48
48
|
var list = listPayload(sc);
|
|
49
49
|
if (!list) return false;
|
|
50
|
+
// listPayload() normalises plain shapes; carry the paging contract through.
|
|
51
|
+
if (sc && sc.page_tool && !list.page_tool) { list.page_tool = sc.page_tool; list.next_args = sc.next_args; list.page = sc.page; }
|
|
50
52
|
state = list;
|
|
51
53
|
el('title').textContent = list.title || 'List';
|
|
52
54
|
var total = list.total != null ? list.total : list.items.length;
|
|
@@ -58,13 +60,41 @@ function boot(sc) {
|
|
|
58
60
|
window.kolbo.notifySize();
|
|
59
61
|
return true;
|
|
60
62
|
}
|
|
61
|
-
|
|
63
|
+
// Render every item the server sent (the old slice(0, 40) silently dropped
|
|
64
|
+
// rows, and nothing told the user). Load more only when the server said how.
|
|
65
|
+
var h = list.items.map(itemHTML).join('');
|
|
66
|
+
var shown = list.items.length;
|
|
67
|
+
if (list.page_tool && list.next_args) {
|
|
68
|
+
h += '<button class="k-btn" id="load-more" style="width:100%;margin-top:10px">Load more' +
|
|
69
|
+
(total > shown ? ' (' + shown + ' of ' + total + ' shown)' : '') + '</button>';
|
|
70
|
+
}
|
|
71
|
+
el('stage').innerHTML = h;
|
|
62
72
|
el('stage').classList.remove('k-empty');
|
|
63
73
|
wire();
|
|
74
|
+
var lm = el('load-more');
|
|
75
|
+
if (lm) lm.onclick = function () { fetchNextPage(lm); };
|
|
64
76
|
window.kolbo.notifySize();
|
|
65
77
|
return true;
|
|
66
78
|
}
|
|
67
79
|
|
|
80
|
+
function fetchNextPage(btn) {
|
|
81
|
+
if (!state || !state.page_tool || !state.next_args || btn.disabled) return;
|
|
82
|
+
btn.disabled = true;
|
|
83
|
+
var label = btn.textContent;
|
|
84
|
+
btn.innerHTML = '<span class="k-spin"></span> Loading';
|
|
85
|
+
window.kolbo.callTool(state.page_tool, state.next_args).then(function (res) {
|
|
86
|
+
var sc = structured(res);
|
|
87
|
+
var next = sc ? listPayload(sc) || sc : null;
|
|
88
|
+
var more = (next && next.items) || [];
|
|
89
|
+
if ((res && res.isError) || !next) { btn.disabled = false; btn.textContent = 'Could not load more — try again'; return; }
|
|
90
|
+
if (!more.length) { btn.textContent = 'No more results'; return; }
|
|
91
|
+
state.items = state.items.concat(more);
|
|
92
|
+
state.next_args = sc.next_args;
|
|
93
|
+
if (sc.total != null) state.total = sc.total;
|
|
94
|
+
boot(Object.assign({}, state, { widget: 'list', page_tool: state.page_tool, next_args: state.next_args }));
|
|
95
|
+
}).catch(function () { btn.disabled = false; btn.textContent = label; });
|
|
96
|
+
}
|
|
97
|
+
|
|
68
98
|
function apply(result) {
|
|
69
99
|
if (!result) return false;
|
|
70
100
|
var inner = result.result || result;
|
|
@@ -60,9 +60,14 @@ function boot(sc) {
|
|
|
60
60
|
h += audioItems.map(audioRowHTML).join('');
|
|
61
61
|
}
|
|
62
62
|
var shown = visualItems.length + audioItems.length;
|
|
63
|
-
|
|
63
|
+
// Only a tool that told us HOW to page (page_tool + next_args / query) gets a
|
|
64
|
+
// button. Every other grid used to render a Load more that did nothing —
|
|
65
|
+
// fetchNextPage returned early with no page_tool — which read as "broken".
|
|
66
|
+
if (sc.total != null && sc.total > shown && sc.page_tool && (sc.next_args || sc.query || sc.page)) {
|
|
64
67
|
h += '<button class="k-btn" id="load-more" style="width:100%;margin-top:10px">Load more (' +
|
|
65
68
|
shown + ' of ' + sc.total + ' shown)</button>';
|
|
69
|
+
} else if (sc.total != null && sc.total > shown) {
|
|
70
|
+
h += '<div style="text-align:center;font-size:12px;color:var(--text-muted);margin-top:10px">' + shown + ' of ' + sc.total + ' shown</div>';
|
|
66
71
|
}
|
|
67
72
|
el('stage').innerHTML = h;
|
|
68
73
|
el('stage').classList.remove('k-empty');
|
|
@@ -177,14 +182,26 @@ function fetchNextPage(btn) {
|
|
|
177
182
|
btn.disabled = true;
|
|
178
183
|
var label = btn.textContent;
|
|
179
184
|
btn.innerHTML = '<span class="k-spin"></span> Loading';
|
|
185
|
+
// The server knows its own paging arg names (page/limit, offset, cursor…):
|
|
186
|
+
// when it shipped next_args, send exactly that. The query+page shape is the
|
|
187
|
+
// legacy fallback list_media has always used.
|
|
180
188
|
var args = {};
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
189
|
+
if (state.next_args) {
|
|
190
|
+
for (var nk in state.next_args) { if (state.next_args[nk] !== undefined && state.next_args[nk] !== null) args[nk] = state.next_args[nk]; }
|
|
191
|
+
} else {
|
|
192
|
+
var q = state.query || {};
|
|
193
|
+
for (var k in q) { if (q[k] !== undefined && q[k] !== null && q[k] !== '') args[k] = q[k]; }
|
|
194
|
+
args.page = next;
|
|
195
|
+
if (state.page_size) args.page_size = state.page_size;
|
|
196
|
+
}
|
|
185
197
|
|
|
186
198
|
window.kolbo.callTool(state.page_tool, args).then(function (res) {
|
|
187
199
|
var sc = structured(res);
|
|
200
|
+
if (res && res.isError) {
|
|
201
|
+
btn.disabled = false;
|
|
202
|
+
btn.textContent = 'Could not load more — try again';
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
188
205
|
var more = (sc && sc.items) || [];
|
|
189
206
|
if (!more.length) {
|
|
190
207
|
// Nothing came back: say so rather than restoring a button that still
|
|
@@ -194,6 +211,9 @@ function fetchNextPage(btn) {
|
|
|
194
211
|
}
|
|
195
212
|
state.items = (state.items || []).concat(more);
|
|
196
213
|
state.page = (sc && sc.page) || next;
|
|
214
|
+
// The next page's args come from THIS response; none means this was the last page.
|
|
215
|
+
state.next_args = sc && sc.next_args;
|
|
216
|
+
if (sc && sc.next_args == null && sc.page_tool == null && state.next_args === undefined) state.next_args = undefined;
|
|
197
217
|
if (sc && sc.total != null) state.total = sc.total;
|
|
198
218
|
boot(state); // re-renders the grid + a fresh Load more button
|
|
199
219
|
}).catch(function () {
|
|
@@ -40,7 +40,7 @@ function registerColorPaletteTools(server, client, options = {}) {
|
|
|
40
40
|
return uiResult(UI.mediaGrid, text, {
|
|
41
41
|
widget: 'media-grid',
|
|
42
42
|
title: 'Color DNA Palettes',
|
|
43
|
-
items: palettes.slice(0,
|
|
43
|
+
items: palettes.slice(0, 300).map(p => ({
|
|
44
44
|
id: p.id,
|
|
45
45
|
title: p.is_active ? `${p.name} (active)` : p.name,
|
|
46
46
|
// First source image when available, else an SVG strip of the palette itself.
|
|
@@ -48,8 +48,8 @@ function registerColorPaletteTools(server, client, options = {}) {
|
|
|
48
48
|
media_type: 'image',
|
|
49
49
|
use_hint: 'Activate Color DNA palette "{TITLE}" (color_palette_id: {ID}) so it grades my generations.'
|
|
50
50
|
})),
|
|
51
|
-
total:
|
|
52
|
-
has_more: palettes.length >
|
|
51
|
+
total: palettes.length,
|
|
52
|
+
has_more: palettes.length > 300
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
|
package/src/tools/docs.js
CHANGED
|
@@ -63,7 +63,9 @@ function registerDocTools(server, client) {
|
|
|
63
63
|
subtitle: (d.updated_at ? String(d.updated_at).slice(0, 10) : '') + (d.is_shared ? ' · shared' : ''),
|
|
64
64
|
open_url: d.share_url || null
|
|
65
65
|
})),
|
|
66
|
-
total: docs.length
|
|
66
|
+
total: docs.length,
|
|
67
|
+
page_tool: 'list_docs',
|
|
68
|
+
next_args: docs.length >= (limit || 20) ? { project_id, page: (page || 1) + 1, limit } : undefined
|
|
67
69
|
});
|
|
68
70
|
}
|
|
69
71
|
);
|
package/src/tools/moodboards.js
CHANGED
|
@@ -11,14 +11,16 @@ function registerMoodboardTools(server, client, options = {}) {
|
|
|
11
11
|
// ─── list_moodboards ───────────────────────────────────────
|
|
12
12
|
server.tool(
|
|
13
13
|
'list_moodboards',
|
|
14
|
-
'List moodboards.
|
|
14
|
+
'List moodboards. Default scope is "personal" — the user\'s OWN custom moodboards, which is what the card shows. Pass scope "preset"/"global" for the system presets, "organization" for org-shared, or "all" for everything. Returns id, name, master_prompt, thumbnail, and image URLs for each.',
|
|
15
15
|
{
|
|
16
|
-
scope: z.enum(['all', 'personal', 'preset', 'global', 'organization']).optional().describe('Filter by scope. Default: "
|
|
16
|
+
scope: z.enum(['all', 'personal', 'preset', 'global', 'organization']).optional().describe('Filter by scope. Default: "personal" (only the user\'s own custom moodboards). "preset"/"global" = system presets. "organization" = org-shared. "all" = everything accessible.'),
|
|
17
17
|
project_id: projectScopeReadField
|
|
18
18
|
},
|
|
19
19
|
async ({ scope, project_id } = {}) => {
|
|
20
20
|
const params = new URLSearchParams();
|
|
21
|
-
|
|
21
|
+
// Users asked to see only THEIR moodboards on the card, not the system presets.
|
|
22
|
+
const effectiveScope = scope || 'personal';
|
|
23
|
+
if (effectiveScope !== 'all') params.set('scope', effectiveScope);
|
|
22
24
|
if (project_id) params.set('project_id', project_id);
|
|
23
25
|
const qs = params.toString();
|
|
24
26
|
const result = await client.get(`/v1/moodboards${qs ? '?' + qs : ''}`);
|
|
@@ -36,7 +38,7 @@ function registerMoodboardTools(server, client, options = {}) {
|
|
|
36
38
|
return uiResult(UI.mediaGrid, text, {
|
|
37
39
|
widget: 'media-grid',
|
|
38
40
|
title: 'Moodboards',
|
|
39
|
-
items: moodboards.slice(0,
|
|
41
|
+
items: moodboards.slice(0, 300).map(mb => ({
|
|
40
42
|
id: mb.id,
|
|
41
43
|
title: mb.name,
|
|
42
44
|
// API returns thumbnail_url + images[] (sdk listMoodboards) — both
|
|
@@ -46,8 +48,8 @@ function registerMoodboardTools(server, client, options = {}) {
|
|
|
46
48
|
media_type: 'image',
|
|
47
49
|
use_hint: 'Apply moodboard "{TITLE}" (moodboard_id: {ID}) to my next generation.'
|
|
48
50
|
})),
|
|
49
|
-
total:
|
|
50
|
-
has_more: moodboards.length >
|
|
51
|
+
total: moodboards.length,
|
|
52
|
+
has_more: moodboards.length > 300
|
|
51
53
|
});
|
|
52
54
|
}
|
|
53
55
|
);
|
|
@@ -26,7 +26,7 @@ function trackItem(track) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function tracksResult(ui, title, tracks, total) {
|
|
29
|
+
function tracksResult(ui, title, tracks, total, paging) {
|
|
30
30
|
if (!tracks.length) return { content: [{ type: 'text', text: 'No SYNCI tracks found.' }] };
|
|
31
31
|
const text = [
|
|
32
32
|
`Found ${tracks.length} track${tracks.length === 1 ? '' : 's'}${total ? ` (of ${total})` : ''}.`,
|
|
@@ -38,8 +38,11 @@ function tracksResult(ui, title, tracks, total) {
|
|
|
38
38
|
return uiResult(UI.mediaGrid, text, {
|
|
39
39
|
widget: 'media-grid',
|
|
40
40
|
title,
|
|
41
|
-
items: tracks.
|
|
41
|
+
items: tracks.map(trackItem),
|
|
42
42
|
total: total != null ? total : tracks.length,
|
|
43
|
+
...(paging && total != null && (paging.offset + tracks.length) < total
|
|
44
|
+
? { page_tool: paging.tool, next_args: { ...paging.args, offset: paging.offset + tracks.length, limit: paging.limit } }
|
|
45
|
+
: {}),
|
|
43
46
|
});
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -90,7 +93,8 @@ function registerMusicLibraryTools(server, client, options = {}) {
|
|
|
90
93
|
},
|
|
91
94
|
async (args) => {
|
|
92
95
|
const result = await client.post('/v1/music-library/search', args);
|
|
93
|
-
return tracksResult(ui, `SYNCI — ${args.query || 'Search'}`, result.tracks || [], result.total
|
|
96
|
+
return tracksResult(ui, `SYNCI — ${args.query || 'Search'}`, result.tracks || [], result.total,
|
|
97
|
+
{ tool: 'search_music_library', args, offset: args.offset || 0, limit: args.limit || (result.tracks || []).length || 20 });
|
|
94
98
|
},
|
|
95
99
|
);
|
|
96
100
|
|
|
@@ -118,7 +122,8 @@ function registerMusicLibraryTools(server, client, options = {}) {
|
|
|
118
122
|
if (limit != null) params.set('limit', String(limit));
|
|
119
123
|
if (offset != null) params.set('offset', String(offset));
|
|
120
124
|
const result = await client.get(`/v1/music-library/catalog?${params.toString()}`);
|
|
121
|
-
return tracksResult(ui, 'SYNCI Music Library', result.tracks || [], result.total
|
|
125
|
+
return tracksResult(ui, 'SYNCI Music Library', result.tracks || [], result.total,
|
|
126
|
+
{ tool: 'browse_music_library', args: { sort }, offset: offset || 0, limit: limit || (result.tracks || []).length || 20 });
|
|
122
127
|
},
|
|
123
128
|
);
|
|
124
129
|
|
package/src/tools/projects.js
CHANGED
|
@@ -60,7 +60,9 @@ function registerProjectTools(server, client) {
|
|
|
60
60
|
open_url: p.open_url,
|
|
61
61
|
use_hint: 'Use my "{TITLE}" project (project_id: {ID}) for what I do next.'
|
|
62
62
|
})),
|
|
63
|
-
total: projects.length
|
|
63
|
+
total: projects.length,
|
|
64
|
+
page_tool: 'list_projects',
|
|
65
|
+
next_args: projects.length >= (limit || 50) ? { search, include_archived, page: (page || 1) + 1, limit } : undefined
|
|
64
66
|
});
|
|
65
67
|
}
|
|
66
68
|
);
|
|
@@ -359,7 +361,9 @@ function registerProjectTools(server, client) {
|
|
|
359
361
|
].filter(Boolean).join(' · '),
|
|
360
362
|
badge: (s.types && s.types[0]) || undefined
|
|
361
363
|
})),
|
|
362
|
-
total: sessions.length
|
|
364
|
+
total: sessions.length,
|
|
365
|
+
page_tool: 'list_sessions',
|
|
366
|
+
next_args: sessions.length >= (limit || 20) ? { project_id, type, types, page: (page || 1) + 1, limit } : undefined
|
|
363
367
|
});
|
|
364
368
|
}
|
|
365
369
|
);
|
|
@@ -84,7 +84,7 @@ function registerStockLibraryTools(server, client, options = {}) {
|
|
|
84
84
|
// the media grid on them, so the card rendered one broken-file glyph per cell.
|
|
85
85
|
// media.js and listResult() have always done it this way; these five lagged.
|
|
86
86
|
{
|
|
87
|
-
const items = assets.
|
|
87
|
+
const items = assets.map((a) => {
|
|
88
88
|
const mt = widgetMediaType(a.mediaType);
|
|
89
89
|
return {
|
|
90
90
|
id: a.source + ':' + a.sourceId,
|
|
@@ -107,7 +107,12 @@ function registerStockLibraryTools(server, client, options = {}) {
|
|
|
107
107
|
title: 'Stock — "' + (args.query || 'browse') + '"',
|
|
108
108
|
items,
|
|
109
109
|
total: result.total != null ? result.total : assets.length,
|
|
110
|
-
has_more: !!result.hasMore
|
|
110
|
+
has_more: !!result.hasMore,
|
|
111
|
+
page: args.page || 1,
|
|
112
|
+
page_tool: 'search_stock_media',
|
|
113
|
+
next_args: result.hasMore
|
|
114
|
+
? { ...args, page: (args.page || 1) + 1, ...(result.nextCursor ? { cursor: result.nextCursor } : {}) }
|
|
115
|
+
: undefined
|
|
111
116
|
});
|
|
112
117
|
}
|
|
113
118
|
|
|
@@ -172,7 +177,7 @@ function registerStockLibraryTools(server, client, options = {}) {
|
|
|
172
177
|
// media.js and listResult() have always done it this way; these five lagged.
|
|
173
178
|
{
|
|
174
179
|
const collections = result.collections || [];
|
|
175
|
-
const items = collections.slice(0,
|
|
180
|
+
const items = collections.slice(0, 300).map((c) => ({
|
|
176
181
|
id: c.id,
|
|
177
182
|
title: c.title,
|
|
178
183
|
subtitle: [c.kind, c.mediaType].filter(Boolean).join(' · '),
|
package/src/tools/visual_dna.js
CHANGED
|
@@ -154,7 +154,7 @@ function registerVisualDnaTools(server, client, options = {}) {
|
|
|
154
154
|
return uiResult(UI.mediaGrid, text, {
|
|
155
155
|
widget: 'media-grid',
|
|
156
156
|
title: 'Visual DNA Profiles',
|
|
157
|
-
items: dnas.
|
|
157
|
+
items: dnas.map(d => ({
|
|
158
158
|
id: d.id,
|
|
159
159
|
title: d.name,
|
|
160
160
|
subtitle: (d.dna_type || '') + (Array.isArray(d.tags) && d.tags.length ? ' · ' + d.tags.slice(0, 3).join(', ') : ''),
|
|
@@ -164,7 +164,12 @@ function registerVisualDnaTools(server, client, options = {}) {
|
|
|
164
164
|
use_hint: 'Use Visual DNA "{TITLE}" (id: {ID}) in my next generation for character/style consistency.'
|
|
165
165
|
})),
|
|
166
166
|
total,
|
|
167
|
-
has_more: result.has_more
|
|
167
|
+
has_more: !!result.has_more,
|
|
168
|
+
page: page && page > 0 ? Math.floor(page) : 1,
|
|
169
|
+
page_tool: 'list_visual_dnas',
|
|
170
|
+
next_args: result.has_more
|
|
171
|
+
? { scope, search, collection, tags, project_id, page: (page && page > 0 ? Math.floor(page) : 1) + 1, limit }
|
|
172
|
+
: undefined
|
|
168
173
|
});
|
|
169
174
|
}
|
|
170
175
|
);
|
package/src/tools/voices.js
CHANGED
|
@@ -99,7 +99,10 @@ function registerVoiceTools(server, client, options = {}) {
|
|
|
99
99
|
use_hint: 'Use voice "{TITLE}" (voice_id: {ID}) for text-to-speech — ask me what text to speak.'
|
|
100
100
|
})),
|
|
101
101
|
total: voices.length,
|
|
102
|
-
has_more: more > 0
|
|
102
|
+
has_more: more > 0,
|
|
103
|
+
page: pageNum,
|
|
104
|
+
page_tool: 'list_voices',
|
|
105
|
+
next_args: more > 0 ? { language, gender, provider, page: pageNum + 1, limit: perPage } : undefined
|
|
103
106
|
});
|
|
104
107
|
}
|
|
105
108
|
|