@slatesvideo/shared 0.5.1 → 0.5.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/index.d.ts CHANGED
@@ -5,4 +5,5 @@ export { SKILLS } from './skills/content.js';
5
5
  export * as operations from './operations/index.js';
6
6
  export { ALL_OPERATIONS, VIDEO_MODELS, defaultContext, type Operation, type OperationContext, type OperationResult } from './operations/index.js';
7
7
  export { MODEL_FACTS, getModelFact, type ModelFact } from './prompts/model-facts.js';
8
+ export { PROMPTING_TIPS, getPromptingTips, type PromptingTipsEntry, type PromptingTipCard, type PromptingTipsKey } from './prompts/prompting-tips.js';
8
9
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -8,4 +8,7 @@ export { ALL_OPERATIONS, VIDEO_MODELS, defaultContext } from './operations/index
8
8
  // prompt derives its MODEL ROUTING doctrine from (kind: image vs video,
9
9
  // default/premium/niche notes). Edit model-facts.ts, never prose copies.
10
10
  export { MODEL_FACTS, getModelFact } from './prompts/model-facts.js';
11
+ // Per-model prompting tips — the SSOT for the desktop "See prompting tips"
12
+ // modals. The desktop renders these; it never hand-writes tips content.
13
+ export { PROMPTING_TIPS, getPromptingTips } from './prompts/prompting-tips.js';
11
14
  //# sourceMappingURL=index.js.map
@@ -67,6 +67,7 @@ export declare const uploadReferenceImage: Operation<{
67
67
  projectId: string;
68
68
  filePath?: string;
69
69
  dataUrl?: string;
70
+ type?: 'image' | 'video';
70
71
  }>;
