@slatesvideo/shared 0.4.5 → 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.
- package/dist/operations/index.d.ts +8 -2
- package/dist/operations/index.js +183 -14
- package/package.json +1 -1
|
@@ -25,7 +25,9 @@ export declare const getWorkspaceState: Operation<{
|
|
|
25
25
|
}>;
|
|
26
26
|
export declare const getMe: Operation<Record<string, never>>;
|
|
27
27
|
export declare const getCreditBalance: Operation<Record<string, never>>;
|
|
28
|
-
export declare const listAvailableModels: Operation<
|
|
28
|
+
export declare const listAvailableModels: Operation<{
|
|
29
|
+
filter?: string;
|
|
30
|
+
}>;
|
|
29
31
|
export declare const estimateGenerationCost: Operation<{
|
|
30
32
|
model: string;
|
|
31
33
|
quantity?: number;
|
|
@@ -39,6 +41,9 @@ export declare const getProject: Operation<{
|
|
|
39
41
|
}>;
|
|
40
42
|
export declare const listAssets: Operation<{
|
|
41
43
|
projectId: string;
|
|
44
|
+
type?: 'image' | 'video' | 'audio';
|
|
45
|
+
search?: string;
|
|
46
|
+
limit?: number;
|
|
42
47
|
}>;
|
|
43
48
|
export declare const getAssetImage: Operation<{
|
|
44
49
|
id: string;
|
|
@@ -166,7 +171,7 @@ export declare function videoCostKey(input: {
|
|
|
166
171
|
}): string;
|
|
167
172
|
export declare const generateVideo: Operation<{
|
|
168
173
|
prompt: string;
|
|
169
|
-
model:
|
|
174
|
+
model: string;
|
|
170
175
|
projectId?: string;
|
|
171
176
|
aspectRatio?: '1:1' | '16:9' | '9:16' | '4:3' | '3:4' | '21:9' | '9:21' | '4:5' | '5:4' | '2:3' | '3:2';
|
|
172
177
|
duration?: number;
|
|
@@ -216,6 +221,7 @@ export declare const generateMotionTransfer: Operation<{
|
|
|
216
221
|
}>;
|
|
217
222
|
export declare const getGenerationStatus: Operation<{
|
|
218
223
|
generationId: string;
|
|
224
|
+
waitSeconds?: number;
|
|
219
225
|
}>;
|
|
220
226
|
export declare const listGenerations: Operation<{
|
|
221
227
|
projectId?: string;
|
package/dist/operations/index.js
CHANGED
|
@@ -22,7 +22,9 @@ export function defaultContext() {
|
|
|
22
22
|
// ── Helpers ─────────────────────────────────────────────────────
|
|
23
23
|
function ok(data, text) {
|
|
24
24
|
return {
|
|
25
|
-
|
|
25
|
+
// Compact JSON — pretty-printing (indent 2) cost ~10-15% extra tokens
|
|
26
|
+
// on every tool result the calling LLM reads.
|
|
27
|
+
text: text ?? JSON.stringify(data),
|
|
26
28
|
data,
|
|
27
29
|
};
|
|
28
30
|
}
|
|
@@ -77,11 +79,27 @@ export const getCreditBalance = {
|
|
|
77
79
|
};
|
|
78
80
|
export const listAvailableModels = {
|
|
79
81
|
id: 'slates_list_available_models',
|
|
80
|
-
description: '
|
|
81
|
-
input: z.object({
|
|
82
|
-
|
|
82
|
+
description: 'Registry of generation model cost keys with per-call credit cost in cents, as a compact "key cents" table. Optional `filter` substring (e.g. "kling" or "nano-banana") keeps the result small — prefer it. For a single known model, slates_estimate_generation_cost is cheaper still.',
|
|
83
|
+
input: z.object({
|
|
84
|
+
filter: z.string().optional().describe('Substring match on the model key, e.g. "kling-v3" or "seedance"'),
|
|
85
|
+
}),
|
|
86
|
+
async run(input, ctx) {
|
|
83
87
|
const r = await ctx.cloud().get('/api/agent/models');
|
|
84
|
-
|
|
88
|
+
let models = r.models;
|
|
89
|
+
if (input.filter) {
|
|
90
|
+
const q = input.filter.toLowerCase();
|
|
91
|
+
models = models.filter((m) => m.model.toLowerCase().includes(q));
|
|
92
|
+
}
|
|
93
|
+
// Compact text table — "key cents" lines are ~5x denser than the raw
|
|
94
|
+
// JSON registry (the full pretty-printed dump was an ~8k-token leak).
|
|
95
|
+
const table = models.map((m) => `${m.model} ${m.cost_cents}`).join('\n');
|
|
96
|
+
return {
|
|
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` +
|
|
100
|
+
table,
|
|
101
|
+
data: { count: models.length, credit_markup: r.credit_markup },
|
|
102
|
+
};
|
|
85
103
|
},
|
|
86
104
|
};
|
|
87
105
|
export const estimateGenerationCost = {
|
|
@@ -138,15 +156,56 @@ export const getProject = {
|
|
|
138
156
|
},
|
|
139
157
|
};
|
|
140
158
|
// ── Assets ──────────────────────────────────────────────────────
|
|
159
|
+
/** Compact projection of a raw asset row — the full row (prompt, settings,
|
|
160
|
+
* paths, thumbnails) is 20-50x heavier and was the single biggest context
|
|
161
|
+
* leak in early agent sessions (494 assets ≈ 130KB ≈ 33k tokens in ONE
|
|
162
|
+
* tool result). Every op that embeds asset lists uses this. */
|
|
163
|
+
function compactAsset(a) {
|
|
164
|
+
const r = a;
|
|
165
|
+
const prompt = typeof r.prompt === 'string' ? r.prompt : '';
|
|
166
|
+
return {
|
|
167
|
+
id: r.id,
|
|
168
|
+
code: r.code ?? null,
|
|
169
|
+
label: r.label ?? (prompt ? prompt.slice(0, 40) : null),
|
|
170
|
+
type: r.type,
|
|
171
|
+
created_at: r.createdAt ?? r.created_at ?? undefined,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
141
174
|
export const listAssets = {
|
|
142
175
|
id: 'slates_list_assets',
|
|
143
|
-
description: 'List
|
|
144
|
-
input: z.object({
|
|
176
|
+
description: 'List assets in a Slates project as COMPACT rows (id, code, label, type) — newest first, default limit 50. Each asset carries its short code (IMG-A12 / VID-V3 / AUD-S1 — the badge the user sees on the gallery card) and label. When the user names an asset by code ("use IMG-A36 as the reference"), pass it as `search` to resolve the assetId. Always speak about assets by code + label, never by UUID. NOTE: generate_* results already return the new asset ids — do NOT call this to find an asset you just created.',
|
|
177
|
+
input: z.object({
|
|
178
|
+
projectId: z.string().uuid(),
|
|
179
|
+
type: z.enum(['image', 'video', 'audio']).optional().describe('Only this asset type'),
|
|
180
|
+
search: z.string().optional().describe('Case-insensitive match on code or label (e.g. "IMG-A36" or "sunset")'),
|
|
181
|
+
limit: z.number().int().min(1).max(500).optional().describe('Max rows (default 50, newest first)'),
|
|
182
|
+
}),
|
|
145
183
|
async run(input, ctx) {
|
|
146
184
|
const r = await ctx.desktop().get('/agent/assets', {
|
|
147
185
|
projectId: input.projectId,
|
|
148
186
|
});
|
|
149
|
-
|
|
187
|
+
const all = (r.assets ?? []).map(compactAsset);
|
|
188
|
+
let rows = all;
|
|
189
|
+
if (input.type)
|
|
190
|
+
rows = rows.filter((a) => a.type === input.type);
|
|
191
|
+
if (input.search) {
|
|
192
|
+
const q = input.search.toLowerCase();
|
|
193
|
+
rows = rows.filter((a) => String(a.code ?? '').toLowerCase().includes(q) ||
|
|
194
|
+
String(a.label ?? '').toLowerCase().includes(q));
|
|
195
|
+
}
|
|
196
|
+
// Newest first, then cap. (created_at is ISO — lexicographic sort works.)
|
|
197
|
+
rows = rows
|
|
198
|
+
.slice()
|
|
199
|
+
.sort((a, b) => String(b.created_at ?? '').localeCompare(String(a.created_at ?? '')));
|
|
200
|
+
const limit = input.limit ?? 50;
|
|
201
|
+
const truncated = rows.length > limit;
|
|
202
|
+
rows = rows.slice(0, limit);
|
|
203
|
+
return ok({
|
|
204
|
+
total_matching: truncated ? undefined : rows.length,
|
|
205
|
+
total_in_project: all.length,
|
|
206
|
+
truncated,
|
|
207
|
+
assets: rows.map(({ created_at: _drop, ...rest }) => rest),
|
|
208
|
+
});
|
|
150
209
|
},
|
|
151
210
|
};
|
|
152
211
|
export const getAssetImage = {
|
|
@@ -760,7 +819,10 @@ export const generateImage = {
|
|
|
760
819
|
resolution,
|
|
761
820
|
cost_cents: totalCents,
|
|
762
821
|
cost_dollars: (totalCents / 100).toFixed(2),
|
|
763
|
-
|
|
822
|
+
// Compact refs only — the full rows (prompt/settings/paths) were a
|
|
823
|
+
// multi-KB leak per generation and everything needed downstream is
|
|
824
|
+
// the id/code/label.
|
|
825
|
+
assets: assetList.map(compactAsset),
|
|
764
826
|
generationIds: result.generationIds ?? (result.generationId ? [result.generationId] : []),
|
|
765
827
|
...(partialFailure
|
|
766
828
|
? { partial: true, error: result.error ?? 'unknown error', requested_count: requestedCount }
|
|
@@ -1010,6 +1072,60 @@ export function videoCostKey(input) {
|
|
|
1010
1072
|
}
|
|
1011
1073
|
throw new Error(`Unknown video model: ${input.model}`);
|
|
1012
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
|
+
}
|
|
1013
1129
|
// Maps a video model id to its bundled prompting skill (frontmatter `name:`),
|
|
1014
1130
|
// so guidance text points at a skill that actually exists. Deriving the name
|
|
1015
1131
|
// via model.split('-')[0] produced 'slates-prompting-kling' / '...-veo', which
|
|
@@ -1028,7 +1144,7 @@ export const generateVideo = {
|
|
|
1028
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.',
|
|
1029
1145
|
input: z.object({
|
|
1030
1146
|
prompt: z.string().min(1).max(4000),
|
|
1031
|
-
model: z.
|
|
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).'),
|
|
1032
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.'),
|
|
1033
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.'),
|
|
1034
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).'),
|
|
@@ -1051,6 +1167,25 @@ export const generateVideo = {
|
|
|
1051
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).'),
|
|
1052
1168
|
}),
|
|
1053
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;
|
|
1054
1189
|
// projectId is required for video — without it there's no UI feedback,
|
|
1055
1190
|
// no asset to reference later, and a failed gen leaves the user with
|
|
1056
1191
|
// nothing. The MCP-only headless path that exists for image gen is
|
|
@@ -1455,12 +1590,45 @@ export const generateMotionTransfer = {
|
|
|
1455
1590
|
// ── Generation status (background mode) ─────────────────────────
|
|
1456
1591
|
export const getGenerationStatus = {
|
|
1457
1592
|
id: 'slates_get_generation_status',
|
|
1458
|
-
description: 'Poll one generation by id. Returns status (pending/processing/completed/failed/cancelled), the error message on failure, and the finished asset record (with id, code, filePath) on completion. Use after submitting any generate_* op with background=true
|
|
1459
|
-
input: z.object({
|
|
1593
|
+
description: 'Poll one generation by id. Returns status (pending/processing/completed/failed/cancelled), the error message on failure, and the finished asset record (with id, code, filePath) on completion. Use after submitting any generate_* op with background=true. ALWAYS pass waitSeconds (use 45) — the call long-polls internally and returns early the moment the generation reaches a terminal state, so ONE call replaces a dozen rapid polls (each poll turn re-reads your whole context; rapid polling is the #1 orchestration-cost leak). Video generations commonly take 1-5 minutes: expect 1-4 long-poll calls, never a tight loop. Generations survive app restarts (the desktop resumes in-flight provider jobs on boot).',
|
|
1594
|
+
input: z.object({
|
|
1595
|
+
generationId: z.string().uuid(),
|
|
1596
|
+
waitSeconds: z
|
|
1597
|
+
.number()
|
|
1598
|
+
.int()
|
|
1599
|
+
.min(0)
|
|
1600
|
+
.max(50)
|
|
1601
|
+
.optional()
|
|
1602
|
+
.describe('Long-poll: block up to N seconds, returning early on completion/failure. Use 45.'),
|
|
1603
|
+
}),
|
|
1460
1604
|
async run(input, ctx) {
|
|
1461
1605
|
const desktop = ctx.desktop();
|
|
1462
1606
|
await desktop.requireCapability('background-generation', 'background generation');
|
|
1463
|
-
|
|
1607
|
+
const deadline = Date.now() + Math.min(Math.max(input.waitSeconds ?? 0, 0), 50) * 1000;
|
|
1608
|
+
for (;;) {
|
|
1609
|
+
const r = await desktop.get('/agent/generation/status', { id: input.generationId });
|
|
1610
|
+
const g = r.generation;
|
|
1611
|
+
const status = g?.status ?? r.status;
|
|
1612
|
+
const terminal = status === 'completed' || status === 'failed' || status === 'cancelled';
|
|
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
|
+
}
|
|
1630
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(3000, deadline - Date.now())));
|
|
1631
|
+
}
|
|
1464
1632
|
},
|
|
1465
1633
|
};
|
|
1466
1634
|
export const listGenerations = {
|
|
@@ -1477,7 +1645,8 @@ export const listGenerations = {
|
|
|
1477
1645
|
return ok(await desktop.get('/agent/generation/list', {
|
|
1478
1646
|
projectId: input.projectId,
|
|
1479
1647
|
status: input.status,
|
|
1480
|
-
|
|
1648
|
+
// Default cap — an unbounded history dump is a context leak.
|
|
1649
|
+
limit: input.limit ?? 20,
|
|
1481
1650
|
}));
|
|
1482
1651
|
},
|
|
1483
1652
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slatesvideo/shared",
|
|
3
|
-
"version": "0.4.
|
|
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",
|