@kolbo/mcp 1.79.2 → 1.79.4
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/cdn.js +54 -0
- package/src/client.js +3 -2
- package/src/progress.js +14 -1
- package/src/tools/_shared.js +8 -0
- package/src/tools/chat.js +3 -3
- package/src/tools/generate.js +19 -19
- package/src/tools/visual_dna.js +1 -1
package/package.json
CHANGED
package/src/cdn.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rewrite Kolbo Spaces origin URLs to the custom-domain CDN.
|
|
3
|
+
*
|
|
4
|
+
* Origin (`*.digitaloceanspaces.com`) is uncached and makes every MCP image
|
|
5
|
+
* / video / Visual DNA sheet crawl. Custom hosts:
|
|
6
|
+
* production → media.kolbo.ai
|
|
7
|
+
* development → media-dev.kolbo.ai
|
|
8
|
+
* staging → media-staging.kolbo.ai
|
|
9
|
+
* sapir → media-sapir.kolbo.ai
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const HOST_MAP = [
|
|
13
|
+
['kolboai-production.ams3.cdn.digitaloceanspaces.com', 'media.kolbo.ai'],
|
|
14
|
+
['kolboai-production.ams3.digitaloceanspaces.com', 'media.kolbo.ai'],
|
|
15
|
+
['kolboai-development.ams3.cdn.digitaloceanspaces.com', 'media-dev.kolbo.ai'],
|
|
16
|
+
['kolboai-development.ams3.digitaloceanspaces.com', 'media-dev.kolbo.ai'],
|
|
17
|
+
['kolboai-staging.ams3.cdn.digitaloceanspaces.com', 'media-staging.kolbo.ai'],
|
|
18
|
+
['kolboai-staging.ams3.digitaloceanspaces.com', 'media-staging.kolbo.ai'],
|
|
19
|
+
['kolboai-sapir.ams3.cdn.digitaloceanspaces.com', 'media-sapir.kolbo.ai'],
|
|
20
|
+
['kolboai-sapir.ams3.digitaloceanspaces.com', 'media-sapir.kolbo.ai'],
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const URL_IN_TEXT = /https?:\/\/[^\s"'<>\\]+/gi;
|
|
24
|
+
const SKIP_KEYS = /^(upload_url|uploadUrl|signed_url|signedUrl|presigned_url|presignedUrl|put_url|putUrl)$/;
|
|
25
|
+
|
|
26
|
+
function rewriteUrl(url) {
|
|
27
|
+
if (!url || typeof url !== 'string') return url;
|
|
28
|
+
if (/[?&](X-Amz-Algorithm|AWSAccessKeyId|Signature=)/i.test(url)) return url;
|
|
29
|
+
let out = url;
|
|
30
|
+
for (const [from, to] of HOST_MAP) {
|
|
31
|
+
if (out.includes(from)) out = out.split(from).join(to);
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function rewriteTree(value, seen) {
|
|
37
|
+
const visited = seen || new WeakSet();
|
|
38
|
+
if (typeof value === 'string') {
|
|
39
|
+
if (!value.includes('digitaloceanspaces.com')) return value;
|
|
40
|
+
if (/^https?:\/\//i.test(value) && !/\s/.test(value)) return rewriteUrl(value);
|
|
41
|
+
return value.replace(URL_IN_TEXT, rewriteUrl);
|
|
42
|
+
}
|
|
43
|
+
if (!value || typeof value !== 'object') return value;
|
|
44
|
+
if (visited.has(value)) return value;
|
|
45
|
+
visited.add(value);
|
|
46
|
+
if (Array.isArray(value)) return value.map((item) => rewriteTree(item, visited));
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const [key, item] of Object.entries(value)) {
|
|
49
|
+
out[key] = SKIP_KEYS.test(key) ? item : rewriteTree(item, visited);
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = { rewriteUrl, rewriteTree, HOST_MAP };
|
package/src/client.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const progress = require('./progress');
|
|
5
|
+
const { rewriteTree } = require('./cdn');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Kolbo API HTTP client wrapper
|
|
@@ -402,7 +403,7 @@ class KolboClient {
|
|
|
402
403
|
progress.trackGeneration(generationId, () => this._cancelAfterCallerAbort(generationId));
|
|
403
404
|
}
|
|
404
405
|
}
|
|
405
|
-
return data;
|
|
406
|
+
return rewriteTree(data);
|
|
406
407
|
}
|
|
407
408
|
|
|
408
409
|
async _cancelAfterCallerAbort(generationId) {
|
|
@@ -543,7 +544,7 @@ class KolboClient {
|
|
|
543
544
|
await progress.generation(generationId);
|
|
544
545
|
progress.trackGeneration(generationId, () => this._cancelAfterCallerAbort(generationId));
|
|
545
546
|
}
|
|
546
|
-
return data;
|
|
547
|
+
return rewriteTree(data);
|
|
547
548
|
}
|
|
548
549
|
}
|
|
549
550
|
|
package/src/progress.js
CHANGED
|
@@ -77,6 +77,19 @@ function trackGeneration(id, cancel) {
|
|
|
77
77
|
if (extra.signal.aborted) cancelTracked();
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Remove a generation from the tracked set so it will NOT be cancelled when
|
|
82
|
+
* the MCP host aborts the tool call. Use this when the CLIENT-SIDE poll
|
|
83
|
+
* window times out but the generation should keep running server-side — the
|
|
84
|
+
* tool returns early with generation_id + _timed_out:true, and the caller
|
|
85
|
+
* re-issues get_generation_status to collect the result later.
|
|
86
|
+
*/
|
|
87
|
+
function untrackGeneration(id) {
|
|
88
|
+
const extra = storage.getStore();
|
|
89
|
+
if (!extra?.__kolboTrackedGenerations || !id) return;
|
|
90
|
+
extra.__kolboTrackedGenerations.delete(String(id));
|
|
91
|
+
}
|
|
92
|
+
|
|
80
93
|
async function generation(id) {
|
|
81
94
|
const extra = storage.getStore();
|
|
82
95
|
const token = extra?._meta?.progressToken;
|
|
@@ -123,4 +136,4 @@ async function sendGenerationProgress(extra, token, id) {
|
|
|
123
136
|
});
|
|
124
137
|
}
|
|
125
138
|
|
|
126
|
-
module.exports = { run, generation, tick, signal, wait, trackGeneration };
|
|
139
|
+
module.exports = { run, generation, tick, signal, wait, trackGeneration, untrackGeneration };
|
package/src/tools/_shared.js
CHANGED
|
@@ -290,7 +290,13 @@ async function resolveToBuffer(source, kind, opts = {}) {
|
|
|
290
290
|
// it instead of duplicating the try/catch. Genuine failures (state
|
|
291
291
|
// failed/cancelled → GenerationFailedError) are NOT caught here; they still
|
|
292
292
|
// throw and surface as a real tool error.
|
|
293
|
+
//
|
|
294
|
+
// CRITICAL: when the poll window times out, we UNTRACK the generation so that
|
|
295
|
+
// when the MCP host eventually aborts the tool call (e.g., at ~180s), it does
|
|
296
|
+
// NOT cancel the server-side generation. The generation keeps running, and the
|
|
297
|
+
// caller can collect it later with get_generation_status.
|
|
293
298
|
const { pollUntilDone } = require('../polling');
|
|
299
|
+
const progress = require('../progress');
|
|
294
300
|
|
|
295
301
|
/**
|
|
296
302
|
* @returns {Promise<{result: object}|{timedOut: object}>}
|
|
@@ -301,6 +307,8 @@ async function pollOrTimedOut(client, generationId, pollOpts) {
|
|
|
301
307
|
return { result: await pollUntilDone(client, generationId, pollOpts) };
|
|
302
308
|
} catch (err) {
|
|
303
309
|
if (!err || !err.timedOut) throw err;
|
|
310
|
+
// Untrack so the MCP host aborting this tool call does NOT cancel the generation.
|
|
311
|
+
progress.untrackGeneration(generationId);
|
|
304
312
|
return {
|
|
305
313
|
timedOut: {
|
|
306
314
|
content: [{
|
package/src/tools/chat.js
CHANGED
|
@@ -43,9 +43,9 @@ function registerChatTools(server, client) {
|
|
|
43
43
|
project_id
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
const timeout =
|
|
46
|
+
// Return before host cutoff (150s) even for deep think / web search.
|
|
47
|
+
// The LLM can call this tool again with the same session_id to continue.
|
|
48
|
+
const timeout = 150000;
|
|
49
49
|
|
|
50
50
|
const poll = await pollOrTimedOut(client, gen.message_id, {
|
|
51
51
|
interval: (gen.poll_interval_hint || 2) * 1000,
|
package/src/tools/generate.js
CHANGED
|
@@ -245,7 +245,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
245
245
|
status_args: { generation_ids: batch.ids, wait: true },
|
|
246
246
|
reference_images
|
|
247
247
|
});
|
|
248
|
-
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout:
|
|
248
|
+
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 150000 }, 'generate_image');
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
const gen = await client.post('/v1/generate/image', { ...shared, prompt, num_images });
|
|
@@ -258,7 +258,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
258
258
|
|
|
259
259
|
const poll = await pollOrTimedOut(client, gen.generation_id, {
|
|
260
260
|
interval: (gen.poll_interval_hint || 3) * 1000,
|
|
261
|
-
timeout:
|
|
261
|
+
timeout: 150000
|
|
262
262
|
});
|
|
263
263
|
if (poll.timedOut) return poll.timedOut;
|
|
264
264
|
const result = poll.result;
|
|
@@ -328,7 +328,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
328
328
|
status_args: { generation_ids: batch.ids, wait: true },
|
|
329
329
|
reference_images: source_images
|
|
330
330
|
});
|
|
331
|
-
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout:
|
|
331
|
+
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 150000 }, 'generate_image_edit');
|
|
332
332
|
}
|
|
333
333
|
|
|
334
334
|
const gen = await client.post('/v1/generate/image-edit', { ...shared, prompt, num_images });
|
|
@@ -341,12 +341,12 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
341
341
|
});
|
|
342
342
|
|
|
343
343
|
// Multi-source compositing or DNA-anchored edits routinely exceed 120s
|
|
344
|
-
// server-side.
|
|
345
|
-
//
|
|
346
|
-
|
|
344
|
+
// server-side. But we still need to return before the 180s host cutoff,
|
|
345
|
+
// so use 150s for all cases. The LLM can call get_generation_status
|
|
346
|
+
// with wait=true to continue polling.
|
|
347
347
|
const poll = await pollOrTimedOut(client, gen.generation_id, {
|
|
348
348
|
interval: (gen.poll_interval_hint || 3) * 1000,
|
|
349
|
-
timeout:
|
|
349
|
+
timeout: 150000
|
|
350
350
|
});
|
|
351
351
|
if (poll.timedOut) return poll.timedOut;
|
|
352
352
|
const result = poll.result;
|
|
@@ -576,7 +576,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
576
576
|
status_args: { generation_ids: batch.ids, wait: true },
|
|
577
577
|
reference_images
|
|
578
578
|
});
|
|
579
|
-
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 8) * 1000, timeout:
|
|
579
|
+
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 8) * 1000, timeout: 150000 }, 'generate_video');
|
|
580
580
|
}
|
|
581
581
|
|
|
582
582
|
const gen = await client.post('/v1/generate/video', { ...shared, prompt });
|
|
@@ -592,7 +592,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
592
592
|
// 5-min window forced routine calls into the timeout-and-recover path.
|
|
593
593
|
const poll = await pollOrTimedOut(client, gen.generation_id, {
|
|
594
594
|
interval: (gen.poll_interval_hint || 8) * 1000,
|
|
595
|
-
timeout:
|
|
595
|
+
timeout: 150000
|
|
596
596
|
});
|
|
597
597
|
if (poll.timedOut) return poll.timedOut;
|
|
598
598
|
const result = poll.result;
|
|
@@ -678,7 +678,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
678
678
|
// Veo 3.1, Hailuo 2.3, Seedance 2 at higher durations/resolutions).
|
|
679
679
|
const poll = await pollOrTimedOut(client, gen.generation_id, {
|
|
680
680
|
interval: (gen.poll_interval_hint || 8) * 1000,
|
|
681
|
-
timeout:
|
|
681
|
+
timeout: 150000
|
|
682
682
|
});
|
|
683
683
|
if (poll.timedOut) return poll.timedOut;
|
|
684
684
|
const result = poll.result;
|
|
@@ -746,7 +746,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
746
746
|
|
|
747
747
|
const poll = await pollOrTimedOut(client, gen.generation_id, {
|
|
748
748
|
interval: (gen.poll_interval_hint || 8) * 1000,
|
|
749
|
-
timeout:
|
|
749
|
+
timeout: 150000
|
|
750
750
|
});
|
|
751
751
|
if (poll.timedOut) return poll.timedOut;
|
|
752
752
|
const result = poll.result;
|
|
@@ -1334,7 +1334,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1334
1334
|
|
|
1335
1335
|
const poll = await pollOrTimedOut(client, startResponse.generation_id, {
|
|
1336
1336
|
interval: (startResponse.poll_interval_hint || 8) * 1000,
|
|
1337
|
-
timeout:
|
|
1337
|
+
timeout: 150000
|
|
1338
1338
|
});
|
|
1339
1339
|
if (poll.timedOut) return poll.timedOut;
|
|
1340
1340
|
const result = poll.result;
|
|
@@ -1435,7 +1435,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1435
1435
|
|
|
1436
1436
|
const poll = await pollOrTimedOut(client, startResponse.generation_id, {
|
|
1437
1437
|
interval: (startResponse.poll_interval_hint || 8) * 1000,
|
|
1438
|
-
timeout:
|
|
1438
|
+
timeout: 150000
|
|
1439
1439
|
});
|
|
1440
1440
|
if (poll.timedOut) return poll.timedOut;
|
|
1441
1441
|
const result = poll.result;
|
|
@@ -1553,7 +1553,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1553
1553
|
|
|
1554
1554
|
const poll = await pollOrTimedOut(client, startResponse.generation_id, {
|
|
1555
1555
|
interval: (startResponse.poll_interval_hint || 8) * 1000,
|
|
1556
|
-
timeout:
|
|
1556
|
+
timeout: 150000
|
|
1557
1557
|
});
|
|
1558
1558
|
if (poll.timedOut) return poll.timedOut;
|
|
1559
1559
|
const result = poll.result;
|
|
@@ -1684,7 +1684,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1684
1684
|
|
|
1685
1685
|
const poll = await pollOrTimedOut(client, startResponse.generation_id, {
|
|
1686
1686
|
interval: (startResponse.poll_interval_hint || 8) * 1000,
|
|
1687
|
-
timeout:
|
|
1687
|
+
timeout: 150000
|
|
1688
1688
|
});
|
|
1689
1689
|
if (poll.timedOut) return poll.timedOut;
|
|
1690
1690
|
const result = poll.result;
|
|
@@ -1770,7 +1770,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1770
1770
|
|
|
1771
1771
|
const poll = await pollOrTimedOut(client, startResponse.generation_id, {
|
|
1772
1772
|
interval: (startResponse.poll_interval_hint || 5) * 1000,
|
|
1773
|
-
timeout:
|
|
1773
|
+
timeout: 150000 // Return before host cutoff; LLM can call get_generation_status with wait=true for long podcasts
|
|
1774
1774
|
});
|
|
1775
1775
|
if (poll.timedOut) return poll.timedOut;
|
|
1776
1776
|
const result = poll.result;
|
|
@@ -1835,7 +1835,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1835
1835
|
|
|
1836
1836
|
const poll = await pollOrTimedOut(client, startResponse.generation_id, {
|
|
1837
1837
|
interval: (startResponse.poll_interval_hint || 8) * 1000,
|
|
1838
|
-
timeout:
|
|
1838
|
+
timeout: 150000 // Return before host cutoff; LLM can call get_generation_status with wait=true for slow 3D jobs
|
|
1839
1839
|
});
|
|
1840
1840
|
if (poll.timedOut) return poll.timedOut;
|
|
1841
1841
|
const result = poll.result;
|
|
@@ -1996,7 +1996,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1996
1996
|
|
|
1997
1997
|
const poll = await pollOrTimedOut(client, gen.generation_id, {
|
|
1998
1998
|
interval: (gen.poll_interval_hint || 5) * 1000,
|
|
1999
|
-
timeout:
|
|
1999
|
+
timeout: 150000 // Return before host cutoff; LLM can call get_generation_status with wait=true for split_upscale and multi_shot
|
|
2000
2000
|
});
|
|
2001
2001
|
if (poll.timedOut) return poll.timedOut;
|
|
2002
2002
|
const result = poll.result;
|
|
@@ -2165,7 +2165,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2165
2165
|
|
|
2166
2166
|
const poll = await pollOrTimedOut(client, gen.generation_id, {
|
|
2167
2167
|
interval: (gen.poll_interval_hint || 8) * 1000,
|
|
2168
|
-
timeout:
|
|
2168
|
+
timeout: 150000
|
|
2169
2169
|
});
|
|
2170
2170
|
if (poll.timedOut) return poll.timedOut;
|
|
2171
2171
|
const result = poll.result;
|
package/src/tools/visual_dna.js
CHANGED
|
@@ -135,7 +135,7 @@ function registerVisualDnaTools(server, client, options = {}) {
|
|
|
135
135
|
// such a DNA brings its own voice as reference audio on models with audio slots. Without
|
|
136
136
|
// it the model has to fetch each DNA in full just to find out.
|
|
137
137
|
const text = compactList(dnas, {
|
|
138
|
-
fields: ['id', 'name', 'type', 'folder_id', 'tags', 'thumbnail', 'has_voice_reference'],
|
|
138
|
+
fields: ['id', 'name', 'type', 'dna_type', 'folder_id', 'tags', 'thumbnail', 'description', 'sheet_url', 'has_voice_reference'],
|
|
139
139
|
cap: 60,
|
|
140
140
|
total,
|
|
141
141
|
note: 'Narrow with `search`, `tags`, or `collection`, or pass `page`/`limit` for the rest; get_visual_dna returns one in full.',
|