@nodaro/prompts 1.51.0 → 1.53.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/index.js CHANGED
@@ -2,13 +2,13 @@ export { BODY_CAPTIONS_PRESET_ID, BRAND_PRESETS, BRAND_PRESET_IDS, BRAND_PRESET_
2
2
  export { readDirectionFields, readStructuredFields, readSubjectFields } from './chunk-ZRORO6OM.js';
3
3
  export { TEXT_REQUIRED_NODE_TYPES, WIRED_OUTPUT_NODE_TYPES, computeAiWriterInput, computeNodeSendText, stripAudioTags } from './chunk-YS6HOWMW.js';
4
4
  export { NODE_PROMPT_CANDIDATE_FIELDS, NODE_PROMPT_FIELDS, PROMPT_AFFIX_CORE_FIELD_OVERRIDES, PROMPT_AFFIX_NODE_TYPES, applyPromptAffixes, composeNegative, computeLlmChatFields, computeNodePrompt, computeScriptTopic, getPromptFields, getSnippetMedia, joinPromptParts, nodeHasInlinePrompt, nodeHasPromptField, nodeSupportsPromptAffixes, promptAffixCoreField, promptFieldCarriesAffixes, promptPartSeparator, resolvePrompt } from './chunk-N6VMPG5S.js';
5
- export { REF_BINDING, SEEDANCE_VIDEO_EDIT_PREFIX, appendField, appendMusicMeta, assembleImageInput, assembleSunoInput, buildSeedanceVideoEditPrompt, buildSurroundFillPrompt, composeSoundHintFromConnections, composeVideoPromptText, getEffectiveSunoCustomMode, identityRefsSentence, imageBindingFor, placeFramesAfterReferences, planFrameDelivery, promptBindsFirstFrame, resolveGeminiOmniI2vInputs, resolveRefIdTokens, resolveReferenceTokens, resolveSeedance2Inputs, resolveVeoI2vInputs, resolveVideoReferenceCore, truncateForField } from './chunk-HETYHGGW.js';
5
+ export { REF_BINDING, SEEDANCE_VIDEO_EDIT_PREFIX, appendField, appendMusicMeta, assembleImageInput, assembleSunoInput, buildSeedanceVideoEditPrompt, buildSurroundFillPrompt, composeSoundHintFromConnections, composeVideoPromptText, getEffectiveSunoCustomMode, identityRefsSentence, imageBindingFor, placeFramesAfterReferences, planFrameDelivery, promptBindsFirstFrame, resolveGeminiOmniI2vInputs, resolveRefIdTokens, resolveReferenceTokens, resolveSeedance2Inputs, resolveVeoI2vInputs, resolveVideoReferenceCore, truncateForField } from './chunk-XR7RAHZI.js';
6
6
  export { ANGLE_LABELS, ASPECT_RATIO_LABELS, MAX_SUBJECT_KEYS, MOVEMENT_LABELS, SCENE_PROMPT_MAX_LENGTH, SHOT_LABELS, STYLE_SECTION_GAP, STYLE_SECTION_HEADER, SUBJECT_ARRAY_CEILING, SUBJECT_CUSTOM_AGE_KEY, SUBJECT_FIELDS, SUBJECT_FOLD_KEYS, SUBJECT_ID_MAX_CHARS, SUBJECT_IMAGE_HINT_MODE_DEFAULT, SUBJECT_KEYS, SUBJECT_KEY_MAX_CHARS, SUBJECT_VIDEO_HINT_MODE_DEFAULT, appendReferenceLines, applyReferenceOrderToVideo, asBodyClauses, buildIdentityDirectives, buildImagePrompt, buildImagePromptSegments, buildImagePromptWithOverflow, buildReferenceBlocks, buildScenePrompt, composeSectionedPrompt, endsInsideStyleSection, expandImagePositionRefs, expandImageRefTokens, getRegisteredSubjectKeys, insertBeforeStyleSection, normalizeSubjectFields, partitionStyleClauses, referenceDescriptionLine, renderDescribedReferenceLines, renderReferenceCaptionLines, renderStructuredFields, renderStyleSection, renderSubjectHints, resolveCharacterMentions, resolveLocationMentions, sectionedClauseCosts, splitStyleSection, styleSectionFromClauses, styleSlotFor, subjectFieldsForSurface, truncateText } from './chunk-YAIPYPMM.js';
7
7
  import './chunk-GILUVQQJ.js';
8
8
  export { keepableDirectionHints } from './chunk-JZOPMVGY.js';
9
9
  export { PROMPT_HINT_SEPARATOR, joinPromptHints } from './chunk-35UHMDGS.js';
10
10
  export { computePackSidecarCoverage } from './chunk-FDOG7BNB.js';
11
- export { AD_CREATIVE_ANALYSIS_FIELDS, AD_CREATIVE_ANALYSIS_SYSTEM_PROMPT, AUDIO_WIZARD_CATEGORIES, DEFAULT_OWN_MOTION, IMAGE_REFERENCE_PROMPT_DOCTRINE, IMAGE_WIZARD_CATEGORIES, LLM_CHAT_WIZARD_CATEGORIES, MUSIC_WIZARD_CATEGORIES, OWN_MOTION_NODE_TYPES, POST_CONTENT_ANALYSIS_SYSTEM_PROMPT, PROVIDER_CAPABILITIES, PROVIDER_PROMPT_DOCTRINES, REFERENCE_IMAGE_ROLES, SCENE3D_FIGURE_REFERENCE_RULE, SCENE3D_FREE_PLATE_SLOTS, SCENE3D_LAYOUT_REFERENCE_SCOPING_FIXTURE, SCENE3D_LAYOUT_REFERENCE_SCOPING_LINE, SCENE3D_LAYOUT_SCOPING_FOR, SCENE3D_LAYOUT_SCOPING_IGNORE, SCENE3D_LAYOUT_SCOPING_LOOK_SOURCE, SCENE3D_LAYOUT_SCOPING_MARKER, SCENE3D_UNREFERENCED_FIGURES_WARNING_CODE, TEXT_WIZARD_CATEGORIES, VIDEO_WIZARD_CATEGORIES, appendScene3DStillScopingLines, buildAdCreativeAnalysisUserText, buildCharacterPrompt, buildCreaturePrompt, buildFaceTemplateInputs, buildLocationMotionPrompt, buildLocationPrompt, buildLocationRefinePrompt, buildMotionPrompt, buildObjectMotionPrompt, buildObjectPrompt, buildScene3DLayoutScopingLine, buildScene3DUnreferencedFiguresWarning, getCategoriesForNodeType, getPromptDoctrine, getPromptTips, hasScene3DLayoutScopingLine, isWizardSupported, ownMotionHint, renderScene3DLayoutScopingLine, scene3DLayoutVideoCaptions } from './chunk-T4TZY5ST.js';
11
+ export { AD_CREATIVE_ANALYSIS_FIELDS, AD_CREATIVE_ANALYSIS_SYSTEM_PROMPT, AUDIO_WIZARD_CATEGORIES, DEFAULT_OWN_MOTION, IMAGE_REFERENCE_PROMPT_DOCTRINE, IMAGE_WIZARD_CATEGORIES, LLM_CHAT_WIZARD_CATEGORIES, MUSIC_WIZARD_CATEGORIES, OWN_MOTION_NODE_TYPES, POST_CONTENT_ANALYSIS_SYSTEM_PROMPT, PROVIDER_CAPABILITIES, PROVIDER_PROMPT_DOCTRINES, REFERENCE_IMAGE_ROLES, SCENE3D_FIGURE_REFERENCE_RULE, SCENE3D_FREE_PLATE_SLOTS, SCENE3D_LAYOUT_REFERENCE_SCOPING_FIXTURE, SCENE3D_LAYOUT_REFERENCE_SCOPING_LINE, SCENE3D_LAYOUT_SCOPING_FOR, SCENE3D_LAYOUT_SCOPING_IGNORE, SCENE3D_LAYOUT_SCOPING_LOOK_SOURCE, SCENE3D_LAYOUT_SCOPING_MARKER, SCENE3D_UNREFERENCED_FIGURES_WARNING_CODE, TEXT_WIZARD_CATEGORIES, VIDEO_WIZARD_CATEGORIES, appendScene3DStillScopingLines, buildAdCreativeAnalysisUserText, buildCharacterPrompt, buildCreaturePrompt, buildFaceTemplateInputs, buildLocationMotionPrompt, buildLocationPrompt, buildLocationRefinePrompt, buildMotionPrompt, buildObjectMotionPrompt, buildObjectPrompt, buildScene3DLayoutScopingLine, buildScene3DUnreferencedFiguresWarning, getCategoriesForNodeType, getPromptDoctrine, getPromptTips, hasScene3DLayoutScopingLine, isWizardSupported, ownMotionHint, renderScene3DLayoutScopingLine, scene3DLayoutVideoCaptions } from './chunk-PHCYQWJP.js';
12
12
  export { CINEMATIC_LOOK_TAIL, FILM_STILL_PREFIX, NEUTRAL_PERSON, REALISM_FINISH, REALISM_PASS_INSTRUCTION, REALISM_SKIN, REFERENCE_RULES, REFERENCE_RULES_MULTI_PERSON, SCENE_FRAME_RULE, filmStillPrefix, realismEmptyHands, realismViewpoint, referenceRulesBlock } from './chunk-T64D26P4.js';
13
13
  export { DEFAULT_IDENTITY_LOCK, DEFAULT_TEMPLATES, applyTemplate, buildIdentityLockLine, characterLockToRefLock, collectIdentityLockClause, getIdentityLockClause, hasUpstreamCharacter, resolveTemplate, toIdentityLockMode, withForcedIdentityLock } from './chunk-BQWIPLLR.js';
14
14
  import './chunk-ADMHDFHB.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodaro/prompts",
3
- "version": "1.51.0",
3
+ "version": "1.53.0",
4
4
  "description": "Nodaro's prompt-engineering layer — person/picker catalogs with prompt hints, identity-lock clauses, entity prompt builders, brand presets, and prompt/reference assembly shared by the Nodaro platform and SDK.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",
@@ -70,7 +70,7 @@
70
70
  "test": "vitest run"
71
71
  },
