@kolbo/mcp 1.82.6 → 1.83.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 +1 -1
- package/skill/SKILL.md +2 -0
- package/src/apps/index.js +50 -0
- package/src/tools/editModelCatalog.js +64 -0
- package/src/tools/generate.js +33 -14
- package/src/tools/models.js +13 -1
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -105,10 +105,12 @@ Each `references/models/*.md` mirrors the matching skill prompt in `kolbo-api/sr
|
|
|
105
105
|
|------|-------------|
|
|
106
106
|
| `generate_image` | Single image from a text prompt. Supports Visual DNA, moodboards, image presets (custom instructions live here), reference images, web-search grounding. Named sheets/styles: `list_presets({ type: "image", search: "headless" })` then `preset_id`. |
|
|
107
107
|
| `generate_image_edit` | Edit/transform an existing image. Pass `source_images` + edit prompt. Image-editing presets are supported through `preset_id` from `list_presets({ type: "image_edit" })`. |
|
|
108
|
+
| `edit_image` | Operation-routed image tools such as upscale, reframe, outpaint, background work, inpaint and enhance. Before choosing `model`, call `list_models` with the operation family (`image_upscale`, `image_reframe`, `image_zoom_out`, `background_remove`, `graphics_enhance`, etc.) and pass a concrete returned identifier. `multi_shot` and `split` use pinned processing, so omit `model` for those. |
|
|
108
109
|
| `generate_creative_director` | **2–8 related images or videos as one coherent set.** Use INSTEAD of multiple `generate_image` calls for any related multi-output. |
|
|
109
110
|
| `generate_video` | Text-to-video. Accepts `visual_dna_ids` and `sound_enabled`; `generate_elements` is still the primary reference-driven route for a DNA-anchored film. |
|
|
110
111
|
| `generate_video_from_image` | Animate a still. Prompt describes motion, not subject. |
|
|
111
112
|
| `generate_video_from_video` | Restyle/transform an existing video. Keeps original motion. |
|
|
113
|
+
| `edit_video` | Operation-routed video tools such as upscale, reframe, audio generation, watermark/background removal, face swap, extend, inpaint and retake. Discover the real engines through the operation family (`video_upscale`, `video_reframe`, `video_to_sound`, `video_extend`, etc.), compare caps/cost/`params`, and pass a concrete identifier — never a `kolbo_gateway_*` navigation row. |
|
|
112
114
|
| `generate_elements` | Reference-driven video. **Primary route for DNA → video.** Prompt = Seedance Locked Intro (`Total` + `[GLOBAL LOOK]` / `[CAST]` / `[LOCATION]` + `SHOT N`). Every DNA in `visual_dna_ids` must also be `@Name` in that prompt. |
|
|
113
115
|
| `generate_first_last_frame` | Keyframe interpolation between two frames. |
|
|
114
116
|
| `generate_lipsync` | Lipsync an existing waveform onto a face. **Not the route for dialogue in a film you are generating** — write the line in the Seedance prompt instead. |
|
package/src/apps/index.js
CHANGED
|
@@ -562,6 +562,30 @@ async function canonicalModelId(client, input, type) {
|
|
|
562
562
|
const ids = new Set((narrowed.length ? narrowed : prefixed).map((i) => i.id));
|
|
563
563
|
if (ids.size === 1) return [...ids][0];
|
|
564
564
|
|
|
565
|
+
// The general catalog is cached for widget performance, while list_models
|
|
566
|
+
// is intentionally live. On a just-published model, refresh only the typed
|
|
567
|
+
// family before reporting an unknown identifier so a model discovered one
|
|
568
|
+
// moment ago is immediately usable.
|
|
569
|
+
if (types.length && typeof client.get === 'function') {
|
|
570
|
+
try {
|
|
571
|
+
const freshRows = [];
|
|
572
|
+
for (const expectedType of types) {
|
|
573
|
+
const response = await client.get(`/v1/models?type=${encodeURIComponent(expectedType)}`);
|
|
574
|
+
freshRows.push(...(response?.models || response?.data?.models || []));
|
|
575
|
+
}
|
|
576
|
+
const freshMatches = freshRows.filter((row) => {
|
|
577
|
+
const id = row?.identifier;
|
|
578
|
+
const name = row?.name;
|
|
579
|
+
return (id && (id.toLowerCase() === key || normId(id) === want))
|
|
580
|
+
|| (name && (name.toLowerCase() === key || normId(name) === want));
|
|
581
|
+
});
|
|
582
|
+
const freshIds = [...new Set(freshMatches.map((row) => row.identifier).filter(Boolean))];
|
|
583
|
+
if (freshIds.length === 1) return freshIds[0];
|
|
584
|
+
} catch (_) {
|
|
585
|
+
// Keep the existing actionable near-miss error when the refresh fails.
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
565
589
|
// 4. unknown — name the near misses instead of dead-ending at the API.
|
|
566
590
|
const stem = normId(key.split(/[\s._/-]+/).filter(Boolean)[0] || key);
|
|
567
591
|
const near = [...new Set(
|
|
@@ -577,6 +601,31 @@ async function canonicalModelId(client, input, type) {
|
|
|
577
601
|
);
|
|
578
602
|
}
|
|
579
603
|
|
|
604
|
+
/**
|
|
605
|
+
* Reject a published model that belongs to a different operation family.
|
|
606
|
+
* Hidden/unpublished identifiers still fail open so existing pinned engines
|
|
607
|
+
* remain usable; the API remains authoritative for those.
|
|
608
|
+
*/
|
|
609
|
+
async function assertModelSupportsType(client, modelId, type) {
|
|
610
|
+
if (!modelId || !type) return modelId;
|
|
611
|
+
let all;
|
|
612
|
+
try {
|
|
613
|
+
all = (await modelCatalog(client)).all;
|
|
614
|
+
} catch (_) {
|
|
615
|
+
return modelId;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const row = (all || []).find((item) => item.id === modelId);
|
|
619
|
+
if (!row) return modelId;
|
|
620
|
+
const expected = (Array.isArray(type) ? type : [type]).filter(Boolean);
|
|
621
|
+
if (!expected.length || row.types.some((value) => expected.includes(value))) return modelId;
|
|
622
|
+
|
|
623
|
+
throw new Error(
|
|
624
|
+
`Model "${modelId}" cannot be used for this operation (expected type: ${expected.join(' or ')}). `
|
|
625
|
+
+ `Call list_models with type="${expected[0]}" and pass a concrete identifier it returns.`
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
580
629
|
/* ------------------------------------------------------------------ */
|
|
581
630
|
/* Declaration-level widget metadata */
|
|
582
631
|
/* ------------------------------------------------------------------ */
|
|
@@ -668,6 +717,7 @@ module.exports = {
|
|
|
668
717
|
modelInfoMap,
|
|
669
718
|
voiceInfo,
|
|
670
719
|
canonicalModelId,
|
|
720
|
+
assertModelSupportsType,
|
|
671
721
|
normalizeAspectRatio,
|
|
672
722
|
closestAspectRatio,
|
|
673
723
|
resolveCatalogAspectRatio,
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Operation-specific model catalogs for edit_image / edit_video.
|
|
3
|
+
*
|
|
4
|
+
* The `kolbo_gateway_*` rows in video_to_video are navigation aliases used by
|
|
5
|
+
* Kolbo's web picker. They are not provider engines. MCP callers must discover
|
|
6
|
+
* and submit a concrete model from the operation's real DB type instead.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const VIDEO_EDIT_MODEL_TYPES = Object.freeze({
|
|
10
|
+
upscale: 'video_upscale',
|
|
11
|
+
reframe: 'video_reframe',
|
|
12
|
+
generate_audio: 'video_to_sound',
|
|
13
|
+
remove_watermark: 'video_watermark_removal',
|
|
14
|
+
face_swap: 'video_face_swap',
|
|
15
|
+
extend: 'video_extend',
|
|
16
|
+
magic_edit: 'video_to_video',
|
|
17
|
+
lipsync: 'lipsync-video',
|
|
18
|
+
remove_background: 'video_background_removal',
|
|
19
|
+
inpaint: 'video_inpaint',
|
|
20
|
+
retake: 'video_retake',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const IMAGE_EDIT_MODEL_TYPES = Object.freeze({
|
|
24
|
+
upscale: 'image_upscale',
|
|
25
|
+
clarity_upscale: 'image_upscale',
|
|
26
|
+
reframe: 'image_reframe',
|
|
27
|
+
zoom_out: 'image_zoom_out',
|
|
28
|
+
inpaint: 'inpaint',
|
|
29
|
+
erase: 'erase',
|
|
30
|
+
face_swap: 'face_swap',
|
|
31
|
+
background_remove: 'background_remove',
|
|
32
|
+
removebg: 'background_remove',
|
|
33
|
+
background_replace: 'background_replace',
|
|
34
|
+
magic_edit: 'image_editing',
|
|
35
|
+
camera_angle: 'image_editing',
|
|
36
|
+
enhance_skin: 'skin_enhancer',
|
|
37
|
+
enhance: 'graphics_enhance',
|
|
38
|
+
// The API intentionally pins multi_shot to its dedicated engine.
|
|
39
|
+
multi_shot: null,
|
|
40
|
+
split_upscale: 'image_upscale',
|
|
41
|
+
split: null,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function modelTypeForEditOperation(kind, operation) {
|
|
45
|
+
const map = kind === 'image' ? IMAGE_EDIT_MODEL_TYPES : VIDEO_EDIT_MODEL_TYPES;
|
|
46
|
+
return map[operation] || null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function assertExecutableEditModel(model, kind, operation) {
|
|
50
|
+
if (!model || !/^kolbo_gateway_/i.test(String(model))) return;
|
|
51
|
+
const type = modelTypeForEditOperation(kind, operation);
|
|
52
|
+
throw new Error(
|
|
53
|
+
`"${model}" is a Kolbo navigation alias, not an executable AI model. ` +
|
|
54
|
+
`Call list_models with type="${type || (kind === 'image' ? 'image_editing' : 'video_to_video')}" ` +
|
|
55
|
+
`and pass one of the concrete model identifiers it returns.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
VIDEO_EDIT_MODEL_TYPES,
|
|
61
|
+
IMAGE_EDIT_MODEL_TYPES,
|
|
62
|
+
modelTypeForEditOperation,
|
|
63
|
+
assertExecutableEditModel,
|
|
64
|
+
};
|
package/src/tools/generate.js
CHANGED
|
@@ -8,7 +8,8 @@ const FormData = require('form-data');
|
|
|
8
8
|
const { pollUntilDone, waitWindowMs } = require('../polling');
|
|
9
9
|
const { resolveToBuffer, pollOrTimedOut, creditFields, projectIdField, sessionIdField, inlineImageBlocks, linkFields, uiGenerating, uiCompleted, appsEnabled } = require('./_shared');
|
|
10
10
|
const { ownedUrl } = require('./owned-url');
|
|
11
|
-
const { UI, uiResult, canonicalModelId, modelInfo, voiceInfo, resolveCatalogAspectRatio } = require('../apps');
|
|
11
|
+
const { UI, uiResult, canonicalModelId, assertModelSupportsType, modelInfo, voiceInfo, resolveCatalogAspectRatio } = require('../apps');
|
|
12
|
+
const { modelTypeForEditOperation, assertExecutableEditModel } = require('./editModelCatalog');
|
|
12
13
|
|
|
13
14
|
// ─── Cinematic Dimensions schema (shared by generate_image + generate_image_edit) ───
|
|
14
15
|
// Kolbo's "Cinema mode": eight independent photographic dimensions, each an OPTIONAL
|
|
@@ -1979,7 +1980,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1979
1980
|
].join(' ')),
|
|
1980
1981
|
|
|
1981
1982
|
model: z.string().optional()
|
|
1982
|
-
.describe('
|
|
1983
|
+
.describe('Concrete model identifier for this operation. Dynamically discover it with list_models using the operation-specific type: upscale/clarity_upscale/split_upscale → "image_upscale"; reframe → "image_reframe"; zoom_out → "image_zoom_out"; inpaint → "inpaint"; erase → "erase"; face_swap → "face_swap"; removebg → "background_remove"; background_replace → "background_replace"; enhance_skin → "skin_enhancer"; enhance → "graphics_enhance"; magic_edit/camera_angle → "image_editing". multi_shot and split use pinned/non-selectable processing, so omit model for those operations. Omit elsewhere to use the platform default. Never pass a kolbo_gateway_* navigation alias.'),
|
|
1983
1984
|
|
|
1984
1985
|
// ── upscale ────────────────────────────────────────────
|
|
1985
1986
|
scale: z.number().optional()
|
|
@@ -2049,11 +2050,15 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2049
2050
|
enhancement_model, output_format,
|
|
2050
2051
|
project_id, session_id
|
|
2051
2052
|
}) => {
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2053
|
+
const editModelType = modelTypeForEditOperation('image', operation);
|
|
2054
|
+
if ((operation === 'multi_shot' || operation === 'split') && model) {
|
|
2055
|
+
throw new Error(`model is not configurable for ${operation}; omit it to use the operation's pinned processing path`);
|
|
2056
|
+
}
|
|
2057
|
+
assertExecutableEditModel(model, 'image', operation);
|
|
2058
|
+
model = await canonicalModelId(client, model, editModelType || undefined);
|
|
2059
|
+
assertExecutableEditModel(model, 'image', operation);
|
|
2060
|
+
await assertModelSupportsType(client, model, editModelType || undefined);
|
|
2061
|
+
aspect_ratio = await resolveCatalogAspectRatio(client, model, aspect_ratio, editModelType || undefined);
|
|
2057
2062
|
|
|
2058
2063
|
// Basic validation
|
|
2059
2064
|
if (operation === 'reframe' && !aspect_ratio) throw new Error('aspect_ratio is required for reframe');
|
|
@@ -2133,7 +2138,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2133
2138
|
].join(' ')),
|
|
2134
2139
|
|
|
2135
2140
|
model: z.string().optional()
|
|
2136
|
-
.describe('
|
|
2141
|
+
.describe('Concrete model identifier for this operation. Dynamically discover it with list_models using the operation-specific type: upscale → "video_upscale"; reframe → "video_reframe"; generate_audio → "video_to_sound"; remove_watermark → "video_watermark_removal"; face_swap → "video_face_swap"; extend → "video_extend"; magic_edit → "video_to_video"; lipsync → "lipsync-video"; remove_background → "video_background_removal"; inpaint → "video_inpaint"; retake → "video_retake". Compare the returned credit, supported resolutions/aspect ratios, duration limits, resolution multipliers, and params, then pass a concrete identifier. Omit to use the platform default. Never pass a kolbo_gateway_* navigation alias.'),
|
|
2137
2142
|
|
|
2138
2143
|
// ── upscale ────────────────────────────────────────────
|
|
2139
2144
|
scale: z.number().optional()
|
|
@@ -2164,6 +2169,14 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2164
2169
|
.describe('When true, keeps the original video audio and mixes in the generated audio. Used with "generate_audio". Default: false.'),
|
|
2165
2170
|
cfg_strength: z.number().optional()
|
|
2166
2171
|
.describe('Guidance strength for audio generation (higher = follows prompt more strictly). Used with "generate_audio".'),
|
|
2172
|
+
audio_format: z.enum(['wav', 'mp3', 'aac', 'flac']).optional()
|
|
2173
|
+
.describe('Separate generated-audio format for Sonilo sound-effects models. Read output_audio_formats/default_output_audio_format from list_models; default is "aac".'),
|
|
2174
|
+
segments: z.array(z.object({
|
|
2175
|
+
start: z.number().nonnegative(),
|
|
2176
|
+
end: z.number().positive(),
|
|
2177
|
+
prompt: z.string()
|
|
2178
|
+
})).optional()
|
|
2179
|
+
.describe('Optional contiguous Sonilo sound-design ranges. The first start must be 0, each end must equal the next start, and the final end must not exceed the video duration. Omit to let Sonilo detect scenes automatically.'),
|
|
2167
2180
|
|
|
2168
2181
|
// ── face_swap ──────────────────────────────────────────
|
|
2169
2182
|
image_url: z.string().optional()
|
|
@@ -2210,21 +2223,26 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2210
2223
|
target_fps, resolution, enhancement_model,
|
|
2211
2224
|
grid_position_x, grid_position_y,
|
|
2212
2225
|
sound_effect_prompt, background_music_prompt, original_sound, cfg_strength,
|
|
2226
|
+
audio_format, segments,
|
|
2213
2227
|
refine_edges, subject_is_person,
|
|
2214
2228
|
text_prompt, context,
|
|
2215
2229
|
mask_video_url, object_prompt, video_strength,
|
|
2216
2230
|
start_time,
|
|
2217
2231
|
project_id, session_id
|
|
2218
2232
|
}) => {
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
model
|
|
2223
|
-
|
|
2233
|
+
const editModelType = modelTypeForEditOperation('video', operation);
|
|
2234
|
+
assertExecutableEditModel(model, 'video', operation);
|
|
2235
|
+
model = await canonicalModelId(client, model, editModelType || undefined);
|
|
2236
|
+
assertExecutableEditModel(model, 'video', operation);
|
|
2237
|
+
await assertModelSupportsType(client, model, editModelType || undefined);
|
|
2238
|
+
aspect_ratio = await resolveCatalogAspectRatio(client, model, aspect_ratio, editModelType || undefined);
|
|
2224
2239
|
|
|
2225
2240
|
// Validation
|
|
2226
2241
|
if (operation === 'magic_edit' && !prompt) throw new Error('prompt is required for magic_edit');
|
|
2227
|
-
if (operation === 'generate_audio'&& !prompt
|
|
2242
|
+
if (operation === 'generate_audio' && !prompt && !sound_effect_prompt && !background_music_prompt
|
|
2243
|
+
&& !(typeof model === 'string' && model.includes('sonilo'))) {
|
|
2244
|
+
throw new Error('prompt is required for generate_audio unless dedicated sound/music prompts are provided or the selected model supports automatic captioning');
|
|
2245
|
+
}
|
|
2228
2246
|
if (operation === 'reframe' && !aspect_ratio)throw new Error('aspect_ratio is required for reframe');
|
|
2229
2247
|
if (operation === 'face_swap' && !image_url) throw new Error('image_url (reference face) is required for face_swap');
|
|
2230
2248
|
if (operation === 'lipsync' && !audio_url && !text_prompt) throw new Error('audio_url or text_prompt is required for lipsync');
|
|
@@ -2236,6 +2254,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
2236
2254
|
target_fps, resolution,
|
|
2237
2255
|
grid_position_x, grid_position_y,
|
|
2238
2256
|
sound_effect_prompt, background_music_prompt, original_sound, cfg_strength,
|
|
2257
|
+
audio_format, segments,
|
|
2239
2258
|
refine_edges, subject_is_person,
|
|
2240
2259
|
text_prompt, context,
|
|
2241
2260
|
mask_video_url, object_prompt, video_strength,
|
package/src/tools/models.js
CHANGED
|
@@ -15,6 +15,18 @@ const TYPE_GROUPS = {
|
|
|
15
15
|
text_to_speech: 'Voice',
|
|
16
16
|
image_editing: 'Image Editing',
|
|
17
17
|
video_to_video: 'Video to Video',
|
|
18
|
+
image_upscale: 'Image Upscale',
|
|
19
|
+
image_reframe: 'Image Reframe',
|
|
20
|
+
image_zoom_out: 'Image Expand',
|
|
21
|
+
video_upscale: 'Video Upscale',
|
|
22
|
+
video_reframe: 'Video Reframe',
|
|
23
|
+
video_background_removal: 'Video Background Removal',
|
|
24
|
+
video_to_sound: 'Video Audio Generation',
|
|
25
|
+
video_face_swap: 'Video Face Swap',
|
|
26
|
+
video_watermark_removal: 'Video Watermark Removal',
|
|
27
|
+
video_extend: 'Video Extend',
|
|
28
|
+
video_inpaint: 'Video Inpaint',
|
|
29
|
+
video_retake: 'Video Retake',
|
|
18
30
|
elements: 'Elements',
|
|
19
31
|
};
|
|
20
32
|
|
|
@@ -105,7 +117,7 @@ function registerModelTools(server, client, options = {}) {
|
|
|
105
117
|
'list_models',
|
|
106
118
|
'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
119
|
{
|
|
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"
|
|
120
|
+
type: z.string().optional().describe('Filter by DB type name. Generation: "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". Image-edit engines: "image_upscale", "image_reframe", "image_zoom_out", "inpaint", "erase", "face_swap", "background_remove", "background_replace", "skin_enhancer", "graphics_enhance". Video-edit engines: "video_upscale", "video_reframe", "video_background_removal", "video_to_sound", "video_face_swap", "video_watermark_removal", "video_extend", "video_inpaint", "video_retake". For edit_image/edit_video, query the operation-specific type and pass a CONCRETE returned identifier; never submit a kolbo_gateway_* row, because those are web-navigation aliases rather than AI engines. Legacy aliases also accepted: "image", "image_edit", "video", "video_from_image", "video_from_video", "music", "speech", "sound", "chat", "lipsync", "three_d", "first_last_frame", "transcription". Omit for all models.'),
|
|
109
121
|
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.'),
|
|
110
122
|
display_catalog: z.boolean().optional().describe('Set true when the USER explicitly asked to see/browse the available models — the visual catalog opens expanded. Leave unset for internal lookups (verifying a model name, checking caps before a generation): the catalog stays collapsed to a single row the user can tap to browse.')
|
|
111
123
|
},
|