@slatesvideo/shared 0.6.10 → 0.7.0

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.
Files changed (85) hide show
  1. package/README.md +1 -1
  2. package/dist/auth.js +2 -2
  3. package/dist/clients/cloud.js +1 -1
  4. package/dist/index.d.ts +2 -1
  5. package/dist/index.js +4 -1
  6. package/dist/manual/content.d.ts +1 -1
  7. package/dist/manual/content.js +1 -1
  8. package/dist/operations/index.d.ts +817 -16
  9. package/dist/operations/index.js +1423 -372
  10. package/dist/operations/surface.d.ts +3 -1
  11. package/dist/operations/surface.js +37 -10
  12. package/dist/prompts/ad-presets.d.ts +77 -0
  13. package/dist/prompts/ad-presets.js +43 -0
  14. package/dist/prompts/agent-doctrine.js +5 -4
  15. package/dist/prompts/banned-tokens.d.ts +4 -29
  16. package/dist/prompts/banned-tokens.js +29 -204
  17. package/dist/prompts/craft-cards.js +2 -2
  18. package/dist/prompts/generation-policy.d.ts +41 -0
  19. package/dist/prompts/generation-policy.js +53 -0
  20. package/dist/prompts/guide-retrieval.d.ts +9 -0
  21. package/dist/prompts/guide-retrieval.js +53 -0
  22. package/dist/prompts/index.d.ts +1 -0
  23. package/dist/prompts/index.js +1 -0
  24. package/dist/prompts/model-capabilities.d.ts +18 -1
  25. package/dist/prompts/model-capabilities.js +72 -19
  26. package/dist/prompts/model-facts.d.ts +59 -0
  27. package/dist/prompts/model-facts.js +121 -15
  28. package/dist/prompts/partials.generated.js +8 -2
  29. package/dist/prompts/prompting-tips.d.ts +1 -1
  30. package/dist/prompts/prompting-tips.js +63 -18
  31. package/dist/prompts/reference-composer.d.ts +2 -0
  32. package/dist/prompts/reference-composer.js +51 -50
  33. package/dist/prompts/script-document.d.ts +165 -0
  34. package/dist/prompts/script-document.js +11 -0
  35. package/dist/prompts/shot-grammar.d.ts +4 -4
  36. package/dist/prompts/shot-grammar.js +3 -3
  37. package/dist/prompts/shot-spec.d.ts +13 -0
  38. package/dist/prompts/shot-spec.js +23 -5
  39. package/dist/skills/content.js +26 -23
  40. package/dist/update-check.d.ts +22 -0
  41. package/dist/update-check.js +109 -0
  42. package/exports/slates-chatgpt-images/generated/SKILL.md +107 -0
  43. package/exports/slates-chatgpt-images/generated/slates-chatgpt-images.skill +0 -0
  44. package/exports/slates-prompt-builder/generated/SKILL.md +3 -3
  45. package/exports/slates-prompt-builder/generated/reference-character.md +9 -1
  46. package/exports/slates-prompt-builder/generated/reference-kling.md +3 -3
  47. package/exports/slates-prompt-builder/generated/reference-nano-banana.md +22 -10
  48. package/exports/slates-prompt-builder/generated/reference-seedance.md +4 -4
  49. package/exports/slates-prompt-builder/generated/slates-prompt-builder-manifest.json +17 -17
  50. package/exports/slates-prompt-builder/generated/slates-prompt-builder.skill +0 -0
  51. package/package.json +10 -4
  52. package/skills/_partials/cinematic-card.md +8 -0
  53. package/skills/_partials/cinematic-routes-short.md +2 -0
  54. package/skills/_partials/cinematic-tips-short.md +2 -0
  55. package/skills/_partials/decision-log.md +1 -13
  56. package/skills/_partials/image-defaults.md +11 -0
  57. package/skills/_partials/lens-video-split.md +1 -0
  58. package/skills/_partials/reference-rules-core.md +1 -1
  59. package/skills/_partials/sheet-tool-defaults.md +6 -0
  60. package/skills/slates-character-identity.md +9 -1
  61. package/skills/slates-chatgpt-images.md +107 -0
  62. package/skills/slates-cinematic-look.md +237 -0
  63. package/skills/slates-cost-discipline.md +18 -12
  64. package/skills/slates-direct-response-ad.md +13 -53
  65. package/skills/slates-edit-and-iterate.md +1 -1
  66. package/skills/slates-model-selection.md +139 -133
  67. package/skills/slates-one-prompt-film.md +38 -95
  68. package/skills/slates-project-organization.md +7 -3
  69. package/skills/slates-prompting-flux-2-max.md +15 -4
  70. package/skills/slates-prompting-gpt-image-2-5.md +41 -28
  71. package/skills/slates-prompting-kling-v3.md +3 -3
  72. package/skills/slates-prompting-lip-sync.md +1 -1
  73. package/skills/slates-prompting-minimax-h3.md +30 -17
  74. package/skills/slates-prompting-motion-transfer.md +1 -1
  75. package/skills/slates-prompting-nano-banana-2.md +24 -11
  76. package/skills/slates-prompting-seedance-2-5.md +12 -12
  77. package/skills/slates-prompting-seedance.md +5 -5
  78. package/skills/slates-prompting-seedream-5-lite.md +14 -3
  79. package/skills/slates-prompting-veo-3.md +1 -1
  80. package/skills/slates-script-craft.md +45 -0
  81. package/skills/slates-shot-variety.md +11 -40
  82. package/skills/slates-storyboard-from-script.md +14 -66
  83. package/skills/slates-style-prompting.md +54 -54
  84. package/skills/slates-ugc-influencer-ad.md +32 -309
  85. package/skills/slates-vision-feedback-loop.md +2 -1