71
72
  export declare const listFolders: Operation<{
72
73
  projectId: string;
@@ -169,7 +170,7 @@ export declare const editImage: Operation<{
169
170
  confirm?: boolean;
170
171
  background?: boolean;
171
172
  }>;
172
- export declare const VIDEO_MODELS: readonly ["kling-v3.0-std", "kling-v3.0-pro", "kling-v3.0-omni", "veo-3.1-fast", "veo-3.1-standard", "seedance-2"];
173
+ export declare const VIDEO_MODELS: readonly ["kling-v3.0-std", "kling-v3.0-pro", "kling-v3.0-omni", "veo-3.1-fast", "veo-3.1-standard", "seedance-2", "omni-flash"];
173
174
  type VideoModel = (typeof VIDEO_MODELS)[number];
174
175
  export declare function videoCostKey(input: {
175
176
  model: VideoModel;
@@ -184,6 +185,7 @@ export declare function videoCostKey(input: {
184
185
  videoRefSeconds?: number;
185
186
  }): string;
186
187
  export declare function klingEditCostKey(model: 'kling-v3.0-omni-edit' | 'kling-v3.0-omni-pro-edit', duration: number): string;
188
+ export declare function omniFlashEditCostKey(duration: number): string;
187
189
  export declare const generateVideo: Operation<{
188
190
  prompt: string;
189
191
  model: string;
@@ -255,13 +257,19 @@ export declare const editVideo: Operation<{
255
257
  projectId: string;
256
258
  sourceVideoAssetId: string;
257
259
  prompt: string;
258
- model?: 'kling-v3.0-omni-edit' | 'kling-v3.0-omni-pro-edit';
260
+ model?: 'kling-v3.0-omni-edit' | 'kling-v3.0-omni-pro-edit' | 'omni-flash-edit';
259
261
  characterAssetIds?: string[];
260
262
  styleAssetIds?: string[];
261
263
  keepAudio?: boolean;
262
264
  background?: boolean;
263
265
  confirm?: boolean;
264
266
  }>;
267
+ export declare const trimVideo: Operation<{
268
+ projectId: string;
269
+ assetId: string;
270
+ inSec?: number;
271
+ outSec: number;
272
+ }>;
265
273
  export declare const getGenerationStatus: Operation<{
266
274
  generationId: string;
267
275
  waitSeconds?: number;
@@ -289,6 +297,30 @@ export declare const reorderClips: Operation<{
289
297
  export declare const removeClip: Operation<{
290
298
  clipId: string;
291
299
  }>;
300
+ export declare const addTimelineTrack: Operation<{
301
+ projectId: string;
302
+ type?: 'video' | 'audio';
303
+ name?: string;
304
+ }>;
305
+ export declare const updateTimelineTrack: Operation<{
306
+ projectId: string;
307
+ trackId: string;
308
+ name?: string;
309
+ muted?: boolean;
310
+ locked?: boolean;
311
+ volume?: number;
312
+ }>;
313
+ export declare const removeTimelineTrack: Operation<{
314
+ projectId: string;
315
+ trackId: string;
316
+ }>;
317
+ export declare const updateTimelineSettings: Operation<{
318
+ projectId: string;
319
+ width?: number;
320
+ height?: number;
321
+ frameRate?: 24 | 30 | 60;
322
+ masterVolume?: number;
323
+ }>;
292
324
  export declare const exportVideo: Operation<{
293
325
  projectId?: string;
294
326
  timelineId?: string;
@@ -439,12 +439,16 @@ export const getAssetVideoFrames = {
439
439
  };
440
440
  export const uploadReferenceImage = {
441
441
  id: 'slates_upload_reference_image',
442
- description: 'Add a reference image to a Slates project. Pass either filePath (absolute path to a local file) or dataUrl (base64 data: URL) — exactly one.',
442
+ description: 'Add a reference image OR video clip to a Slates project. Pass either filePath (absolute path to a local file) or dataUrl (base64 data: URL) — exactly one. Set type:"video" on a filePath import to bring in a clip (the user\'s own footage to edit/relocate/trim); imported videos are probed on ingest, so duration + dimensions are available immediately. Default type is "image". dataUrl is image-only.',
443
443
  input: z
444
444
  .object({
445
445
  projectId: z.string().uuid(),
446
446
  filePath: z.string().optional(),
447
447
  dataUrl: z.string().optional(),
448
+ type: z
449
+ .enum(['image', 'video'])
450
+ .optional()
451
+ .describe('Asset kind for a filePath import — "image" (default) or "video". A dataUrl is always an image.'),
448
452
  })
449
453
  .refine((d) => !!d.filePath !== !!d.dataUrl, {
450
454
  message: 'Pass exactly one of filePath or dataUrl',
@@ -455,10 +459,13 @@ export const uploadReferenceImage = {
455
459
  const r = await desktop.post('/agent/assets/upload', {
456
460
  projectId: input.projectId,
457
461
  filePath: input.filePath,
458
- type: 'image',
462
+ type: input.type ?? 'image',
459
463
  });
460
464
  return ok(r);
461
465
  }
466
+ if (input.type === 'video') {
467
+ throw new Error('dataUrl uploads are image-only — pass a filePath to import a video clip.');
468
+ }
462
469
  const r = await desktop.post('/agent/assets/upload-base64', {
463
470
  projectId: input.projectId,
464
471
  dataUrl: input.dataUrl,
@@ -1204,6 +1211,7 @@ export const VIDEO_MODELS = [
1204
1211
  'veo-3.1-fast',
1205
1212
  'veo-3.1-standard',
1206
1213
  'seedance-2',
1214
+ 'omni-flash',
1207
1215
  ];
1208
1216
  // Model → registry cost-key. Each provider's keys ship with their own
1209
1217
  // shape (verified against /api/agent/models):
@@ -1264,6 +1272,11 @@ export function videoCostKey(input) {
1264
1272
  }
1265
1273
  return `${tier}-${input.duration}s${input.sound === true ? '-audio' : ''}`;
1266
1274
  }
1275
+ if (input.model === 'omni-flash') {
1276
+ // Mirrors omniFlashCreditKey() in slate/src/shared/pricing.ts — flat 720p
1277
+ // rate, audio native + included, no resolution/audio key dimension.
1278
+ return `omni-flash-${input.duration}s`;
1279
+ }
1267
1280
  throw new Error(`Unknown video model: ${input.model}`);
1268
1281
  }
1269
1282
  // Kling O3 video-to-video edit cost key — mirrors klingEditCreditKey() in
@@ -1274,6 +1287,12 @@ export function klingEditCostKey(model, duration) {
1274
1287
  const tier = model === 'kling-v3.0-omni-pro-edit' ? 'kling-v3-omni-pro-edit' : 'kling-v3-omni-edit';
1275
1288
  return `${tier}-${duration}s`;
1276
1289
  }
1290
+ // Omni Flash video-edit cost key — mirrors omniFlashCreditKey() in
1291
+ // slate/src/shared/pricing.ts (must byte-match; checked by the slates-api
1292
+ // pricing-consistency script). Duration is the CEILED source-clip length.
1293
+ export function omniFlashEditCostKey(duration) {
1294
+ return `omni-flash-edit-${duration}s`;
1295
+ }
1277
1296
  /**
1278
1297
  * Forgiving model-id resolver. Agents routinely paste registry COST keys
1279
1298
  * ("kling-v3-standard-8s", "seedance-2-1080p-8s") into the `model` param —
@@ -1321,6 +1340,9 @@ function resolveVideoModel(raw) {
1321
1340
  seedance: 'seedance-2',
1322
1341
  'veo-3.1': 'veo-3.1-fast',
1323
1342
  'veo-3': 'veo-3.1-fast',
1343
+ 'gemini-omni-flash': 'omni-flash',
1344
+ 'gemini-omni-flash-preview': 'omni-flash',
1345
+ 'omni-flash-preview': 'omni-flash',
1324
1346
  };
1325
1347
  if (aliases[s]) {
1326
1348
  out.model = aliases[s];
@@ -1339,6 +1361,8 @@ function promptingSkillFor(model) {
1339
1361
  return 'slates-prompting-veo-3';
1340
1362
  if (model.startsWith('seedance'))
1341
1363
  return 'slates-prompting-seedance';
1364
+ if (model.startsWith('omni-flash'))
1365
+ return 'slates-prompting-omni-flash';
1342
1366
  return 'slates-cost-discipline';
1343
1367
  }
1344
1368
  export const generateVideo = {
@@ -1346,14 +1370,14 @@ export const generateVideo = {
1346
1370
  description: 'Generate video via Slates credits. REQUIRED before calling: read slates-model-selection (the routing doctrine), slates-cost-discipline, and the matching per-model prompting skill (slates-prompting-seedance / slates-prompting-kling-v3 / slates-prompting-veo-3) — video models prompt very differently; load them via slates_get_prompting_guide if no skill files are installed. 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). Cost > $0.50 returns requires_confirm — pass confirm=true after explicit user OK. Image-to-video via firstFrameAssetId; first+last frames = Veo/Seedance only; ingredients via ingredientAssetIds (Kling Omni / Seedance). Asset params take UUIDs or badge codes ("IMG-A8").',
1347
1371
  input: z.object({
1348
1372
  prompt: z.string().min(1).max(4000),
1349
- model: z.string().describe('One of: kling-v3.0-std | kling-v3.0-pro | kling-v3.0-omni | seedance-2 | veo-3.1-fast | veo-3.1-standard. Pass the BASE id — duration and videoResolution are separate params (registry cost keys like "kling-v3-standard-8s" auto-resolve). Route per the slates-model-selection skill: Kling std = general-purpose DEFAULT, Seedance 2 = premium physics/effects/hero tier, Veo = native-synced-audio niche only (16:9, 4/6/8s) — never the default. All are VIDEO-only. For per-call cost, call slates_estimate_generation_cost — never quote prices from memory.'),
1373
+ model: z.string().describe('One of: kling-v3.0-std | kling-v3.0-pro | kling-v3.0-omni | seedance-2 | veo-3.1-fast | veo-3.1-standard | omni-flash. Pass the BASE id — duration and videoResolution are separate params (registry cost keys like "kling-v3-standard-8s" auto-resolve). Route per the slates-model-selection skill: Kling std = general-purpose DEFAULT, Seedance 2 = premium physics/effects/hero tier, Veo = native-synced-audio niche only (16:9, 4/6/8s) — never the default, omni-flash = cheap 720p tier with audio included (3-10s, 16:9/9:16; t2v, single-start-frame i2v, or up to 7 reference images; no last frame / video / audio refs). All are VIDEO-only. For per-call cost, call slates_estimate_generation_cost — never quote prices from memory.'),
1350
1374
  projectId: z.string().uuid().optional().describe('Save into this Slates project. Strongly recommended — the desktop UI shows a progress card live and the asset appears when complete.'),
1351
1375
  aspectRatio: z.enum(['1:1', '16:9', '9:16', '4:3', '3:4', '21:9', '9:21', '4:5', '5:4', '2:3', '3:2']).optional().describe('Veo locks to 16:9 — passing anything else will be ignored or fail. Kling/Seedance support all.'),
1352
- duration: z.number().int().min(4).max(15).optional().describe('Seconds. Kling: 5-15. Veo: 4, 6, or 8 only (4K only at 8s). Seedance: 4-15. Default 5 if omitted but always be explicit (cost scales linearly).'),
1376
+ duration: z.number().int().min(3).max(15).optional().describe('Seconds. Kling: 5-15. Veo: 4, 6, or 8 only (4K only at 8s). Seedance: 4-15. Omni Flash: 3-10. Default 5 if omitted but always be explicit (cost scales linearly).'),
1353
1377
  videoResolution: z.enum(['480p', '720p', '1080p', '4k']).optional().describe('Veo + Seedance. Seedance: 480p/720p/1080p/4K (default 1080p; 4K is native, the most expensive). Veo: 720p/1080p same price, 4K more (8s only).'),
1354
1378
  firstFrameAssetId: z.string().optional().describe('Starting frame for image-to-video: asset UUID or badge code ("IMG-A8") — codes resolve against the project at call time, so a code the user just spoke is always safe to pass.'),
1355
1379
  lastFrameAssetId: z.string().optional().describe('Ending frame (UUID or badge code). Veo and Seedance only. Pairs with firstFrameAssetId for guided transitions.'),
1356
- ingredientAssetIds: z.array(z.string()).max(9).optional().describe('Visual reference / ingredient assets (UUIDs or badge codes) for Kling Omni or Seedance. Up to 9 (Seedance) or 4 (Kling).'),
1380
+ ingredientAssetIds: z.array(z.string()).max(9).optional().describe('Visual reference / ingredient assets (UUIDs or badge codes) for Kling Omni, Seedance, or Omni Flash. Up to 9 (Seedance), 4 (Kling), or 7 (Omni Flash, combined across all ref params).'),
1357
1381
  characterAssetIds: z.array(z.string()).optional().describe('Character sheet assets (UUIDs or badge codes) — keeps a character consistent across the shot.'),
1358
1382
  environmentAssetIds: z.array(z.string()).optional().describe('Environment grid assets (UUIDs or badge codes) — keeps a location/setting consistent across the shot.'),
1359
1383
  styleAssetIds: z.array(z.string()).optional().describe('Style reference assets (UUIDs or badge codes) — locks the visual style of the shot.'),
@@ -1412,10 +1436,41 @@ export const generateVideo = {
1412
1436
  missing,
1413
1437
  message: `Missing required field(s): ${missing.join(', ')}. ` +
1414
1438
  `Read the slates-cost-discipline + ${promptingSkillFor(input.model)} skills, ` +
1415
- `or ask the user. Veo locks to 16:9. Kling/Seedance support 1:1 16:9 9:16 4:3 3:4 21:9. ` +
1416
- `Duration: Kling 5-15s, Veo 4/6/8s (4K only at 8s), Seedance 4-15s. Cost scales linearly with duration.`,
1439
+ `or ask the user. Veo locks to 16:9. Kling/Seedance support 1:1 16:9 9:16 4:3 3:4 21:9. Omni Flash: 16:9/9:16 only. ` +
1440
+ `Duration: Kling 5-15s, Veo 4/6/8s (4K only at 8s), Seedance 4-15s, Omni Flash 3-10s. Cost scales linearly with duration.`,
1417
1441
  });
1418
1442
  }
1443
+ // Omni Flash: 3-10s, 720p only — t2v, single-start-frame i2v, or ref2v
1444
+ // with up to 7 reference IMAGES. No last frame, no video/audio refs.
1445
+ // Validate up front so the agent gets an actionable message instead of
1446
+ // a registry throw.
1447
+ if (input.model === 'omni-flash') {
1448
+ if (input.duration < 3 || input.duration > 10) {
1449
+ return ok({
1450
+ requires_clarification: true,
1451
+ missing: ['duration'],
1452
+ message: `Omni Flash supports 3-10 seconds (you passed ${input.duration}s). Pick a duration in that range.`,
1453
+ });
1454
+ }
1455
+ if (input.lastFrameAssetId || input.videoReferenceAssetId || input.audioReferenceAssetId) {
1456
+ return ok({
1457
+ requires_clarification: true,
1458
+ missing: [],
1459
+ message: 'Omni Flash takes a prompt, an optional start frame, and up to 7 reference IMAGES — last frames and video/audio references are not supported. Drop those, or switch to seedance-2 (video/audio refs, last frame) or veo (last frame).',
1460
+ });
1461
+ }
1462
+ const refCount = (input.ingredientAssetIds?.length ?? 0) +
1463
+ (input.characterAssetIds?.length ?? 0) +
1464
+ (input.environmentAssetIds?.length ?? 0) +
1465
+ (input.styleAssetIds?.length ?? 0);
1466
+ if (refCount > 7) {
1467
+ return ok({
1468
+ requires_clarification: true,
1469
+ missing: [],
1470
+ message: `Omni Flash takes at most 7 reference images combined (you passed ${refCount}). Trim the list.`,
1471
+ });
1472
+ }
1473
+ }
1419
1474
  // Veo exists only at discrete durations 4/6/8s, and 4K only at 8s.
1420
1475
  // Validate up front so the agent gets an actionable message instead of
1421
1476
  // a generic "Model variant not in registry" throw from the cost lookup.
@@ -1503,7 +1558,7 @@ export const generateVideo = {
1503
1558
  const entry = registry.models.find((m) => m.model === costKey);
1504
1559
  if (!entry) {
1505
1560
  throw new Error(`Model variant not in registry: ${costKey}. ` +
1506
- `Available video models: ${registry.models.filter((m) => m.model.startsWith('kling') || m.model.startsWith('veo') || m.model.startsWith('seedance')).map((m) => m.model).slice(0, 20).join(', ')}`);
1561
+ `Available video models: ${registry.models.filter((m) => m.model.startsWith('kling') || m.model.startsWith('veo') || m.model.startsWith('seedance') || m.model.startsWith('omni-flash')).map((m) => m.model).slice(0, 20).join(', ')}`);
1507
1562
  }
1508
1563
  const totalCents = creditCost(entry);
1509
1564
  // Pre-flight confirm gate. Fires when:
@@ -1955,15 +2010,15 @@ export const generateMotionTransfer = {
1955
2010
  // ── Edit video (Kling O3 video-to-video) ────────────────────────
1956
2011
  export const editVideo = {
1957
2012
  id: 'slates_edit_video',
1958
- description: 'Edit an EXISTING video clip with one instruction via Kling O3 video-to-video edit — 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. Source clip constraints: 3–15s, 720–3840px, MP4/MOV. Cost = per second of OUTPUT (≈ clip length, rounded UP to the next second): kling-v3.0-omni-edit ≈ 19¢/s, kling-v3.0-omni-pro-edit ≈ 25¢/s. Subjects to swap IN go as characterAssetIds (frontal + angle images become Kling elements); 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 edit tool (element lock + audio intact); prefer Seedance edit/relocate only for style-transfer-heavy jobs — see slates-model-selection. Prompting: slates-prompting-kling-v3 §Edit.',
2013
+ description: '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; 3–15s clips, 720–3840px, subject/style refs via elements) or omni-flash-edit (Gemini Omni Flash; 3–10s clips, 720p output, PROMPT-ONLY — no refs, cheapest seat). Cost = per second of OUTPUT (≈ clip length, rounded UP to the next second): omni-flash-edit ≈ 19¢/s ≈ kling-v3.0-omni-edit ≈ 19¢/s, kling-v3.0-omni-pro-edit ≈ 25¢/s. 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 edit tool (element lock + audio intact); omni-flash-edit for cheap prompt-only footage-synced swaps; prefer Seedance edit/relocate only for style-transfer-heavy jobs — see slates-model-selection. Prompting: slates-prompting-kling-v3 §Edit / slates-prompting-omni-flash.',
1959
2014
  input: z.object({
1960
2015
  projectId: z.string().uuid().describe('Project the source clip lives in.'),
1961
- 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. 3–15s clips only.'),
1962
- prompt: z.string().min(1).max(2500).describe('The change, not the whole scene — e.g. "replace the man with @marcus", "make it a rainy night", "turn the street into a neon Tokyo alley". Mention subjects with @name; the transport compiles them to Kling\'s @ElementN notation.'),
1963
- model: z.enum(['kling-v3.0-omni-edit', 'kling-v3.0-omni-pro-edit']).optional().describe('Default kling-v3.0-omni-edit. Pro (~25¢/s vs ~19¢/s) only for hero shots where fidelity matters.'),
1964
- characterAssetIds: z.array(z.string()).max(4).optional().describe('Subject/element image assets to swap IN (UUIDs or badge codes). Each becomes a Kling element (@ElementN).'),
1965
- styleAssetIds: z.array(z.string()).max(4).optional().describe('Style/appearance reference images (@ImageN). Max 4 combined with characterAssetIds.'),
1966
- keepAudio: z.boolean().optional().describe('Preserve the original audio track (default true).'),
2016
+ 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.'),
2017
+ prompt: z.string().min(1).max(2500).describe('The change, not the whole scene — e.g. "replace the man with @marcus", "make it a rainy night", "turn the street into a neon Tokyo alley". Mention subjects with @name; the transport compiles them to Kling\'s @ElementN notation (Kling models). For omni-flash-edit keep it simple and add "Keep everything else the same."'),
2018
+ model: z.enum(['kling-v3.0-omni-edit', 'kling-v3.0-omni-pro-edit', 'omni-flash-edit']).optional().describe('Default kling-v3.0-omni-edit. Pro (~25¢/s vs ~19¢/s) only for hero shots where fidelity matters. omni-flash-edit (~19¢/s, 720p, 3–10s) for prompt-only edits — it takes NO character/style refs.'),
2019
+ characterAssetIds: z.array(z.string()).max(4).optional().describe('Subject/element image assets to swap IN (UUIDs or badge codes). Each becomes a Kling element (@ElementN). KLING MODELS ONLY — rejected on omni-flash-edit.'),
2020
+ 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.'),
2021
+ keepAudio: z.boolean().optional().describe('Preserve the original audio track (default true; Kling models only — omni-flash-edit output carries its own audio).'),
1967
2022
  background: z.boolean().optional().describe(BACKGROUND_DESCRIBE),
1968
2023
  confirm: z.boolean().optional().describe('Set true to bypass the cost confirm gate after the user OKs the spend.'),
1969
2024
  }),
@@ -1974,6 +2029,12 @@ export const editVideo = {
1974
2029
  await desktop.requireCapability('background-generation', 'background generation');
1975
2030
  }
1976
2031
  const model = input.model ?? 'kling-v3.0-omni-edit';
2032
+ const isOmniFlashEdit = model === 'omni-flash-edit';
2033
+ // Kling edit: 3–15s source clips; Omni Flash edit: 3–10s.
2034
+ const maxClipSeconds = isOmniFlashEdit ? 10 : 15;
2035
+ if (isOmniFlashEdit && ((input.characterAssetIds?.length ?? 0) > 0 || (input.styleAssetIds?.length ?? 0) > 0)) {
2036
+ throw new Error('omni-flash-edit is prompt-only — it takes no character/style reference images. Drop the refs, or switch to kling-v3.0-omni-edit which supports elements.');
2037
+ }
1977
2038
  // Resolve refs (UUIDs or badge codes) against the project AT CALL TIME.
1978
2039
  const refInputs = [
1979
2040
  { ref: input.sourceVideoAssetId, role: 'source clip' },
@@ -2001,11 +2062,12 @@ export const editVideo = {
2001
2062
  if (!Number.isFinite(clipSeconds) || clipSeconds <= 0) {
2002
2063
  throw new Error('Source clip has no recorded duration — cannot quote the edit. Re-import the clip or pick another.');
2003
2064
  }
2004
- if (clipSeconds > 15.05 || clipSeconds < 2.95) {
2005
- throw new Error(`Source clip is ${clipSeconds.toFixed(1)}s — Kling O3 edit accepts 3–15s. Trim it first (agents can pre-trim on the timeline).`);
2065
+ if (clipSeconds > maxClipSeconds + 0.05 || clipSeconds < 2.95) {
2066
+ throw new Error(`Source clip is ${clipSeconds.toFixed(1)}s — ${model} accepts 3–${maxClipSeconds}s. ` +
2067
+ `Trim it first with slates_trim_video (e.g. inSec 0, outSec ${maxClipSeconds}), then edit the trimmed clip.`);
2006
2068
  }
2007
- const billedSeconds = Math.min(15, Math.max(3, Math.ceil(clipSeconds - 0.05)));
2008
- const costKey = klingEditCostKey(model, billedSeconds);
2069
+ const billedSeconds = Math.min(maxClipSeconds, Math.max(3, Math.ceil(clipSeconds - 0.05)));
2070
+ const costKey = isOmniFlashEdit ? omniFlashEditCostKey(billedSeconds) : klingEditCostKey(model, billedSeconds);
2009
2071
  const cloud = ctx.cloud();
2010
2072
  const registry = await cloud.get('/api/agent/models');
2011
2073
  const entry = registry.models.find((m) => m.model === costKey);
@@ -2078,6 +2140,41 @@ export const editVideo = {
2078
2140
  };
2079
2141
  },
2080
2142
  };
2143
+ // ── Trim a video to an exact window (fit-to-model primitive) ────
2144
+ export const trimVideo = {
2145
+ id: 'slates_trim_video',
2146
+ 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) or Kling edit (3–15s), and a Seedance video reference must be 2–15s — trim it first, then edit/relocate 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.',
2147
+ input: z.object({
2148
+ projectId: z.string().uuid().describe('Project the clip lives in.'),
2149
+ assetId: z
2150
+ .string()
2151
+ .describe('The VIDEO asset to trim — UUID or badge code ("VID-V3", bare "V3"); resolves against the project at call time.'),
2152
+ inSec: z.number().min(0).optional().describe('Trim start in seconds (default 0).'),
2153
+ outSec: z.number().positive().describe('Trim end in seconds. Must be greater than inSec.'),
2154
+ }),
2155
+ async run(input, ctx) {
2156
+ const desktop = ctx.desktop();
2157
+ const resolved = await resolveAssetRefs(ctx, input.projectId, [input.assetId]);
2158
+ const assetId = resolved.get(input.assetId)?.id ?? input.assetId;
2159
+ const inSec = input.inSec ?? 0;
2160
+ if (input.outSec - inSec < 0.05) {
2161
+ throw new Error('outSec must be at least ~0.1s after inSec.');
2162
+ }
2163
+ const r = await desktop.post('/agent/assets/trim-video', {
2164
+ assetId,
2165
+ inSec,
2166
+ outSec: input.outSec,
2167
+ });
2168
+ const a = r.asset;
2169
+ const name = a?.code ?? a?.id ?? 'new clip';
2170
+ return {
2171
+ text: `Trimmed clip saved as ${name}${a?.label ? ` — ${a.label}` : ''} ` +
2172
+ `(${(input.outSec - inSec).toFixed(1)}s, ${inSec.toFixed(1)}–${input.outSec.toFixed(1)}s). ` +
2173
+ `Edit or generate from it by its new id/code.`,
2174
+ data: { asset: r.asset },
2175
+ };
2176
+ },
2177
+ };
2081
2178
  // ── Generation status (background mode) ─────────────────────────
2082
2179
  export const getGenerationStatus = {
2083
2180
  id: 'slates_get_generation_status',
@@ -2163,11 +2260,11 @@ export const getTimeline = {
2163
2260
  };
2164
2261
  export const addClipToTimeline = {
2165
2262
  id: 'slates_add_clip_to_timeline',
2166
- description: "Append a video asset from the project to the project's timeline (or place it at an explicit startFrame). Defaults match the desktop UI: clip goes to the end of the first video track, full source duration, and an empty timeline auto-adopts the clip's resolution and frame rate. Optionally trim with sourceInFrame/sourceOutFrame (frames at the SOURCE fps). Use slates_get_timeline first to see current clips and pick positions. Only video assets are accepted.",
2263
+ description: "Append a video or audio asset from the project to the project's timeline (or place it at an explicit startFrame). Defaults match the desktop UI: video clips go to the end of the first video track; audio clips (music, voiceover, AI audio) go after the last clip on the first AUDIO track and are mixed under the video on export. An empty timeline auto-adopts the first video clip's resolution and frame rate; later higher-resolution clips raise the canvas. Overlapping video clips resolve top-track-wins. Optionally trim with sourceInFrame/sourceOutFrame (frames at the SOURCE fps). Use slates_get_timeline first to see current clips and pick positions.",
2167
2264
  input: z.object({
2168
2265
  projectId: z.string().uuid(),
2169
- assetId: z.string().uuid().describe('Video asset already in the project.'),
2170
- trackId: z.string().uuid().optional().describe('Target track. Default: the first video track.'),
2266
+ assetId: z.string().uuid().describe('Video or audio asset already in the project.'),
2267
+ trackId: z.string().uuid().optional().describe('Target track (type must match the asset: video asset → video track, audio asset → audio track). Default: the first track of the matching type.'),
2171
2268
  startFrame: z.number().int().min(0).optional().describe('Timeline frame to place the clip at. Default: append after the last clip.'),
2172
2269
  sourceInFrame: z.number().int().min(0).optional(),
2173
2270
  sourceOutFrame: z.number().int().min(1).optional(),
@@ -2218,6 +2315,66 @@ export const removeClip = {
2218
2315
  return ok(await desktop.post('/agent/timeline/remove-clip', input));
2219
2316
  },
2220
2317
  };
2318
+ export const addTimelineTrack = {
2319
+ id: 'slates_add_timeline_track',
2320
+ description: "Add a track to the project's timeline (default: an audio track, for layering voiceover + music + AI audio). The new track is appended below existing tracks. Returns the new track and the full timeline.",
2321
+ input: z.object({
2322
+ projectId: z.string().uuid(),
2323
+ type: z.enum(['video', 'audio']).optional().describe('Default: audio.'),
2324
+ name: z.string().optional().describe("Default: 'Audio N' / 'Video N'."),
2325
+ }),
2326
+ async run(input, ctx) {
2327
+ const desktop = ctx.desktop();
2328
+ await desktop.requireCapability('timeline-tracks', 'timeline tracks + audio mixing');
2329
+ return ok(await desktop.post('/agent/timeline/add-track', input));
2330
+ },
2331
+ };
2332
+ export const updateTimelineTrack = {
2333
+ id: 'slates_update_timeline_track',
2334
+ description: 'Update a timeline track: rename, mute/unmute, lock/unlock, or set its volume fader (linear gain, -∞ to +12 dB). Track volume applies to both preview and MP4 export — audio-track clips are mixed at this gain; a muted video track still shows video but its embedded audio is silenced.',
2335
+ input: z.object({
2336
+ projectId: z.string().uuid(),
2337
+ trackId: z.string().uuid(),
2338
+ name: z.string().optional(),
2339
+ muted: z.boolean().optional(),
2340
+ locked: z.boolean().optional(),
2341
+ volume: z.number().min(0).max(4).optional().describe('Track fader as LINEAR gain: 0 = -∞ (silent), 1 = 0 dB (unity), ~3.98 = +12 dB (max boost).'),
2342
+ }),
2343
+ async run(input, ctx) {
2344
+ const desktop = ctx.desktop();
2345
+ await desktop.requireCapability('timeline-tracks', 'timeline tracks + audio mixing');
2346
+ return ok(await desktop.post('/agent/timeline/update-track', input));
2347
+ },
2348
+ };
2349
+ export const removeTimelineTrack = {
2350
+ id: 'slates_remove_timeline_track',
2351
+ description: 'Remove an EMPTY timeline track (fails if it still has clips, or if it is the last track of its type).',
2352
+ input: z.object({
2353
+ projectId: z.string().uuid(),
2354
+ trackId: z.string().uuid(),
2355
+ }),
2356
+ async run(input, ctx) {
2357
+ const desktop = ctx.desktop();
2358
+ await desktop.requireCapability('timeline-tracks', 'timeline tracks + audio mixing');
2359
+ return ok(await desktop.post('/agent/timeline/remove-track', input));
2360
+ },
2361
+ };
2362
+ export const updateTimelineSettings = {
2363
+ id: 'slates_update_timeline_settings',
2364
+ description: "Update the project timeline's output settings: resolution, frame rate (24/30/60 — all clips are conformed to it on export), and masterVolume, the output fader (linear gain, -∞ to +12 dB) applied to the final mix in both preview and MP4 export (use it to prevent clipping when stacking loud tracks). Note these are normally auto-managed: the first video clip sets fps + resolution, and higher-res clips raise the canvas. Changing frameRate after clips are placed retimes them — avoid unless the timeline is empty.",
2365
+ input: z.object({
2366
+ projectId: z.string().uuid(),
2367
+ width: z.number().int().min(16).optional(),
2368
+ height: z.number().int().min(16).optional(),
2369
+ frameRate: z.union([z.literal(24), z.literal(30), z.literal(60)]).optional(),
2370
+ masterVolume: z.number().min(0).max(4).optional().describe('Output fader as LINEAR gain: 0 = -∞ (silent), 1 = 0 dB (unity), ~3.98 = +12 dB (max boost).'),
2371
+ }),
2372
+ async run(input, ctx) {
2373
+ const desktop = ctx.desktop();
2374
+ await desktop.requireCapability('timeline-tracks', 'timeline tracks + audio mixing');
2375
+ return ok(await desktop.post('/agent/timeline/update-settings', input));
2376
+ },
2377
+ };
2221
2378
  // ── Export ──────────────────────────────────────────────────────
2222
2379
  const ABSOLUTE_PATH_RE = /^([A-Za-z]:[\\/]|\/)/;
2223
2380
  export const exportVideo = {
@@ -2584,6 +2741,8 @@ function resolveGuideTopic(topic) {
2584
2741
  return 'slates-prompting-seedream-5-lite';
2585
2742
  if (t.startsWith('veo'))
2586
2743
  return 'slates-prompting-veo-3';
2744
+ if (t.startsWith('omni-flash') || t.startsWith('gemini-omni') || t === 'omni flash')
2745
+ return 'slates-prompting-omni-flash';
2587
2746
  if (t.startsWith('kling-mc'))
2588
2747
  return 'slates-prompting-motion-transfer';
2589
2748
  if (t === 'edit-video' || t === 'video-edit' || t === 'edit video' || t === 'video edit')
@@ -2666,6 +2825,7 @@ export const ALL_OPERATIONS = [
2666
2825
  generateLipSync,
2667
2826
  generateMotionTransfer,
2668
2827
  editVideo,
2828
+ trimVideo,
2669
2829
  editImage,
2670
2830
  getGenerationStatus,
2671
2831
  listGenerations,
@@ -2673,6 +2833,10 @@ export const ALL_OPERATIONS = [
2673
2833
  addClipToTimeline,
2674
2834
  reorderClips,
2675
2835
  removeClip,
2836
+ addTimelineTrack,
2837
+ updateTimelineTrack,
2838
+ removeTimelineTrack,
2839
+ updateTimelineSettings,
2676
2840
  exportVideo,
2677
2841
  exportTimelineXml,
2678
2842
  revealFile,
@@ -5,4 +5,5 @@ export * from './style-library.js';
5
5
  export * from './model-facts.js';
6
6
  export * from './character-sheet.js';
7
7
  export * from './environment-sheet.js';
8
+ export * from './prompting-tips.js';
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -16,4 +16,8 @@ export * from './style-library.js';
16
16
  export * from './model-facts.js';
17
17
  export * from './character-sheet.js';
18
18
  export * from './environment-sheet.js';
19
+ // Exported from the `./prompts` subpath (not just the root barrel) because the
20
+ // desktop RENDERER imports the tips — the root barrel re-exports auth.js
21
+ // (node:fs/os/path), which breaks browser bundling. `./prompts` stays Node-free.
22
+ export * from './prompting-tips.js';
19
23
  //# sourceMappingURL=index.js.map
@@ -58,7 +58,7 @@ export const MODEL_FACTS = [
58
58
  kind: 'video',
59
59
  maxRefImages: null,
60
60
  maxIngredients: 9, // ingredient images per video gen
61
- notes: 'PREMIUM video tier — route here the moment physics, effects, destruction, or scale matter, and for hero shots. VIDEO-ONLY: cannot generate standalone images (use NB2/FLUX.2/Seedream for those). Up to 9 ingredient images. Strong I2V / own-footage restyle. Native 4K. Also the PREMIUM engine inside the Motion Transfer and Lip Sync tools (single-pass: driving video / dialogue are native conditioning signals — better motion fidelity, natural speech, voice cloned from a video source; video references bill input+output seconds).',
61
+ notes: 'PREMIUM video tier — route here the moment physics, effects, destruction, or scale matter, and for hero shots. VIDEO-ONLY: cannot generate standalone images (use NB2/FLUX.2/Seedream for those). Up to 9 ingredient images. Strong I2V / own-footage restyle. Native 4K, but 4K VIDEO is a Pro-only tier gate (base maxes at 1080p; server returns PRO_REQUIRED) — default 1080p unless the user is on Pro. Also the PREMIUM engine inside the Motion Transfer and Lip Sync tools (single-pass: driving video / dialogue are native conditioning signals — better motion fidelity, natural speech, voice cloned from a video source; video references bill input+output seconds).',
62
62
  },
63
63
  {
64
64
  id: 'kling-v3',
@@ -74,7 +74,7 @@ export const MODEL_FACTS = [
74
74
  kind: 'video',
75
75
  maxRefImages: null,
76
76
  maxIngredients: 4, // combined subject elements + style refs per edit
77
- notes: 'VIDEO-TO-VIDEO EDIT (default edit tool) — takes an EXISTING 3–15s clip and changes only what the prompt names: character swap, environment change, style transfer. Original motion/camera/audio preserved. One pass, no masking. Use to FIX a 90%-right clip instead of re-rolling it, or to AI-edit the user\'s own footage. Elements (@ElementN = frontal + angles) lock subject identity; max 4 combined refs. Billed per second of output (≈ clip length, rounded up). Seedance edit/relocate is the alternative for style-transfer-heavy jobs.',
77
+ notes: 'VIDEO-TO-VIDEO EDIT — the REF-DRIVEN edit tool: takes an EXISTING 3–15s clip and changes what the prompt names, with element/style reference images (@ElementN = frontal + angles) locking subject identity; max 4 combined refs. keep_audio preserves the ORIGINAL audio verbatim (spoken words cannot drift) — but video lips can drift slightly against it, and multi-beat instructions get under-executed (7/09 receipt: missed a second action beat Omni Flash edit landed) — ONE beat per pass. Route here when an edit NEEDS reference images or bit-exact audio; for prompt-only footage-synced VFX, omni-flash-edit won the 7/09 fidelity head-to-head. Billed per second of output (≈ clip length, rounded up). Seedance edit/relocate is the alternative for style-transfer-heavy jobs.',
78
78
  },
79
79
  {
80
80
  id: 'veo-3.1',
@@ -84,6 +84,22 @@ export const MODEL_FACTS = [
84
84
  maxIngredients: 3,
85
85
  notes: 'NICHE, never the default — pick only when native synchronized audio must generate WITH the video in one gen. 16:9 only, 4/6/8s only. Otherwise Kling (default) or Seedance (physics/premium) win.',
86
86
  },
87
+ {
88
+ id: 'omni-flash',
89
+ label: 'Gemini Omni Flash',
90
+ kind: 'video',
91
+ maxRefImages: null,
92
+ maxIngredients: 7, // ref2v image_urls; 7 mirrors Google's own reference limit
93
+ notes: 'CHEAP 720p tier with native synced audio included — t2v, single-start-frame i2v, or reference-to-video with up to 7 reference images. 3-10s, 16:9/9:16 only. No last frame, no video/audio references. VIDEO-ONLY. New seat: quality vs Kling/Seedance unproven pending comparison gens — do not route hero shots here; use it for cheap drafts, audio-in-one-gen at low cost, ref2v character consistency trials, and its edit variant.',
94
+ },
95
+ {
96
+ id: 'omni-flash-edit',
97
+ label: 'Omni Flash Edit',
98
+ kind: 'video',
99
+ maxRefImages: null,
100
+ maxIngredients: 0, // prompt + source clip ONLY — no element/style refs on this endpoint
101
+ notes: 'VIDEO-TO-VIDEO EDIT, prompt-only — THE EDIT-FIDELITY WINNER (7/09 head-to-head vs Kling edit on real talking footage: lips held perfectly, audio near-identical, both action beats landed). Takes an EXISTING 3-10s clip and changes what the prompt names, footage-synced (prop/effect/environment/lighting swaps). Fidelity is EARNED by prompt discipline: ONE short instruction + "Keep everything else the same." — long descriptive prompts DESTROY it (Google-documented + 7/09 receipt). Never name objects as metaphors ("candle-like" → literal candle). Quirk: occasional tail jitter/doubled last speech beat — trim the tail. NO reference images (identity swaps needing refs → Kling edit); bit-exact audio needs → Kling keep_audio or segment-splice. 720p output, cheapest edit seat (~2/3 of Kling edit Std).',
102
+ },
87
103
  ];
88
104
  const FACT_BY_ID = new Map(MODEL_FACTS.map((m) => [m.id, m]));
89
105
  export function getModelFact(id) {
@@ -0,0 +1,23 @@
1
+ export interface PromptingTipCard {
2
+ heading: string;
3
+ /** Monospace example line(s). \n renders as a line break. */
4
+ example?: string;
5
+ note: string;
6
+ /** Render highlighted as a critical/mandatory card. */
7
+ critical?: boolean;
8
+ }
9
+ export interface PromptingTipsEntry {
10
+ /** Family label for the modal title ("Prompting Tips — {label}"). */
11
+ label: string;
12
+ /** Intro paragraphs above the cards. */
13
+ intro: string[];
14
+ /** Two columns of tip cards. */
15
+ columns: [PromptingTipCard[], PromptingTipCard[]];
16
+ /** Footer callout paragraphs. */
17
+ footer?: string[];
18
+ }
19
+ export type PromptingTipsKey = 'seedance' | 'kling' | 'kling-edit' | 'veo' | 'omni-flash' | 'omni-flash-edit' | 'nano-banana' | 'nano-banana-lite';
20
+ export declare const PROMPTING_TIPS: Record<PromptingTipsKey, PromptingTipsEntry>;
21
+ /** Null when no tips exist for the key — callers render an honest fallback. */
22
+ export declare function getPromptingTips(key: string): PromptingTipsEntry | null;
23
+ //# sourceMappingURL=prompting-tips.d.ts.map