@kolbo/mcp 1.73.0 → 1.74.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
package/skill/GENERATED.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AUTO-GENERATED — do not edit
|
|
2
2
|
|
|
3
|
-
This tree is mirrored from kolbo-code@
|
|
3
|
+
This tree is mirrored from kolbo-code@d0d959b, the single source of truth.
|
|
4
4
|
Canonical source: packages/opencode/skills/kolbo/
|
|
5
5
|
Distribution: .github/workflows/sync-skill-to-plugin.yml
|
|
6
6
|
|
package/skill/SKILL.md
CHANGED
|
@@ -222,6 +222,7 @@ Full tables + formulas in `references/workflows/cost-and-validation.md`. Quick r
|
|
|
222
222
|
- **Otherwise confirm** via the labeled-question card: the parameters + the credit cost, suggest a cheaper alternative if one fits, wait for the user's pick. Never fire on defaults the user didn't choose.
|
|
223
223
|
- **Batch totalling 100+ credits**: run `check_credits` first.
|
|
224
224
|
- **Quote real cost**: after firing, log `credits_used` (from the tool result) to `.kolbo/production.md` — never `base × count`.
|
|
225
|
+
- **Video/lipsync `credit` is per-SECOND, not per-clip**: `total = credit × duration`. This is the universal rule for video/firstlast/elements/motion_graphic/cast types, not a per-model exception — `list_models` states it inline now. The one carve-out is a model with `flat_credit_by_resolution` set.
|
|
225
226
|
- **Never state "credits remaining" from arithmetic** (opening balance − generation costs). Coding/chat usage deducts credits too, so the math is always wrong. Report cost only; if the user asks for their balance, call `check_credits` fresh at that moment.
|
|
226
227
|
|
|
227
228
|
## Rate Limiting & Batch Generation
|
|
@@ -112,7 +112,6 @@ The match is **literal and case-insensitive**, so:
|
|
|
112
112
|
- The `@name` must equal the stored `name` field (e.g. if `name: "esther_model"` → write `@esther_model`, not `@Esther`, not `@אסתר`, not `@the model`).
|
|
113
113
|
- Any-language characters are supported — if the DNA was created with `name: "אסתר"` you write `@אסתר`. Use the EXACT stored string.
|
|
114
114
|
- Mentions terminate at punctuation (`.,!?`), double-spaces, another `@`, or end of string. So `@maya, wearing...` matches `maya`.
|
|
115
|
-
- **Never glue a possessive onto the tag.** Write `the face of @maya stays stable`, not `@maya's face stays stable` — the tag must end at the stored name. kolbo-api does strip a trailing `'s` / `’s` before the lookup, so a possessive no longer loses the DNA, but the apostrophe is left sitting in the prompt text the model sees.
|
|
116
115
|
|
|
117
116
|
This composes with `@image1` / `@image2` positional tags for plain reference/source images — see "Reference Tagging" below.
|
|
118
117
|
|
|
@@ -70,7 +70,8 @@ var TOOL_TITLES = {
|
|
|
70
70
|
generate_first_last_frame: 'First–Last Frame', generate_lipsync: 'Lipsync',
|
|
71
71
|
generate_music: 'Music Generation', generate_speech: 'Text to Speech',
|
|
72
72
|
generate_sound: 'Sound Effect', generate_3d: '3D Generation',
|
|
73
|
-
generate_creative_director: 'Creative Director', edit_image: 'Image Edit', edit_video: 'Video Edit'
|
|
73
|
+
generate_creative_director: 'Creative Director', edit_image: 'Image Edit', edit_video: 'Video Edit',
|
|
74
|
+
get_generation_status: 'Generations'
|
|
74
75
|
};
|
|
75
76
|
|
|
76
77
|
// Long text is clamped by CSS (.k-prompt 2 lines / .k-caption 1 line). When it
|
|
@@ -591,6 +592,7 @@ function renderResult(sc) {
|
|
|
591
592
|
// voice that actually ran, so the finished card must not keep the guess.
|
|
592
593
|
renderChips(sc);
|
|
593
594
|
setPhaseChip('', false);
|
|
595
|
+
if (sc.kind === 'status' && Array.isArray(sc.items)) return renderStatusGrid(sc);
|
|
594
596
|
if (sc.batch && sc.scenes && sc.scenes.length) return renderBatchGrid(sc);
|
|
595
597
|
if (sc.kind === 'scenes' && sc.scenes && sc.scenes.length) return renderScenes(sc);
|
|
596
598
|
var urls = sc.urls || [];
|
|
@@ -773,6 +775,49 @@ function renderBatchGrid(sc) {
|
|
|
773
775
|
window.kolbo.notifySize();
|
|
774
776
|
}
|
|
775
777
|
|
|
778
|
+
// get_generation_status checking SEVERAL ids in one call (the "run these N
|
|
779
|
+
// generations in parallel, then check on all of them" pattern). Each item is
|
|
780
|
+
// independently image/video/audio and independently pending/completed/failed
|
|
781
|
+
// — a single generation-in-progress tile grid can't express that. Completed
|
|
782
|
+
// items get a real thumbnail (same tile markup as renderBatchGrid, so the two
|
|
783
|
+
// grids read as one visual language); pending/failed items get the same
|
|
784
|
+
// spinner/error badge language renderGenerating/renderError already use, so
|
|
785
|
+
// nothing here is a new visual pattern, only a new combination of them.
|
|
786
|
+
function renderStatusGrid(sc) {
|
|
787
|
+
var items = sc.items;
|
|
788
|
+
if (!items.length) return renderError('No results');
|
|
789
|
+
el('stage').innerHTML = '<div class="k-gen-grid n' + Math.min(items.length, 4) + '">' +
|
|
790
|
+
items.map(function (it, i) {
|
|
791
|
+
var cap = it.title ? '<span class="k-skel-cap" title="' + esc(it.title) + '">' + esc(it.title) + '</span>' : '';
|
|
792
|
+
if (it.state === 'completed' && it.url) {
|
|
793
|
+
// Classify by extension, same as every other reference/thumb in this
|
|
794
|
+
// file — the tool only knows a url came back, not what kind it is.
|
|
795
|
+
it.kind = refKind(it.url, 'image');
|
|
796
|
+
var shape = it.kind === 'video' ? 'video' : 'square';
|
|
797
|
+
return '<div class="k-skel done ' + shape + '" data-focus="' + i + '">' +
|
|
798
|
+
(it.kind === 'video'
|
|
799
|
+
? '<video class="k-cell-fill" src="' + esc(it.url) + '" controls playsinline preload="metadata"></video>'
|
|
800
|
+
: it.kind === 'audio'
|
|
801
|
+
? '<div class="k-cell-fill" style="display:flex;align-items:center;justify-content:center">' + ICONS.sound + '</div>'
|
|
802
|
+
: '<img class="k-cell-fill" src="' + esc(it.url) + '" alt="" loading="lazy" style="cursor:zoom-in">') +
|
|
803
|
+
cap + dlBtnHTML(it.url) + '</div>';
|
|
804
|
+
}
|
|
805
|
+
var failed = it.state === 'failed' || it.state === 'cancelled';
|
|
806
|
+
var badge = failed
|
|
807
|
+
? '<span class="k-gen-badge" style="background:var(--error,#e5484d)"><span aria-hidden="true">✕</span>' + esc(it.state) + '</span>'
|
|
808
|
+
: '<span class="k-gen-badge"><span class="k-spin"></span>' + esc(it.state || 'processing') + '</span>';
|
|
809
|
+
return '<div class="k-skel square">' + badge + cap + '</div>';
|
|
810
|
+
}).join('') + '</div>';
|
|
811
|
+
wireDlButtons(el('stage'));
|
|
812
|
+
Array.prototype.forEach.call(el('stage').querySelectorAll('[data-focus]'), function (cell) {
|
|
813
|
+
var it = items[+cell.getAttribute('data-focus')];
|
|
814
|
+
if (it.kind !== 'image') return; // <video controls> owns its own clicks
|
|
815
|
+
cell.onclick = function () { focusMedia(it.url); };
|
|
816
|
+
});
|
|
817
|
+
renderActions(sc);
|
|
818
|
+
window.kolbo.notifySize();
|
|
819
|
+
}
|
|
820
|
+
|
|
776
821
|
function renderScenes(sc) {
|
|
777
822
|
var items = sceneItems(sc);
|
|
778
823
|
if (!items.length) return renderError('No completed scenes received');
|
package/src/tools/_shared.js
CHANGED
|
@@ -650,6 +650,11 @@ async function uiCompleted(p, textPayload, extraContent) {
|
|
|
650
650
|
title: p.title,
|
|
651
651
|
duration: p.duration,
|
|
652
652
|
scenes: p.scenes,
|
|
653
|
+
// Independent per-item results (get_generation_status checking several ids
|
|
654
|
+
// at once) — each one carries its OWN state/media, unlike `scenes`/`urls`
|
|
655
|
+
// above which assume everything finished together. Only set when the
|
|
656
|
+
// caller actually has this shape; every existing caller is unaffected.
|
|
657
|
+
...(Array.isArray(p.items) ? { items: p.items } : {}),
|
|
653
658
|
credits_used: p.credits_used,
|
|
654
659
|
open_url: buildOpenUrl(p.tool, p.gen),
|
|
655
660
|
};
|
package/src/tools/generate.js
CHANGED
|
@@ -978,18 +978,45 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
978
978
|
};
|
|
979
979
|
}
|
|
980
980
|
|
|
981
|
+
const text = JSON.stringify({
|
|
982
|
+
all_done: pending.length === 0,
|
|
983
|
+
completed: results.filter(r => r.state === 'completed').length,
|
|
984
|
+
failed: results.filter(r => r.state === 'failed' || r.state === 'cancelled').length,
|
|
985
|
+
still_processing: pending.map(r => r.generation_id),
|
|
986
|
+
_hint: pending.length === 0 ? doneHint : pendingHint,
|
|
987
|
+
generations: results
|
|
988
|
+
}, null, 2);
|
|
989
|
+
|
|
990
|
+
// Checking SEVERAL ids in one call — the "run N generations in parallel,
|
|
991
|
+
// then check on all of them" pattern — is the one shape hasGeneratedOutput
|
|
992
|
+
// (the host's card-selection check) cannot see into: media here sits one
|
|
993
|
+
// level under EACH ARRAY ITEM's own `result`, and nothing walks arrays
|
|
994
|
+
// looking for it. Without structuredContent the card degrades to a plain
|
|
995
|
+
// status list — ids and state, no thumbnails, even for completed items
|
|
996
|
+
// whose media is sitting right there. uiCompleted gives it the same rich
|
|
997
|
+
// per-item render every generate_* tool already gets; kind:'status' picks
|
|
998
|
+
// renderStatusGrid, the one path here that mixes independent
|
|
999
|
+
// pending/completed/failed items in one grid instead of assuming they all
|
|
1000
|
+
// finished together.
|
|
1001
|
+
if (ui()) {
|
|
1002
|
+
return uiCompleted({
|
|
1003
|
+
tool: 'get_generation_status', kind: 'status', client,
|
|
1004
|
+
model: 'Generations', gen: { generation_id: ids[0] },
|
|
1005
|
+
settings: {},
|
|
1006
|
+
items: results.map(r => {
|
|
1007
|
+
const res = r.result || {};
|
|
1008
|
+
return {
|
|
1009
|
+
id: r.generation_id,
|
|
1010
|
+
state: r.state,
|
|
1011
|
+
title: res.prompt_used || res.prompt || undefined,
|
|
1012
|
+
url: Array.isArray(res.urls) ? res.urls[0] : undefined,
|
|
1013
|
+
};
|
|
1014
|
+
}),
|
|
1015
|
+
}, text);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
981
1018
|
return {
|
|
982
|
-
content: [{
|
|
983
|
-
type: 'text',
|
|
984
|
-
text: JSON.stringify({
|
|
985
|
-
all_done: pending.length === 0,
|
|
986
|
-
completed: results.filter(r => r.state === 'completed').length,
|
|
987
|
-
failed: results.filter(r => r.state === 'failed' || r.state === 'cancelled').length,
|
|
988
|
-
still_processing: pending.map(r => r.generation_id),
|
|
989
|
-
_hint: pending.length === 0 ? doneHint : pendingHint,
|
|
990
|
-
generations: results
|
|
991
|
-
}, null, 2)
|
|
992
|
-
}]
|
|
1019
|
+
content: [{ type: 'text', text }]
|
|
993
1020
|
};
|
|
994
1021
|
}
|
|
995
1022
|
);
|
package/src/tools/models.js
CHANGED
|
@@ -103,7 +103,7 @@ function registerModelTools(server, client, options = {}) {
|
|
|
103
103
|
// ─── list_models ───────────────────────────────────────────
|
|
104
104
|
server.tool(
|
|
105
105
|
'list_models',
|
|
106
|
-
'List available AI models on Kolbo. Filter by `type` to narrow to a generation type, and pass `format: "json"` to enumerate the catalog with exact identifiers — `format: "json"` + `type` returns the full raw model documents (every constraint field, for programmatic comparison / cap validation before submitting a generation); `format: "json"` alone returns a compact index of EVERY model and its identifier. Default `format: "text"` returns the human-readable summary. NEVER guess a model identifier: call this tool.',
|
|
106
|
+
'List available AI models on Kolbo. Filter by `type` to narrow to a generation type, and pass `format: "json"` to enumerate the catalog with exact identifiers — `format: "json"` + `type` returns the full raw model documents (every constraint field, for programmatic comparison / cap validation before submitting a generation); `format: "json"` alone returns a compact index of EVERY model and its identifier. Default `format: "text"` returns the human-readable summary. NEVER guess a model identifier: call this tool. ⚠️ COST: for any model whose type is video / firstlast / elements / motion_graphic / cast, `credit` is a PER-SECOND rate, not a per-clip price — multiply by the requested `duration` before quoting cost to the user (e.g. `credit: 9` at `duration: 8` is 72 credits, not 9). This is the universal rule, not a per-model exception. The one carve-out is a model with `flat_credit_by_resolution` set — those charge the flat rate regardless of duration. Every other model type (image, audio, 3D, per-token text) already bills flat per generation as `credit` states.',
|
|
107
107
|
{
|
|
108
108
|
type: z.string().optional().describe('Filter by DB type name: "text_to_img", "image_editing", "text_to_video", "img_to_video", "draw_to_video", "video_to_video", "elements", "firstlastgenerations", "lipsync-image", "lipsync-video", "music_gen", "text_to_speech", "text_to_sound", "stt", "text". Legacy aliases also accepted: "image", "image_edit", "video", "video_from_image", "video_from_video", "music", "speech", "sound", "chat", "lipsync" (both lipsync types), "three_d" (all 3D types), "first_last_frame", "transcription". Omit for all models.'),
|
|
109
109
|
format: z.enum(['text', 'json']).optional().describe('Output format. "text" (default) returns a human-readable summary with the most-used caps. "json" is the source of truth for identifiers and caps: with `type` it returns the raw model documents from the API (identifier, credit, supported_durations, supported_resolutions, supported_aspect_ratios, max_reference_images, max_visual_dna, max_video_duration, …) for EVERY model of that type; without `type` it returns a compact index of every model in the catalog and its exact identifier. Use it whenever you need an identifier you have not seen listed, or must verify a cap before passing a value that might exceed a model-specific limit.'),
|
|
@@ -334,9 +334,31 @@ function registerModelTools(server, client, options = {}) {
|
|
|
334
334
|
// Text models bill per token — the flat `credit` is not what the user pays,
|
|
335
335
|
// so show the real per-1K rates when the API supplies them. Without this the
|
|
336
336
|
// "cheapest model that fits" rule is unusable for chat.
|
|
337
|
+
//
|
|
338
|
+
// Video-type models are the same problem in a different shape: kolbo-api's
|
|
339
|
+
// credit engine (credManagment.js) treats "charge per second of requested
|
|
340
|
+
// duration" as the UNIVERSAL rule for any type in
|
|
341
|
+
// [video, firstlast, elements, motion_graphic, cast] — not a per-model
|
|
342
|
+
// exception, the default. So `credit: 9` on a model with duration 8 is
|
|
343
|
+
// really 72 credits, and nothing in the catalog said so: an agent quoting
|
|
344
|
+
// cost from the bare `credit` field alone is wrong by exactly the
|
|
345
|
+
// requested duration, every time. `flat_credit_by_resolution` is the one
|
|
346
|
+
// carve-out — those models are charged the flat rate regardless of
|
|
347
|
+
// duration, so they're excluded here the same way credManagment.js
|
|
348
|
+
// excludes them (resolveFlatCredit wins over the multiplier).
|
|
349
|
+
const PER_SECOND_TYPES = ['video', 'firstlast', 'elements', 'motion_graphic', 'cast'];
|
|
350
|
+
const isPerSecondVideo = m => {
|
|
351
|
+
const types = Array.isArray(m.types) ? m.types : (m.type ? [m.type] : []);
|
|
352
|
+
const billedPerSecond = types.some(t => PER_SECOND_TYPES.some(kw => String(t).includes(kw)));
|
|
353
|
+
const hasFlatOverride = m.flat_credit_by_resolution && typeof m.flat_credit_by_resolution === 'object'
|
|
354
|
+
&& Object.keys(m.flat_credit_by_resolution).length > 0;
|
|
355
|
+
return billedPerSecond && !hasFlatOverride;
|
|
356
|
+
};
|
|
337
357
|
const cost = m => (m.output_token_rate != null
|
|
338
358
|
? `${m.input_token_rate ?? '?'}/${m.output_token_rate} credits per 1K tokens (in/out)`
|
|
339
|
-
:
|
|
359
|
+
: isPerSecondVideo(m)
|
|
360
|
+
? `${m.credit} credits/second (× requested duration — NOT a flat per-clip price)`
|
|
361
|
+
: `${m.credit} credits`);
|
|
340
362
|
const formatModel = m =>
|
|
341
363
|
`${m.identifier} (${m.name}) - ${cost(m)}${m.recommended ? ' [RECOMMENDED]' : ''}${m.new_model ? ' [NEW]' : ''}${m.summary ? ` — ${detailed ? m.summary : brief(m.summary)}` : ''}${detailed ? formatSpecs(m) : ''}`;
|
|
342
364
|
|