@@ -0,0 +1,41 @@
1
+ /** Product fan-out policy: one provider request and debit per output.
2
+ * This is NOT a model capability or provider batch limit. Desktop mirror is
3
+ * generated by slate/scripts/sync-generation-policy.mjs; never edit it there. */
4
+ export declare const IMAGE_QUANTITIES: readonly [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
5
+ export declare const MAX_IMAGE_VARIATIONS: 1 | 2 | 4 | 5 | 3 | 10 | 8 | 7 | 6 | 9;
6
+ export type ImageQuantity = typeof IMAGE_QUANTITIES[number];
7
+ export declare const clampImageQuantity: (count?: number) => ImageQuantity;
8
+ /** Default saved-recipe framing, shared by composer restore, quote and dispatch. */
9
+ export declare const DEFAULT_SHOT_ASPECT_RATIO: "16:9";
10
+ /**
11
+ * The built-in tools, and the seat each one renders on when its caller names no
12
+ * model. THE ONE HOME, for both repos: the desktop reads it through its generated
13
+ * mirror (`toolModel` in slate/src/shared/pricing.ts), this package reads it for
14
+ * the op descriptions and the generated skill partial.
15
+ *
16
+ * `'default-image'` means "follow the app's default image model"
17
+ * (`defaultModelFor('image')` in model-facts.ts), so promoting a better image
18
+ * model moves these tools with it and nothing else is edited. A model id pins a
19
+ * tool instead, for a reason recorded beside it. This file stays a
20
+ * dependency-free leaf (it is copied verbatim into the desktop), which is why
21
+ * the seat is a token each consumer resolves rather than a call made here.
22
+ *
23
+ * Before 2026-09-21 the sheet tools' model was typed at every use on the desktop
24
+ * and twice more here (an op description and a skill), so three screens quoted
25
+ * a sheet from one typed id while the handler billed from another.
26
+ */
27
+ export declare const BUILT_IN_TOOLS: readonly ["character-sheet", "environment-plate", "grid-extract", "image-edit"];
28
+ export type BuiltInTool = typeof BUILT_IN_TOOLS[number];
29
+ export declare const DEFAULT_IMAGE_SEAT: "default-image";
30
+ export declare const TOOL_SEAT: Record<BuiltInTool, typeof DEFAULT_IMAGE_SEAT | string>;
31
+ /** Both sheet tools frame one wide image. On GPT Image the aspect is part of the price. */
32
+ export declare const SHEET_TOOL_ASPECT_RATIO: "16:9";
33
+ /**
34
+ * The GPT Image tier a sheet tool pins. It read 'medium' until 2026-09-09: GPT
35
+ * Image 2.5 renamed the ladder, so the tier GPT Image 2 called `medium` is now
36
+ * `high`, and a stored 'medium' would have kept compiling while naming a
37
+ * materially cheaper tier. Sheets are the identity lane (every downstream shot
38
+ * inherits this frame's likeness), so this is deliberately not the draft tier.
39
+ */
40
+ export declare const SHEET_TOOL_GPT_QUALITY: "high";
41
+ //# sourceMappingURL=generation-policy.d.ts.map
@@ -0,0 +1,53 @@
1
+ /** Product fan-out policy: one provider request and debit per output.
2
+ * This is NOT a model capability or provider batch limit. Desktop mirror is
3
+ * generated by slate/scripts/sync-generation-policy.mjs; never edit it there. */
4
+ export const IMAGE_QUANTITIES = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
5
+ export const MAX_IMAGE_VARIATIONS = IMAGE_QUANTITIES[IMAGE_QUANTITIES.length - 1];
6
+ export const clampImageQuantity = (count = 1) => Math.max(1, Math.min(MAX_IMAGE_VARIATIONS, Number.isFinite(count) ? Math.floor(count) : 1));
7
+ /** Default saved-recipe framing, shared by composer restore, quote and dispatch. */
8
+ export const DEFAULT_SHOT_ASPECT_RATIO = '16:9';
9
+ /**
10
+ * The built-in tools, and the seat each one renders on when its caller names no
11
+ * model. THE ONE HOME, for both repos: the desktop reads it through its generated
12
+ * mirror (`toolModel` in slate/src/shared/pricing.ts), this package reads it for
13
+ * the op descriptions and the generated skill partial.
14
+ *
15
+ * `'default-image'` means "follow the app's default image model"
16
+ * (`defaultModelFor('image')` in model-facts.ts), so promoting a better image
17
+ * model moves these tools with it and nothing else is edited. A model id pins a
18
+ * tool instead, for a reason recorded beside it. This file stays a
19
+ * dependency-free leaf (it is copied verbatim into the desktop), which is why
20
+ * the seat is a token each consumer resolves rather than a call made here.
21
+ *
22
+ * Before 2026-09-21 the sheet tools' model was typed at every use on the desktop
23
+ * and twice more here (an op description and a skill), so three screens quoted
24
+ * a sheet from one typed id while the handler billed from another.
25
+ */
26
+ export const BUILT_IN_TOOLS = ['character-sheet', 'environment-plate', 'grid-extract', 'image-edit'];
27
+ export const DEFAULT_IMAGE_SEAT = 'default-image';
28
+ export const TOOL_SEAT = {
29
+ 'character-sheet': DEFAULT_IMAGE_SEAT,
30
+ 'environment-plate': DEFAULT_IMAGE_SEAT,
31
+ // Pinned: the image viewer offers grid extraction a fixed short list of seats,
32
+ // and the cell-extraction prompt has a variant per seat family. Moving it is a
33
+ // picker and a prompt change first.
34
+ 'grid-extract': 'nano-banana-2',
35
+ // Follows the default image model (2026-09-29). The viewer's Edit box lists
36
+ // every image model from the registry, and the edit handler sends references
37
+ // to each one up to its own cap, so nothing ties this tool to one family. Until
38
+ // then it was pinned to Nano Banana 2 while the app's default was GPT Image 2.5
39
+ // Sunburst, so Edit and the prompt bar opened on different models for the
40
+ // same picture.
41
+ 'image-edit': DEFAULT_IMAGE_SEAT,
42
+ };
43
+ /** Both sheet tools frame one wide image. On GPT Image the aspect is part of the price. */
44
+ export const SHEET_TOOL_ASPECT_RATIO = '16:9';
45
+ /**
46
+ * The GPT Image tier a sheet tool pins. It read 'medium' until 2026-09-09: GPT
47
+ * Image 2.5 renamed the ladder, so the tier GPT Image 2 called `medium` is now
48
+ * `high`, and a stored 'medium' would have kept compiling while naming a
49
+ * materially cheaper tier. Sheets are the identity lane (every downstream shot
50
+ * inherits this frame's likeness), so this is deliberately not the draft tier.
51
+ */
52
+ export const SHEET_TOOL_GPT_QUALITY = 'high';
53
+ //# sourceMappingURL=generation-policy.js.map
@@ -0,0 +1,9 @@
1
+ export type GuideDepth = 'card' | 'index' | 'section' | 'full';
2
+ /** Parse headings outside code fences; maintainer comments never reach agents. */
3
+ export declare function guideSections(content: string): Array<{
4
+ title: string;
5
+ body: string;
6
+ }>;
7
+ /** Bounded retrieval. A missing card never silently expands to the full guide. */
8
+ export declare function retrieveGuide(skill: string, content: string, depth: GuideDepth, query?: string): string;
9
+ //# sourceMappingURL=guide-retrieval.d.ts.map
@@ -0,0 +1,53 @@
1
+ import { craftCard } from './craft-cards.js';
2
+ /** Parse headings outside code fences; maintainer comments never reach agents. */
3
+ export function guideSections(content) {
4
+ const clean = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, '').replace(/<!--[\s\S]*?-->/g, '').trim();
5
+ const sections = [];
6
+ let current = { title: 'Overview', body: '' };
7
+ let fenced = false;
8
+ for (const line of clean.split(/\r?\n/)) {
9
+ if (/^\s*```/.test(line))
10
+ fenced = !fenced;
11
+ if (!fenced && /^#{1,3} /.test(line)) {
12
+ if (current.body.trim())
13
+ sections.push(current);
14
+ current = { title: line.replace(/^#+ /, ''), body: line };
15
+ }
16
+ else
17
+ current.body += '\n' + line;
18
+ }
19
+ if (current.body.trim())
20
+ sections.push(current);
21
+ return sections;
22
+ }
23
+ /** Bounded retrieval. A missing card never silently expands to the full guide. */
24
+ export function retrieveGuide(skill, content, depth, query) {
25
+ const sections = guideSections(content);
26
+ const index = sections.map((s) => `- ${s.title}`).join('\n');
27
+ if (depth === 'full')
28
+ return sections.map((s) => s.body.trim()).join('\n\n');
29
+ if (depth === 'index')
30
+ return index;
31
+ if (query?.trim()) {
32
+ const q = query.trim().toLowerCase();
33
+ // Exact technique IDs return one complete row with its source heading.
34
+ for (const s of sections) {
35
+ const row = s.body.split('\n').find((line) => line.toLowerCase().startsWith(`| \`${q}\` |`));
36
+ if (row)
37
+ return `${s.title}\n\n| Technique | Evidence | What it does | Reach for · skip | Say |\n|---|---|---|---|---|\n${row}`;
38
+ }
39
+ const words = q.split(/[^\p{L}\p{N}-]+/u).filter(Boolean);
40
+ const ranked = sections.map((s) => ({ s, score: words.reduce((n, w) => n + (s.title.toLowerCase().includes(w) ? 4 : s.body.toLowerCase().includes(w) ? 1 : 0), 0) }))
41
+ .filter((x) => x.score > 0).sort((a, b) => b.score - a.score);
42
+ if (!ranked.length)
43
+ return `No section matches "${query}". Available sections:\n${index}`;
44
+ const body = ranked[0].s.body.trim();
45
+ if (body.length <= 6000)
46
+ return body;
47
+ // Never cut a table row or a worked prompt mid-sentence.
48
+ return `Section "${ranked[0].s.title}" is too large for a selective response. Use a technique ID or depth "full".\n${index}`;
49
+ }
50
+ const card = craftCard(skill);
51
+ return `${card ?? sections[0]?.body.trim().slice(0, 1600) ?? 'No overview available.'}\n\nSections (request with query, or depth "full"):\n${index}`;
52
+ }
53
+ //# sourceMappingURL=guide-retrieval.js.map
@@ -8,4 +8,5 @@ export * from './character-sheet.js';
8
8
  export * from './environment-sheet.js';
