@nodaro/prompts 1.39.0 → 1.41.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/{chunk-XIWWDGT5.cjs → chunk-2EW5B7ZV.cjs} +7 -7
- package/dist/{chunk-XIWWDGT5.cjs.map → chunk-2EW5B7ZV.cjs.map} +1 -1
- package/dist/{chunk-22KZOAGL.cjs → chunk-C3NHNECH.cjs} +86 -48
- package/dist/chunk-C3NHNECH.cjs.map +1 -0
- package/dist/{chunk-XU54YJXQ.js → chunk-HQTHDTHN.js} +85 -49
- package/dist/chunk-HQTHDTHN.js.map +1 -0
- package/dist/{chunk-KUBZVWME.cjs → chunk-LXPKTI2K.cjs} +12 -5
- package/dist/chunk-LXPKTI2K.cjs.map +1 -0
- package/dist/{chunk-3INXNDV7.cjs → chunk-LZ53F5BN.cjs} +8 -8
- package/dist/chunk-LZ53F5BN.cjs.map +1 -0
- package/dist/{chunk-7XMTVYS5.js → chunk-N6VMPG5S.js} +12 -6
- package/dist/chunk-N6VMPG5S.js.map +1 -0
- package/dist/{chunk-XRLOMTBZ.js → chunk-QBUSJGV3.js} +84 -5
- package/dist/chunk-QBUSJGV3.js.map +1 -0
- package/dist/{chunk-6MPZHFIS.js → chunk-RB2F5JQF.js} +5 -5
- package/dist/chunk-RB2F5JQF.js.map +1 -0
- package/dist/{chunk-NMWKXAYA.cjs → chunk-XTOSNRWT.cjs} +84 -5
- package/dist/chunk-XTOSNRWT.cjs.map +1 -0
- package/dist/{chunk-Z3SZXWRU.js → chunk-YS6HOWMW.js} +3 -3
- package/dist/{chunk-Z3SZXWRU.js.map → chunk-YS6HOWMW.js.map} +1 -1
- package/dist/entries/assembly.cjs +30 -22
- package/dist/entries/assembly.d.cts +90 -34
- package/dist/entries/assembly.d.ts +90 -34
- package/dist/entries/assembly.js +1 -1
- package/dist/entries/doctrine.cjs +46 -46
- package/dist/entries/doctrine.js +1 -1
- package/dist/entries/presets.cjs +32 -32
- package/dist/entries/presets.d.cts +2 -0
- package/dist/entries/presets.d.ts +2 -0
- package/dist/entries/presets.js +2 -2
- package/dist/entries/prompt-fields.cjs +26 -26
- package/dist/entries/prompt-fields.d.cts +2 -1
- package/dist/entries/prompt-fields.d.ts +2 -1
- package/dist/entries/prompt-fields.js +2 -2
- package/dist/index.cjs +133 -125
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -5
- package/package.json +2 -2
- package/src/__tests__/factory-presets-prototype-keys.test.ts +43 -0
- package/src/__tests__/frame-reference-slots.test.ts +43 -0
- package/src/__tests__/gemini-omni-inputs.test.ts +12 -10
- package/src/__tests__/provider-prompt-doctrine.test.ts +14 -0
- package/src/__tests__/veo-i2v-inputs.test.ts +16 -11
- package/src/entries/assembly.ts +1 -0
- package/src/factory-presets/index.ts +4 -1
- package/src/factory-presets/references.ts +2 -1
- package/src/frame-reference-slots.ts +92 -0
- package/src/gemini-omni-inputs.ts +23 -22
- package/src/node-prompt-fields.ts +5 -3
- package/src/own-entry.ts +11 -0
- package/src/prompt-wizard-categories.ts +6 -0
- package/src/provider-prompt-doctrine.ts +89 -3
- package/src/ref-binding.ts +28 -3
- package/src/resolve-prompt.ts +2 -1
- package/src/seedance-2-inputs.ts +15 -40
- package/src/veo-i2v-inputs.ts +24 -17
- package/src/video-reference-resolver.ts +1 -1
- package/dist/chunk-22KZOAGL.cjs.map +0 -1
- package/dist/chunk-3INXNDV7.cjs.map +0 -1
- package/dist/chunk-6MPZHFIS.js.map +0 -1
- package/dist/chunk-7XMTVYS5.js.map +0 -1
- package/dist/chunk-KUBZVWME.cjs.map +0 -1
- package/dist/chunk-NMWKXAYA.cjs.map +0 -1
- package/dist/chunk-XRLOMTBZ.js.map +0 -1
- package/dist/chunk-XU54YJXQ.js.map +0 -1
|
@@ -245,7 +245,7 @@ const GEMINI_OMNI_DOCTRINE: ProviderPromptDoctrine = {
|
|
|
245
245
|
"Multimodal Google video with native audio: text-to-video, image-to-video, and video-edit through the same prompt surface. 4/6/8/10s; 720p/1080p or 4K tier.",
|
|
246
246
|
"Structure like the platform default: subject → action → scene → lighting → camera → style. Quote dialogue lines to have them spoken; describe SFX/ambience plainly in the prompt.",
|
|
247
247
|
"Text-to-video REQUIRES a concrete aspect ratio (the API hard-rejects a missing one); image runs infer aspect from the input.",
|
|
248
|
-
"Reference images ride
|
|
248
|
+
"Reference images ride in image_urls, your references first — bind them by ordinal ('the woman from @image_1'); a wired start frame is appended after them and bound as the opening frame. Video-edit: describe the change.",
|
|
249
249
|
],
|
|
250
250
|
doctrine: `Prompt structure (no public Google prompt guide exists for the Omni video endpoint —
|
|
251
251
|
the API contract is the doctrine source, like MiniMax H3; structure guidance mirrors the
|
|
@@ -254,7 +254,7 @@ subject → action → scene/environment → lighting → camera movement → st
|
|
|
254
254
|
|
|
255
255
|
**Modes (picked from the wired inputs)**
|
|
256
256
|
- Nothing visual → text-to-video. A concrete aspect ratio is REQUIRED — the API hard-rejects a missing one (Nodaro sends the node's ratio; there is no adaptive).
|
|
257
|
-
- Image(s) wired → image-to-video
|
|
257
|
+
- Image(s) wired → image-to-video. Your reference images come FIRST, in your order (\`@image_1\`, \`@image_2\`, …) — bind each in the prompt ("the woman from @image_1", "the interior from @image_2"); a wired start frame is appended after them and the platform names it as the opening frame.
|
|
258
258
|
- Source video wired → video-edit (served through the same handle): describe the CHANGE ("replace the daylight with dusk, keep the motion and framing"), not a full re-description.
|
|
259
259
|
|
|
260
260
|
**Audio (native)**
|
|
@@ -284,6 +284,7 @@ descriptions of static/unchanged parts — spend the words on what MOVES.
|
|
|
284
284
|
**Image-to-video (the primary mode)**
|
|
285
285
|
- The input image is the FIRST FRAME, not a loose reference: composition, subject identity, and visual style carry over. Describe motion and camera only ("she turns toward the window as the camera slowly pushes in"); re-describing the still wastes adherence.
|
|
286
286
|
- grok-imagine-video-1.5 accepts up to 7 images (identity/scene references beyond the first frame); at 1080p only ONE image is allowed.
|
|
287
|
+
- grok-i2v with reference images: images are addressed \`@image(n)\` in array order. Your references come first (\`@image(1)\` is your first reference); a wired start frame is appended after them and the platform names it as the opening frame.
|
|
287
288
|
|
|
288
289
|
**Audio (video-1.5)**
|
|
289
290
|
- Native audio generates with the clip — background music, SFX, and lip-synced dialogue. Quote the spoken line; describe the music/SFX plainly. There is no audio toggle on the KIE contract — cue (or exclude) sound in the prompt text.
|
|
@@ -382,6 +383,89 @@ Source: KIE Wan 3.0 market contract (docs.kie.ai/market/wan/3-0-video,
|
|
|
382
383
|
docs.kie.ai/market/wan/3-0-video-prime). Captured 2026-09-01.`,
|
|
383
384
|
}
|
|
384
385
|
|
|
386
|
+
// Vidu Q4 — no public Q4 prompt guide is captured, so like Wan 3.0 / MiniMax H3
|
|
387
|
+
// the body is KIE contract facts plus the platform-standard structure. The
|
|
388
|
+
// contract names references "reference image 2" / "reference audio 1"; the
|
|
389
|
+
// editor's reference tokens emit the platform-wide `@image_N` / `@audio_N`
|
|
390
|
+
// binding instead (see ref-binding.ts), which is what this doctrine teaches.
|
|
391
|
+
const VIDU_Q4_DOCTRINE: ProviderPromptDoctrine = {
|
|
392
|
+
providers: ["vidu-q4"],
|
|
393
|
+
heading: "Vidu Q4 (vidu-q4)",
|
|
394
|
+
tips: [
|
|
395
|
+
"A start frame alone animates that frame (prompt optional). Any reference image or audio switches to reference mode: the prompt is REQUIRED and the start frame joins the reference images.",
|
|
396
|
+
"Reference mode: 1-15 images, up to 3 audio clips (MP3 only, 3-12s each). Name each by ordinal — \"the man from @image_2 says … in the voice of @audio_1\".",
|
|
397
|
+
"No text-only runs and no end frame: wire a start frame or at least one reference image.",
|
|
398
|
+
"3-16s (default 5); 540p / 720p (default) / 1080p / 2K / 4K; reference runs take 16:9 / 9:16 / 1:1 / 4:3 / 3:4. Prompt cap 20,000 chars.",
|
|
399
|
+
"`audio` is ON by default (synchronized sound). Cue the sound you want or state the exclusion; dialogue is not verified here, so plan key lines as a TTS + lip-sync pass.",
|
|
400
|
+
],
|
|
401
|
+
doctrine: `Prompt structure (no public Vidu Q4 prompt guide is captured — the KIE API contract is the
|
|
402
|
+
doctrine source; platform-standard structure applies):
|
|
403
|
+
subject → action → scene/environment → lighting → camera movement → style → constraints.
|
|
404
|
+
|
|
405
|
+
**Modes (picked by what is wired)**
|
|
406
|
+
- Start frame only → image-to-video. The frame anchors the shot and its aspect ratio; describe MOTION and camera, not the still. The prompt is optional.
|
|
407
|
+
- Any reference image or reference audio → reference-to-video. The prompt is REQUIRED. A wired start frame cannot be sent as a frame here, so the platform adds it to the reference images (after the caller's own, ordinals unchanged) and names it in the prompt as the opening frame.
|
|
408
|
+
- There is no text-only mode and no first+last frame mode.
|
|
409
|
+
|
|
410
|
+
**Reference binding**
|
|
411
|
+
- Refer to inputs by ordinal in attachment order. In the editor the reference tokens emit "@image_N" / "@audio_N" — write the binding explicitly ("the woman from @image_1 turns to the man from @image_2 and says …, in the voice of @audio_1").
|
|
412
|
+
- Caps: 1-15 reference images (JPG / PNG / WEBP) and 0-3 reference audio clips. Audio must be MP3, each 3-12 seconds — the platform refuses any other format or length before the run starts.
|
|
413
|
+
|
|
414
|
+
**Duration, resolution, aspect**
|
|
415
|
+
- 3-16 seconds (default 5). 540p / 720p (default) / 1080p / 2K / 4K.
|
|
416
|
+
- Image-to-video has no aspect setting — the output inherits the start frame's shape. Reference runs take 16:9 (default) / 9:16 / 1:1 / 4:3 / 3:4.
|
|
417
|
+
- Prompts up to 20,000 characters.
|
|
418
|
+
|
|
419
|
+
**Audio**
|
|
420
|
+
- The "audio" boolean defaults ON and generates synchronized sound with the clip (the contract says "dialogue and sound effects"). Describe the soundscape plainly or state the exclusion, or turn the toggle off for a silent clip.
|
|
421
|
+
|
|
422
|
+
Source: KIE Vidu Q4 market contracts (docs.kie.ai/market/vidu-q4/image-to-video,
|
|
423
|
+
docs.kie.ai/market/vidu-q4/reference-to-video). Captured 2026-10-17.`,
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Kling 4.0 — two KIE endpoints, two ids. No public Kling 4.0 prompt guide is
|
|
427
|
+
// captured, so like Vidu Q4 the body is KIE contract facts plus the
|
|
428
|
+
// platform-standard structure. The provider runner maps the platform's
|
|
429
|
+
// `@image_N` / `@audio_N` tokens and wired characters onto Kling's own names
|
|
430
|
+
// (`@image_1`, `@keyframe_1`, a subject's `@Name`, a voice's `@VoiceN`) and drops
|
|
431
|
+
// the `@` from any mention the request does not declare, which KIE rejects.
|
|
432
|
+
const KLING_4_DOCTRINE: ProviderPromptDoctrine = {
|
|
433
|
+
providers: ["kling-4", "kling-4-keyframes"],
|
|
434
|
+
heading: "Kling 4.0 (kling-4, kling-4-keyframes)",
|
|
435
|
+
tips: [
|
|
436
|
+
"kling-4: text only, or up to 10 images, 5 videos (2-30s), 7 characters and 3 voices (15 in all). Name each: @image_1, @video_1, a character's @Name, a voice clip @audio_1.",
|
|
437
|
+
"kling-4-keyframes: start frame, up to 8 in-between frames (Image References, in order), end frame — @first_frame, @keyframe_1 …, @last_frame. 3s takes 4 frames in all, 9s or longer 10.",
|
|
438
|
+
"3-30s (default 5), 720p (default) / 1080p, native audio always on. A reference video lends its camera moves and pacing; keep-original-sound keeps its own audio.",
|
|
439
|
+
"Prompt cap 8,000 chars. Aspect auto / 16:9 / 9:16 / 1:1 / 21:9 — Keyframes follows the start frame's shape once a frame is attached.",
|
|
440
|
+
],
|
|
441
|
+
doctrine: `Prompt structure (no public Kling 4.0 prompt guide is captured — the KIE API contract is the
|
|
442
|
+
doctrine source; platform-standard structure applies):
|
|
443
|
+
subject → action → scene/environment → lighting → camera movement → style → constraints.
|
|
444
|
+
|
|
445
|
+
**Two models**
|
|
446
|
+
- Kling 4.0 (kling-4) — reference-to-video. With no materials it is plain text-to-video. Materials: up to 10 reference images, 5 reference videos (2-30 seconds each), 7 subjects (characters) and 3 voices, at most 15 together. A wired start frame cannot be a frame here, so the platform adds it to the reference images and names it as the opening frame.
|
|
447
|
+
- Kling 4.0 Keyframes (kling-4-keyframes) — keyframe-to-video. A start frame, up to 8 in-between frames in time order, and an end frame; the story passes through each. With no frame it is text-to-video.
|
|
448
|
+
|
|
449
|
+
**Naming materials in the prompt**
|
|
450
|
+
- Every material is named with "@": @image_1 … for reference images, @video_1 … for reference videos, a character by its own name (@Maya), a voice clip as @audio_1 (sent to Kling as a named voice). Keyframes: @first_frame, @keyframe_1 … @keyframe_8, @last_frame.
|
|
451
|
+
- Kling refuses a prompt that names a material the request does not carry; the platform removes the "@" from such a mention so the run still starts — wire the material if the mention matters.
|
|
452
|
+
- Write the binding explicitly: "@Maya walks through the street from @image_1 and greets the camera in the voice of @audio_1".
|
|
453
|
+
|
|
454
|
+
**Keyframe rules**
|
|
455
|
+
- Total frames (start + in-between + end) are capped by duration: 3s → 4, 4s → 5, 5s → 6, 6s → 7, 7s → 8, 8s → 9, 9s or longer → 10. Describe how the shot moves from one frame to the next.
|
|
456
|
+
- Once any frame is attached the output takes the start frame's shape; aspect ratio applies to text-only runs.
|
|
457
|
+
|
|
458
|
+
**Reference videos**
|
|
459
|
+
- A reference video lends its camera moves and pacing — swap the characters or products in the prompt ("recreate the camera move of @video_1 with …"). Keep original sound keeps the clip's own audio; otherwise new sound is generated.
|
|
460
|
+
|
|
461
|
+
**Duration, resolution, audio**
|
|
462
|
+
- 3-30 seconds (default 5); 720p (default) or 1080p; aspect auto (follows the materials, 16:9 with none) / 16:9 / 9:16 / 1:1 / 21:9. Prompts up to 8,000 characters.
|
|
463
|
+
- Native audio is always generated (the contract cites lip-sync and dialogue in 10+ languages); not yet verified for scripted lines here, so plan key dialogue as a TTS + lip-sync pass.
|
|
464
|
+
|
|
465
|
+
Source: KIE Kling 4.0 market contracts (docs.kie.ai/market/kling/v4-reference-to-video,
|
|
466
|
+
docs.kie.ai/market/kling/v4-keyframe-to-video), live-probed 2026-10-23.`,
|
|
467
|
+
}
|
|
468
|
+
|
|
385
469
|
const HAPPYHORSE_DOCTRINE: ProviderPromptDoctrine = {
|
|
386
470
|
providers: ["happyhorse", "happyhorse-i2v", "happyhorse-ref2v", "happyhorse-edit"],
|
|
387
471
|
heading: "HappyHorse 1.1 (happyhorse, happyhorse-i2v, happyhorse-ref2v)",
|
|
@@ -398,7 +482,7 @@ subject → action → scene/environment → lighting → camera movement → st
|
|
|
398
482
|
**Contract facts (KIE, per-mode pages)**
|
|
399
483
|
- Prompts: any language, up to 5000 non-Chinese / 2500 Chinese characters — excess is TRUNCATED silently, so put the load-bearing content first.
|
|
400
484
|
- Duration 3-15s (default 5), billed per second. Resolution 720p / 1080p (default). Aspect 16:9 (default) / 9:16 / 1:1 / 4:3 / 3:4.
|
|
401
|
-
- Modes: text-to-video (happyhorse), image-to-video (happyhorse-i2v), reference-to-video (happyhorse-ref2v) — ref2v preserves wired identities; name each reference in the prompt so the binding is explicit.
|
|
485
|
+
- Modes: text-to-video (happyhorse), image-to-video (happyhorse-i2v), reference-to-video (happyhorse-ref2v) — ref2v preserves wired identities; name each reference in the prompt so the binding is explicit, as \`[Image N]\` in array order: your references come first (\`[Image 1]\` is your first), and a wired start frame is appended after them and named as the opening frame.
|
|
402
486
|
|
|
403
487
|
**Style guidance (platform-standard, honestly generic)**
|
|
404
488
|
- One camera movement per shot; physical, quantified action ("slowly raises a hand") over abstract emotion words; state exclusions ("no on-screen text, no watermark") in the prompt.
|
|
@@ -440,6 +524,8 @@ export const PROVIDER_PROMPT_DOCTRINES: readonly ProviderPromptDoctrine[] = [
|
|
|
440
524
|
GROK_IMAGINE_DOCTRINE,
|
|
441
525
|
WAN_DOCTRINE,
|
|
442
526
|
WAN_3_DOCTRINE,
|
|
527
|
+
VIDU_Q4_DOCTRINE,
|
|
528
|
+
KLING_4_DOCTRINE,
|
|
443
529
|
HAPPYHORSE_DOCTRINE,
|
|
444
530
|
RUNWAY_KIE_DOCTRINE,
|
|
445
531
|
]
|
package/src/ref-binding.ts
CHANGED
|
@@ -28,10 +28,14 @@
|
|
|
28
28
|
* says the two things a multimodal model needs to hear — match exactly, and
|
|
29
29
|
* these are not frames. One spelling; both resolvers ride it.
|
|
30
30
|
*/
|
|
31
|
-
export function identityRefsSentence(
|
|
31
|
+
export function identityRefsSentence(
|
|
32
|
+
firstOrdinal: number,
|
|
33
|
+
lastOrdinal: number,
|
|
34
|
+
ordinal: (n: number) => string = REF_BINDING.ordinal,
|
|
35
|
+
): string {
|
|
32
36
|
return firstOrdinal === lastOrdinal
|
|
33
|
-
? `${
|
|
34
|
-
: `${
|
|
37
|
+
? `${ordinal(firstOrdinal)} is an identity reference for this shot's subjects — match its subject's exact appearance; it is not a frame.`
|
|
38
|
+
: `${ordinal(firstOrdinal)} through ${ordinal(lastOrdinal)} are identity references for this shot's subjects — match each subject's exact appearance; they are not frames.`
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
export const REF_BINDING = {
|
|
@@ -44,6 +48,27 @@ export const REF_BINDING = {
|
|
|
44
48
|
`Use @image_${n} as the ${role} (${role === "opening" ? "first" : "last"}) frame of the video.`,
|
|
45
49
|
} as const
|
|
46
50
|
|
|
51
|
+
/** A binding sentence's ordinal + frame directive in one provider syntax. */
|
|
52
|
+
export interface ImageBinding {
|
|
53
|
+
ordinal: (n: number) => string
|
|
54
|
+
frame: (n: number, role: "opening" | "closing") => string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The image binding in a provider's OWN reference syntax
|
|
59
|
+
* (`videoProviderReferenceSyntax` in @nodaro/shared). `@image_N` IS
|
|
60
|
+
* {@link REF_BINDING} — the platform swap point, byte for byte; the other two
|
|
61
|
+
* spell the same sentence in the form the provider documents.
|
|
62
|
+
*/
|
|
63
|
+
export function imageBindingFor(syntax: "@image_N" | "@image(n)" | "[Image N]"): ImageBinding {
|
|
64
|
+
if (syntax === "@image_N") return { ordinal: REF_BINDING.ordinal, frame: REF_BINDING.frame }
|
|
65
|
+
const ordinal = syntax === "@image(n)" ? (n: number) => `@image(${n})` : (n: number) => `[Image ${n}]`
|
|
66
|
+
return {
|
|
67
|
+
ordinal,
|
|
68
|
+
frame: (n, role) => `Use ${ordinal(n)} as the ${role} (${role === "opening" ? "first" : "last"}) frame of the video.`,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
47
72
|
/**
|
|
48
73
|
* The instruction that makes Seedance EDIT a wired clip rather than use it as a
|
|
49
74
|
* style reference — the Video to Video node's Seedance lane prepends it to the
|
package/src/resolve-prompt.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { resolveNodeRefs } from "@nodaro/shared/workflow"
|
|
|
2
2
|
import { readPromptAffixes, type PromptAffixes } from "@nodaro/shared/nodes"
|
|
3
3
|
import { SOCIAL_POST_NODE_TYPES } from "@nodaro/shared/social"
|
|
4
4
|
import { joinSentences } from "./hint-join.js"
|
|
5
|
+
import { ownEntry } from "./own-entry.js"
|
|
5
6
|
|
|
6
7
|
export interface ResolvePromptArgs {
|
|
7
8
|
override?: string
|
|
@@ -165,7 +166,7 @@ export function computeNodePrompt(
|
|
|
165
166
|
? [data.directText as string | undefined]
|
|
166
167
|
: []
|
|
167
168
|
} else {
|
|
168
|
-
const fields = NODE_PROMPT_CANDIDATE_FIELDS
|
|
169
|
+
const fields = ownEntry(NODE_PROMPT_CANDIDATE_FIELDS, nodeType) ?? ["prompt"]
|
|
169
170
|
typed = fields.map((f) => data[f] as string | undefined)
|
|
170
171
|
}
|
|
171
172
|
return resolvePrompt({ override, typed, wired, refMap, appendWired, affixes: readPromptAffixes(data) })
|
package/src/seedance-2-inputs.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SEEDANCE_2_REF_LIMITS } from "@nodaro/shared/models"
|
|
2
|
-
import {
|
|
2
|
+
import { placeFramesAfterReferences } from "./frame-reference-slots.js"
|
|
3
3
|
|
|
4
4
|
export type Seedance2Mode = "first-frame" | "first-last-frame" | "reference"
|
|
5
5
|
|
|
@@ -46,18 +46,7 @@ const cleanList = (xs?: readonly string[]): string[] =>
|
|
|
46
46
|
* reference_image_urls (after the user's own images, so their @Image ordinals do
|
|
47
47
|
* not shift) and named in a doctrine-compliant prompt suffix.
|
|
48
48
|
*/
|
|
49
|
-
|
|
50
|
-
* e.g. the gvp overlap transport's colon-position directive ("use @image_1
|
|
51
|
-
* as the first frame, it is the last keyframe of @video_1"). Field finding
|
|
52
|
-
* (2026-07-20): the first-frame directive only works adjacent to the extend
|
|
53
|
-
* colon; a duplicate sentence at the END of the prompt (this resolver's
|
|
54
|
-
* default position) dilutes it back into coin-flip behavior — so when the
|
|
55
|
-
* prompt carries its own binding, the suffix is suppressed (the frame image
|
|
56
|
-
* itself still rides the reference list). */
|
|
57
|
-
export function promptBindsFirstFrame(prompt: string | undefined): boolean {
|
|
58
|
-
if (!prompt) return false
|
|
59
|
-
return /@image_\d+\s+as\s+the\s+(first|opening)\s*(\(first\))?\s*frame/i.test(prompt)
|
|
60
|
-
}
|
|
49
|
+
export { promptBindsFirstFrame } from "./frame-reference-slots.js"
|
|
61
50
|
|
|
62
51
|
export function resolveSeedance2Inputs(args: Seedance2InputsArgs): Seedance2InputsResult {
|
|
63
52
|
const limits = args.limits ?? SEEDANCE_2_REF_LIMITS
|
|
@@ -85,33 +74,19 @@ export function resolveSeedance2Inputs(args: Seedance2InputsArgs): Seedance2Inpu
|
|
|
85
74
|
}
|
|
86
75
|
|
|
87
76
|
// Reference mode: keep both frames (explicit intent), drop trailing user images
|
|
88
|
-
// if the
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
let promptSuffix = ""
|
|
102
|
-
const suppressFrameSentence = promptBindsFirstFrame(args.prompt)
|
|
103
|
-
if (suppressFrameSentence && firstOrdinal > 0 && lastOrdinal === 0) {
|
|
104
|
-
// The prompt already binds the first frame at its own (working) position.
|
|
105
|
-
} else if (firstOrdinal > 0 && lastOrdinal > 0) {
|
|
106
|
-
// Combined sentence — REF_BINDING.frame() emits a single-frame sentence, so use
|
|
107
|
-
// REF_BINDING.ordinal() for each ordinal inline to keep the combined wording AND
|
|
108
|
-
// route both ordinals through the single swap-point.
|
|
109
|
-
promptSuffix = `Use ${REF_BINDING.ordinal(firstOrdinal)} as the opening (first) frame and ${REF_BINDING.ordinal(lastOrdinal)} as the closing (last) frame of the video.`
|
|
110
|
-
} else if (firstOrdinal > 0) {
|
|
111
|
-
promptSuffix = REF_BINDING.frame(firstOrdinal, "opening")
|
|
112
|
-
} else if (lastOrdinal > 0) {
|
|
113
|
-
promptSuffix = REF_BINDING.frame(lastOrdinal, "closing")
|
|
114
|
-
}
|
|
77
|
+
// if the image cap is exceeded. Frames are appended AFTER the kept user images
|
|
78
|
+
// so existing user @Image ordinals are preserved — the platform convention
|
|
79
|
+
// (placeFramesAfterReferences), shared with every frames-in-the-list model.
|
|
80
|
+
const placed = placeFramesAfterReferences({
|
|
81
|
+
prompt: args.prompt,
|
|
82
|
+
firstFrameUrl,
|
|
83
|
+
lastFrameUrl,
|
|
84
|
+
refImageUrls: refImages,
|
|
85
|
+
imageCap: limits.images,
|
|
86
|
+
})
|
|
87
|
+
const referenceImageUrls = placed.imageUrls
|
|
88
|
+
const promptSuffix = placed.promptSuffix
|
|
89
|
+
const droppedRefImages = placed.droppedRefImages
|
|
115
90
|
|
|
116
91
|
return {
|
|
117
92
|
mode: "reference",
|
package/src/veo-i2v-inputs.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { identityRefsSentence, REF_BINDING } from "./video-reference-resolver.js"
|
|
1
|
+
import { placeFramesAfterReferences } from "./frame-reference-slots.js"
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* VEO 3.x i2v input resolution — the mutually-exclusive sibling of
|
|
@@ -8,13 +7,15 @@ import { identityRefsSentence, REF_BINDING } from "./video-reference-resolver.js
|
|
|
8
7
|
* [first(, last)] frames; REFERENCE_2_VIDEO reads every entry as a
|
|
9
8
|
* reference ingredient. Frames and identities cannot ride separate
|
|
10
9
|
* channels, so an anchored call that must carry identity references moves
|
|
11
|
-
* to REFERENCE_2_VIDEO with the
|
|
12
|
-
*
|
|
13
|
-
* same as seedance-2's reference mode).
|
|
10
|
+
* to REFERENCE_2_VIDEO with the frames bound in prose (requested, not
|
|
11
|
+
* pixel-guaranteed — the accepted trade, same as seedance-2's reference mode).
|
|
14
12
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* THE PLATFORM CONVENTION (2026-10-18, `placeFramesAfterReferences`): the
|
|
14
|
+
* caller's references take the first seats, so their image token N is their
|
|
15
|
+
* Nth reference; the start frame — and the end frame, VEO takes one — follow
|
|
16
|
+
* them, bound as the opening / closing frame. At the 3-ingredient cap the
|
|
17
|
+
* frames are kept (explicit intent) and trailing references drop, reported.
|
|
18
|
+
* (Until then the anchor took seat 1 and an end frame was surrendered.)
|
|
18
19
|
*
|
|
19
20
|
* BYTE-IDENTICAL with no references: plain frame mode, frames kept, no
|
|
20
21
|
* generationType, no suffix — exactly what every veo i2v call has always
|
|
@@ -30,14 +31,15 @@ export interface VeoI2vInputsArgs {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
export interface VeoI2vInputsResult {
|
|
33
|
-
/** The `imageUrls` payload: frames in plain mode, [
|
|
34
|
+
/** The `imageUrls` payload: frames in plain mode, [...refs, start, end?] in
|
|
34
35
|
* reference mode — never more than VEO's 3-ingredient cap. */
|
|
35
36
|
imageUrls: string[]
|
|
36
37
|
/** Present (REFERENCE_2_VIDEO) exactly when references ride. */
|
|
37
38
|
generationType?: "REFERENCE_2_VIDEO"
|
|
38
39
|
promptSuffix: string
|
|
39
40
|
droppedRefImages: number
|
|
40
|
-
/**
|
|
41
|
+
/** Always false since the frames-after-references convention: an end frame
|
|
42
|
+
* is kept in reference mode. Kept for callers that log it. */
|
|
41
43
|
droppedEndFrame: boolean
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -55,14 +57,19 @@ export function resolveVeoI2vInputs(args: VeoI2vInputsArgs): VeoI2vInputsResult
|
|
|
55
57
|
droppedEndFrame: false,
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
const placed = placeFramesAfterReferences({
|
|
61
|
+
prompt: args.prompt,
|
|
62
|
+
firstFrameUrl: args.firstFrameUrl,
|
|
63
|
+
lastFrameUrl: args.endFrameUrl,
|
|
64
|
+
refImageUrls: refs,
|
|
65
|
+
imageCap: VEO_INGREDIENT_SLOTS,
|
|
66
|
+
bindIdentities: true,
|
|
67
|
+
})
|
|
61
68
|
return {
|
|
62
|
-
imageUrls:
|
|
69
|
+
imageUrls: placed.imageUrls,
|
|
63
70
|
generationType: "REFERENCE_2_VIDEO",
|
|
64
|
-
promptSuffix:
|
|
65
|
-
droppedRefImages,
|
|
66
|
-
droppedEndFrame:
|
|
71
|
+
promptSuffix: placed.promptSuffix,
|
|
72
|
+
droppedRefImages: placed.droppedRefImages,
|
|
73
|
+
droppedEndFrame: false,
|
|
67
74
|
}
|
|
68
75
|
}
|
|
@@ -54,7 +54,7 @@ import { tidyReferenceTokenGaps, unboundReferenceTokenText } from "./unbound-ref
|
|
|
54
54
|
// The binding surface string and the id-addressed token resolver live in their
|
|
55
55
|
// own modules (see them for the contracts); re-exported here so every existing
|
|
56
56
|
// importer of this module — and the package index's `export *` — keeps working.
|
|
57
|
-
export { REF_BINDING, identityRefsSentence, SEEDANCE_VIDEO_EDIT_PREFIX, buildSeedanceVideoEditPrompt } from "./ref-binding.js"
|
|
57
|
+
export { REF_BINDING, identityRefsSentence, imageBindingFor, SEEDANCE_VIDEO_EDIT_PREFIX, buildSeedanceVideoEditPrompt, type ImageBinding } from "./ref-binding.js"
|
|
58
58
|
export { resolveRefIdTokens, type RefIdTokenContext } from "./ref-id-tokens.js"
|
|
59
59
|
|
|
60
60
|
|