@kolbo/mcp 1.40.0 → 1.42.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
CHANGED
|
@@ -122,16 +122,35 @@ function renderGenerating(sc) {
|
|
|
122
122
|
schedulePoll(sc);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
// Poll ceilings so the card never spins "Generating" forever. A generation
|
|
126
|
+
// that genuinely stalls, or a status call that keeps failing (e.g. the record
|
|
127
|
+
// can't be resolved), surfaces an error + Try Again instead of hanging.
|
|
128
|
+
var MAX_POLL_MS = 12 * 60 * 1000; // hard wall-clock ceiling (covers slow 4K video)
|
|
129
|
+
var MAX_POLL_ERRORS = 12; // ~48s of consecutive status-call failures → give up
|
|
130
|
+
var pollStart = 0, pollErrors = 0;
|
|
131
|
+
|
|
125
132
|
function schedulePoll(sc) {
|
|
133
|
+
if (!pollStart) pollStart = Date.now();
|
|
126
134
|
clearTimeout(pollTimer);
|
|
127
135
|
pollTimer = setTimeout(function () { poll(sc); }, 4000);
|
|
128
136
|
}
|
|
129
137
|
function poll(sc) {
|
|
138
|
+
if (pollStart && (Date.now() - pollStart) > MAX_POLL_MS) {
|
|
139
|
+
return renderError('This is taking longer than expected and may have stalled. Try again — if it keeps happening the model may be busy. Any finished result will also appear in your Kolbo library.');
|
|
140
|
+
}
|
|
130
141
|
var args = sc.status_args || { generation_id: sc.generation_id };
|
|
131
142
|
window.kolbo.callTool(sc.poll_tool || 'get_generation_status', args).then(function (res) {
|
|
132
143
|
var st = structured(res) || {};
|
|
133
144
|
var stateName = st.state || st.phase || st.status;
|
|
145
|
+
// A failed status CALL (tool error / not-found / {success:false}) is not a
|
|
146
|
+
// generation state — count it toward the consecutive-error cap so a record
|
|
147
|
+
// that can't be resolved errors out fast instead of polling forever.
|
|
148
|
+
if ((res && res.isError) || st.success === false || (st.error && !stateName)) {
|
|
149
|
+
if (++pollErrors >= MAX_POLL_ERRORS) return renderError(st.error || 'Could not track this generation. Please try again.');
|
|
150
|
+
return schedulePoll(sc);
|
|
151
|
+
}
|
|
134
152
|
if (stateName === 'completed') {
|
|
153
|
+
pollErrors = 0;
|
|
135
154
|
var r = st.result || st;
|
|
136
155
|
|
|
137
156
|
var done = Object.assign({}, sc, r, {
|
|
@@ -152,9 +171,13 @@ function poll(sc) {
|
|
|
152
171
|
} else if (stateName === 'failed' || stateName === 'error' || stateName === 'cancelled') {
|
|
153
172
|
renderError(st.error || 'Generation ' + stateName);
|
|
154
173
|
} else {
|
|
174
|
+
pollErrors = 0; // a valid in-progress response resets the failure streak
|
|
155
175
|
schedulePoll(sc);
|
|
156
176
|
}
|
|
157
|
-
}).catch(function () {
|
|
177
|
+
}).catch(function () {
|
|
178
|
+
if (++pollErrors >= MAX_POLL_ERRORS) return renderError('Lost connection while tracking this generation. Please try again.');
|
|
179
|
+
schedulePoll(sc);
|
|
180
|
+
});
|
|
158
181
|
}
|
|
159
182
|
|
|
160
183
|
/* ---------- results ---------- */
|
|
@@ -82,18 +82,31 @@ function boot(sc) {
|
|
|
82
82
|
window.kolbo.notifySize();
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
var pollStart = 0, pollErrors = 0;
|
|
86
|
+
var MAX_POLL_MS = 12 * 60 * 1000, MAX_POLL_ERRORS = 12;
|
|
85
87
|
function poll() {
|
|
88
|
+
if (!pollStart) pollStart = Date.now();
|
|
89
|
+
if ((Date.now() - pollStart) > MAX_POLL_MS) {
|
|
90
|
+
return boot(Object.assign({}, state, { phase: 'failed', error: 'Transcription is taking longer than expected and may have stalled — please try again.' }));
|
|
91
|
+
}
|
|
86
92
|
window.kolbo.callTool(state.poll_tool || 'get_generation_status', { generation_id: state.generation_id })
|
|
87
93
|
.then(function (res) {
|
|
88
94
|
var st = structured(res) || {};
|
|
89
95
|
var s = st.state || st.phase;
|
|
96
|
+
if ((res && res.isError) || st.success === false || (st.error && !s)) {
|
|
97
|
+
if (++pollErrors >= MAX_POLL_ERRORS) return boot(Object.assign({}, state, { phase: 'failed', error: st.error || 'Could not track this transcription. Please try again.' }));
|
|
98
|
+
pollTimer = setTimeout(poll, 5000); return;
|
|
99
|
+
}
|
|
90
100
|
if (s === 'completed') {
|
|
91
101
|
var r = st.result || st;
|
|
92
102
|
boot(Object.assign({}, state, r, { phase: 'completed', credits_used: st.credits_used }));
|
|
93
103
|
} else if (s === 'failed' || s === 'cancelled') {
|
|
94
104
|
boot(Object.assign({}, state, { phase: 'failed', error: st.error }));
|
|
95
|
-
} else { pollTimer = setTimeout(poll, 5000); }
|
|
96
|
-
}).catch(function () {
|
|
105
|
+
} else { pollErrors = 0; pollTimer = setTimeout(poll, 5000); }
|
|
106
|
+
}).catch(function () {
|
|
107
|
+
if (++pollErrors >= MAX_POLL_ERRORS) return boot(Object.assign({}, state, { phase: 'failed', error: 'Lost connection while tracking this transcription. Please try again.' }));
|
|
108
|
+
pollTimer = setTimeout(poll, 5000);
|
|
109
|
+
});
|
|
97
110
|
}
|
|
98
111
|
|
|
99
112
|
window.kolbo.onToolResult(function (result) {
|
|
@@ -90,7 +90,8 @@ function boot(sc) {
|
|
|
90
90
|
var exts = [];
|
|
91
91
|
kinds.forEach(function (k) { if (KINDS[k]) exts = exts.concat(KINDS[k].exts); });
|
|
92
92
|
el('picker').setAttribute('accept', exts.map(function (e) { return '.' + e; }).join(','));
|
|
93
|
-
|
|
93
|
+
var maxN = sc.max_files || 10;
|
|
94
|
+
el('accept-hint').textContent = kinds.join(' · ') + (maxN === 1 ? ' — one file' : ' — up to ' + maxN + ' files');
|
|
94
95
|
if (expired()) return showExpired();
|
|
95
96
|
window.kolbo.notifySize();
|
|
96
97
|
}
|
package/src/tools/generate.js
CHANGED
|
@@ -105,7 +105,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
105
105
|
'THE tool for ANY prompt-driven / content edit of an existing image — changing the scene ("make it night", "change the sky to sunset"), adding/removing/replacing objects, restyling, recoloring, compositing, or any "edit this image to…" request. This is the image-editing equivalent of generate_image and runs on strong dedicated editing models (nano-banana-2, gpt-image-2). Provide the source image URL(s) in `source_images` and the instruction in `prompt`. Supports Visual DNA profiles and moodboards for style-consistent edits. Do NOT use `edit_image` for these — that tool is only for mechanical enhancements (upscale/reframe/remove-background/skin). For a brand-new image from scratch, use generate_image. Returns the edited image URL(s) when complete.',
|
|
106
106
|
{
|
|
107
107
|
prompt: z.string().describe('Description of the edit to apply (e.g., "remove the background", "change the sky to sunset")'),
|
|
108
|
-
model: z.string().optional().describe('Model identifier — REQUIRED in practice: pick a specific
|
|
108
|
+
model: z.string().optional().describe('Model identifier — REQUIRED in practice: pick a specific IMAGE-EDITING model, do NOT omit (omitting = Smart Select auto-pick, which we avoid). Strong current defaults: "nano-banana-pro/edit" (best general prompt editor), "gpt-image/1.5-image-to-image" (photoreal), or "flux-2/edit". NOTE: text-to-image ids like "nano-banana-2"/"gpt-image-2" are NOT editors — don\'t use them here. Call list_models type="image_editing" to see all options and pick per the user\'s intent.'),
|
|
109
109
|
source_images: z.array(z.string()).describe('PIXEL-ACCURATE compositing. Array of source image URLs whose pixel content is composited into the output. **Cap: pass at most `max_reference_images` URLs from list_models for the chosen model — exceeding it is a deterministic 400.** Three modes the model auto-detects from input shape: (1) Single image → edit/transform that image. (2) Multiple images, one base + others → composite the others into the base. (3) Multiple images with no clear base → generate a new scene that pixel-accurately embeds the supplied images at positions described in the prompt. Mode 3 is the canonical pattern for thumbnails / branded compositions where exact-pixel logo + face fidelity matter. Refer to source images in the prompt by ordinal position ("FIRST source image", "SECOND source image") or use @image1/@image2 tags. Add "composite AS-IS, do not redraw or restyle" to lock pixels.'),
|
|
110
110
|
aspect_ratio: z.string().optional().describe('Output aspect ratio (e.g., "1:1", "16:9", "9:16"). Must be in the chosen model\'s `supported_aspect_ratios` from list_models. Default: "1:1"'),
|
|
111
111
|
enhance_prompt: z.boolean().optional().describe('Enhance the prompt for better results. Default: true'),
|
|
@@ -295,7 +295,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
295
295
|
'Generate a video from a text prompt using Kolbo AI. For animating an existing still image into motion, use generate_video_from_image instead. For a coordinated multi-scene video campaign, use generate_creative_director with workflow_type="video". Supports reference images (for style/composition guidance). Does NOT support Visual DNA — for character-consistent video use generate_elements or animate a DNA-locked still via generate_video_from_image. Returns the final video URL when complete.',
|
|
296
296
|
{
|
|
297
297
|
prompt: z.string().describe('Text description of the video to generate'),
|
|
298
|
-
model: z.string().optional().describe('Model identifier.
|
|
298
|
+
model: z.string().optional().describe('Model identifier — pick a SPECIFIC model, do NOT omit (omitting = Smart Select auto-pick, which we avoid). Strong current defaults: "seedance-2" (versatile) or "veo3" (Veo 3.1, cinematic + native audio); the Kling family (call list_models for exact ids like kling-video/v3/pro/text-to-video) is strongest for motion. Call list_models type="text_to_video" to see all options + check supported_durations / supported_aspect_ratios, and choose per the user\'s intent.'),
|
|
299
299
|
aspect_ratio: z.string().optional().describe('Aspect ratio (e.g., "16:9", "9:16", "1:1"). Must be in the chosen model\'s `supported_aspect_ratios` from list_models. Default: "16:9"'),
|
|
300
300
|
duration: z.number().optional().describe('Duration in seconds. Must be a value in `supported_durations` from list_models, OR within `min_output_duration`-`max_output_duration` (whichever the model exposes). Default: 5'),
|
|
301
301
|
enhance_prompt: z.boolean().optional().describe('Enhance the prompt. Default: true'),
|
|
@@ -346,7 +346,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
346
346
|
{
|
|
347
347
|
image_url: z.string().describe('URL of the source image to animate'),
|
|
348
348
|
prompt: z.string().describe('Text description of the desired MOTION (e.g., "camera slowly pans right while the character walks forward")'),
|
|
349
|
-
model: z.string().optional().describe('Model identifier.
|
|
349
|
+
model: z.string().optional().describe('Model identifier — pick a SPECIFIC model, do NOT omit (omitting = Smart Select auto-pick, which we avoid). Strong current defaults: "seedance-2" (versatile) or "veo3" (Veo 3.1, cinematic + native audio); the Kling family (call list_models for exact ids like kling-video/v3/pro/image-to-video) is strongest for motion. Call list_models type="img_to_video" to see all options and choose per the user\'s intent.'),
|
|
350
350
|
aspect_ratio: z.string().optional().describe('Output aspect ratio (e.g., "16:9", "9:16", "1:1"). Must be in the chosen model\'s `supported_aspect_ratios` from list_models. Default: "16:9"'),
|
|
351
351
|
duration: z.number().optional().describe('Duration in seconds. Must be in `supported_durations` from list_models, OR within `min_output_duration`-`max_output_duration`. Default: 5'),
|
|
352
352
|
enhance_prompt: z.boolean().optional().describe('Enhance the motion prompt. Default: true'),
|
package/src/tools/media.js
CHANGED
|
@@ -18,7 +18,7 @@ function registerMediaTools(server, client, options = {}) {
|
|
|
18
18
|
{
|
|
19
19
|
purpose: z.string().optional().describe('Short title shown on the card, e.g. "Upload the photo to animate". Helps the user know what to drop.'),
|
|
20
20
|
media_types: z.array(z.enum(['image', 'video', 'audio', 'document'])).optional().describe('Restrict which file kinds the widget accepts. Omit to accept all types.'),
|
|
21
|
-
max_files: z.number().optional().describe('Maximum number of files (default 10, max 20).'),
|
|
21
|
+
max_files: z.number().optional().describe('Maximum number of files the user may upload (default 10, max 20). LEAVE UNSET in almost all cases so the user can drop multiple files — only set this (e.g. to 1) if the task genuinely requires exactly one file. Do not restrict to 1 just because the current step uses one image; the user may want to upload several.'),
|
|
22
22
|
project_id: z.string().optional().describe('Project ObjectId to file the uploads into (resolve names via `list_projects`).')
|
|
23
23
|
},
|
|
24
24
|
async ({ purpose, media_types, max_files, project_id }) => {
|