9
9
  export * from './prompting-tips.js';
10
10
  export * from './asset-label.js';
11
+ export * from './generation-policy.js';
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -28,4 +28,5 @@ export * from './prompting-tips.js';
28
28
  // gallery. Also its own leaf subpath (`@slatesvideo/shared/asset-label`) for the
29
29
  // renderer, which cannot import the root barrel.
30
30
  export * from './asset-label.js';
31
+ export * from './generation-policy.js';
31
32
  //# sourceMappingURL=index.js.map
@@ -47,6 +47,14 @@ export interface VideoResolutionCapability {
47
47
  fixed?: VideoResolution;
48
48
  /** Default when this model is chosen (falls back to `options[0]`). */
49
49
  default?: VideoResolution;
50
+ /**
51
+ * Tiers the provider makes by UPSCALING a smaller native render, keyed to that
52
+ * render. They stay selectable; every surface that offers one says so and says
53
+ * we do not recommend it (Eric, 2026-09-30: "more expensive and they actually
54
+ * look worse"). A refinement pass the provider documents as its own stage, such
55
+ * as H3 Max's 1080p, is not an upscale and is not listed.
56
+ */
57
+ upscaledFrom?: Partial<Record<VideoResolution, VideoResolution>>;
50
58
  }
51
59
  /** Everything a model will ACCEPT. Capability only — never a price. */
