@kolbo/mcp 1.110.3 → 1.111.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/README.md +1 -1
- package/package.json +1 -1
- package/src/apps/widgets/generation.js +46 -6
- package/src/tools/_shared.js +3 -0
- package/src/tools/generate.js +37 -6
package/README.md
CHANGED
|
@@ -147,7 +147,7 @@ Without the optional skill, the config block alone already exposes every tool
|
|
|
147
147
|
| `generate_lipsync` | Source image/video + audio → lipsynced video (Sync-3 adds active-speaker selection, emotion, model mode, temperature) |
|
|
148
148
|
| `generate_creative_director` | One brief → N coordinated scenes (image or video) |
|
|
149
149
|
| `generate_music` | Text (+ optional lyrics) → song. Style, title, negative tags, length, and Suno fine-controls (style weight, weirdness, audio weight, persona / singing voice) |
|
|
150
|
-
| `generate_speech` | Text + voice → spoken audio. Full expressive/style control: Google/Gemini named voice-direction presets (`style_instructions_preset_id`: warm/dramatic/whisper/…) or free-form `style_instructions`, preset styles + emotions (DeepDub / MiniMax / Cartesia), speed, accent/`language`, and per-provider voice settings (ElevenLabs similarity/style, DeepDub accent/variance/tempo, MiniMax pitch/volume/intensity/timbre). Status returns the same fields for reuse. |
|
|
150
|
+
| `generate_speech` | Text + voice → spoken audio. Full expressive/style control: Google/Gemini named voice-direction presets (`style_instructions_preset_id`: warm/dramatic/whisper/…) or free-form `style_instructions`, preset styles + emotions (DeepDub / MiniMax / Cartesia), speed, accent/`language`, and per-provider voice settings (ElevenLabs stability/similarity/style/speaker boost/seed, `enforce_language`, `output_format` mp3/wav, `text_normalization`; DeepDub accent/variance/tempo, MiniMax pitch/volume/intensity/timbre), plus `audio_effect` (phone/radio/megaphone/small_room/hall/distant) + `audio_effect_strength` for any voice at no extra credits. Status returns the same fields for reuse. |
|
|
151
151
|
| `generate_sound` | Text → sound effect. Duration, prompt influence, and per-provider controls (Stable Audio guidance, Kie loop/tempo/key, Seed-Audio voice/speed/volume/pitch + reference audio/image) |
|
|
152
152
|
| `generate_3d` | Text or reference images → 3D model (GLB/FBX/OBJ/USDZ) |
|
|
153
153
|
| `transcribe_audio` | Audio/video URL or file → text + SRT subtitles. Language, speaker diarization, audio-event tagging, and SRT formatting (words/line, lines/subtitle, caption stretch) |
|
package/package.json
CHANGED
|
@@ -230,6 +230,45 @@ function modelLabel(sc) {
|
|
|
230
230
|
}
|
|
231
231
|
function voiceLabel(sc) { return sc.voice_name || sc.voice || (sc.settings || {}).voice; }
|
|
232
232
|
|
|
233
|
+
// generate_speech controls. What the API reports it APPLIED wins: \`speech\`
|
|
234
|
+
// (status / completed payload; a key there is authoritative even when null),
|
|
235
|
+
// then the raw status result's own fields, then what the caller submitted.
|
|
236
|
+
var SPEECH_KEYS = ['turbo', 'speaking_speed', 'stability', 'similarity_boost', 'enforce_language',
|
|
237
|
+
'output_format', 'audio_effect', 'audio_effect_strength', 'seed'];
|
|
238
|
+
var SPEECH_FORMATS = { mp3_44100_128: 'MP3 128', mp3_44100_192: 'MP3 192', wav_44100: 'WAV' };
|
|
239
|
+
function withSpeech(settings, a) {
|
|
240
|
+
SPEECH_KEYS.forEach(function (k) { if (a && a[k] != null) settings[k] = a[k]; });
|
|
241
|
+
return settings;
|
|
242
|
+
}
|
|
243
|
+
function speechVal(sc, k) {
|
|
244
|
+
if (sc.speech && Object.prototype.hasOwnProperty.call(sc.speech, k)) return sc.speech[k];
|
|
245
|
+
var vs = sc.voice_settings || {};
|
|
246
|
+
if (vs[k] != null) return vs[k];
|
|
247
|
+
if (sc[k] != null) return sc[k];
|
|
248
|
+
return (sc.settings || {})[k];
|
|
249
|
+
}
|
|
250
|
+
function num(v) { return String(Math.round(Number(v) * 100) / 100); }
|
|
251
|
+
function speechChipsHTML(sc) {
|
|
252
|
+
if (sc.tool !== 'generate_speech' && !sc.speech) return '';
|
|
253
|
+
var h = '', v;
|
|
254
|
+
if (speechVal(sc, 'turbo') === true) h += chip('Turbo');
|
|
255
|
+
v = speechVal(sc, 'speaking_speed'); if (v != null) h += chip('Speed ' + esc(num(v)) + '×');
|
|
256
|
+
v = speechVal(sc, 'stability'); if (v != null) h += chip('Stability ' + esc(num(v)));
|
|
257
|
+
v = speechVal(sc, 'similarity_boost'); if (v != null) h += chip('Similarity ' + esc(num(v)));
|
|
258
|
+
v = speechVal(sc, 'enforce_language');
|
|
259
|
+
h += chip('Language ' + (v ? esc(String(v).toUpperCase()) : 'Auto'));
|
|
260
|
+
v = speechVal(sc, 'output_format'); if (v) h += chip(esc(SPEECH_FORMATS[v] || v));
|
|
261
|
+
v = speechVal(sc, 'audio_effect');
|
|
262
|
+
if (v && v !== 'none') {
|
|
263
|
+
var strength = speechVal(sc, 'audio_effect_strength');
|
|
264
|
+
var name = String(v).replace(/_/g, ' ');
|
|
265
|
+
h += chip(esc(name.charAt(0).toUpperCase() + name.slice(1)) +
|
|
266
|
+
(strength != null && Number(strength) < 1 ? ' ' + Math.round(Number(strength) * 100) + '%' : ''));
|
|
267
|
+
}
|
|
268
|
+
v = speechVal(sc, 'seed'); if (v != null) h += chip('Seed ' + esc(String(v)));
|
|
269
|
+
return h;
|
|
270
|
+
}
|
|
271
|
+
|
|
233
272
|
// @VisualDNA / #Moodboard mentions are the tag syntax the server resolves into
|
|
234
273
|
// real reference assets — rendering them as flat prose hid the single most
|
|
235
274
|
// consequential part of the prompt. Escape FIRST, then wrap: the pattern only
|
|
@@ -340,6 +379,7 @@ function renderChips(sc) {
|
|
|
340
379
|
: ICONS.mic;
|
|
341
380
|
h += chip(face + ' ' + esc(voice));
|
|
342
381
|
}
|
|
382
|
+
h += speechChipsHTML(sc);
|
|
343
383
|
if (s.mode) h += chip(esc(editLabel(s.mode)));
|
|
344
384
|
if (Array.isArray(s.details)) s.details.forEach(function (d) { if (d) h += chip(esc(String(d))); });
|
|
345
385
|
if (many && !kind) h += chip(sc.count + ' items');
|
|
@@ -1358,7 +1398,7 @@ function preRefSc(toolName, a) {
|
|
|
1358
1398
|
reference_videos: vid,
|
|
1359
1399
|
reference_audio: aud,
|
|
1360
1400
|
local_references: local,
|
|
1361
|
-
settings: {
|
|
1401
|
+
settings: withSpeech({
|
|
1362
1402
|
duration: a.duration,
|
|
1363
1403
|
resolution: a.draft === true ? '480p-draft' : a.draft === false ? String(a.resolution || '').replace(/-draft$/i, '') : a.resolution,
|
|
1364
1404
|
aspect_ratio: a.aspect_ratio,
|
|
@@ -1369,7 +1409,7 @@ function preRefSc(toolName, a) {
|
|
|
1369
1409
|
moodboard_ids: a.moodboard_ids,
|
|
1370
1410
|
moodboard_id: a.moodboard_id,
|
|
1371
1411
|
preset_id: a.preset_id
|
|
1372
|
-
}
|
|
1412
|
+
}, a)
|
|
1373
1413
|
};
|
|
1374
1414
|
}
|
|
1375
1415
|
|
|
@@ -1427,7 +1467,7 @@ function liveFromTimedOut(sc) {
|
|
|
1427
1467
|
kind: kindFromTool(originTool, sc),
|
|
1428
1468
|
prompt: originArgs.prompt || originArgs.text || '',
|
|
1429
1469
|
model: sc.model || originArgs.model,
|
|
1430
|
-
settings: {
|
|
1470
|
+
settings: withSpeech({
|
|
1431
1471
|
duration: originArgs.duration,
|
|
1432
1472
|
is_draft: originArgs.draft,
|
|
1433
1473
|
resolution: originArgs.draft === true ? '480p-draft' : originArgs.draft === false ? String(originArgs.resolution || '').replace(/-draft$/i, '') : originArgs.resolution,
|
|
@@ -1436,7 +1476,7 @@ function liveFromTimedOut(sc) {
|
|
|
1436
1476
|
mode: originArgs.operation,
|
|
1437
1477
|
visual_dna_ids: originArgs.visual_dna_ids,
|
|
1438
1478
|
moodboard_id: originArgs.moodboard_id
|
|
1439
|
-
},
|
|
1479
|
+
}, originArgs),
|
|
1440
1480
|
count: ids ? ids.length : (originArgs.num_images || 1),
|
|
1441
1481
|
generation_id: sc.generation_id,
|
|
1442
1482
|
poll_tool: 'get_generation_status',
|
|
@@ -1457,14 +1497,14 @@ function completedFromPlain(sc) {
|
|
|
1457
1497
|
kind: kindFromTool(originTool, sc),
|
|
1458
1498
|
prompt: originArgs.prompt || originArgs.text || sc.prompt_used || '',
|
|
1459
1499
|
model: sc.model || originArgs.model,
|
|
1460
|
-
settings: {
|
|
1500
|
+
settings: withSpeech({
|
|
1461
1501
|
duration: sc.duration || originArgs.duration,
|
|
1462
1502
|
is_draft: originArgs.draft,
|
|
1463
1503
|
resolution: originArgs.draft === true ? '480p-draft' : originArgs.draft === false ? String(originArgs.resolution || '').replace(/-draft$/i, '') : originArgs.resolution,
|
|
1464
1504
|
aspect_ratio: originArgs.aspect_ratio,
|
|
1465
1505
|
quality: originArgs.quality,
|
|
1466
1506
|
mode: originArgs.operation
|
|
1467
|
-
},
|
|
1507
|
+
}, originArgs),
|
|
1468
1508
|
urls: sc.urls || [],
|
|
1469
1509
|
session_id: sc.session_id || originArgs.session_id,
|
|
1470
1510
|
project_id: sc.project_id || originArgs.project_id,
|
package/src/tools/_shared.js
CHANGED
|
@@ -1066,6 +1066,9 @@ async function uiCompleted(p, textPayload, extraContent) {
|
|
|
1066
1066
|
// voice's face. Exactly the "never show a raw id on a card" rule, broken on
|
|
1067
1067
|
// the one path the user actually ends up looking at.
|
|
1068
1068
|
...(p.voice ? { voice_name: p.voice.name, voice_thumbnail: p.voice.thumbnail } : {}),
|
|
1069
|
+
// Speech settings the API reports it applied (generate.js speechFromResult);
|
|
1070
|
+
// the card prefers them over the submitted `settings`.
|
|
1071
|
+
...(p.speech ? { speech: p.speech } : {}),
|
|
1069
1072
|
// The RAW generation state, when the caller has one. `phase` above is
|
|
1070
1073
|
// hardcoded 'completed' — it means "this tool call finished", not "the
|
|
1071
1074
|
// generation finished" — so a status check on a still-running job looked
|
package/src/tools/generate.js
CHANGED
|
@@ -151,6 +151,24 @@ function mediaKind(url) {
|
|
|
151
151
|
return 'image';
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
// The speech settings the API says it APPLIED (status result, kolbo-api
|
|
155
|
+
// sdk/controller.js extractResult). Keys are always present (null = not
|
|
156
|
+
// applied) so the card prefers these over what the caller asked for; only a
|
|
157
|
+
// speech result carries `enforce_language`, so every other result returns
|
|
158
|
+
// undefined and its card is untouched.
|
|
159
|
+
function speechFromResult(r) {
|
|
160
|
+
if (!r || typeof r !== 'object' || !Object.prototype.hasOwnProperty.call(r, 'enforce_language')) return undefined;
|
|
161
|
+
const vs = r.voice_settings || {};
|
|
162
|
+
const out = {
|
|
163
|
+
stability: vs.stability, similarity_boost: vs.similarity_boost, style: vs.style, use_speaker_boost: vs.use_speaker_boost,
|
|
164
|
+
speaking_speed: r.speaking_speed, enforce_language: r.enforce_language, output_format: r.output_format,
|
|
165
|
+
text_normalization: r.text_normalization, audio_effect: r.audio_effect,
|
|
166
|
+
audio_effect_strength: r.audio_effect_strength, seed: r.seed,
|
|
167
|
+
};
|
|
168
|
+
for (const k of Object.keys(out)) if (out[k] === undefined) out[k] = null;
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
|
|
154
172
|
function preferOwned(urls) {
|
|
155
173
|
const list = (urls || []).filter((item) => typeof item === 'string' && item);
|
|
156
174
|
const ours = list.filter(ownedUrl);
|
|
@@ -1099,14 +1117,20 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1099
1117
|
emotion: z.string().optional().describe('MiniMax / Cartesia voices. Emotion: happy, sad, angry, fearful, disgusted, surprised, calm, fluent, whisper. Prefer this OR selected_style (both map to the same delivery control).'),
|
|
1100
1118
|
speaking_speed: z.number().optional().describe('Speech speed 0.5 (slow) – 2.0 (fast); Cartesia clamped 0.6–1.5. Default 1.0. Applies to ElevenLabs / OpenAI / Google / MiniMax / Cartesia / DeepDub (as tempo when tempo omitted).'),
|
|
1101
1119
|
// ── ElevenLabs voice settings ──
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1120
|
+
stability: z.number().min(0).max(1).optional().describe('ElevenLabs Stability, 0–1. Default 0.5. Lower = more expressive and varied between takes (Creative); higher = steadier, more consistent delivery (Robust). Supported on V4.'),
|
|
1121
|
+
similarity_boost: z.number().optional().describe('ElevenLabs voice similarity, 0–1. Default 0.75. Higher hews closer to the original voice. Also applies to the user\'s own cloned/character ElevenLabs voices.'),
|
|
1122
|
+
style: z.number().optional().describe('ElevenLabs style exaggeration, 0–1. Default 0. Higher = more expressive/dramatic. V3/V2 voices only — ElevenLabs V4 ignores it (use stability or inline audio tags instead).'),
|
|
1123
|
+
use_speaker_boost: z.boolean().optional().describe('ElevenLabs speaker boost (presence/clarity). Default true.'),
|
|
1124
|
+
enforce_language: z.string().optional().describe('ElevenLabs voices ONLY. Force the spoken language with an ISO 639-1 code ("he", "en", "fr"; a locale like "he-IL" is also accepted). Omit (recommended) and ElevenLabs auto-detects the language from the text. Different from `language`, which stays the accent/locale control.'),
|
|
1125
|
+
output_format: z.enum(['mp3_44100_128', 'mp3_44100_192', 'wav_44100']).optional().describe('ElevenLabs voices ONLY. Audio file format: "mp3_44100_128" (default), "mp3_44100_192" (higher-bitrate MP3) or "wav_44100" (uncompressed; the result is a .wav file). ElevenLabs\' native render is delivered as is. Any other provider rejects it (OUTPUT_FORMAT_UNSUPPORTED).'),
|
|
1126
|
+
text_normalization: z.enum(['auto', 'on', 'off']).optional().describe('ElevenLabs voices. Spell out numbers, dates and abbreviations before speaking: "auto" (default), "on" (always), "off" (read as written).'),
|
|
1127
|
+
audio_effect: z.enum(['none', 'phone', 'radio', 'megaphone', 'small_room', 'hall', 'distant']).optional().describe('Kolbo post-processing effect applied to the finished voice: phone, radio, megaphone, small_room, hall, distant, or "none" (default). Works for ANY voice/provider, no extra credits.'),
|
|
1128
|
+
audio_effect_strength: z.number().min(0).max(1).optional().describe('Mix of audio_effect with the dry voice, 0–1. Default 1 (full effect). Ignored without audio_effect.'),
|
|
1105
1129
|
// ── DeepDub controls ──
|
|
1106
1130
|
variance: z.number().optional().describe('DeepDub voice variance, 0–1. Default 0.2. Higher = more takes/variation.'),
|
|
1107
1131
|
tempo: z.number().optional().describe('DeepDub tempo multiplier, 0–2. Default 1.0.'),
|
|
1108
1132
|
promptBoost: z.boolean().optional().describe('DeepDub prompt-fidelity boost. Default true.'),
|
|
1109
|
-
seed: z.number().optional().describe('Reproducibility seed (DeepDub / Zonos). Same seed + inputs → same output.'),
|
|
1133
|
+
seed: z.number().optional().describe('Reproducibility seed (ElevenLabs / DeepDub / Zonos). Same seed + inputs → same output. ElevenLabs: integer 0–4294967295, best-effort determinism (not guaranteed).'),
|
|
1110
1134
|
accentControl: z.object({
|
|
1111
1135
|
accentBaseLocale: z.string().describe('Base accent locale, e.g. "en-US".'),
|
|
1112
1136
|
accentLocale: z.string().describe('Target accent locale, e.g. "en-GB".'),
|
|
@@ -1121,7 +1145,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1121
1145
|
project_id: projectIdField,
|
|
1122
1146
|
session_id: sessionIdField
|
|
1123
1147
|
},
|
|
1124
|
-
async ({ text, voice, model, turbo, language, style_instructions_preset_id, style_instructions, style_instructions_label, selected_style, emotion, speaking_speed, similarity_boost, style, use_speaker_boost, variance, tempo, promptBoost, seed, accentControl, voiceTitle, minimax_pitch, minimax_vol, minimax_intensity, minimax_timbre, project_id, session_id }) => {
|
|
1148
|
+
async ({ text, voice, model, turbo, language, style_instructions_preset_id, style_instructions, style_instructions_label, selected_style, emotion, speaking_speed, stability, similarity_boost, style, use_speaker_boost, enforce_language, output_format, text_normalization, audio_effect, audio_effect_strength, variance, tempo, promptBoost, seed, accentControl, voiceTitle, minimax_pitch, minimax_vol, minimax_intensity, minimax_timbre, project_id, session_id }) => {
|
|
1125
1149
|
model = await canonicalModelId(client, model, 'text_to_speech'); // lenient id resolution ("z-image" → "z-image/turbo")
|
|
1126
1150
|
// Resolve the requested voice against the REAL catalog (cached) so the card
|
|
1127
1151
|
// can show its display name + portrait instead of a raw id, and so an id
|
|
@@ -1133,11 +1157,14 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1133
1157
|
const unknownVoice = voice && !voiceRecord && !/^custom_/i.test(voice)
|
|
1134
1158
|
? `Voice "${voice}" is not in the Kolbo voice catalog. Call list_voices and pass a voice_id it returns — an unrecognised id is NOT rejected, it is silently mapped to a different voice, so the audio will not be the voice you named.`
|
|
1135
1159
|
: null;
|
|
1160
|
+
// ElevenLabs / effect controls carried on the card (undefined keys drop out of the JSON).
|
|
1161
|
+
const ttsControls = { turbo, stability, similarity_boost, style, use_speaker_boost, enforce_language, output_format, text_normalization, audio_effect, audio_effect_strength, seed };
|
|
1136
1162
|
const gen = await client.post('/v1/generate/speech', {
|
|
1137
1163
|
text, voice, model, turbo, language,
|
|
1138
1164
|
style_instructions_preset_id, style_instructions, style_instructions_label,
|
|
1139
1165
|
selected_style, emotion, speaking_speed,
|
|
1140
|
-
similarity_boost, style, use_speaker_boost,
|
|
1166
|
+
stability, similarity_boost, style, use_speaker_boost,
|
|
1167
|
+
enforce_language, output_format, text_normalization, audio_effect, audio_effect_strength,
|
|
1141
1168
|
variance, tempo, promptBoost, seed, accentControl, voiceTitle,
|
|
1142
1169
|
minimax_pitch, minimax_vol, minimax_intensity, minimax_timbre,
|
|
1143
1170
|
project_id, session_id
|
|
@@ -1151,6 +1178,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1151
1178
|
style: selected_style || emotion || style_instructions_preset_id || style_instructions,
|
|
1152
1179
|
speaking_speed,
|
|
1153
1180
|
language,
|
|
1181
|
+
...ttsControls,
|
|
1154
1182
|
},
|
|
1155
1183
|
warning: unknownVoice
|
|
1156
1184
|
});
|
|
@@ -1178,7 +1206,9 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1178
1206
|
style: selected_style || emotion || style_instructions_preset_id || style_instructions,
|
|
1179
1207
|
speaking_speed,
|
|
1180
1208
|
language,
|
|
1209
|
+
...ttsControls,
|
|
1181
1210
|
},
|
|
1211
|
+
speech: speechFromResult(result.result),
|
|
1182
1212
|
urls: result.result.urls,
|
|
1183
1213
|
playback_urls: result.result.playback_urls,
|
|
1184
1214
|
duration: result.result.duration,
|
|
@@ -1420,6 +1450,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1420
1450
|
txt_url: res.txt_url || undefined,
|
|
1421
1451
|
audio_url: res.audio_url || undefined,
|
|
1422
1452
|
prompt: res.prompt_used || res.prompt || undefined,
|
|
1453
|
+
speech: speechFromResult(res),
|
|
1423
1454
|
credits_used: creditFields(single).credits_used,
|
|
1424
1455
|
items: [{
|
|
1425
1456
|
id: single.generation_id,
|