@slatesvideo/shared 0.6.11 → 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.
- package/dist/auth.js +2 -2
- package/dist/clients/cloud.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/manual/content.d.ts +1 -1
- package/dist/manual/content.js +1 -1
- package/dist/operations/index.d.ts +817 -16
- package/dist/operations/index.js +1410 -360
- package/dist/operations/surface.d.ts +3 -1
- package/dist/operations/surface.js +37 -10
- package/dist/prompts/ad-presets.d.ts +77 -0
- package/dist/prompts/ad-presets.js +43 -0
- package/dist/prompts/agent-doctrine.js +5 -4
- package/dist/prompts/banned-tokens.d.ts +4 -29
- package/dist/prompts/banned-tokens.js +29 -204
- package/dist/prompts/craft-cards.js +2 -2
- package/dist/prompts/generation-policy.d.ts +41 -0
- package/dist/prompts/generation-policy.js +53 -0
- package/dist/prompts/guide-retrieval.d.ts +9 -0
- package/dist/prompts/guide-retrieval.js +53 -0
- package/dist/prompts/index.d.ts +1 -0
- package/dist/prompts/index.js +1 -0
- package/dist/prompts/model-capabilities.d.ts +18 -1
- package/dist/prompts/model-capabilities.js +72 -19
- package/dist/prompts/model-facts.d.ts +34 -2
- package/dist/prompts/model-facts.js +66 -5
- package/dist/prompts/partials.generated.js +8 -2
- package/dist/prompts/prompting-tips.d.ts +1 -1
- package/dist/prompts/prompting-tips.js +61 -16
- package/dist/prompts/reference-composer.d.ts +2 -0
- package/dist/prompts/reference-composer.js +51 -50
- package/dist/prompts/script-document.d.ts +165 -0
- package/dist/prompts/script-document.js +11 -0
- package/dist/prompts/shot-grammar.d.ts +4 -4
- package/dist/prompts/shot-grammar.js +3 -3
- package/dist/prompts/shot-spec.d.ts +13 -0
- package/dist/prompts/shot-spec.js +23 -5
- package/dist/skills/content.js +26 -23
- package/exports/slates-chatgpt-images/generated/SKILL.md +107 -0
- package/exports/slates-chatgpt-images/generated/slates-chatgpt-images.skill +0 -0
- package/exports/slates-prompt-builder/generated/SKILL.md +1 -1
- package/exports/slates-prompt-builder/generated/reference-character.md +9 -1
- package/exports/slates-prompt-builder/generated/reference-kling.md +3 -3
- package/exports/slates-prompt-builder/generated/reference-nano-banana.md +22 -10
- package/exports/slates-prompt-builder/generated/reference-seedance.md +4 -4
- package/exports/slates-prompt-builder/generated/slates-prompt-builder-manifest.json +17 -17
- package/exports/slates-prompt-builder/generated/slates-prompt-builder.skill +0 -0
- package/package.json +10 -4
- package/skills/_partials/cinematic-card.md +8 -0
- package/skills/_partials/cinematic-routes-short.md +2 -0
- package/skills/_partials/cinematic-tips-short.md +2 -0
- package/skills/_partials/decision-log.md +1 -13
- package/skills/_partials/image-defaults.md +11 -0
- package/skills/_partials/lens-video-split.md +1 -0
- package/skills/_partials/reference-rules-core.md +1 -1
- package/skills/_partials/sheet-tool-defaults.md +6 -0
- package/skills/slates-character-identity.md +9 -1
- package/skills/slates-chatgpt-images.md +107 -0
- package/skills/slates-cinematic-look.md +237 -0
- package/skills/slates-cost-discipline.md +18 -12
- package/skills/slates-direct-response-ad.md +13 -53
- package/skills/slates-edit-and-iterate.md +1 -1
- package/skills/slates-model-selection.md +20 -14
- package/skills/slates-one-prompt-film.md +19 -77
- package/skills/slates-project-organization.md +7 -3
- package/skills/slates-prompting-flux-2-max.md +15 -4
- package/skills/slates-prompting-gpt-image-2-5.md +41 -28
- package/skills/slates-prompting-kling-v3.md +3 -3
- package/skills/slates-prompting-lip-sync.md +1 -1
- package/skills/slates-prompting-minimax-h3.md +30 -17
- package/skills/slates-prompting-motion-transfer.md +1 -1
- package/skills/slates-prompting-nano-banana-2.md +24 -11
- package/skills/slates-prompting-seedance-2-5.md +7 -6
- package/skills/slates-prompting-seedance.md +5 -5
- package/skills/slates-prompting-seedream-5-lite.md +14 -3
- package/skills/slates-prompting-veo-3.md +1 -1
- package/skills/slates-script-craft.md +45 -0
- package/skills/slates-shot-variety.md +11 -40
- package/skills/slates-storyboard-from-script.md +14 -66
- package/skills/slates-style-prompting.md +54 -54
- package/skills/slates-ugc-influencer-ad.md +32 -309
- 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
|
package/dist/prompts/index.d.ts
CHANGED
package/dist/prompts/index.js
CHANGED
|
@@ -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
|
|
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,
|
|
78
|
-
* `minimax/h3/text-to-video` and `minimax/h3-max/text-to-video
|
|
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: ['
|
|
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
|
-
|
|
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 ~
|
|
439
|
-
// against a 1,000-credit welcome grant
|
|
440
|
-
//
|
|
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 (
|
|
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
|
|
487
|
-
// `minimax-h3`,
|
|
488
|
-
//
|
|
489
|
-
//
|
|
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
|
-
|
|
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
|
|
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;
|
|
@@ -32,8 +43,14 @@ export interface ModelFact {
|
|
|
32
43
|
* best in the world". 2.0 is the specialist for native 4K and for the same
|
|
33
44
|
* resolution cheaper; Kling is the specialist for cost-effective start-frame,
|
|
34
45
|
* performance and lip-sync work and the only engine behind Motion Transfer and
|
|
35
|
-
* Lip Sync. Recorded in the vault's prompting-ssot.md the same day.
|
|
36
|
-
*
|
|
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
|
|
37
54
|
* and to fail its build when a niche seat is named more often than the default.
|
|
38
55
|
*/
|
|
39
56
|
tier: 'default' | 'specialist' | 'niche';
|
|
@@ -94,6 +111,21 @@ export declare function seedanceTaskIntentWords(prompt: string): string[];
|
|
|
94
111
|
/** Every model that reads reference video and/or audio, for op descriptions. */
|
|
95
112
|
export declare function multimodalRefModels(): string[];
|
|
96
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;
|
|
97
129
|
/**
|
|
98
130
|
* Routing prose for one lane, generated from the SSOT.
|
|
99
131
|
*
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/** Connected host, not an API model: capabilities are checked at runtime. */
|
|
2
|
+
export const CHATGPT_IMAGE_HOST = {
|
|
3
|
+
id: 'chatgpt-account', label: 'ChatGPT',
|
|
4
|
+
note: 'Generate with your connected ChatGPT account',
|
|
5
|
+
usage: 'Uses your ChatGPT account limits',
|
|
6
|
+
};
|
|
1
7
|
// Per-model prompting facts — routing doctrine and the prompt formula, as
|
|
2
8
|
// KNOWLEDGE (for skills + lead-magnet + op descriptions).
|
|
3
9
|
//
|
|
@@ -26,6 +32,19 @@
|
|
|
26
32
|
// Relative cost claims STAY ("dearer than 2.0 at every shared tier") — that is
|
|
27
33
|
// routing. The figures go, because those are data.
|
|
28
34
|
import { MODEL_CAPABILITIES } from './model-capabilities.js';
|
|
35
|
+
import { DEFAULT_IMAGE_SEAT, TOOL_SEAT } from './generation-policy.js';
|
|
36
|
+
/** Authoring presets only: these do not claim hosted ChatGPT API capabilities. */
|
|
37
|
+
export const CHATGPT_FRAMING_RATIOS = MODEL_CAPABILITIES['gpt-image-2-5-sunburst'].aspectRatios;
|
|
38
|
+
export function composeChatGptFraming(prompt, aspectRatio) {
|
|
39
|
+
if (aspectRatio === undefined)
|
|
40
|
+
return prompt;
|
|
41
|
+
if (!CHATGPT_FRAMING_RATIOS.includes(aspectRatio)) {
|
|
42
|
+
throw new Error('Choose a supported ChatGPT framing preset');
|
|
43
|
+
}
|
|
44
|
+
// Reuse may contain our previous appended request. Replace only this exact suffix.
|
|
45
|
+
const base = prompt.replace(/\n\nRequested output framing: \d+:\d+ aspect ratio\.$/, '');
|
|
46
|
+
return `${base}\n\nRequested output framing: ${aspectRatio} aspect ratio.`;
|
|
47
|
+
}
|
|
29
48
|
/**
|
|
30
49
|
* Reference caps for a fact, read out of the capability SSOT.
|
|
31
50
|
*
|
|
@@ -120,7 +139,7 @@ export const MODEL_FACTS = [
|
|
|
120
139
|
{
|
|
121
140
|
id: 'nano-banana-2',
|
|
122
141
|
route: 'generate',
|
|
123
|
-
tier: '
|
|
142
|
+
tier: 'specialist',
|
|
124
143
|
// Gemini 3.1 FLASH Image — verified against the runtime slug map in
|
|
125
144
|
// slate/src/main/api/google.ts. Nano Banana PRO is a different model
|
|
126
145
|
// (gemini-3-pro-image-preview); do not conflate them.
|
|
@@ -128,7 +147,7 @@ export const MODEL_FACTS = [
|
|
|
128
147
|
kind: 'image',
|
|
129
148
|
// 14 = 10 object-fidelity + 4 character-consistency; the categories don't trade.
|
|
130
149
|
...caps('nano-banana-2'),
|
|
131
|
-
notes: '
|
|
150
|
+
notes: 'The all-rounder and the only image seat with a headless path: holds many subjects coherently in one frame, and the start-frame for legible in-scene text. Knowledge cutoff Jan 2025: anything later needs reference images.',
|
|
132
151
|
},
|
|
133
152
|
{
|
|
134
153
|
id: 'nano-banana-2-lite',
|
|
@@ -160,11 +179,11 @@ export const MODEL_FACTS = [
|
|
|
160
179
|
{
|
|
161
180
|
id: 'gpt-image-2-5-sunburst',
|
|
162
181
|
route: 'generate',
|
|
163
|
-
tier: '
|
|
182
|
+
tier: 'default',
|
|
164
183
|
label: 'GPT Image 2.5 Sunburst',
|
|
165
184
|
kind: 'image',
|
|
166
185
|
...caps('gpt-image-2-5-sunburst'),
|
|
167
|
-
notes: 'THE QUALITY GPT IMAGE SEAT — OpenAI\'s most capable image model, higher quality than GPT Image 2, same price as Flare, deliberately SLOWER. Route here
|
|
186
|
+
notes: 'THE QUALITY GPT IMAGE SEAT — OpenAI\'s most capable image model, higher quality than GPT Image 2, same price as Flare, deliberately SLOWER. Route here unless speed is the point: finals, hero frames, photoreal people, and multi-reference edits where every reference must survive into one frame — its widest lead. Explore on Flare, finish on Sunburst.',
|
|
168
187
|
},
|
|
169
188
|
{
|
|
170
189
|
id: 'flux-2-max',
|
|
@@ -293,7 +312,19 @@ export const MODEL_FACTS = [
|
|
|
293
312
|
// from the base row: "Audio cannot be the only reference input; provide at
|
|
294
313
|
// least one reference image or video with it."
|
|
295
314
|
audioRefNeedsCompanion: true,
|
|
296
|
-
notes: 'THE SPEED SEAT, and the DEARER one at the tier they share — never the cheap H3 and never the default. fal\'s post-train of the H3 weights: MEASURED 2026-08-27 at about 12x faster than base H3 on the same prompt and params, queue to finished file, plus a thin vendor-reported quality edge. It
|
|
315
|
+
notes: 'THE SPEED SEAT, and the DEARER one at the tier they share — never the cheap H3 and never the default. fal\'s post-train of the H3 weights: MEASURED 2026-08-27 at about 12x faster than base H3 on the same prompt and params, queue to finished file, plus a thin vendor-reported quality edge. It tops out at a 1080p refinement of its 768p render. It takes the same omni-reference set as base H3 and animates start and end frames — but not both in one call: its reference endpoint has no start/end-frame fields, where base H3\'s does. Never describe this row as taking no image or reference input. Route here when a fast turnaround on text-to-video or a start-frame shot is worth the premium.',
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
id: 'minimax-h3-max-turbo',
|
|
319
|
+
route: 'generate',
|
|
320
|
+
tier: 'specialist',
|
|
321
|
+
label: 'MiniMax H3 Max Turbo',
|
|
322
|
+
kind: 'video',
|
|
323
|
+
// Added 2026-09-29. No reference caps: fal publishes text-to-video and
|
|
324
|
+
// image-to-video for Turbo and its reference-to-video returns 404, so
|
|
325
|
+
// `caps()` returns nulls and the composer refuses references.
|
|
326
|
+
...caps('minimax-h3-max-turbo'),
|
|
327
|
+
notes: 'THE BUDGET SEAT of the MiniMax family: a second fal post-train of the H3 weights, billed at half H3 Max\'s rate at every tier. Its 1080p is a refinement of the native 768p render, not a native 1080p generation. INPUTS ARE FRAMES, NOT REFERENCES: text-to-video and start/end frames only, with no reference endpoint, so reference-driven consistency goes to H3 Max or base H3. Route here for drafts, volume and cheap coverage, then re-run the keeper on H3 Max or a hero seat.',
|
|
297
328
|
},
|
|
298
329
|
{
|
|
299
330
|
id: 'ltx-2-5',
|
|
@@ -362,7 +393,37 @@ for (const kind of ['image', 'video', 'audio']) {
|
|
|
362
393
|
}
|
|
363
394
|
}
|
|
364
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* The one `default` seat for a kind on the generate route, READ from `tier`.
|
|
398
|
+
* Anything that needs "the default image model" calls this instead of naming
|
|
399
|
+
* one: the op surface and slates-web both hand-typed nano-banana-2 for six days
|
|
400
|
+
* after the app picker moved to Sunburst.
|
|
401
|
+
*/
|
|
402
|
+
export function defaultModelFor(kind) {
|
|
403
|
+
const fact = MODEL_FACTS.find((f) => f.kind === kind && f.route === 'generate' && f.tier === 'default');
|
|
404
|
+
if (!fact)
|
|
405
|
+
throw new Error(`MODEL_FACTS: no default ${kind} seat on the generate route`);
|
|
406
|
+
return fact.id;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* The seat a built-in tool renders on when its caller names no model. Resolves
|
|
410
|
+
* `TOOL_SEAT` (generation-policy.ts, THE home): `'default-image'` follows the
|
|
411
|
+
* default image seat above, a model id pins the tool. The desktop resolves the
|
|
412
|
+
* same table through its generated mirror, so an op description, the skill
|
|
413
|
+
* partial and the handler that bills cannot name different models.
|
|
414
|
+
*/
|
|
415
|
+
export function toolModelFor(tool) {
|
|
416
|
+
const seat = TOOL_SEAT[tool];
|
|
417
|
+
return seat === DEFAULT_IMAGE_SEAT ? defaultModelFor('image') : seat;
|
|
418
|
+
}
|
|
365
419
|
const FACT_BY_ID = new Map(MODEL_FACTS.map((m) => [m.id, m]));
|
|
420
|
+
// A pinned tool seat must be a live image model, or the tool quotes and fires
|
|
421
|
+
// an id nothing can render. Asserted at load, like the one-default-per-kind rule.
|
|
422
|
+
for (const [tool, seat] of Object.entries(TOOL_SEAT)) {
|
|
423
|
+
if (seat !== DEFAULT_IMAGE_SEAT && FACT_BY_ID.get(seat)?.kind !== 'image') {
|
|
424
|
+
throw new Error(`TOOL_SEAT: ${tool} is pinned to "${seat}", which is not an image model in MODEL_FACTS`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
366
427
|
/**
|
|
367
428
|
* Routing prose for one lane, generated from the SSOT.
|
|
368
429
|
*
|