@slatesvideo/shared 0.7.2 → 0.7.3
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/dist/clients/cloud.d.ts +4 -0
- package/dist/clients/cloud.js +11 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/manual/content.d.ts +1 -1
- package/dist/manual/content.js +1 -1
- package/dist/operations/index.d.ts +12 -13
- package/dist/operations/index.js +158 -133
- package/dist/operations/surface.d.ts +6 -2
- package/dist/operations/surface.js +29 -5
- package/dist/prompts/agent-doctrine.d.ts +4 -4
- package/dist/prompts/agent-doctrine.js +17 -28
- package/dist/prompts/guide-discovery.d.ts +23 -0
- package/dist/prompts/guide-discovery.js +39 -0
- package/dist/prompts/guide-retrieval.js +1 -1
- package/dist/prompts/model-capabilities.d.ts +8 -9
- package/dist/prompts/model-capabilities.js +11 -51
- package/dist/prompts/model-facts.d.ts +2 -2
- package/dist/prompts/model-facts.js +15 -26
- package/dist/prompts/partials.generated.js +6 -3
- package/dist/prompts/prompting-tips.d.ts +1 -1
- package/dist/prompts/prompting-tips.js +21 -63
- package/dist/prompts/search-terms.d.ts +3 -0
- package/dist/prompts/search-terms.js +24 -0
- package/dist/skills/content.js +36 -37
- package/dist/skills/metadata.d.ts +7 -0
- package/dist/skills/metadata.js +29 -0
- package/exports/slates-chatgpt-images/generated/SKILL.md +7 -1
- package/exports/slates-chatgpt-images/generated/slates-chatgpt-images.skill +0 -0
- package/exports/slates-prompt-builder/generated/SKILL.md +28 -16
- package/exports/slates-prompt-builder/generated/reference-character.md +12 -13
- package/exports/slates-prompt-builder/generated/reference-content-policy.md +2 -2
- package/exports/slates-prompt-builder/generated/reference-gpt-image-2-5.md +191 -0
- package/exports/slates-prompt-builder/generated/reference-kling.md +32 -11
- package/exports/slates-prompt-builder/generated/reference-nano-banana.md +24 -6
- package/exports/slates-prompt-builder/generated/reference-omni-flash.md +65 -0
- package/exports/slates-prompt-builder/generated/reference-seedance-2-5.md +362 -0
- package/exports/slates-prompt-builder/generated/reference-seedance.md +34 -4
- package/exports/slates-prompt-builder/generated/slates-prompt-builder-manifest.json +77 -23
- package/exports/slates-prompt-builder/generated/slates-prompt-builder.skill +0 -0
- package/package.json +2 -1
- package/skills/_partials/blender-action-curves.md +24 -0
- package/skills/_partials/iteration-diagnosis.md +5 -0
- package/skills/_partials/model-routing.md +35 -0
- package/skills/_partials/seedance-25-timestamps.md +2 -2
- package/skills/_partials/still-gate.md +2 -2
- package/skills/_partials/thresholds.md +1 -1
- package/skills/slates-blocking-to-prompt.md +15 -13
- package/skills/slates-camera-language.md +45 -7
- package/skills/slates-character-identity.md +8 -6
- package/skills/slates-chatgpt-images.md +7 -1
- package/skills/slates-cinematic-look.md +1 -1
- package/skills/slates-content-policy.md +4 -6
- package/skills/slates-cost-discipline.md +18 -12
- package/skills/slates-dialogue-blocking.md +6 -6
- package/skills/slates-direct-response-ad.md +1 -1
- package/skills/slates-edit-and-iterate.md +12 -4
- package/skills/slates-model-selection.md +82 -90
- package/skills/slates-one-prompt-film.md +1 -1
- package/skills/slates-previs-blocking.md +44 -13
- package/skills/slates-project-organization.md +2 -2
- package/skills/slates-prompting-elevenlabs.md +4 -4
- package/skills/slates-prompting-flux-2-max.md +2 -3
- package/skills/slates-prompting-gpt-image-2-5.md +2 -2
- package/skills/slates-prompting-inworld-tts.md +174 -174
- package/skills/slates-prompting-kling-v3.md +11 -9
- package/skills/slates-prompting-lip-sync.md +15 -15
- package/skills/slates-prompting-ltx-2-5.md +5 -6
- package/skills/slates-prompting-minimax-h3.md +11 -11
- package/skills/slates-prompting-motion-transfer.md +8 -8
- package/skills/slates-prompting-nano-banana-2.md +8 -4
- package/skills/slates-prompting-omni-flash.md +9 -9
- package/skills/slates-prompting-seed-audio.md +24 -4
- package/skills/slates-prompting-seedance-2-5.md +40 -30
- package/skills/slates-prompting-seedance.md +4 -4
- package/skills/slates-prompting-seedream-5-lite.md +6 -6
- package/skills/slates-restyle-from-blocking.md +2 -2
- package/skills/slates-script-craft.md +1 -1
- package/skills/slates-shot-variety.md +1 -1
- package/skills/slates-storyboard-from-script.md +1 -1
- package/skills/slates-style-prompting.md +56 -54
- package/skills/slates-ugc-influencer-ad.md +1 -1
- package/skills/slates-vision-feedback-loop.md +118 -110
- package/skills/slates-prompting-veo-3.md +0 -224
package/dist/operations/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MAX_IMAGE_VARIATIONS } from '../prompts/generation-policy.js';
|
|
2
|
+
import { discoverGuides, guideCatalog } from '../prompts/guide-discovery.js';
|
|
2
3
|
import { retrieveGuide } from '../prompts/guide-retrieval.js';
|
|
3
4
|
import { MINIMAX_MAX_REFERENCE, minimaxMaxReferenceTokens, MODEL_CAPABILITIES, GPT_QUALITY_TIERS, DEFAULT_GPT_QUALITY, GPT_BACKGROUNDS } from '../prompts/model-capabilities.js';
|
|
4
5
|
// Operations layer — the ONE place every Slates agent tool is defined.
|
|
@@ -13,7 +14,7 @@ import { MINIMAX_MAX_REFERENCE, minimaxMaxReferenceTokens, MODEL_CAPABILITIES, G
|
|
|
13
14
|
// - chooses its transport (cloud vs desktop) internally — callers
|
|
14
15
|
// don't need to know which side a given op talks to
|
|
15
16
|
import { z } from 'zod';
|
|
16
|
-
import { SlatesCloudClient } from '../clients/cloud.js';
|
|
17
|
+
import { SlatesCloudClient, SlatesCloudHttpError } from '../clients/cloud.js';
|
|
17
18
|
import { SlatesDesktopClient } from '../clients/desktop.js';
|
|
18
19
|
import { BlenderBridgeClient, BLENDER_SETUP_HINT, RENDER_TIMEOUT_MS } from '../clients/blender.js';
|
|
19
20
|
import { SKILLS } from '../skills/content.js';
|
|
@@ -59,10 +60,12 @@ import { SHOT_SIZE_BUCKETS, CAMERA_MOVE_BUCKETS, SPEECH_RATE, } from '../prompts
|
|
|
59
60
|
// Annotations, tiers and the ONE schema renderer. Declared next door so this
|
|
60
61
|
// module never hand-sets a hint or a tier per op: `annotate()` derives all four
|
|
61
62
|
// from the id and the lockstep check re-derives them from the transport verbs.
|
|
62
|
-
import { annotate, groupFor, tierFor, toolDefinitions, GROUP_SUMMARY, OPERATION_GROUPS, } from './surface.js';
|
|
63
|
+
import { annotate, groupFor, tierFor, toolDefinitions, searchTools, GROUP_SUMMARY, OPERATION_GROUPS, } from './surface.js';
|
|
64
|
+
// One factory for every default context, so per-connection caches can key on it.
|
|
65
|
+
const defaultCloud = () => new SlatesCloudClient();
|
|
63
66
|
export function defaultContext() {
|
|
64
67
|
return {
|
|
65
|
-
cloud:
|
|
68
|
+
cloud: defaultCloud,
|
|
66
69
|
desktop: () => new SlatesDesktopClient(),
|
|
67
70
|
};
|
|
68
71
|
}
|
|
@@ -127,7 +130,10 @@ export const DEVIATION_FACTOR = 1.2;
|
|
|
127
130
|
* to find three wordings. Byte-stable (a template over a literal), so the
|
|
128
131
|
* desktop's prompt-cached prefix is unaffected.
|
|
129
132
|
*/
|
|
130
|
-
|
|
133
|
+
// The consent half rides every generation tool because a host may drop the
|
|
134
|
+
// server instructions: Codex CLI 0.159.1 passed none to the model (probes
|
|
135
|
+
// 2026-09-30 and 2026-10-02), so a small spend had no approval rule in view.
|
|
136
|
+
const CONFIRM_GATE_SENTENCE = `Show the user the estimate and wait for their OK before any generation, however small. Cost above ${CONFIRM_CREDITS} credits (and, on image and video, any attached reference) also returns requires_confirm — pass confirm=true only to relay that OK.`;
|
|
131
137
|
// Declared HERE, above every op, because `slates_estimate_generation_cost`
|
|
132
138
|
// renders them into its `duration` description at MODULE LOAD — a const
|
|
133
139
|
// declared below the first schema that reads it is a temporal-dead-zone
|
|
@@ -239,9 +245,9 @@ const folderBody = (folderId) => (folderId === undefined ? {} : { folderId });
|
|
|
239
245
|
const IMAGE_INLINE_REVIEW = 'The image is attached to this result — look at it against the brief before you describe it. ' +
|
|
240
246
|
'Any claim about how it LOOKS must come from pixels you actually received (slates-vision-feedback-loop).';
|
|
241
247
|
const VIDEO_REVIEW_POINTER = 'You have NOT seen this clip: call slates_get_asset_video_frames on the asset id above before ' +
|
|
242
|
-
'describing
|
|
248
|
+
'describing visible composition or identity. Sampled stills do not establish continuous motion, lip sync or sound; use actual playback through a capable host for those, or report them unreviewed.';
|
|
243
249
|
const BACKGROUND_REVIEW_POINTER = 'When it completes, look at it before you describe it — slates_get_asset_image for images, ' +
|
|
244
|
-
'slates_get_asset_video_frames for video. For audio, audition the saved file;
|
|
250
|
+
'slates_get_asset_video_frames for video. For audio, audition the saved file only through a host that can receive/listen to audio; otherwise report it unreviewed. Metadata does not establish voice similarity or delivery quality.';
|
|
245
251
|
// The image saved, but reading it back off disk failed (best-effort fetch). The
|
|
246
252
|
// agent has an asset and NO pixels, which is the one state where a quality
|
|
247
253
|
// claim would be pure invention — so this branch has to say so rather than
|
|
@@ -959,8 +965,8 @@ export const VIDEO_MODELS = [
|
|
|
959
965
|
'kling-v3.0-std',
|
|
960
966
|
'kling-v3.0-pro',
|
|
961
967
|
'kling-v3.0-omni',
|
|
962
|
-
|
|
963
|
-
|
|
968
|
+
// Veo 3.1 Fast and Standard were retired on 2026-10-02 (Eric). The server
|
|
969
|
+
// keeps their keys for older desktops only; nothing here offers them.
|
|
964
970
|
'seedance-2',
|
|
965
971
|
// Seedance 2.5 is the DEFAULT video model (Eric, 2026-09-13): 30s takes, 30
|
|
966
972
|
// image references, audio-only references, up to 1080p (2026-08-24). 2.0 stays
|
|
@@ -985,8 +991,8 @@ export const VIDEO_MODELS = [
|
|
|
985
991
|
'minimax-h3-max',
|
|
986
992
|
'minimax-h3-max-turbo',
|
|
987
993
|
// LTX-2.5, two seats in one family (2026-08-29). Base is the VOLUME seat —
|
|
988
|
-
// cheapest
|
|
989
|
-
// only row reaching 1440p, and
|
|
994
|
+
// cheapest 1080p second with sound included, free native audio at every tier,
|
|
995
|
+
// the only row reaching 1440p, and clips up to 20s.
|
|
990
996
|
// Pro is the fidelity seat and is NOT a superset: shorter ladder (no
|
|
991
997
|
// 1440p/4K), shorter clips (6/8/10 only) and ~1/3 dearer.
|
|
992
998
|
//
|
|
@@ -1108,7 +1114,7 @@ function editClipBounds(model) {
|
|
|
1108
1114
|
const EDIT_VIDEO_RESOLUTIONS = videoResolutionUnion(['seedance-2.5-edit']);
|
|
1109
1115
|
export const estimateGenerationCost = {
|
|
1110
1116
|
id: 'slates_estimate_generation_cost',
|
|
1111
|
-
description: 'Quote credits before any generate_* op. Accepts the
|
|
1117
|
+
description: 'Quote credits before any generate_* op. Accepts the generation model ids and parameters (edit seats other than Kling, lip-sync and motion-transfer engines need an exact registry cost key), or an exact registry cost key. Pairs with the confirm gate.',
|
|
1112
1118
|
input: z.object({
|
|
1113
1119
|
model: z.string().describe('Base model id as passed to the generate op (e.g. "seedance-2", "kling-v3.0-std", "nano-banana-2") or an exact registry cost key ("nano-banana-2-2k", "seedance-2-1080p-8s")'),
|
|
1114
1120
|
quantity: z.number().int().min(1).max(10).optional().describe('Number of generations (default 1)'),
|
|
@@ -1126,8 +1132,8 @@ export const estimateGenerationCost = {
|
|
|
1126
1132
|
videoResolution: zEnum(VIDEO_RESOLUTIONS).optional().describe('Video only. Omitted, each model quotes at its own default. Per-model ladders: see slates_generate_video\'s videoResolution.'),
|
|
1127
1133
|
resolution: z.enum(['1k', '2k', '3k', '4k']).optional().describe('Image only. Omit for the model default; pass the same value to generation.'),
|
|
1128
1134
|
quality: z.enum(GPT_QUALITY_TIERS).optional().describe(`GPT Image tier; default ${DEFAULT_GPT_QUALITY}.`),
|
|
1129
|
-
aspectRatio: z.string().optional().describe('Image only
|
|
1130
|
-
sound: z.boolean().optional().describe('
|
|
1135
|
+
aspectRatio: z.string().optional().describe('GPT Image only: 1:1, 4:3 and 3:4 cost more than 16:9.'),
|
|
1136
|
+
sound: z.boolean().optional().describe('Kling 3.0: the audio flag changes the cost key. Omitted means sound on, as generation bills it; pass false to price a silent take. Kling 4K keys include audio.'),
|
|
1131
1137
|
seedanceFace: z.boolean().optional().describe('Seedance AI-face route (pricier key).'),
|
|
1132
1138
|
seedanceRealFace: z.boolean().optional().describe('Seedance consented real-face route (premium key).'),
|
|
1133
1139
|
videoRefSeconds: z.number().nonnegative().optional().describe('Combined reference-video seconds, measured from the clips.'),
|
|
@@ -1514,7 +1520,7 @@ export const getAssetsBatch = {
|
|
|
1514
1520
|
};
|
|
1515
1521
|
export const getAssetVideoFrames = {
|
|
1516
1522
|
id: 'slates_get_asset_video_frames',
|
|
1517
|
-
description: 'Extract
|
|
1523
|
+
description: 'Extract evenly-spaced sampled still frames from a video asset and return them inline as JPEGs. Inspect visible appearance, composition and identity before revising a prompt. Stills do not verify continuous motion, timing, lip sync or sound; actual playback through a capable host is needed for those claims. Default 3 samples; count can request more. The response includes the asset code and label; use them when discussing the clip.',
|
|
1518
1524
|
input: z.object({
|
|
1519
1525
|
id: z.string().uuid(),
|
|
1520
1526
|
count: z.number().int().min(1).max(8).optional().describe('Number of frames to extract. Default 3.'),
|
|
@@ -2190,19 +2196,21 @@ const LEGACY_DESKTOP_IMAGE_BATCH = 4;
|
|
|
2190
2196
|
export const generateImage = {
|
|
2191
2197
|
id: 'slates_generate_image',
|
|
2192
2198
|
billable: true,
|
|
2193
|
-
description:
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
`${describeRouting('image')}\n` +
|
|
2198
|
-
'Full table: the slates-model-selection skill. ' +
|
|
2199
|
-
'Pass projectId to save into a Slates project (asset appears live in the desktop UI). All models except nano-banana-2 REQUIRE projectId (no headless path). REQUIRED before calling: read the slates-cost-discipline skill (and the model\'s slates-prompting-* skill). You MUST pass aspectRatio and resolution explicitly (the server returns requires_clarification when missing — defaults waste credits). ' +
|
|
2199
|
+
description:
|
|
2200
|
+
// Rules first: Claude Code keeps only the first 2,048 characters of a tool
|
|
2201
|
+
// description, and the generated roster below runs past that cut.
|
|
2202
|
+
'Generate an image via Slates credits. Pass projectId to save into a Slates project (asset appears live in the desktop UI). All models except nano-banana-2 REQUIRE projectId (no headless path). You MUST pass aspectRatio (the server returns requires_clarification when missing); resolution defaults to the model\'s own. ' +
|
|
2200
2203
|
CONFIRM_GATE_SENTENCE +
|
|
2201
|
-
' MCP/CLI generation always charges credits.
|
|
2204
|
+
' MCP/CLI generation always charges credits. The estimate returns the model\'s prompting card; load its slates-prompting-* guide with slates_get_prompting_guide for anything the card leaves out. ' +
|
|
2202
2205
|
// GENERATED from the skill file's own never-use list -- the one piece of
|
|
2203
2206
|
// prompting doctrine that is ALWAYS in context, because the agent has
|
|
2204
2207
|
// demonstrably skipped the call that would have taught it.
|
|
2205
|
-
describeBannedTokens('image')
|
|
2208
|
+
describeBannedTokens('image') +
|
|
2209
|
+
// GENERATED from MODEL_FACTS — the hand-typed model list that stood here
|
|
2210
|
+
// was a third copy of the routing doctrine, and it had already gone stale
|
|
2211
|
+
// (it still described nano-banana-2-lite by a capability the param owns).
|
|
2212
|
+
`\n${describeRouting('image')}\n` +
|
|
2213
|
+
'Full table: the slates-model-selection skill.',
|
|
2206
2214
|
input: z.object({
|
|
2207
2215
|
prompt: z.string().min(1).max(4000),
|
|
2208
2216
|
model: zEnum(IMAGE_MODELS).optional().describe(`Image model. Omitted: ${DEFAULT_IMAGE_MODEL} with projectId, nano-banana-2 (the only headless seat) without. Routing: slates-model-selection skill.`),
|
|
@@ -2477,7 +2485,7 @@ export const generateImage = {
|
|
|
2477
2485
|
};
|
|
2478
2486
|
}
|
|
2479
2487
|
// /proxy/generate kicks off a credit-aware job and returns a jobId
|
|
2480
|
-
// for fal
|
|
2488
|
+
// for fal (async providers). We poll /proxy/jobs/{jobId} until
|
|
2481
2489
|
// the status is `completed` or `failed`, then fetch each image URL
|
|
2482
2490
|
// and inline as base64 so the calling LLM sees the pixels.
|
|
2483
2491
|
// The fal endpoint differs by mode: bare model id for text-to-image,
|
|
@@ -2590,7 +2598,7 @@ const LEGACY_EDIT_REFERENCE_MODELS = ['nano-banana-2', 'nano-banana-2-lite', 'na
|
|
|
2590
2598
|
export const editImage = {
|
|
2591
2599
|
id: 'slates_edit_image',
|
|
2592
2600
|
billable: true,
|
|
2593
|
-
description: 'Surgically edit an image asset with a text instruction (e.g. \'make the jacket red\') instead of regenerating from scratch — use when ~90% of the image is already right. The result is a NEW asset (prompt prefixed \'[Edit]\'); the source is untouched. Default model ' + toolModelFor('image-edit') + ' (the image-edit tool seat, the model the app\'s Edit box opens on). Every model also takes referenceAssetIds, up to its reference cap less one: the source is image 1 of the request.
|
|
2601
|
+
description: 'Surgically edit an image asset with a text instruction (e.g. \'make the jacket red\') instead of regenerating from scratch — use when ~90% of the image is already right. The result is a NEW asset (prompt prefixed \'[Edit]\'); the source is untouched. Default model ' + toolModelFor('image-edit') + ' (the image-edit tool seat, the model the app\'s Edit box opens on). Every model also takes referenceAssetIds, up to its reference cap less one: the source is image 1 of the request. Use slates-edit-and-iterate for missing edit craft; reuse current guidance already in context.',
|
|
2594
2602
|
input: z.object({
|
|
2595
2603
|
projectId: z.string().uuid(),
|
|
2596
2604
|
sourceAssetId: z.string().uuid().describe('Image asset to edit. Must exist in the project.'),
|
|
@@ -2834,10 +2842,9 @@ export const extractGridCells = {
|
|
|
2834
2842
|
* same teaching `requires_clarification` shape as every other gate in this op,
|
|
2835
2843
|
* rather than a raw Zod error the agent has to guess its way out of.
|
|
2836
2844
|
*
|
|
2837
|
-
* `promptMode` matters
|
|
2838
|
-
*
|
|
2839
|
-
* first/last frame IS ingredients mode
|
|
2840
|
-
* `buildFalVeoRequest` uses to pick the ref2v endpoint.
|
|
2845
|
+
* `promptMode` matters for any row that declares `duration.modeOverrides.ingredients`
|
|
2846
|
+
* (retired Veo's reference-to-video endpoint was 8s only). Free reference images
|
|
2847
|
+
* with no first/last frame IS ingredients mode.
|
|
2841
2848
|
*/
|
|
2842
2849
|
function assertVideoCapabilities(input) {
|
|
2843
2850
|
const freeRefs = (input.ingredientAssetIds?.length ?? 0) +
|
|
@@ -2866,7 +2873,6 @@ function assertVideoCapabilities(input) {
|
|
|
2866
2873
|
// shape (verified against /api/agent/models):
|
|
2867
2874
|
// Kling: kling-v3-{standard|pro|omni}-{N}s — note the user-facing
|
|
2868
2875
|
// model id `kling-v3.0-std` maps to registry key `kling-v3-standard`.
|
|
2869
|
-
// Veo: veo-3.1-{fast|standard}[-4k]-{N}s[-audio]
|
|
2870
2876
|
// Seedance: seedance-2-{res}-{N}s (BytePlus ModelArk, sole provider). The
|
|
2871
2877
|
// cost key encodes resolution (480p/720p/1080p/4k) — price scales with
|
|
2872
2878
|
// resolution, so the key MUST carry it or the pre-flight quote is wrong.
|
|
@@ -2932,29 +2938,21 @@ export function videoCostKey(input) {
|
|
|
2932
2938
|
}
|
|
2933
2939
|
return `${input.model}${face}-${res}-${input.duration}s`;
|
|
2934
2940
|
}
|
|
2935
|
-
if (input.model.startsWith('veo')) {
|
|
2936
|
-
const is4k = input.videoResolution === '4k';
|
|
2937
|
-
const audio = input.sound !== false; // default audio on for Veo
|
|
2938
|
-
const parts = [input.model];
|
|
2939
|
-
if (is4k)
|
|
2940
|
-
parts.push('4k');
|
|
2941
|
-
parts.push(`${input.duration}s`);
|
|
2942
|
-
if (audio)
|
|
2943
|
-
parts.push('audio');
|
|
2944
|
-
return parts.join('-');
|
|
2945
|
-
}
|
|
2946
2941
|
if (input.model.startsWith('kling-v3.0')) {
|
|
2947
2942
|
// Mirrors klingCreditKey() in slate/src/shared/pricing.ts. Kling native 4K
|
|
2948
2943
|
// bills flat-rate keys: std/pro/omni all get a `-4k` tier key, and omni-pro
|
|
2949
2944
|
// shares kling-v3-omni-4k (the o3/4k endpoint has one flat rate, audio
|
|
2950
2945
|
// included). At 1080p AUDIO IS A KEY DIMENSION (credits = COGS × markup,
|
|
2951
|
-
// locked 2026-07-05): sound → `-audio` variant.
|
|
2946
|
+
// locked 2026-07-05): sound → `-audio` variant. An OMITTED sound is ON: the
|
|
2947
|
+
// desktop agent route sends `sound ?? true` and bills the audio key, so a
|
|
2948
|
+
// quote that read omitted as silent under-quoted every default Kling take
|
|
2949
|
+
// (21 credits quoted, 32 billed at std 5s; found 2026-10-02).
|
|
2952
2950
|
const tier = KLING_TIER_MAP[input.model] ?? input.model;
|
|
2953
2951
|
if (input.videoResolution === '4k') {
|
|
2954
2952
|
const tier4k = tier === 'kling-v3-omni-pro' ? 'kling-v3-omni' : tier;
|
|
2955
2953
|
return `${tier4k}-4k-${input.duration}s`;
|
|
2956
2954
|
}
|
|
2957
|
-
return `${tier}-${input.duration}s${input.sound
|
|
2955
|
+
return `${tier}-${input.duration}s${input.sound !== false ? '-audio' : ''}`;
|
|
2958
2956
|
}
|
|
2959
2957
|
if (input.model === 'omni-flash') {
|
|
2960
2958
|
// Mirrors omniFlashCreditKey() in slate/src/shared/pricing.ts — flat 720p
|
|
@@ -3079,13 +3077,19 @@ function resolveVideoModel(raw) {
|
|
|
3079
3077
|
// so a pasted `minimax-h3-768p-10s-ref2` resolves instead of erroring. The
|
|
3080
3078
|
// number it carries is K (images PAST the free five), so it is converted back
|
|
3081
3079
|
// to a TOTAL before anything can re-surcharge it.
|
|
3080
|
+
// The free allowance differs per row (H3 five, H3 Max four), so the total is
|
|
3081
|
+
// computed once the model is known, in `withRefTotal`; reading `out.model`
|
|
3082
|
+
// here read the placeholder and gave every row five.
|
|
3082
3083
|
const ref = /-ref(\d+)\b/.exec(s);
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
(MINIMAX_FREE_REF_IMAGES_BY_MODEL[out.model] ?? MINIMAX_FREE_REF_IMAGES) +
|
|
3086
|
-
parseInt(ref[1], 10);
|
|
3084
|
+
const paidRefs = ref ? parseInt(ref[1], 10) : null;
|
|
3085
|
+
if (ref)
|
|
3087
3086
|
s = s.replace(/-ref(\d+)\b/, '');
|
|
3088
|
-
|
|
3087
|
+
const withRefTotal = (resolved) => {
|
|
3088
|
+
if (paidRefs !== null) {
|
|
3089
|
+
resolved.referenceImages = (MINIMAX_FREE_REF_IMAGES_BY_MODEL[resolved.model] ?? MINIMAX_FREE_REF_IMAGES) + paidRefs;
|
|
3090
|
+
}
|
|
3091
|
+
return resolved;
|
|
3092
|
+
};
|
|
3089
3093
|
// The RESOLUTION vocabulary is GENERATED from MODEL_CAPABILITIES — the
|
|
3090
3094
|
// hand-typed list that stood here went stale the day 768p and 2k shipped.
|
|
3091
3095
|
const resRe = new RegExp(`-(${VIDEO_RESOLUTION_VOCAB.join('|')})\\b`);
|
|
@@ -3111,7 +3115,7 @@ function resolveVideoModel(raw) {
|
|
|
3111
3115
|
const direct = VIDEO_MODELS.find((m) => m === s);
|
|
3112
3116
|
if (direct) {
|
|
3113
3117
|
out.model = direct;
|
|
3114
|
-
return out;
|
|
3118
|
+
return withRefTotal(out);
|
|
3115
3119
|
}
|
|
3116
3120
|
const aliases = {
|
|
3117
3121
|
'kling-v3-standard': 'kling-v3.0-std',
|
|
@@ -3133,8 +3137,6 @@ function resolveVideoModel(raw) {
|
|
|
3133
3137
|
'seedance-2-5': 'seedance-2.5',
|
|
3134
3138
|
'seedance2.5': 'seedance-2.5',
|
|
3135
3139
|
seedance: 'seedance-2',
|
|
3136
|
-
'veo-3.1': 'veo-3.1-fast',
|
|
3137
|
-
'veo-3': 'veo-3.1-fast',
|
|
3138
3140
|
'gemini-omni-flash': 'omni-flash',
|
|
3139
3141
|
'gemini-omni-flash-preview': 'omni-flash',
|
|
3140
3142
|
'omni-flash-preview': 'omni-flash',
|
|
@@ -3159,7 +3161,7 @@ function resolveVideoModel(raw) {
|
|
|
3159
3161
|
};
|
|
3160
3162
|
if (aliases[s]) {
|
|
3161
3163
|
out.model = aliases[s];
|
|
3162
|
-
return out;
|
|
3164
|
+
return withRefTotal(out);
|
|
3163
3165
|
}
|
|
3164
3166
|
return null;
|
|
3165
3167
|
}
|
|
@@ -3215,11 +3217,11 @@ const VIDEO_MODEL_GUIDES = [
|
|
|
3215
3217
|
export const generateVideo = {
|
|
3216
3218
|
id: 'slates_generate_video',
|
|
3217
3219
|
billable: true,
|
|
3218
|
-
description: 'Generate video via Slates credits.
|
|
3220
|
+
description: 'Generate video via Slates credits. Choose the model with slates-model-selection. Video models prompt very differently: the estimate returns the chosen model\'s prompting card, and its full guide (' +
|
|
3219
3221
|
VIDEO_MODEL_GUIDES +
|
|
3220
|
-
')
|
|
3222
|
+
') covers modes the card leaves out, via slates_get_prompting_guide. Read slates-content-policy when the scene involves conflict, creatures, crowds, destruction, weapons, or young characters. projectId, aspectRatio, and duration are required (requires_clarification otherwise). ' +
|
|
3221
3223
|
CONFIRM_GATE_SENTENCE +
|
|
3222
|
-
' Image-to-video via firstFrameAssetId; first+last frames
|
|
3224
|
+
' Image-to-video via firstFrameAssetId; first+last frames on every model except Omni Flash; ingredients via ingredientAssetIds (Kling Omni, Seedance, Omni Flash, H3 and H3 Max; Kling Std/Pro only with a first frame). Asset params take UUIDs or badge codes ("IMG-A8"). ' +
|
|
3223
3225
|
// GENERATED from the skill's own slop-token list. Always in context on both
|
|
3224
3226
|
// surfaces, so it survives an agent that skips slates_get_prompting_guide.
|
|
3225
3227
|
describeBannedTokens('video'),
|
|
@@ -3239,7 +3241,7 @@ export const generateVideo = {
|
|
|
3239
3241
|
`Full table: the slates-model-selection skill. Each model's legal aspect ratios, durations and ` +
|
|
3240
3242
|
`resolutions are in those params' own descriptions — read them there, not from memory. ` +
|
|
3241
3243
|
`For per-call cost, call slates_estimate_generation_cost.`),
|
|
3242
|
-
projectId: z.string().uuid().optional().describe('Save into this Slates project.
|
|
3244
|
+
projectId: z.string().uuid().optional().describe('Save into this Slates project. Required — the desktop UI shows a progress card live and the asset appears when complete.'),
|
|
3243
3245
|
// 🚨 THESE THREE DESCRIPTIONS ARE GENERATED FROM `MODEL_CAPABILITIES`.
|
|
3244
3246
|
// Never hand-write a ratio, resolution or duration into them again — every
|
|
3245
3247
|
// one of the hand-written claims that stood here had drifted, and an
|
|
@@ -3256,7 +3258,7 @@ export const generateVideo = {
|
|
|
3256
3258
|
// demand, by the one session that needs it. If you are about to explain
|
|
3257
3259
|
// WHY here, you are writing the skill in the wrong file.
|
|
3258
3260
|
firstFrameAssetId: z.string().optional().describe('Starting frame for image-to-video (UUID or badge code, resolved at call time).'),
|
|
3259
|
-
lastFrameAssetId: z.string().optional().describe('Ending frame
|
|
3261
|
+
lastFrameAssetId: z.string().optional().describe('Ending frame; every model except Omni Flash. Pairs with firstFrameAssetId.'),
|
|
3260
3262
|
ingredientAssetIds: z.array(z.string()).max(30).optional().describe(
|
|
3261
3263
|
// Caps DERIVED from MODEL_CAPABILITIES — the hand-typed list omitted
|
|
3262
3264
|
// kling-v3.0-omni-pro entirely and read as if 7 were an Omni Flash-only rule.
|
|
@@ -3271,15 +3273,15 @@ export const generateVideo = {
|
|
|
3271
3273
|
// The capacity sentences are DERIVED from MODEL_FACTS (see
|
|
3272
3274
|
// multimodalRefSummary) rather than hand-typed, so a cap change in one
|
|
3273
3275
|
// place cannot leave a stale number in a description an LLM reads.
|
|
3274
|
-
videoReferenceAssetIds: z.array(z.string()).optional().describe(`Reference VIDEOS, cited in the prompt as "video 1", "video 2"… in the order given. ${multimodalRefModels().join(' / ')} only; per-model caps are on audioReferenceAssetIds.
|
|
3276
|
+
videoReferenceAssetIds: z.array(z.string()).optional().describe(`Reference VIDEOS, cited in the prompt as "video 1", "video 2"… in the order given. ${multimodalRefModels().join(' / ')} only; per-model caps are on audioReferenceAssetIds. On Seedance, billing switches to the vref key (input+output seconds) — pass videoReferenceSecondsEach. Over the cap is REFUSED, never trimmed.`),
|
|
3275
3277
|
videoReferenceSecondsEach: z.array(z.number()).optional().describe('REQUIRED with videoReferenceAssetIds, same order and length: each clip\'s duration in seconds. Feeds the vref cost key; the server re-probes and corrects an understated value upward.'),
|
|
3276
|
-
audioReferenceAssetIds: z.array(z.string()).optional().describe(`Reference AUDIO, cited as "audio 1", "audio 2"… in the order given. ${multimodalRefModels().join(' / ')} only. No
|
|
3278
|
+
audioReferenceAssetIds: z.array(z.string()).optional().describe(`Reference AUDIO, cited as "audio 1", "audio 2"… in the order given. ${multimodalRefModels().join(' / ')} only. No surcharge on Seedance; on H3 Max, audio counts toward the reference-token pool. ${multimodalRefModels().map(multimodalRefSummary).join(' ')}`),
|
|
3277
3279
|
audioReferenceSpokenText: z.array(z.string()).optional().describe('The exact words in each reference clip — same order and length as audioReferenceAssetIds, "" for a clip with no speech. The model RE-TRANSCRIBES a take rather than using it verbatim, so audio decides voice/accent/timing and only this decides the WORDS. Omit it and the words are a guess.'),
|
|
3278
|
-
sound: z.boolean().optional().describe('Kling
|
|
3279
|
-
audioLanguage: z.enum(['EN', 'ZH', 'JA', 'KO', 'ES']).optional().describe('Kling
|
|
3280
|
-
generateMusic: z.boolean().optional().describe('Kling
|
|
3280
|
+
sound: z.boolean().optional().describe('Kling (every tier) and LTX: sound on (default) or silent (false); Kling bills audio as its own key. Seedance, Omni Flash and H3 always generate audio.'),
|
|
3281
|
+
audioLanguage: z.enum(['EN', 'ZH', 'JA', 'KO', 'ES']).optional().describe('Any Kling model with sound on — language for dialogue.'),
|
|
3282
|
+
generateMusic: z.boolean().optional().describe('Any Kling model with sound on — auto-generate background music.'),
|
|
3281
3283
|
seedanceFace: z.boolean().optional().describe('Seedance ONLY: a reference shows an AI CHARACTER\'s face. Faces are blocked on the default route, so this reroutes to a face-capable provider at ~45% more. A REAL person fails here with [REAL_FACE_DETECTED] — see seedanceRealFace.'),
|
|
3282
|
-
seedanceRealFace: z.boolean().optional().describe('Seedance ONLY: a reference shows a REAL person. Real-person route,
|
|
3284
|
+
seedanceRealFace: z.boolean().optional().describe('Seedance ONLY: a reference shows a REAL person. Real-person route, about 1.4x the AI-face price (about 2x faceless) — quote it first. REQUIRES realFaceConsent=true.'),
|
|
3283
3285
|
realFaceConsent: z.boolean().optional().describe('MANDATORY with seedanceRealFace: true ONLY after the user has explicitly confirmed they hold rights/consent to this likeness and it does not impersonate or misrepresent them. Refused without it; public figures fail on every route.'),
|
|
3284
3286
|
negativePrompt: z.string().optional(),
|
|
3285
3287
|
background: z.boolean().optional().describe(BACKGROUND_DESCRIBE),
|
|
@@ -3369,7 +3371,7 @@ export const generateVideo = {
|
|
|
3369
3371
|
return ok({
|
|
3370
3372
|
requires_clarification: true,
|
|
3371
3373
|
missing: [],
|
|
3372
|
-
message: `Omni Flash takes a prompt, an optional start frame, and up to ${omniFlashRefCap} reference IMAGES — last frames and video/audio references are not supported. Drop those, or switch to seedance-2 (video/audio refs, last frame)
|
|
3374
|
+
message: `Omni Flash takes a prompt, an optional start frame, and up to ${omniFlashRefCap} reference IMAGES — last frames and video/audio references are not supported. Drop those, or switch to seedance-2.5 (video/audio refs, last frame).`,
|
|
3373
3375
|
});
|
|
3374
3376
|
}
|
|
3375
3377
|
const refCount = (input.ingredientAssetIds?.length ?? 0) +
|
|
@@ -3847,10 +3849,9 @@ export const generateAudio = {
|
|
|
3847
3849
|
id: 'slates_generate_audio',
|
|
3848
3850
|
billable: true,
|
|
3849
3851
|
description: `Generate project audio using credits. Choose the surface via the model routing below. ` +
|
|
3850
|
-
'
|
|
3852
|
+
'The estimate returns the chosen surface\'s prompting card; its full guide (slates-prompting-seed-audio | slates-prompting-elevenlabs | slates-prompting-inworld-tts) covers what the card leaves out. ' +
|
|
3851
3853
|
'Seed Audio bills the requested duration, which is appended to the prompt regardless of output length. Kling "SFX:" / "Ambient noise:" syntax does not transfer. ' +
|
|
3852
|
-
CONFIRM_GATE_SENTENCE
|
|
3853
|
-
' No skill files installed? Call slates_get_prompting_guide first.',
|
|
3854
|
+
CONFIRM_GATE_SENTENCE,
|
|
3854
3855
|
input: z.object({
|
|
3855
3856
|
projectId: z.string().uuid().describe('Slates project the audio asset lands in. Required — the renderer refreshes live.'),
|
|
3856
3857
|
model: z
|
|
@@ -4102,7 +4103,8 @@ async function quoteToolBlocks(ctx, stem, seconds, voiceStep) {
|
|
|
4102
4103
|
export const generateLipSync = {
|
|
4103
4104
|
id: 'slates_generate_lip_sync',
|
|
4104
4105
|
billable: true,
|
|
4105
|
-
description: 'Lip-sync a still image (avatar) or a video clip to audio. KLING-ONLY — this tool wraps Kling\'s dedicated lip-sync endpoints and nothing else: sourceType=video re-syncs a clip, sourceType=image animates a still avatar (avatar-standard, or avatar-pro for the premium seat). Quote each with slates_estimate_generation_cost rather than from memory. Audio from TTS (ttsText + ttsVoice) or an uploaded file. Billed per 5s of the output (the clip, or on a still the voice track). For a Seedance version, do NOT look for an engine switch here — run a normal slates_generate_video on seedance-2 with the clip attached as a video reference and the dialogue written into the prompt; that is the same call, with the prompt visible and editable.
|
|
4106
|
+
description: 'Lip-sync a still image (avatar) or a video clip to audio. KLING-ONLY — this tool wraps Kling\'s dedicated lip-sync endpoints and nothing else: sourceType=video re-syncs a clip, sourceType=image animates a still avatar (avatar-standard, or avatar-pro for the premium seat). Quote each with slates_estimate_generation_cost rather than from memory. Audio from TTS (ttsText + ttsVoice) or an uploaded file. Billed per 5s of the output (the clip, or on a still the voice track). For a Seedance version, do NOT look for an engine switch here — run a normal slates_generate_video on seedance-2 with the clip attached as a video reference and the dialogue written into the prompt; that is the same call, with the prompt visible and editable. The craft is slates-prompting-lip-sync; the confirm response shows the exact cost before anything is charged. projectId is REQUIRED. ' +
|
|
4107
|
+
CONFIRM_GATE_SENTENCE,
|
|
4106
4108
|
input: z.object({
|
|
4107
4109
|
projectId: z.string().uuid().describe('Slates project the source asset lives in. The new lip-synced video lands here.'),
|
|
4108
4110
|
sourceAssetId: z.string().uuid().describe('Asset id of the still image (avatar flow) or video clip (lip-sync flow). Must already exist in the project — use slates_upload_reference_image or slates_generate_image / slates_generate_video first if needed.'),
|
|
@@ -4225,11 +4227,11 @@ export const generateLipSync = {
|
|
|
4225
4227
|
export const generateMotionTransfer = {
|
|
4226
4228
|
id: 'slates_generate_motion_transfer',
|
|
4227
4229
|
billable: true,
|
|
4228
|
-
description: 'Transfer the motion from a reference video onto a target image character. KLING-ONLY — this tool wraps Kling Motion Control and nothing else: kling-mc-std or kling-mc-pro, structured skeleton/depth retargeting, billed per 5s of the driving clip. For a Seedance version, do NOT look for an engine switch here — run a normal slates_generate_video on seedance-2 with the driving clip attached as a video reference and the motion described in the prompt ("the character from image 1 performs the exact motion from video 1"); that is the same call, with the prompt visible and editable.
|
|
4230
|
+
description: 'Transfer the motion from a reference video onto a target image character. KLING-ONLY — this tool wraps Kling Motion Control and nothing else: kling-mc-std or kling-mc-pro, structured skeleton/depth retargeting, billed per 5s of the driving clip. For a Seedance version, do NOT look for an engine switch here — run a normal slates_generate_video on seedance-2 with the driving clip attached as a video reference and the motion described in the prompt ("the character from image 1 performs the exact motion from video 1"); that is the same call, with the prompt visible and editable. The craft is slates-prompting-motion-transfer; the confirm response shows the exact cost before anything is charged. projectId is REQUIRED — both assets must exist in the project. ' +
|
|
4229
4231
|
CONFIRM_GATE_SENTENCE,
|
|
4230
4232
|
input: z.object({
|
|
4231
4233
|
projectId: z.string().uuid().describe('Slates project. Both source and target assets must live here.'),
|
|
4232
|
-
sourceVideoAssetId: z.string().uuid().describe('Asset id of the reference video — its motion will be retargeted onto the target image. Must already exist in the project. Up to 30s.'),
|
|
4234
|
+
sourceVideoAssetId: z.string().uuid().describe('Asset id of the reference video — its motion will be retargeted onto the target image. Must already exist in the project. Up to 30s with characterOrientation video, 10s with image; billed per 5s block.'),
|
|
4233
4235
|
targetImageAssetId: z.string().uuid().describe('Asset id of the target image (the character that will perform the motion). Must already exist in the project.'),
|
|
4234
4236
|
motionModel: z.enum(['kling-mc-std', 'kling-mc-pro']).optional().describe('kling-mc-std general motion; kling-mc-pro cleaner anatomy — default. Quote both with slates_estimate_generation_cost.'),
|
|
4235
4237
|
characterOrientation: z.enum(['video', 'image']).optional().describe('"video" = use the source video\'s framing. "image" = use the target image\'s framing. Default video.'),
|
|
@@ -4336,7 +4338,7 @@ export const editVideo = {
|
|
|
4336
4338
|
// point to in a tool result) and go stale on the next rate change; the
|
|
4337
4339
|
// windows are owned by MODEL_CAPABILITIES and are generated below into the
|
|
4338
4340
|
// params that enforce them.
|
|
4339
|
-
'Edit an EXISTING video clip with one instruction — character swap, environment change, style transfer — in one pass, no masking. Original motion, camera, and audio are preserved; only what the prompt names changes. Use when a clip is ~90% right (fix it, don\'t re-roll it) or to AI-edit the user\'s own footage. Engines: Kling O3 edit (default — subject/style refs via elements), omni-flash-edit (PROMPT-ONLY, no refs, the
|
|
4341
|
+
'Edit an EXISTING video clip with one instruction — character swap, environment change, style transfer — in one pass, no masking. Original motion, camera, and audio are preserved; only what the prompt names changes. Use when a clip is ~90% right (fix it, don\'t re-roll it) or to AI-edit the user\'s own footage. Engines: Kling O3 edit (default — subject/style refs via elements), omni-flash-edit (PROMPT-ONLY, no refs, priced level with Kling O3 Edit Standard; the first pick in the routing guide for footage-synced edits), or seedance-2.5-edit (the only engine that takes a clip over 15s; seedanceFace:true for AI-character faces). Clip-length and resolution windows per engine are on the `model` param. Cost is per second of OUTPUT (≈ clip length, rounded up), and an edit bills input + output seconds on Seedance and output seconds on Kling and Omni Flash — read the quote from the confirm gate or slates_estimate_generation_cost, never from memory. Subjects to swap IN go as characterAssetIds (frontal + angle images become Kling elements — Kling models only); style refs as styleAssetIds; max 4 combined. The edited clip saves as a NEW asset linked to its parent (chain edits freely). Routing: Kling edit is the default (element lock + audio intact); omni-flash-edit for cheap prompt-only footage-synced swaps; Seedance edit for style-transfer-heavy jobs — see slates-model-selection. Prompting: slates-prompting-kling-v3 §Edit / slates-prompting-omni-flash.',
|
|
4340
4342
|
input: z.object({
|
|
4341
4343
|
projectId: z.string().uuid().describe('Project the source clip lives in.'),
|
|
4342
4344
|
sourceVideoAssetId: z.string().describe('The VIDEO asset to edit — UUID or badge code ("VID-V3", bare "V3"); codes resolve against the project at call time. Kling: 3–15s clips; omni-flash-edit: 3–10s.'),
|
|
@@ -4353,7 +4355,7 @@ export const editVideo = {
|
|
|
4353
4355
|
styleAssetIds: z.array(z.string()).max(4).optional().describe('Style/appearance reference images (@ImageN). Max 4 combined with characterAssetIds. KLING MODELS ONLY — rejected on omni-flash-edit.'),
|
|
4354
4356
|
keepAudio: z.boolean().optional().describe('Preserve the original audio track (default true; Kling models only — omni-flash-edit and seedance-2.5-edit output carry their own audio).'),
|
|
4355
4357
|
videoResolution: zEnum(EDIT_VIDEO_RESOLUTIONS).optional().describe(`seedance-2.5-edit ONLY (default ${defaultVideoResolutionFor('seedance-2.5-edit')}). Ignored by the Kling and Omni Flash engines, whose output follows the source clip.`),
|
|
4356
|
-
seedanceFace: z.boolean().optional().describe('seedance-2.5-edit ONLY: set true when a CHARACTER FACE is visible in the clip. Faceless edits run on BytePlus; faces are blocked there and must route to the relaxed provider, which costs
|
|
4358
|
+
seedanceFace: z.boolean().optional().describe('seedance-2.5-edit ONLY: set true when a CHARACTER FACE is visible in the clip. Faceless edits run on BytePlus; faces are blocked there and must route to the relaxed provider, which costs about 40-50% more. A face edit submitted without this flag is rejected by the provider, not silently downgraded.'),
|
|
4357
4359
|
background: z.boolean().optional().describe(BACKGROUND_DESCRIBE),
|
|
4358
4360
|
confirm: z.boolean().optional().describe('Set true to bypass the cost confirm gate after the user OKs the spend.'),
|
|
4359
4361
|
folderId: folderIdField,
|
|
@@ -4503,7 +4505,7 @@ export const editVideo = {
|
|
|
4503
4505
|
// ── Trim a video to an exact window (fit-to-model primitive) ────
|
|
4504
4506
|
export const trimVideo = {
|
|
4505
4507
|
id: 'slates_trim_video',
|
|
4506
|
-
description: 'Trim a video asset to an exact [inSec, outSec] window and save the result as a NEW clip linked to the original (the original is untouched). This is the fit-to-model primitive: an 11s clip will not run on omni-flash-edit (3–10s)
|
|
4508
|
+
description: 'Trim a video asset to an exact [inSec, outSec] window and save the result as a NEW clip linked to the original (the original is untouched). This is the fit-to-model primitive: an 11s clip will not run on omni-flash-edit (3–10s), a 16s one not on Kling edit (3–15s), and a Seedance 2.0 video reference must be 2–15s (2.5 takes up to 30s combined) — trim it first, then edit the trimmed clip. EXACT re-encode (not a keyframe-snapped cut) so the result honors hard duration caps to the frame; any phone rotation flag is baked into the pixels in the same pass. The new clip lands with correct duration/width/height immediately. inSec defaults to 0. Pass pieces to cut the window into SEVERAL clips in one call, as the Trim & split dialog does (split points, or Auto-split by longest piece, with seconds shared between neighbours); every new clip comes back.',
|
|
4507
4509
|
input: z.object({
|
|
4508
4510
|
projectId: z.string().uuid().describe('Project the clip lives in.'),
|
|
4509
4511
|
assetId: z
|
|
@@ -4859,7 +4861,7 @@ export const exportVideo = {
|
|
|
4859
4861
|
};
|
|
4860
4862
|
export const exportTimelineXml = {
|
|
4861
4863
|
id: 'slates_export_timeline_xml',
|
|
4862
|
-
description: "Export the project's timeline as FCP7/XMEML XML — the file DaVinci Resolve imports directly (File → Import → Timeline) to recreate the edit with references to the original clip media on disk.
|
|
4864
|
+
description: "Export the project's timeline as FCP7/XMEML XML — the file DaVinci Resolve imports directly (File → Import → Timeline) to recreate the edit with references to the original clip media on disk. Use this for DaVinci Resolve or Premiere; current Final Cut Pro requires FCPXML, which this tool does not export. Pass an absolute outputPath ending in .xml; fails if the file exists unless overwrite=true.",
|
|
4863
4865
|
input: z
|
|
4864
4866
|
.object({
|
|
4865
4867
|
projectId: z.string().uuid().optional(),
|
|
@@ -6576,7 +6578,7 @@ export const editCut = {
|
|
|
6576
6578
|
};
|
|
6577
6579
|
export function resolveGuideTopic(topic) {
|
|
6578
6580
|
const t = topic.trim().toLowerCase();
|
|
6579
|
-
if (SKILLS
|
|
6581
|
+
if (Object.hasOwn(SKILLS, t))
|
|
6580
6582
|
return t;
|
|
6581
6583
|
if (t === 'slates-character-turnaround' || t === 'character-turnaround') {
|
|
6582
6584
|
return 'slates-character-identity';
|
|
@@ -6650,8 +6652,6 @@ export function resolveGuideTopic(topic) {
|
|
|
6650
6652
|
return 'slates-prompting-flux-2-max';
|
|
6651
6653
|
if (t.startsWith('seedream'))
|
|
6652
6654
|
return 'slates-prompting-seedream-5-lite';
|
|
6653
|
-
if (t.startsWith('veo'))
|
|
6654
|
-
return 'slates-prompting-veo-3';
|
|
6655
6655
|
if (t.startsWith('omni-flash') || t.startsWith('gemini-omni') || t === 'omni flash')
|
|
6656
6656
|
return 'slates-prompting-omni-flash';
|
|
6657
6657
|
// MiniMax H3 — both seats share one skill. Placed BEFORE the seed/seedance
|
|
@@ -6672,7 +6672,7 @@ export function resolveGuideTopic(topic) {
|
|
|
6672
6672
|
if (t.startsWith('kling-mc'))
|
|
6673
6673
|
return 'slates-prompting-motion-transfer';
|
|
6674
6674
|
if (t === 'edit-video' || t === 'video-edit' || t === 'edit video' || t === 'video edit')
|
|
6675
|
-
return '
|
|
6675
|
+
return resolveGuideTopic(defaultModelFor('video', 'edit'));
|
|
6676
6676
|
if (t.startsWith('kling-v3'))
|
|
6677
6677
|
return 'slates-prompting-kling-v3';
|
|
6678
6678
|
// Audio — the TTS seat FIRST, then seed-audio, then eleven-sfx.
|
|
@@ -6733,69 +6733,96 @@ export function resolveGuideTopic(topic) {
|
|
|
6733
6733
|
}
|
|
6734
6734
|
return null;
|
|
6735
6735
|
}
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6736
|
+
// Discovery is the first call of most briefs: a slow or unentitled member check
|
|
6737
|
+
// must not cost every call a round trip, or hang free craft behind a 30s read.
|
|
6738
|
+
const MEMBER_CATALOG_TTL_MS = 60_000;
|
|
6739
|
+
const MEMBER_CATALOG_TIMEOUT_MS = 5_000;
|
|
6740
|
+
const memberCatalogCache = new WeakMap();
|
|
6741
|
+
/** Private bodies stay on the entitled member feed, never in public packages. */
|
|
6742
|
+
function memberGuideCatalog(ctx) {
|
|
6743
|
+
const cached = memberCatalogCache.get(ctx.cloud);
|
|
6744
|
+
if (cached && Date.now() - cached.at < MEMBER_CATALOG_TTL_MS)
|
|
6745
|
+
return cached.value;
|
|
6746
|
+
const value = fetchMemberGuideCatalog(ctx);
|
|
6747
|
+
memberCatalogCache.set(ctx.cloud, { at: Date.now(), value });
|
|
6748
|
+
return value;
|
|
6749
|
+
}
|
|
6750
|
+
async function fetchMemberGuideCatalog(ctx) {
|
|
6751
|
+
try {
|
|
6752
|
+
// Request first: a missing token throws here, before any timer exists.
|
|
6753
|
+
const request = ctx.cloud().get('/members/manifest.json');
|
|
6754
|
+
let timer;
|
|
6755
|
+
const timeout = new Promise((_, reject) => { timer = setTimeout(() => reject(new Error('Member guide check timed out')), MEMBER_CATALOG_TIMEOUT_MS); });
|
|
6756
|
+
const manifest = await Promise.race([request, timeout]).finally(() => clearTimeout(timer));
|
|
6757
|
+
if (!Array.isArray(manifest?.skills))
|
|
6758
|
+
throw new Error('Invalid member guide manifest');
|
|
6759
|
+
// A malformed entry is skipped; it never hides the account's other playbooks.
|
|
6760
|
+
const entries = manifest.skills
|
|
6761
|
+
.filter(entry => entry.tier === 'paid' && typeof entry.name === 'string' && /^slates-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(entry.name) && entry.name.length <= 64 && typeof entry.description === 'string' && entry.description.length <= 1024)
|
|
6762
|
+
.map(entry => ({ name: entry.name, description: entry.description, tier: 'paid' }));
|
|
6763
|
+
return { entries, access: 'available' };
|
|
6764
|
+
}
|
|
6765
|
+
catch (error) {
|
|
6766
|
+
if (error.code === 'CLOUD_TOKEN_MISSING')
|
|
6767
|
+
return { entries: [], access: 'not connected; bundled guides available' };
|
|
6768
|
+
if (error instanceof SlatesCloudHttpError && error.status === 402)
|
|
6769
|
+
return { entries: [], access: 'no active skills entitlement; bundled guides available' };
|
|
6770
|
+
if (error instanceof SlatesCloudHttpError && error.status === 404)
|
|
6771
|
+
return { entries: [], access: 'member feed unavailable on this API version; bundled guides available' };
|
|
6772
|
+
// Keep free craft usable during a cloud outage, but expose the failed access check.
|
|
6773
|
+
return { entries: [], access: error instanceof SlatesCloudHttpError && error.status === 401 ? 'reconnect Slates to access member guides; bundled guides available' : 'member access check failed; retry for private guides; bundled guides available' };
|
|
6774
|
+
}
|
|
6756
6775
|
}
|
|
6757
6776
|
export const getPromptingGuide = {
|
|
6758
6777
|
id: 'slates_get_prompting_guide',
|
|
6759
|
-
description: '
|
|
6760
|
-
// 🚨 NO "ALWAYS READ THIS FIRST" SENTENCE. It stood here for months and was
|
|
6761
|
-
// MEASURED at 13% compliance before and after the enforcement work — pointer
|
|
6762
|
-
// prose is the shape that does not move the agent. What replaced it is
|
|
6763
|
-
// structural: the never-use list rides the generate ops' descriptions and
|
|
6764
|
-
// the craft card rides the estimate result, so the facts arrive whether or
|
|
6765
|
-
// not this op is ever called.
|
|
6766
|
-
"Return a bundled Slates prompting/workflow guide. MCP-only clients (Claude Desktop, Smithery) don't get the CLI-installed skill files — call this instead. Accepts a guide name or a model id ('veo-3.1-fast', 'kling-v3.0-pro', 'seedance-2', 'nano-banana-2'), which maps to the right guide. Reach for it when a card is not enough: the failure modes, the worked examples and the sources are only in the full text.",
|
|
6778
|
+
description: 'Find production craft from the user vision: pass query alone with the brief (for example "two friends talking in a rainy diner") for keyword-ranked guides with matching sections, followed by every other guide\'s description so you choose by judgment. Omit topic and query, or use topic "catalog", for the whole catalog. Then request a guide name, model id or style name with card/index/section/full depth. Cards are short; query with a topic selects one section or cinematic technique; full returns worked examples, failure modes and sources. Reuse current guidance already in context. Entitled member playbooks are fetched privately through the connected Slates account. For exact buttons and UI paths use topic "app-manual" with relevant question keywords; no query returns its surface map. Users supply the vision; you find the guides.',
|
|
6767
6779
|
input: z.object({
|
|
6768
|
-
query: z.string().max(
|
|
6769
|
-
topic: z
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
depth: z.enum(['card', 'index', 'section', 'full']).optional().describe('Default "card" is a short overview. "index" lists sections; query selects one section or technique; "full" explicitly returns the complete guide.'),
|
|
6780
|
+
query: z.string().min(1).max(1000).optional().describe('Without topic: creative brief or craft need to discover guides. With topic: section keywords, heading or technique ID.'),
|
|
6781
|
+
topic: z.string().min(1).optional().describe('Optional guide name, model id, style name, catalog, or app-manual. Omit to search by intent or browse.'),
|
|
6782
|
+
depth: z.enum(['card', 'index', 'section', 'full']).optional().describe('Default card is a short overview; index lists sections; query selects a section; full returns the complete guide.'),
|
|
6783
|
+
limit: z.number().int().min(1).max(20).optional().describe('Page size: a search returns 8 ranked matches by default, browsing the whole catalog. Does not change guide bodies.'),
|
|
6784
|
+
offset: z.number().int().min(0).optional().describe('Catalog/search offset from nextOffset in the preceding result.'),
|
|
6774
6785
|
}),
|
|
6775
|
-
async run(input) {
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
: appManualIndex();
|
|
6786
|
+
async run(input, ctx) {
|
|
6787
|
+
const topic = input.topic?.trim().toLowerCase();
|
|
6788
|
+
if (topic === 'app-manual') {
|
|
6789
|
+
const content = input.query || input.depth === 'full' ? appManualSections(input.query) : appManualIndex();
|
|
6780
6790
|
return { text: content, data: { topic: 'app-manual', bytes: Buffer.byteLength(content, 'utf8'), guide: content } };
|
|
6781
6791
|
}
|
|
6782
|
-
const resolved = resolveGuideTopic(
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6792
|
+
const resolved = topic ? resolveGuideTopic(topic) : null;
|
|
6793
|
+
if (resolved) {
|
|
6794
|
+
const depth = input.depth ?? 'card';
|
|
6795
|
+
const guide = retrieveGuide(resolved, SKILLS[resolved], depth, input.query);
|
|
6796
|
+
return { text: guide, data: { topic: resolved, tier: 'free', depth, bytes: Buffer.byteLength(guide, 'utf8'), guide } };
|
|
6797
|
+
}
|
|
6798
|
+
const member = await memberGuideCatalog(ctx);
|
|
6799
|
+
const privateEntry = member.entries.find(entry => entry.name === topic);
|
|
6800
|
+
if (privateEntry) {
|
|
6801
|
+
// The feed's 402/404 bodies are purchase pages; the agent needs the fact, not the page.
|
|
6802
|
+
const result = await ctx.cloud().get(`/members/skills/${encodeURIComponent(privateEntry.name)}.md?format=json`).catch((error) => {
|
|
6803
|
+
if (error instanceof SlatesCloudHttpError && error.status === 402)
|
|
6804
|
+
throw new SlatesCloudHttpError(`${privateEntry.name} needs an active skills entitlement; bundled guides remain available.`, 402);
|
|
6805
|
+
if (error instanceof SlatesCloudHttpError && error.status === 404)
|
|
6806
|
+
throw new SlatesCloudHttpError(`${privateEntry.name} is no longer in the member feed.`, 404);
|
|
6807
|
+
throw error;
|
|
6808
|
+
});
|
|
6809
|
+
if (typeof result?.markdown !== 'string')
|
|
6810
|
+
throw new Error('Invalid member guide body');
|
|
6811
|
+
const depth = input.depth ?? 'card';
|
|
6812
|
+
const guide = retrieveGuide(privateEntry.name, result.markdown, depth, input.query);
|
|
6813
|
+
return { text: guide, data: { topic: privateEntry.name, tier: 'paid', depth, bytes: Buffer.byteLength(guide, 'utf8'), guide } };
|
|
6786
6814
|
}
|
|
6787
|
-
const
|
|
6788
|
-
const
|
|
6789
|
-
|
|
6790
|
-
|
|
6815
|
+
const catalog = [...guideCatalog(SKILLS), ...member.entries].sort((a, b) => a.name.localeCompare(b.name));
|
|
6816
|
+
const result = discoverGuides(catalog, SKILLS, input.query ?? (topic && topic !== 'catalog' && topic !== 'index' ? input.topic : undefined), input.limit, input.offset);
|
|
6817
|
+
const guide = result.guides.map(entry => `${entry.name} (${entry.tier}): ${entry.description}${entry.sections.length ? `\n Matching sections: ${entry.sections.join('; ')}` : ''}`).join('\n\n');
|
|
6818
|
+
const rest = result.rest.length ? `\n\nEvery other guide (keyword hints above only see shared words; choose by the brief):\n${result.rest.map(entry => `- ${entry.name} (${entry.tier}): ${entry.description}`).join('\n')}` : '';
|
|
6819
|
+
const text = `${result.fallback ? 'No keyword match; choose relevant craft from the catalog.\n\n' : ''}${guide}${rest}\n\n${result.total} ${result.query && !result.fallback ? 'keyword matches' : 'guides'}; nextOffset: ${result.nextOffset ?? 'none'}. Member guides: ${member.access}. Retrieve a name with depth card/index or query for a section. The user does not need to choose guides.`;
|
|
6820
|
+
return { text, data: { ...result, memberAccess: member.access, bytes: Buffer.byteLength(text, 'utf8') } };
|
|
6791
6821
|
},
|
|
6792
6822
|
};
|
|
6793
6823
|
/**
|
|
6794
|
-
*
|
|
6795
|
-
*
|
|
6796
|
-
* 🚨 IT EXISTS BECAUSE THE SURFACE IS 112 KB AND EVERY TURN PAYS FOR ALL OF IT.
|
|
6797
|
-
* Both surfaces start with the shared core set. Search returns compact metadata;
|
|
6798
|
-
* names/group returns exact schemas and replaces the optional selection.
|
|
6824
|
+
* Task discovery and exact schemas share one operation. The desktop uses
|
|
6825
|
+
* names/group to replace its optional selection; MCP keeps its full list fixed.
|
|
6799
6826
|
*/
|
|
6800
6827
|
export const loadTools = {
|
|
6801
6828
|
id: 'slates_load_tools',
|
|
@@ -6810,9 +6837,7 @@ export const loadTools = {
|
|
|
6810
6837
|
}).refine((v) => [v.group, v.query, v.names].filter(Boolean).length === 1, 'Pass exactly one of query, names, or group.'),
|
|
6811
6838
|
async run(input) {
|
|
6812
6839
|
if (input.query) {
|
|
6813
|
-
const
|
|
6814
|
-
const ranked = ALL_OPERATIONS.map((op) => ({ op, score: words.reduce((n, w) => n + (op.id.includes(w) ? 4 : op.description.toLowerCase().includes(w) ? 1 : 0), 0) }))
|
|
6815
|
-
.filter((x) => x.score > 0).sort((a, b) => b.score - a.score).slice(0, 10);
|
|
6840
|
+
const ranked = searchTools(ALL_OPERATIONS, input.query);
|
|
6816
6841
|
const matches = ranked.map(({ op }) => ({ name: op.id, description: op.description.split(/(?<=\.)\s/)[0], billable: !!op.billable, annotations: op.annotations }));
|
|
6817
6842
|
return ok({ matches }, matches.map((o) => `${o.name}: ${o.description}`).join('\n') || 'No matching tools. Try another task description.');
|
|
6818
6843
|
}
|