52
60
  /**
@@ -86,6 +94,7 @@ export interface VoiceCloneCapability {
86
94
  }
87
95
  export declare const GPT_QUALITY_TIERS: readonly ["low", "medium", "high", "xhigh", "max"];
88
96
  export type GptQuality = (typeof GPT_QUALITY_TIERS)[number];
97
+ export declare const DEFAULT_GPT_QUALITY: GptQuality;
89
98
  export declare const GPT_BACKGROUNDS: readonly ["auto", "transparent", "opaque"];
90
99
  export type GptBackground = (typeof GPT_BACKGROUNDS)[number];
91
100
  export type ImageResolution = '1k' | '2k' | '3k' | '4k';
@@ -128,6 +137,8 @@ export declare function falImageSize(model: string, aspectRatio?: string, resolu
128
137
  };
129
138
  export interface ModelCapability {
130
139
  imageResolutions?: ImageResolution[];
140
+ /** Product default shared by estimates and generation. */
141
+ defaultImageResolution?: ImageResolution;
131
142
  /**
132
143
  * Images ONE request may ask the provider for in a single batch.
133
144
  *
@@ -204,6 +215,8 @@ export declare function voiceCloneFor(model: string): VoiceCloneCapability | und
204
215
  /** Aspect ratios a model accepts, honouring the provider override. */
205
216
  export declare function aspectRatiosFor(model: string, provider?: string): AspectRatio[];
206
217
  /** Video resolutions a model accepts. A FIXED model reports exactly its one value. */
218
+ /** The native render an upscaled tier is made from, or undefined for a native tier. */
219
+ export declare function upscaledFrom(model: string, resolution: string | undefined): VideoResolution | undefined;
207
220
  export declare function videoResolutionsFor(model: string): VideoResolution[];
208
221
  /** The resolution a model would actually run at. Fixed wins; else keep a legal
209
222
  * current value; else the model's own default. Mirrors `clampVideoResolution`. */
@@ -250,7 +263,9 @@ export declare function describeDurations(models: readonly string[]): string;
250
263
  export declare function describeReferenceImageCaps(models: readonly string[]): string;
251
264
  /** H3 Max reference accounting, fal's worked tables read 2026-09-09.
252
265
  * https://fal.ai/models/minimax/h3-max/reference-to-video
253
- * 1080p video-reference pricing is unpublished; never infer it from output rates.
266
+ * 1080p: fal's page, read 2026-09-29: "768p and 1080p output use the same
267
+ * reference-video token counts." So 1080p takes the 768p figure; it was absent
268
+ * until fal published that sentence, and must never be inferred from output rates.
254
269
  */