72
72
  "dependencies": {
73
- "@nodaro/shared": "^3.56.0"
73
+ "@nodaro/shared": "^3.58.0"
74
74
  },
75
75
  "devDependencies": {
76
76
  "tsup": "^8.5.0",
@@ -30,6 +30,22 @@ describe("placeFramesAfterReferences", () => {
30
30
  )
31
31
  })
32
32
 
33
+ it("writes plain `image N` sentences that say what each image is", () => {
34
+ const start = placeFramesAfterReferences({ firstFrameUrl: "F", refImageUrls: R(2), imageCap: 6, syntax: "image N", bindIdentities: true })
35
+ expect(start.promptSuffix).toBe(
36
+ "image 3 is the opening (first) frame of the video. image 1 and image 2 are references for the subject; they are not frames.",
37
+ )
38
+ const both = placeFramesAfterReferences({ firstFrameUrl: "F", lastFrameUrl: "L", refImageUrls: R(3), imageCap: 6, syntax: "image N", bindIdentities: true })
39
+ expect(both.promptSuffix).toBe(
40
+ "image 4 is the opening (first) frame and image 5 is the closing (last) frame of the video. image 1, image 2 and image 3 are references for the subject; they are not frames.",
41
+ )
42
+ const one = placeFramesAfterReferences({ lastFrameUrl: "L", refImageUrls: R(1), imageCap: 6, syntax: "image N", bindIdentities: true })
43
+ expect(one.promptSuffix).toBe("image 2 is the closing (last) frame of the video. image 1 is a reference for the subject; it is not a frame.")
44
+ // Never the platform's `@image_N` duplicate check: a prompt carrying it is not this syntax's binding.
45
+ const prompted = placeFramesAfterReferences({ prompt: "use @image_2 as the first frame", firstFrameUrl: "F", refImageUrls: R(1), imageCap: 6, syntax: "image N" })
46
+ expect(prompted.promptSuffix).toBe("image 2 is the opening (first) frame of the video.")
47
+ })
48
+
33
49
  it("skips the opening sentence when the prompt already binds it (@image_N form)", () => {
34
50
  const r = placeFramesAfterReferences({ prompt: "use @image_2 as the first frame", firstFrameUrl: "F", refImageUrls: R(1), imageCap: 9 })
35
51
  expect(r.promptSuffix).toBe("")
@@ -1,4 +1,4 @@
1
- import { identityRefsSentence, imageBindingFor } from "./ref-binding.js"
1
+ import { imageBindingFor, type ImageReferenceSyntax } from "./ref-binding.js"
2
2
 
3
3
  /**
4
4
  * WHERE A START / END FRAME GOES WHEN IT SHARES A MODEL'S REFERENCE LIST — the
@@ -29,7 +29,7 @@ export interface FrameReferenceSlotsArgs {
29
29
  /** The provider's image cap — frames and references together. */
30
30
  imageCap: number
31
31
  /** The provider's reference syntax (`videoProviderReferenceSyntax`). */
32
- syntax?: "@image_N" | "@image(n)" | "[Image N]"
32
+ syntax?: ImageReferenceSyntax
33
33
  bindIdentities?: boolean
34
34
  }
35
35
 
@@ -65,14 +65,14 @@ export function placeFramesAfterReferences(args: FrameReferenceSlotsArgs): Frame
65
65
  if (promptBindsStart && firstFrameOrdinal > 0 && lastFrameOrdinal === 0) {
66
66
  // The prompt already binds the first frame at its own (working) position.
67
67
  } else if (firstFrameOrdinal > 0 && lastFrameOrdinal > 0) {
68
- frameSentence = `Use ${binding.ordinal(firstFrameOrdinal)} as the opening (first) frame and ${binding.ordinal(lastFrameOrdinal)} as the closing (last) frame of the video.`
68
+ frameSentence = binding.frames(firstFrameOrdinal, lastFrameOrdinal)
69
69
  } else if (firstFrameOrdinal > 0) {
70
70
  frameSentence = binding.frame(firstFrameOrdinal, "opening")
71
71
  } else if (lastFrameOrdinal > 0) {
72
72
  frameSentence = binding.frame(lastFrameOrdinal, "closing")
73
73
  }
74
74
  const identitySentence = args.bindIdentities && kept.length > 0 && frameCount > 0
75
- ? identityRefsSentence(1, kept.length, binding.ordinal)
75
+ ? binding.identities(1, kept.length)
76
76
  : ""
77
77
  const promptSuffix = [frameSentence, identitySentence].filter(Boolean).join(" ")
78
78
  return { imageUrls, promptSuffix, droppedRefImages, firstFrameOrdinal, lastFrameOrdinal }
@@ -273,6 +273,7 @@ export const PROVIDER_CAPABILITIES: Record<string, Record<string, string>> = {
273
273
  "ltx-2.3-fast": "Lightricks LTX 2.3 Fast — text/image→video, 6–20s, up to 4K",
274
274
  "gemini-omni-video": "Google Gemini Omni — multimodal video with native audio, 4–10s, up to 4K.",
275
275
  "gemini-omni-flash": "Google Gemini Omni Flash — faster, cheaper Omni tier; multimodal video with native audio, 4–10s, up to 4K.",
276
+ "gemini-omni-flash-1-2": "Google Gemini Omni Flash 1.2 — video with native audio, 3–15s, 360p to 4K; text, up to 6 reference images, exact first and last frames, or a video edit.",
276
277
  "wan-3": "Wan 3.0 — multimodal refs (10 images / 5 videos / 5 audio) or first+last frame, native audio, 2–30s, 480p/720p/1080p",
277
278
  "wan-3-prime": "Wan 3.0 Prime — high-speed Wan 3.0 tier; same surface, faster turnaround at a higher rate",
278
279
  "vidu-q4": "Vidu Q4 — start frame or up to 15 reference images + 3 MP3 audio refs, native audio, 3–16s, 540p–4K (image required)",
@@ -311,6 +312,7 @@ export const PROVIDER_CAPABILITIES: Record<string, Record<string, string>> = {
311
312
  "ltx-2.3-fast": "Lightricks LTX 2.3 Fast — start/end frame i2v, 6–20s, up to 4K",
312
313
  "gemini-omni-video": "Google Gemini Omni — multimodal video with native audio, 4–10s, up to 4K.",
313
314
  "gemini-omni-flash": "Google Gemini Omni Flash — faster, cheaper Omni tier; multimodal video with native audio, 4–10s, up to 4K.",
315
+ "gemini-omni-flash-1-2": "Google Gemini Omni Flash 1.2 — video with native audio, 3–15s, 360p to 4K; text, up to 6 reference images, exact first and last frames, or a video edit.",
314
316
  "wan-3": "Wan 3.0 — multimodal refs (10 images / 5 videos / 5 audio) or first+last frame, native audio, 2–30s, 480p/720p/1080p",
315
317
  "wan-3-prime": "Wan 3.0 Prime — high-speed Wan 3.0 tier; same surface, faster turnaround at a higher rate",
316
318
  "vidu-q4": "Vidu Q4 — animate a start frame, or direct up to 15 reference images + 3 MP3 audio refs; native audio, 3–16s, 540p–4K",
@@ -268,6 +268,34 @@ Source: KIE gemini-omni-video market contract (parameters + live behavior probed
268
268
  aspect-ratio hard-reject, see providers/kie/video.ts). Captured 2026-08-09.`,
269
269
  }
270
270
 
271
+ const GEMINI_OMNI_12_DOCTRINE: ProviderPromptDoctrine = {
272
+ providers: ["gemini-omni-flash-1-2"],
273
+ heading: "Gemini Omni Flash 1.2 (gemini-omni-flash-1-2)",
274
+ tips: [
275
+ "Google video with native sound: text-to-video, frames, reference images and video-edit through one prompt surface. Every second from 3 to 15; 360p, 720p, 1080p or 4K.",
276
+ "Structure like the platform default: subject → action → scene → lighting → camera → style. Quote dialogue lines to have them spoken; describe SFX and ambience plainly.",
277
+ "Only a start frame (and an end frame) wired: they are sent as exact first and last frames. With reference images wired too, frames join the image list after your references and are named in the prompt.",
278
+ "Up to 6 reference images: bind them by ordinal ('the woman from @image_1'). No character or voice references on this model. Video-edit: describe the change; the output follows the source clip, up to 15s.",
279
+ ],
280
+ doctrine: `Prompt structure (no public Google prompt guide exists for this endpoint — the API
281
+ contract is the doctrine source):
282
+ subject → action → scene/environment → lighting → camera movement → style → constraints.
283
+
284
+ **Modes (picked from the wired inputs)**
285
+ - Nothing visual → text-to-video.
286
+ - Start frame alone, or start + end frame → exact frames. First + last holds up to 10s; past that the two frames go by the reference route below.
287
+ - Reference images wired → your images come FIRST, in your order (\`@image_1\`, \`@image_2\`, …), up to 6. A wired start or end frame is appended after them and the platform names it in the prompt as the opening or closing frame.
288
+ - Source video wired → video-edit: describe the CHANGE, not a full re-description. The output follows the source clip, which is cut to 15s at most. Up to 5 images may ride beside it.
289
+
290
+ **Audio (native)**
291
+ - Audio is generated with the clip. Quote dialogue to have it spoken; describe SFX and ambience plainly. State exclusions ("no music") or a bed may be invented.
292
+
293
+ **Duration & tiers**
294
+ - Any whole second from 3 to 15. 360p is for drafts; 720p and 1080p are close in price; 4K only when the deliverable needs it.
295
+
296
+ Source: KIE google/gemini-omni-flash-1-2 market contract. Captured 2026-11-09.`,
297
+ }
298
+
271
299
  const GROK_IMAGINE_DOCTRINE: ProviderPromptDoctrine = {
272
300
  providers: ["grok-i2v", "grok-imagine-video-1.5"],
273
301
  heading: "Grok Imagine (grok-i2v, grok-imagine-video-1.5)",
@@ -521,6 +549,7 @@ export const PROVIDER_PROMPT_DOCTRINES: readonly ProviderPromptDoctrine[] = [
521
549
  MINIMAX_H3_DOCTRINE,
522
550
  VEO_31_DOCTRINE,
523
551
  GEMINI_OMNI_DOCTRINE,
552
+ GEMINI_OMNI_12_DOCTRINE,
524
553
  GROK_IMAGINE_DOCTRINE,
525
554
  WAN_DOCTRINE,
526
555
  WAN_3_DOCTRINE,
@@ -48,24 +48,61 @@ export const REF_BINDING = {
48
48
  `Use @image_${n} as the ${role} (${role === "opening" ? "first" : "last"}) frame of the video.`,
49
49
  } as const
50
50
 
51
- /** A binding sentence's ordinal + frame directive in one provider syntax. */
51
+ /** How a video provider addresses its images in the prompt, by array order
52
+ * (`VideoReferenceSyntax` in @nodaro/shared). */
53
+ export type ImageReferenceSyntax = "@image_N" | "@image(n)" | "[Image N]" | "image N"
54
+
55
+ /** The binding sentences — frames and identity references — in one provider syntax. */
52
56
  export interface ImageBinding {
53
57
  ordinal: (n: number) => string
58
+ /** One frame: the opening or the closing one. */
54
59
  frame: (n: number, role: "opening" | "closing") => string
60
+ /** A start and an end frame together. */
61
+ frames: (first: number, last: number) => string
62
+ /** Seats `first`..`last` are references, not frames. */
63
+ identities: (first: number, last: number) => string
64
+ }
65
+
66
+ const roleWords = (role: "opening" | "closing") => `${role} (${role === "opening" ? "first" : "last"})`
67
+
68
+ /** "image 1", "image 1 and image 2", "image 1, image 2 and image 3". */
69
+ function plainList(first: number, last: number): string {
70
+ const names = Array.from({ length: last - first + 1 }, (_, i) => `image ${first + i}`)
71
+ return names.length === 1 ? names[0] : `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`
55
72
  }
56
73
 
57
74
  /**
58
75
  * The image binding in a provider's OWN reference syntax
59
76
  * (`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.
77
+ * {@link REF_BINDING} — the platform swap point, byte for byte; `@image(n)` and
78
+ * `[Image N]` spell the same sentences in the form the provider documents.
79
+ *
80
+ * `image N` (Gemini Omni Flash 1.2) is plain words, each sentence stating what
81
+ * an image IS: "image 3 is the opening (first) frame of the video. image 1 and
82
+ * image 2 are references for the subject; they are not frames." It is the form
83
+ * KIE's own example uses. In staging runs neither wording made 1.2 open on the
84
+ * frame: beside references the frame is one more reference, so the sentence
85
+ * guides the clip and never makes the frame exact.
62
86
  */
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}]`
87
+ export function imageBindingFor(syntax: ImageReferenceSyntax): ImageBinding {
88
+ if (syntax === "image N") {
89
+ return {
90
+ ordinal: (n) => `image ${n}`,
91
+ frame: (n, role) => `image ${n} is the ${roleWords(role)} frame of the video.`,
92
+ frames: (first, last) => `image ${first} is the ${roleWords("opening")} frame and image ${last} is the ${roleWords("closing")} frame of the video.`,
93
+ identities: (first, last) => first === last
94
+ ? `image ${first} is a reference for the subject; it is not a frame.`
95
+ : `${plainList(first, last)} are references for the subject; they are not frames.`,
96
+ }
97
+ }
98
+ const ordinal = syntax === "@image_N"
99
+ ? REF_BINDING.ordinal
100
+ : syntax === "@image(n)" ? (n: number) => `@image(${n})` : (n: number) => `[Image ${n}]`
66
101
  return {
67
102
  ordinal,
68
- frame: (n, role) => `Use ${ordinal(n)} as the ${role} (${role === "opening" ? "first" : "last"}) frame of the video.`,
103
+ frame: syntax === "@image_N" ? REF_BINDING.frame : (n, role) => `Use ${ordinal(n)} as the ${roleWords(role)} frame of the video.`,
104
+ frames: (first, last) => `Use ${ordinal(first)} as the ${roleWords("opening")} frame and ${ordinal(last)} as the ${roleWords("closing")} frame of the video.`,
105
+ identities: (first, last) => identityRefsSentence(first, last, ordinal),
69
106
  }
70
107
  }
71
108
 
@@ -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, imageBindingFor, SEEDANCE_VIDEO_EDIT_PREFIX, buildSeedanceVideoEditPrompt, type ImageBinding } from "./ref-binding.js"
57
+ export { REF_BINDING, identityRefsSentence, imageBindingFor, SEEDANCE_VIDEO_EDIT_PREFIX, buildSeedanceVideoEditPrompt, type ImageBinding, type ImageReferenceSyntax } from "./ref-binding.js"
58
58
  export { resolveRefIdTokens, type RefIdTokenContext } from "./ref-id-tokens.js"
59
59
 
60
60