@slatesvideo/shared 0.4.5 → 0.4.6

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.
@@ -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<Record<string, never>>;
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;
@@ -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;
@@ -22,7 +22,9 @@ export function defaultContext() {
22
22
  // ── Helpers ─────────────────────────────────────────────────────
23
23
  function ok(data, text) {
24
24
  return {
25
- text: text ?? JSON.stringify(data, null, 2),
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,25 @@ export const getCreditBalance = {
77
79
  };
78
80
  export const listAvailableModels = {
79
81
  id: 'slates_list_available_models',
80
- description: 'Full registry of generation models with their per-call credit cost. Use this for cost estimation.',
81
- input: z.object({}).strict(),
82
- async run(_input, ctx) {
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
- return ok(r);
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} model cost keys (cents per generation)${input.filter ? ` matching "${input.filter}"` : ''}:\n` +
98
+ table,
99
+ data: { count: models.length, credit_markup: r.credit_markup },
100
+ };
85
101
  },
86
102
  };
87
103
  export const estimateGenerationCost = {
@@ -138,15 +154,56 @@ export const getProject = {
138
154
  },
139
155
  };
140
156
  // ── Assets ──────────────────────────────────────────────────────
157
+ /** Compact projection of a raw asset row — the full row (prompt, settings,
158
+ * paths, thumbnails) is 20-50x heavier and was the single biggest context
159
+ * leak in early agent sessions (494 assets ≈ 130KB ≈ 33k tokens in ONE
160
+ * tool result). Every op that embeds asset lists uses this. */
161
+ function compactAsset(a) {
162
+ const r = a;
163
+ const prompt = typeof r.prompt === 'string' ? r.prompt : '';
164
+ return {
165
+ id: r.id,
166
+ code: r.code ?? null,
167
+ label: r.label ?? (prompt ? prompt.slice(0, 40) : null),
168
+ type: r.type,
169
+ created_at: r.createdAt ?? r.created_at ?? undefined,
170
+ };
171
+ }
141
172
  export const listAssets = {
142
173
  id: 'slates_list_assets',
143
- description: 'List all assets (images + videos) in a Slates project. 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"), call this and match on the code field to resolve the assetId. Always speak about assets by code + label, never by UUID.',
144
- input: z.object({ projectId: z.string().uuid() }),
174
+ 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.',
175
+ input: z.object({
176
+ projectId: z.string().uuid(),
177
+ type: z.enum(['image', 'video', 'audio']).optional().describe('Only this asset type'),
178
+ search: z.string().optional().describe('Case-insensitive match on code or label (e.g. "IMG-A36" or "sunset")'),
179
+ limit: z.number().int().min(1).max(500).optional().describe('Max rows (default 50, newest first)'),
180
+ }),
145
181
  async run(input, ctx) {
146
182
  const r = await ctx.desktop().get('/agent/assets', {
147
183
  projectId: input.projectId,
148
184
  });
149
- return ok(r);
185
+ const all = (r.assets ?? []).map(compactAsset);
186
+ let rows = all;
187
+ if (input.type)
188
+ rows = rows.filter((a) => a.type === input.type);
189
+ if (input.search) {
190
+ const q = input.search.toLowerCase();
191
+ rows = rows.filter((a) => String(a.code ?? '').toLowerCase().includes(q) ||
192
+ String(a.label ?? '').toLowerCase().includes(q));
193
+ }
194
+ // Newest first, then cap. (created_at is ISO — lexicographic sort works.)
195
+ rows = rows
196
+ .slice()
197
+ .sort((a, b) => String(b.created_at ?? '').localeCompare(String(a.created_at ?? '')));
198
+ const limit = input.limit ?? 50;
199
+ const truncated = rows.length > limit;
200
+ rows = rows.slice(0, limit);
201
+ return ok({
202
+ total_matching: truncated ? undefined : rows.length,
203
+ total_in_project: all.length,
204
+ truncated,
205
+ assets: rows.map(({ created_at: _drop, ...rest }) => rest),
206
+ });
150
207
  },
151
208
  };
152
209
  export const getAssetImage = {
@@ -760,7 +817,10 @@ export const generateImage = {
760
817
  resolution,
761
818
  cost_cents: totalCents,
762
819
  cost_dollars: (totalCents / 100).toFixed(2),
763
- assets: assetList,
820
+ // Compact refs only — the full rows (prompt/settings/paths) were a
821
+ // multi-KB leak per generation and everything needed downstream is
822
+ // the id/code/label.
823
+ assets: assetList.map(compactAsset),
764
824
  generationIds: result.generationIds ?? (result.generationId ? [result.generationId] : []),
765
825
  ...(partialFailure
766
826
  ? { partial: true, error: result.error ?? 'unknown error', requested_count: requestedCount }
@@ -1455,12 +1515,31 @@ export const generateMotionTransfer = {
1455
1515
  // ── Generation status (background mode) ─────────────────────────
1456
1516
  export const getGenerationStatus = {
1457
1517
  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; poll every 5-15s video generations commonly take 1-5 minutes. Generations survive app restarts (the desktop resumes in-flight provider jobs on boot).',
1459
- input: z.object({ generationId: z.string().uuid() }),
1518
+ 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).',
1519
+ input: z.object({
1520
+ generationId: z.string().uuid(),
1521
+ waitSeconds: z
1522
+ .number()
1523
+ .int()
1524
+ .min(0)
1525
+ .max(50)
1526
+ .optional()
1527
+ .describe('Long-poll: block up to N seconds, returning early on completion/failure. Use 45.'),
1528
+ }),
1460
1529
  async run(input, ctx) {
1461
1530
  const desktop = ctx.desktop();
1462
1531
  await desktop.requireCapability('background-generation', 'background generation');
1463
- return ok(await desktop.get('/agent/generation/status', { id: input.generationId }));
1532
+ const deadline = Date.now() + Math.min(Math.max(input.waitSeconds ?? 0, 0), 50) * 1000;
1533
+ for (;;) {
1534
+ const r = await desktop.get('/agent/generation/status', {
1535
+ id: input.generationId,
1536
+ });
1537
+ const status = r.status;
1538
+ const terminal = status === 'completed' || status === 'failed' || status === 'cancelled';
1539
+ if (terminal || Date.now() >= deadline)
1540
+ return ok(r);
1541
+ await new Promise((resolve) => setTimeout(resolve, Math.min(3000, deadline - Date.now())));
1542
+ }
1464
1543
  },
1465
1544
  };
1466
1545
  export const listGenerations = {
@@ -1477,7 +1556,8 @@ export const listGenerations = {
1477
1556
  return ok(await desktop.get('/agent/generation/list', {
1478
1557
  projectId: input.projectId,
1479
1558
  status: input.status,
1480
- limit: input.limit,
1559
+ // Default cap — an unbounded history dump is a context leak.
1560
+ limit: input.limit ?? 20,
1481
1561
  }));
1482
1562
  },
1483
1563
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slatesvideo/shared",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
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",