255
270
  export declare const MINIMAX_MAX_REFERENCE: {
256
271
  readonly freeTokens: 4096;
@@ -265,4 +280,6 @@ export declare function minimaxMaxReferenceTokens(input: {
265
280
  audioSeconds: number;
266
281
  resolution: string;
267
282
  }): number;
283
+ /** Quotes and generation use the same model default. */
284
+ export declare function defaultImageResolutionFor(model: string): ImageResolution;
268
285
  //# sourceMappingURL=model-capabilities.d.ts.map
@@ -74,9 +74,9 @@ const OMNI_FLASH_ASPECT_RATIOS = ['16:9', '9:16'];
74
74
  /** Seedance (both seats, and the edit row): six — notably NO `4:5`. */
75
75
  const SEEDANCE_ASPECT_RATIOS = ['21:9', '16:9', '4:3', '1:1', '3:4', '9:16'];
76
76
  /**
77
- * MiniMax H3, both seats: six. Read off fal's live OpenAPI 2026-08-27 for
78
- * `minimax/h3/text-to-video` and `minimax/h3-max/text-to-video` — identical
79
- * enums. It happens to be the same six Seedance takes; kept as its OWN constant
77
+ * MiniMax H3, all three seats: six. Read off fal's live OpenAPI 2026-08-27 for
78
+ * `minimax/h3/text-to-video` and `minimax/h3-max/text-to-video`, and 2026-09-29
79
+ * for `minimax/h3-max-turbo/text-to-video` — identical enums. It happens to be the same six Seedance takes; kept as its OWN constant
80
80
  * because a provider that adds a ratio adds it to ITS family, and sharing the
81
81
  * Seedance constant would silently move H3 the next time ByteDance moves.
82
82
  *
@@ -101,6 +101,7 @@ const MINIMAX_H3_ASPECT_RATIOS = ['21:9', '16:9', '4:3', '1:1', '3:4', '9:16'];
101
101
  */
102
102
  const LTX_2_5_ASPECT_RATIOS = ['16:9', '9:16'];
103
103
  export const GPT_QUALITY_TIERS = ['low', 'medium', 'high', 'xhigh', 'max'];
104
+ export const DEFAULT_GPT_QUALITY = 'high';
104
105
  export const GPT_BACKGROUNDS = ['auto', 'transparent', 'opaque'];
105
106
  // Product output sizes; schema bounds and metering receipt live in the GPT harvest.
106
107
  export const GPT_IMAGE_25_SIZES = {
@@ -221,6 +222,7 @@ export const AGENT_ROUTE_PROVIDER = 'fal';
221
222
  export const MODEL_CAPABILITIES = {
222
223
  // ── Image models ───────────────────────────────────────────────────────────
223
224
  'nano-banana-2': {
225
+ defaultImageResolution: '2k',
224
226
  imageResolutions: ['1k', '2k', '4k'],
225
227
  // fal's nano-banana-2 schema caps `num_images` at 4 (read 2026-09-09). This
226
228
  // is the ONLY model that batches: the MCP's headless path (no projectId) asks
@@ -231,11 +233,13 @@ export const MODEL_CAPABILITIES = {
231
233
  maxRefImages: 14,
232
234
  },
233
235
  'nano-banana-2-lite': {
236
+ defaultImageResolution: '1k',
234
237
  imageResolutions: ['1k'],
235
238
  aspectRatios: FULL_ASPECT_RATIOS,
236
239
  maxRefImages: 4, // fal edit endpoint caps input images at 4
237
240
  },
238
241
  'nano-banana-pro': {
242
+ defaultImageResolution: '2k',
239
243
  imageResolutions: ['1k', '2k', '4k'],
240
244
  aspectRatios: FULL_ASPECT_RATIOS,
241
245
  maxRefImages: 14,
@@ -276,30 +280,39 @@ export const MODEL_CAPABILITIES = {
276
280
  // limits either: the MCP's 4,000-character prompt against fal's 32,000, and
277
281
  // image quantity, which is a fan-out and has no provider ceiling at all.
278
282
  'gpt-image-2-5-flare': {
283
+ defaultImageResolution: '2k',
279
284
  imageResolutions: ['2k', '3k', '4k'],
280
285
  aspectRatios: ['1:1', '16:9', '9:16', '4:3', '3:4'],
281
286
  maxRefImages: 16,
282
287
  },
283
288
  'gpt-image-2-5-sunburst': {
289
+ defaultImageResolution: '3k',
284
290
  imageResolutions: ['2k', '3k', '4k'],
285
291
  aspectRatios: ['1:1', '16:9', '9:16', '4:3', '3:4'],
286
292
  maxRefImages: 16,
287
293
  },
288
294
  'flux-2-max': {
295
+ defaultImageResolution: '1k',
289
296
  imageResolutions: ['1k', '2k', '4k'],
290
297
  aspectRatios: FULL_ASPECT_RATIOS,
291
298
  maxRefImages: 4,
292
299
  },
293
300
  'seedream-5-lite': {
301
+ defaultImageResolution: '2k',
294
302
  imageResolutions: ['2k', '3k', '4k'],
295
303
  aspectRatios: FULL_ASPECT_RATIOS,
296
304
  maxRefImages: 10,
297
305
  },
298
306
  // ── Kling video ────────────────────────────────────────────────────────────
307
+ // Standard is 720p and Pro is 1080p; 4K is a separate endpoint shared by all
308
+ // four. Kling's own API: "std: ... The output video resolution is 720P.
309
+ // pro: ... The output video resolution is 1080P." The fal Standard endpoint
310
+ // takes no resolution parameter, and every Standard and Omni render we
311
+ // measured came back 1280x720 (2026-09-29). This row said 1080p until then.
299
312
  'kling-v3.0-std': {
300
313
  aspectRatios: KLING_DIRECT_ASPECT_RATIOS,
301
314
  providerAspectRatios: { fal: KLING_FAL_ASPECT_RATIOS },
302
- videoResolution: { options: ['1080p', '4k'] },
315
+ videoResolution: { options: ['720p', '4k'] },
303
316
  // 3, not 5. The op claimed "Kling: 5-15" and refused legal 3-4s takes.
304
317
  duration: { min: 3, max: 15, mode: 'continuous' },
305
318
  maxIngredientImages: 4,
@@ -314,7 +327,8 @@ export const MODEL_CAPABILITIES = {
314
327
  'kling-v3.0-omni': {
315
328
  aspectRatios: KLING_DIRECT_ASPECT_RATIOS,
316
329
  providerAspectRatios: { fal: KLING_FAL_ASPECT_RATIOS },
317
- videoResolution: { options: ['1080p', '4k'] },
330
+ // O3 Standard: 720p, as Standard above. Omni Pro is the 1080p tier.
331
+ videoResolution: { options: ['720p', '4k'] },
318
332
  duration: { min: 3, max: 15, mode: 'continuous' },
319
333
  maxIngredientImages: 4,
320
334
  },
@@ -435,10 +449,9 @@ export const MODEL_CAPABILITIES = {
435
449
  // ['480p','720p','1080p']. There is still NO 4K on 2.5 (2.0 is the only
436
450
  // Seedance with one), which is what keeps `is4kVideoKey` version-blind.
437
451
  //
438
- // DEFAULT STAYS 720p, deliberately: a 30s take at 1080p is ~614 credits
439
- // against a 1,000-credit welcome grant, and that is at the promotional
440
- // 1080p rate — it rises when the promo lapses. Reaching a tier and
441
- // defaulting to it are different decisions.
452
+ // DEFAULT STAYS 720p, deliberately: a 30s take at 1080p is ~853 credits
453
+ // against a 1,000-credit welcome grant. Reaching a tier and defaulting to it
454
+ // are different decisions.
442
455
  videoResolution: { options: ['480p', '720p', '1080p'], default: '720p' },
443
456
  duration: { min: 4, max: 30, mode: 'continuous' },
444
457
  maxIngredientImages: 30,
@@ -472,21 +485,25 @@ export const MODEL_CAPABILITIES = {
472
485
  // NO multimodal reference caps, deliberately: on an edit row the clip IS the
473
486
  // canvas and arrives through `sourceVideo`, not as a reference.
474
487
  },
475
- // ── MiniMax H3 (both seats on fal — added 2026-08-27) ──────────────────────
488
+ // ── MiniMax H3 (three seats on fal — base and Max added 2026-08-27, Max
489
+ // Turbo 2026-09-29) ─────────────────────────────────────────────────────
476
490
  //
477
- // Every value below is READ OFF fal's live OpenAPI, fetched 2026-08-27:
491
+ // Every value below is READ OFF fal's live OpenAPI, fetched 2026-08-27, and
492
+ // re-read 2026-09-29 for the Max 1080p tier and the Turbo row:
478
493
  // minimax/h3/{text-to-video,image-to-video,reference-to-video}
479
494
  // minimax/h3-max/{text-to-video,image-to-video,reference-to-video}
495
+ // minimax/h3-max-turbo/{text-to-video,image-to-video} (reference-to-video 404s)
480
496
  // 🚨 CORRECTED 2026-09-09: `minimax/h3-max/reference-to-video` DOES exist —
481
497
  // 9 images, 3 videos, 3 audio. The earlier note here said it 404s; the schema
482
498
  // had never been read. Both rows now declare the full omni-reference set, and
483
499
  // every cap on the Max row was re-read on the Max endpoint rather than copied
484
500
  // down from the base row.
485
501
  //
486
- // 🚨 NEVER PREFIX-MATCH THESE TWO IDS. `minimax-h3-max` starts with
487
- // `minimax-h3`, so any `startsWith('minimax-h3')` swallows the Max row into
488
- // the base row's branch — a different ladder AND a different price at the one
489
- // tier they share. Every lookup downstream is an exact-id map, not a prefix.
502
+ // 🚨 NEVER PREFIX-MATCH THESE THREE IDS. `minimax-h3-max-turbo` starts with
503
+ // `minimax-h3-max`, which starts with `minimax-h3`, so any `startsWith`
504
+ // swallows a row into its neighbour's branch — a different ladder AND a
505
+ // different price at the tiers they share. Every lookup downstream is an
506
+ // exact-id map, not a prefix.
490
507
  'minimax-h3': {
491
508
  // fal reference-to-video schema, 2026-09-09: each audio clip is 2-15s.
492
509
  referenceAudioDuration: { min: 2, max: 15 },
@@ -501,7 +518,7 @@ export const MODEL_CAPABILITIES = {
501
518
  // trained to output and the one every benchmark quotes; 2K is a 2.2x price
502
519
  // step and 4K a 2.7x step, and reaching a tier is a different decision from
503
520
  // defaulting to it (same reasoning that keeps Seedance 2.5 on 720p).
504
- videoResolution: { options: ['480p', '768p', '2k', '4k'], default: '768p' },
521
+ videoResolution: { options: ['480p', '768p', '2k', '4k'], default: '768p', upscaledFrom: { '2k': '768p', '4k': '768p' } },
505
522
  // 5, not 4. MiniMax's own model card says 4-15s; fal's schema — which is
506
523
  // what our request actually hits — says `minimum: 5`. The endpoint wins.
507
524
  duration: { min: 5, max: 15, mode: 'continuous' },
@@ -548,8 +565,28 @@ export const MODEL_CAPABILITIES = {
548
565
  // arms — quoted off this endpoint, not inherited.
549
566
  maxReferenceVideoSeconds: 15,
550
567
  maxReferenceAudioSeconds: 15,
551
- videoResolution: { options: ['480p', '768p'], default: '768p' },
568
+ // 1080P is on all three h3-max endpoints' enum (fal schema, 2026-09-09 and
569
+ // 2026-09-29), described as "1080P latent refinement from a native 768P
570
+ // source": a refinement, not a native generation and not base H3's
571
+ // 2K/4K upscaler. It was keyed 2026-09-09 and pulled 2026-09-10 because the
572
+ // 1080p video-reference token rate was unpublished; fal now states it
573
+ // (see MINIMAX_MAX_REFERENCE). Default stays 768p, the native tier.
574
+ videoResolution: { options: ['480p', '768p', '1080p'], default: '768p' },
575
+ duration: { min: 5, max: 15, mode: 'continuous' },
576
+ },
577
+ 'minimax-h3-max-turbo': {
578
+ aspectRatios: MINIMAX_H3_ASPECT_RATIOS,
579
+ // fal schema for both Turbo endpoints, 2026-09-29: enum
580
+ // ["480P","768P","1080P"], default "768P", the same 1080P "latent
581
+ // refinement from a native 768P source" as the Max row.
582
+ videoResolution: { options: ['480p', '768p', '1080p'], default: '768p' },
583
+ // duration: integer, minimum 5, maximum 15, default 5 (both endpoints).
552
584
  duration: { min: 5, max: 15, mode: 'continuous' },
585
+ // NO reference capacity: `minimax/h3-max-turbo/reference-to-video` returns
586
+ // 404 from fal's OpenAPI endpoint (2026-09-29), and neither published
587
+ // endpoint carries a reference array. Start and end frames ride
588
+ // image-to-video and live in MODEL_REGISTRY.features.lastFrame.
589
+ maxIngredientImages: 0,
553
590
  },
554
591
  // ── LTX-2.5 (both seats on fal — added 2026-08-29) ─────────────────────────
555
592
  //
@@ -706,6 +743,12 @@ export function aspectRatiosFor(model, provider) {
706
743
  return cap.aspectRatios;
707
744
  }
708
745
  /** Video resolutions a model accepts. A FIXED model reports exactly its one value. */
746
+ /** The native render an upscaled tier is made from, or undefined for a native tier. */
747
+ export function upscaledFrom(model, resolution) {
748
+ if (!resolution)
749
+ return undefined;
750
+ return MODEL_CAPABILITIES[model]?.videoResolution?.upscaledFrom?.[resolution];
751
+ }
709
752
  export function videoResolutionsFor(model) {
710
753
  const vr = MODEL_CAPABILITIES[model]?.videoResolution;
711
754
  if (!vr)
@@ -928,14 +971,16 @@ export function describeReferenceImageCaps(models) {
928
971
  }
929
972
  /** H3 Max reference accounting, fal's worked tables read 2026-09-09.
930
973
  * https://fal.ai/models/minimax/h3-max/reference-to-video
931
- * 1080p video-reference pricing is unpublished; never infer it from output rates.
974
+ * 1080p: fal's page, read 2026-09-29: "768p and 1080p output use the same
975
+ * reference-video token counts." So 1080p takes the 768p figure; it was absent
976
+ * until fal published that sentence, and must never be inferred from output rates.
932
977
  */
933
978
  export const MINIMAX_MAX_REFERENCE = {
934
979
  freeTokens: 4096,
935
980
  imagePixelsPerToken: 1024,
936
981
  normalizedImageEdge: 1024,
937
982
  audioTokensPerSecond: 80,
938
- videoTokensPerSecond: { '480p': 2886, '768p': 7459.2 },
983
+ videoTokensPerSecond: { '480p': 2886, '768p': 7459.2, '1080p': 7459.2 },
939
984
  };
940
985
  export function minimaxMaxReferenceTokens(input) {
941
986
  const rate = MINIMAX_MAX_REFERENCE.videoTokensPerSecond[input.resolution];
@@ -950,4 +995,12 @@ export function minimaxMaxReferenceTokens(input) {
950
995
  input.videoSeconds * (rate ?? 0) + input.audioSeconds * MINIMAX_MAX_REFERENCE.audioTokensPerSecond -
951
996
  MINIMAX_MAX_REFERENCE.freeTokens));
952
997
  }
998
+ /** Quotes and generation use the same model default. */
999
+ export function defaultImageResolutionFor(model) {
1000
+ const c = MODEL_CAPABILITIES[model];
1001
+ if (!c?.defaultImageResolution || !c.imageResolutions?.includes(c.defaultImageResolution)) {
1002
+ throw new Error(`Missing or invalid image default for ${model}`);
1003
+ }
1004
+ return c.defaultImageResolution;
1005
+ }
953
1006
  //# sourceMappingURL=model-capabilities.js.map
@@ -1,3 +1,14 @@
1
+ /** Connected host, not an API model: capabilities are checked at runtime. */
2
+ export declare const CHATGPT_IMAGE_HOST: {
3
+ readonly id: "chatgpt-account";
4
+ readonly label: "ChatGPT";
5
+ readonly note: "Generate with your connected ChatGPT account";
6
+ readonly usage: "Uses your ChatGPT account limits";
7
+ };
8
+ import { type BuiltInTool } from './generation-policy.js';
9
+ /** Authoring presets only: these do not claim hosted ChatGPT API capabilities. */
10
+ export declare const CHATGPT_FRAMING_RATIOS: import("./model-capabilities.js").AspectRatio[];
11
+ export declare function composeChatGptFraming(prompt: string, aspectRatio?: string): string;
1
12
  export interface ModelFact {
2
13
  id: string;
3
14
  label: string;
@@ -10,6 +21,39 @@ export interface ModelFact {
10
21
  * only while that substring is unique, which is how isOmniFlashModel broke.
11
22
  */
12
23
  route: 'generate' | 'edit';
24
+ /**
25
+ * Where this seat sits in the routing story, as DATA rather than as a word
26
+ * inside `notes`. `default` is the seat an agent (or a web page) reaches for
27
+ * when nothing about the shot argues otherwise: exactly ONE per kind per
28
+ * route, asserted at module load below. `specialist` is picked for a named
29
+ * reason the notes give (premium, speed, volume, audio, cheapest, edit fidelity).
30
+ * `niche` is never the default and never headlines; the notes say why.
31
+ *
32
+ * WHY A FIELD: "DEFAULT" lived only as a word inside notes. The 2026-08-10
33
+ * Seedance 2.5 commit wrote "the DEFAULT video model" into Seedance 2.0's note
34
+ * meaning the default SEEDANCE seat (a bare "seedance" resolves to 2.0), and for
35
+ * a month two rows read as the default while the routing doctrine then in force
36
+ * (Eric, 2026-07-03: Kling 3.0 the general-purpose default, Seedance the premium
37
+ * escalation) never changed. The marketing site meanwhile headlined Veo, a
38
+ * `niche` row, because no check could read a tier out of prose. The tier is
39
+ * DATA now; the notes describe, they do not rank.
40
+ *
41
+ * CURRENT DOCTRINE (Eric, 2026-09-13, superseding 2026-07-03): SEEDANCE 2.5 IS
42
+ * THE DEFAULT VIDEO MODEL, in the app picker and in agent routing — "it's the
43
+ * best in the world". 2.0 is the specialist for native 4K and for the same
44
+ * resolution cheaper; Kling is the specialist for cost-effective start-frame,
45
+ * performance and lip-sync work and the only engine behind Motion Transfer and
46
+ * Lip Sync. Recorded in the vault's prompting-ssot.md the same day.
47
+ *
48
+ * IMAGE (Eric, 2026-09-15): GPT IMAGE 2.5 SUNBURST IS THE DEFAULT IMAGE MODEL, at
49
+ * `high` and 3k, in agent routing as it has been in the app picker since
50
+ * 2026-09-09. Nano Banana 2 keeps the only headless path, so an image call
51
+ * with no project still runs there. Changing the
52
+ * default lives in this tier field; quality/resolution live in model-capabilities.ts.
53
+ * Consumers and generated skill partials derive them. slates-web reads this to order its model lineup
54
+ * and to fail its build when a niche seat is named more often than the default.
55
+ */
56
+ tier: 'default' | 'specialist' | 'niche';
13
57
  /** Max reference images (image models) — null if not applicable. */
14
58
  maxRefImages: number | null;
15
59
  /** Max ingredient images (video models) — null if not applicable. */
@@ -67,6 +111,21 @@ export declare function seedanceTaskIntentWords(prompt: string): string[];
67
111
  /** Every model that reads reference video and/or audio, for op descriptions. */
68
112
  export declare function multimodalRefModels(): string[];
69
113
  export declare const MODEL_FACTS: ModelFact[];
114
+ /**
115
+ * The one `default` seat for a kind on the generate route, READ from `tier`.
116
+ * Anything that needs "the default image model" calls this instead of naming
117
+ * one: the op surface and slates-web both hand-typed nano-banana-2 for six days
118
+ * after the app picker moved to Sunburst.
119
+ */
120
+ export declare function defaultModelFor(kind: ModelFact['kind']): string;
121
+ /**
122
+ * The seat a built-in tool renders on when its caller names no model. Resolves
123
+ * `TOOL_SEAT` (generation-policy.ts, THE home): `'default-image'` follows the
124
+ * default image seat above, a model id pins the tool. The desktop resolves the
125
+ * same table through its generated mirror, so an op description, the skill
126
+ * partial and the handler that bills cannot name different models.
127
+ */
128
+ export declare function toolModelFor(tool: BuiltInTool): string;
70
129
  /**
71
130
  * Routing prose for one lane, generated from the SSOT.
72
131
  *