@slatesvideo/shared 0.4.6 → 0.4.7

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.
@@ -171,7 +171,7 @@ export declare function videoCostKey(input: {
171
171
  }): string;
172
172
  export declare const generateVideo: Operation<{
173
173
  prompt: string;
174
- model: VideoModel;
174
+ model: string;
175
175
  projectId?: string;
176
176
  aspectRatio?: '1:1' | '16:9' | '9:16' | '4:3' | '3:4' | '21:9' | '9:21' | '4:5' | '5:4' | '2:3' | '3:2';
177
177
  duration?: number;
@@ -94,7 +94,9 @@ export const listAvailableModels = {
94
94
  // JSON registry (the full pretty-printed dump was an ~8k-token leak).
95
95
  const table = models.map((m) => `${m.model} ${m.cost_cents}`).join('\n');
96
96
  return {
97
- text: `${models.length} model cost keys (cents per generation)${input.filter ? ` matching "${input.filter}"` : ''}:\n` +
97
+ text: `${models.length} COST keys (cents per generation)${input.filter ? ` matching "${input.filter}"` : ''}. ` +
98
+ `NOTE: these are billing keys for cost lookup ONLY — the \`model\` param on slates_generate_video takes a BASE id ` +
99
+ `(kling-v3.0-std | kling-v3.0-pro | kling-v3.0-omni | seedance-2 | veo-3.1-fast | veo-3.1-standard) with duration/videoResolution as separate params:\n` +
98
100
  table,
99
101
  data: { count: models.length, credit_markup: r.credit_markup },
100
102
  };
@@ -1070,6 +1072,60 @@ export function videoCostKey(input) {
1070
1072
  }
1071
1073
  throw new Error(`Unknown video model: ${input.model}`);
1072
1074
  }
1075
+ /**
1076
+ * Forgiving model-id resolver. Agents routinely paste registry COST keys
1077
+ * ("kling-v3-standard-8s", "seedance-2-1080p-8s") into the `model` param —
1078
+ * an observed 15-turn error-retry spiral in a live session. Instead of
1079
+ * rejecting, extract the intent: base model + any duration / resolution /
1080
+ * audio the key encodes. Returns null only when nothing matches.
1081
+ */
1082
+ function resolveVideoModel(raw) {
1083
+ let s = raw.trim().toLowerCase();
1084
+ const out = { model: 'kling-v3.0-std' };
1085
+ const dur = /-(\d+)s\b/.exec(s);
1086
+ if (dur) {
1087
+ out.duration = parseInt(dur[1], 10);
1088
+ s = s.replace(/-(\d+)s\b/, '');
1089
+ }
1090
+ const res = /-(480p|720p|1080p|4k)\b/.exec(s);
1091
+ if (res) {
1092
+ out.videoResolution = res[1];
1093
+ s = s.replace(/-(480p|720p|1080p|4k)\b/, '');
1094
+ }
1095
+ if (/-audio\b/.test(s)) {
1096
+ out.sound = true;
1097
+ s = s.replace(/-audio\b/, '');
1098
+ }
1099
+ if (/-(realface|face)\b/.test(s)) {
1100
+ out.seedanceFace = true;
1101
+ s = s.replace(/-(realface|face)\b/, '');
1102
+ }
1103
+ const direct = VIDEO_MODELS.find((m) => m === s);
1104
+ if (direct) {
1105
+ out.model = direct;
1106
+ return out;
1107
+ }
1108
+ const aliases = {
1109
+ 'kling-v3-standard': 'kling-v3.0-std',
1110
+ 'kling-v3-std': 'kling-v3.0-std',
1111
+ 'kling-v3.0-standard': 'kling-v3.0-std',
1112
+ 'kling-v3': 'kling-v3.0-std',
1113
+ 'kling-v3-pro': 'kling-v3.0-pro',
1114
+ 'kling-v3-omni': 'kling-v3.0-omni',
1115
+ 'kling-v3-omni-pro': 'kling-v3.0-omni',
1116
+ 'kling-v3.0-omni-pro': 'kling-v3.0-omni',
1117
+ 'seedance-2.0': 'seedance-2',
1118
+ 'seedance-2-0': 'seedance-2',
1119
+ seedance: 'seedance-2',
1120
+ 'veo-3.1': 'veo-3.1-fast',
1121
+ 'veo-3': 'veo-3.1-fast',
1122
+ };
1123
+ if (aliases[s]) {
1124
+ out.model = aliases[s];
1125
+ return out;
1126
+ }
1127
+ return null;
1128
+ }
1073
1129
  // Maps a video model id to its bundled prompting skill (frontmatter `name:`),
1074
1130
  // so guidance text points at a skill that actually exists. Deriving the name
1075
1131
  // via model.split('-')[0] produced 'slates-prompting-kling' / '...-veo', which
@@ -1088,7 +1144,7 @@ export const generateVideo = {
1088
1144
  description: 'Generate video via Slates credits. REQUIRED before calling: read slates-model-selection (routing: Kling = default, Seedance = premium/physics, Veo = audio-only niche), the slates-cost-discipline skill, plus the per-model prompting skill (slates-prompting-seedance / slates-prompting-kling-v3 / slates-prompting-veo-3) — video models prompt very differently. Also read slates-content-policy when the scene involves conflict, creatures, crowds, destruction, weapons, or young characters (build it safe-by-construction so the filter doesn\'t reject or degrade it). projectId is REQUIRED for UI integration (the user sees a progress card and the asset lands in the project — without it the call fails). aspectRatio + duration are required (server returns requires_clarification when missing). Cost > $0.50 returns requires_confirm — pass confirm=true after explicit user OK. Veo locks to 16:9 and to 4/6/8s durations (4K only at 8s). Image-to-video via firstFrameAssetId. Frames-to-video via firstFrameAssetId + lastFrameAssetId (Veo / Seedance only). Ingredients via ingredientAssetIds (Kling Omni / Seedance). No skill files installed? Call slates_get_prompting_guide with the per-model guide (\'slates-prompting-veo-3\' / \'slates-prompting-kling-v3\' / \'slates-prompting-seedance\') and \'slates-cost-discipline\' before first use.',
1089
1145
  input: z.object({
1090
1146
  prompt: z.string().min(1).max(4000),
1091
- model: z.enum(VIDEO_MODELS).describe('Route per the slates-model-selection skill. DEFAULT = Kling V3.0 std (general-purpose, cost-effective, strong start-frame adherence; pro = higher polish; omni = multi-char dialogue + audio). Seedance 2 = the PREMIUM tier — pick it whenever physics/effects/scale remotely matter or for hero shots; audio included, first+last frame + up to 9 reference images, full 480p–4K ladder (native 4K) via videoResolution. Veo 3.1 = niche, never the default — only when native synced audio must generate WITH the video in one gen; locks 16:9, 4/6/8s. For exact per-call credit cost, call slates_estimate_generation_cost or slates_list_available_models — never quote prices from memory (they change).'),
1147
+ 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 entries like "kling-v3-standard-8s" are COST keys, not model ids; if you pass one it is auto-resolved). Route per the slates-model-selection skill. DEFAULT = Kling V3.0 std (general-purpose, cost-effective, strong start-frame adherence; pro = higher polish; omni = multi-char dialogue + audio). Seedance 2 = the PREMIUM tier — pick it whenever physics/effects/scale remotely matter or for hero shots; audio included, first+last frame + up to 9 reference images, full 480p–4K ladder (native 4K) via videoResolution. Veo 3.1 = niche, never the default — only when native synced audio must generate WITH the video in one gen; locks 16:9, 4/6/8s. For exact per-call credit cost, call slates_estimate_generation_cost — never quote prices from memory (they change).'),
1092
1148
  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.'),
1093
1149
  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.'),
1094
1150
  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).'),
@@ -1111,6 +1167,25 @@ export const generateVideo = {
1111
1167
  confirm: z.boolean().optional().describe('Set true after explicit user OK to bypass the >$0.50 cost confirm gate (which fires for almost every video gen since they\'re expensive).'),
1112
1168
  }),
1113
1169
  async run(input, ctx) {
1170
+ // Resolve the model FIRST — forgiving normalization (cost keys, alias
1171
+ // spellings) with a teaching error, so a wrong id never costs the agent
1172
+ // a retry spiral. Anything the key encoded (duration/res/audio) fills
1173
+ // params the caller left blank; explicit params always win.
1174
+ const resolved = resolveVideoModel(input.model);
1175
+ if (!resolved) {
1176
+ throw new Error(`Unknown video model "${input.model}". Valid model ids: ${VIDEO_MODELS.join(' | ')}. ` +
1177
+ `Registry entries like "kling-v3-standard-8s" or "seedance-2-1080p-8s" are COST keys, not model ids — ` +
1178
+ `pass the base id and set duration / videoResolution as separate params.`);
1179
+ }
1180
+ input.model = resolved.model;
1181
+ if (input.duration == null && resolved.duration != null)
1182
+ input.duration = resolved.duration;
1183
+ if (input.videoResolution == null && resolved.videoResolution != null)
1184
+ input.videoResolution = resolved.videoResolution;
1185
+ if (input.sound == null && resolved.sound != null)
1186
+ input.sound = resolved.sound;
1187
+ if (input.seedanceFace == null && resolved.seedanceFace != null)
1188
+ input.seedanceFace = resolved.seedanceFace;
1114
1189
  // projectId is required for video — without it there's no UI feedback,
1115
1190
  // no asset to reference later, and a failed gen leaves the user with
1116
1191
  // nothing. The MCP-only headless path that exists for image gen is
@@ -1531,13 +1606,27 @@ export const getGenerationStatus = {
1531
1606
  await desktop.requireCapability('background-generation', 'background generation');
1532
1607
  const deadline = Date.now() + Math.min(Math.max(input.waitSeconds ?? 0, 0), 50) * 1000;
1533
1608
  for (;;) {
1534
- const r = await desktop.get('/agent/generation/status', {
1535
- id: input.generationId,
1536
- });
1537
- const status = r.status;
1609
+ const r = await desktop.get('/agent/generation/status', { id: input.generationId });
1610
+ const g = r.generation;
1611
+ const status = g?.status ?? r.status;
1538
1612
  const terminal = status === 'completed' || status === 'failed' || status === 'cancelled';
1539
- if (terminal || Date.now() >= deadline)
1540
- return ok(r);
1613
+ if (terminal || Date.now() >= deadline) {
1614
+ if (!g)
1615
+ return ok(r);
1616
+ // Slim the payload: the raw generation row (full prompt/settings)
1617
+ // and full asset row are context bloat — return the fields the
1618
+ // agent acts on, with the EXACT billed cost front and center.
1619
+ return ok({
1620
+ status,
1621
+ cost_cents: g.cost ?? null,
1622
+ error: g.error ?? null,
1623
+ model: g.model,
1624
+ completed_at: g.completedAt ?? null,
1625
+ asset: r.asset
1626
+ ? { ...compactAsset(r.asset), file_path: r.asset.filePath }
1627
+ : null,
1628
+ });
1629
+ }
1541
1630
  await new Promise((resolve) => setTimeout(resolve, Math.min(3000, deadline - Date.now())));
1542
1631
  }
1543
1632
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slatesvideo/shared",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "description": "Shared operations layer for the Slates MCP server and CLI: auth, cloud/desktop clients, and the single tool surface both consume. Most users want @slatesvideo/mcp-server or @slatesvideo/cli instead.",
5
5
  "license": "MIT",
6
6
  "type": "module",