@nodaro/prompts 1.16.0 → 1.17.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.cjs CHANGED
@@ -10344,32 +10344,6 @@ function resolveBrandInput(brand) {
10344
10344
  return brand;
10345
10345
  }
10346
10346
 
10347
- // src/prompt-templates.ts
10348
- var DEFAULT_TEMPLATES = {
10349
- "character-description": "Include character '{name}': {description}.",
10350
- "object-description": "Include object '{name}': {description}.",
10351
- "location-description": "Include location '{name}': {description}.",
10352
- "face-description": "Include the exact face and facial features of '{name}' from the reference image. Maintain perfect likeness and facial identity.",
10353
- // `-generation` templates drive standalone entity generation routes
10354
- // (`/v1/generate-character`, `/v1/generate-face`, etc.). Duplicated between
10355
- // frontend and backend historically; consolidated here so the backend DAG
10356
- // orchestrator produces the same prompt as a single-node HTTP call.
10357
- "character-generation": "Create a full-body character portrait: {description}. Style: {style}. Gender: {gender}. High quality, detailed, consistent lighting, neutral background.",
10358
- "object-generation": "Create a product photo of: {description}. Category: {category}. Clean background, professional studio lighting, high detail.",
10359
- "location-generation": "Create a cinematic scene of: {description}. Category: {category}. Atmospheric lighting, high detail, wide angle.",
10360
- "face-generation": "Create a professional close-up face portrait headshot: {description}. Style: {style}. Looking directly at camera, sharp focus on facial features, clean background, studio lighting, high resolution. Maintain exact facial identity and features from the reference image.",
10361
- "generate-image-wrapper": "{userPrompt}\n{assetDescriptions}"
10362
- };
10363
- function resolveTemplate(key, userTemplates, flowTemplates) {
10364
- return flowTemplates?.[key] ?? userTemplates?.[key] ?? DEFAULT_TEMPLATES[key] ?? "";
10365
- }
10366
- function applyTemplate(template, vars) {
10367
- return Object.entries(vars).reduce(
10368
- (result, [key, value]) => result.replaceAll(`{${key}}`, value || ""),
10369
- template
10370
- );
10371
- }
10372
-
10373
10347
  // src/prompt-hint-join.ts
10374
10348
  var PROMPT_HINT_SEPARATOR = ". ";
10375
10349
  function joinPromptHints(userPrompt, hints) {
@@ -10581,6 +10555,87 @@ function sectionedClauseCosts(userPrompt, clauses, structuredFragment) {
10581
10555
  }
10582
10556
  return costs;
10583
10557
  }
10558
+
10559
+ // src/described-references.ts
10560
+ var CHARACTER_BLOCK_HEADER = "Use these characters:";
10561
+ function referenceDescriptionLine(subject, description) {
10562
+ const s = subject.trim();
10563
+ const d = description?.trim();
10564
+ if (!s || !d) return "";
10565
+ return `${s} \u2014 ${d}.`;
10566
+ }
10567
+ function renderDescribedReferenceLines(refs) {
10568
+ if (!refs || refs.length === 0) return [];
10569
+ const lines = [];
10570
+ const seen = /* @__PURE__ */ new Set();
10571
+ for (const r of refs) {
10572
+ const key = r.name?.trim().toLowerCase();
10573
+ if (!key || seen.has(key)) continue;
10574
+ const line = referenceDescriptionLine(r.name, r.description);
10575
+ if (!line) continue;
10576
+ seen.add(key);
10577
+ lines.push(line);
10578
+ }
10579
+ return lines;
10580
+ }
10581
+ function renderReferenceCaptionLines(videoCaptions, audioCaptions, counts) {
10582
+ const lines = [];
10583
+ const render = (captions, kind, cap) => {
10584
+ if (!captions) return;
10585
+ for (let i = 0; i < captions.length && i < cap; i++) {
10586
+ const text = captions[i]?.trim();
10587
+ if (!text) continue;
10588
+ lines.push(`@${kind}_${i + 1}: ${text}.`);
10589
+ }
10590
+ };
10591
+ render(videoCaptions, "video", counts.video);
10592
+ render(audioCaptions, "audio", counts.audio);
10593
+ return lines;
10594
+ }
10595
+ function appendReferenceLines(prompt, lines, format) {
10596
+ if (lines.length === 0) return prompt;
10597
+ if (format === "hybrid") return insertBeforeStyleSection(prompt, lines);
10598
+ const bullets = lines.map((l) => `- ${l}`).join("\n");
10599
+ if (prompt.startsWith(`${CHARACTER_BLOCK_HEADER}
10600
+ `)) {
10601
+ const splitIdx = prompt.indexOf("\n\n");
10602
+ if (splitIdx === -1) return `${prompt}
10603
+ ${bullets}`;
10604
+ return `${prompt.slice(0, splitIdx)}
10605
+ ${bullets}${prompt.slice(splitIdx)}`;
10606
+ }
10607
+ const block = `${CHARACTER_BLOCK_HEADER}
10608
+ ${bullets}`;
10609
+ return prompt ? `${block}
10610
+
10611
+ ${prompt}` : block;
10612
+ }
10613
+
10614
+ // src/prompt-templates.ts
10615
+ var DEFAULT_TEMPLATES = {
10616
+ "character-description": "Include character '{name}': {description}.",
10617
+ "object-description": "Include object '{name}': {description}.",
10618
+ "location-description": "Include location '{name}': {description}.",
10619
+ "face-description": "Include the exact face and facial features of '{name}' from the reference image. Maintain perfect likeness and facial identity.",
10620
+ // `-generation` templates drive standalone entity generation routes
10621
+ // (`/v1/generate-character`, `/v1/generate-face`, etc.). Duplicated between
10622
+ // frontend and backend historically; consolidated here so the backend DAG
10623
+ // orchestrator produces the same prompt as a single-node HTTP call.
10624
+ "character-generation": "Create a full-body character portrait: {description}. Style: {style}. Gender: {gender}. High quality, detailed, consistent lighting, neutral background.",
10625
+ "object-generation": "Create a product photo of: {description}. Category: {category}. Clean background, professional studio lighting, high detail.",
10626
+ "location-generation": "Create a cinematic scene of: {description}. Category: {category}. Atmospheric lighting, high detail, wide angle.",
10627
+ "face-generation": "Create a professional close-up face portrait headshot: {description}. Style: {style}. Looking directly at camera, sharp focus on facial features, clean background, studio lighting, high resolution. Maintain exact facial identity and features from the reference image.",
10628
+ "generate-image-wrapper": "{userPrompt}\n{assetDescriptions}"
10629
+ };
10630
+ function resolveTemplate(key, userTemplates, flowTemplates) {
10631
+ return flowTemplates?.[key] ?? userTemplates?.[key] ?? DEFAULT_TEMPLATES[key] ?? "";
10632
+ }
10633
+ function applyTemplate(template, vars) {
10634
+ return Object.entries(vars).reduce(
10635
+ (result, [key, value]) => result.replaceAll(`{${key}}`, value || ""),
10636
+ template
10637
+ );
10638
+ }
10584
10639
  function composeIdentityDescPart(subject, canonicalDesc, elementInjection) {
10585
10640
  const parts = [];
10586
10641
  const c = canonicalDesc?.trim();
@@ -10589,6 +10644,10 @@ function composeIdentityDescPart(subject, canonicalDesc, elementInjection) {
10589
10644
  if (e) parts.push(e);
10590
10645
  return parts.length > 0 ? `${subject} \u2014 ${parts.join(". ")}` : subject;
10591
10646
  }
10647
+ function pushOverrideDirective(out, ref, binding) {
10648
+ const line = referenceDescriptionLine(binding, ref.descriptionOverride);
10649
+ if (line) out.push(line);
10650
+ }
10592
10651
  function resolveCharacterMentions(prompt, tokens, refs) {
10593
10652
  const bySlug = /* @__PURE__ */ new Map();
10594
10653
  const byVariant = /* @__PURE__ */ new Map();
@@ -10629,7 +10688,7 @@ function resolveCharacterMentions(prompt, tokens, refs) {
10629
10688
  const includeCanonicalDesc = effectiveMode === "identical" || effectiveMode === "face-pose";
10630
10689
  const descPart = composeIdentityDescPart(
10631
10690
  subject,
10632
- includeCanonicalDesc ? match.characterCanonicalDescription : void 0,
10691
+ match.descriptionOverride?.trim() || (includeCanonicalDesc ? match.characterCanonicalDescription : void 0),
10633
10692
  match.elementInjection
10634
10693
  );
10635
10694
  directiveLines.push(`- ${descPart}.${directive ? ` ${directive}` : ""}`);
@@ -10707,6 +10766,7 @@ function resolveCharacterMentionsHybrid(prompt, tokens, refs, existingUrls) {
10707
10766
  const binding = bindingFor(m.url);
10708
10767
  const lock = buildIdentityLockLine(withForcedIdentityLock(ref, lockOverrideByUrl.get(m.url)), binding);
10709
10768
  if (lock) lockLines.push(lock);
10769
+ pushOverrideDirective(elementDirectives, ref, binding);
10710
10770
  const inject = ref.elementInjection?.trim();
10711
10771
  if (inject) elementDirectives.push(inject);
10712
10772
  }
@@ -10780,7 +10840,8 @@ function resolveLocationMentions(prompt, tokens, refs) {
10780
10840
  const subject = `Image ${t.imageIndex} (${displayName})`;
10781
10841
  const includeCanonicalDesc = effectiveMode === "identical";
10782
10842
  const canonicalDesc = match.locationCanonicalDescription?.trim();
10783
- const descPart = includeCanonicalDesc && canonicalDesc ? `${subject} \u2014 ${canonicalDesc}` : subject;
10843
+ const shownDesc = match.descriptionOverride?.trim() || (includeCanonicalDesc ? canonicalDesc : void 0);
10844
+ const descPart = shownDesc ? `${subject} \u2014 ${shownDesc}` : subject;
10784
10845
  directiveLines.push(`- ${descPart}.${directive ? ` ${directive}` : ""}`);
10785
10846
  if (t.bucket && t.variant && match.locationVariantDisplayName && match.locationVariantDisplayName !== "canonical") {
10786
10847
  directiveLines.push(` (in this image: ${match.locationVariantDisplayName})`);
@@ -10848,6 +10909,7 @@ function resolveLocationMentionsHybrid(prompt, tokens, refs, existingUrls) {
10848
10909
  const binding = bindingFor(m.url);
10849
10910
  const lock = buildIdentityLockLine(withForcedIdentityLock(ref, lockOverrideByUrl.get(m.url)), binding);
10850
10911
  if (lock) lockLines.push(lock);
10912
+ pushOverrideDirective(elementDirectives, ref, binding);
10851
10913
  const inject = ref.elementInjection?.trim();
10852
10914
  if (inject) elementDirectives.push(inject);
10853
10915
  }
@@ -10897,10 +10959,11 @@ function resolveImageMentionsHybrid(prompt, tokens, refs, existingUrls) {
10897
10959
  const binding = bindingFor(m.url);
10898
10960
  const lock = buildIdentityLockLine(withForcedIdentityLock(ref, lockOverrideByUrl.get(m.url)), binding);
10899
10961
  if (lock) lockLines.push(lock);
10962
+ pushOverrideDirective(elementDirectives, ref, binding);
10900
10963
  const inject = ref.elementInjection?.trim();
10901
10964
  if (inject) elementDirectives.push(inject);
10902
10965
  }
10903
- return { prompt: resolvedPrompt, additionalUrls, lockLines, elementDirectives };
10966
+ return { prompt: resolvedPrompt, additionalUrls, mentionedUrls: seenUrls, lockLines, elementDirectives };
10904
10967
  }
10905
10968
  function resolveEntityMentionsHybrid(prompt, tokens, refs, existingUrls) {
10906
10969
  const bySlug = /* @__PURE__ */ new Map();
@@ -10951,6 +11014,7 @@ function resolveEntityMentionsHybrid(prompt, tokens, refs, existingUrls) {
10951
11014
  const binding = bindingFor(m.url);
10952
11015
  const lock = buildIdentityLockLine(withForcedIdentityLock(ref, lockOverrideByUrl.get(m.url)), binding);
10953
11016
  if (lock) lockLines.push(lock);
11017
+ pushOverrideDirective(elementDirectives, ref, binding);
10954
11018
  const inject = ref.elementInjection?.trim();
10955
11019
  if (inject) elementDirectives.push(inject);
10956
11020
  }
@@ -10986,7 +11050,7 @@ function buildCanonicalFallback(refs, mentionedSlugs, startIndex) {
10986
11050
  const subject = `Image ${cursor} (${displayName})`;
10987
11051
  const descPart = composeIdentityDescPart(
10988
11052
  subject,
10989
- includeCanonicalDesc ? r.characterCanonicalDescription : void 0,
11053
+ r.descriptionOverride?.trim() || (includeCanonicalDesc ? r.characterCanonicalDescription : void 0),
10990
11054
  r.elementInjection
10991
11055
  );
10992
11056
  directiveLines.push(`- ${descPart}.${directive ? ` ${directive}` : ""}`);
@@ -11002,7 +11066,7 @@ function buildExtraRefDirectives(refs, emittedCharacterPositions, startIndex) {
11002
11066
  for (const r of refs) {
11003
11067
  if (!r.isExtraRef) continue;
11004
11068
  if (!r.url) continue;
11005
- const description = (r.description ?? r.variantDescription ?? "").trim();
11069
+ const description = (r.descriptionOverride ?? "").trim() || (r.description ?? r.variantDescription ?? "").trim();
11006
11070
  if (r.source === "wired-character" && r.characterSlug) {
11007
11071
  const effectiveMode = r.defaultUsageMode ?? shared.DEFAULT_USAGE_MODE;
11008
11072
  const earlierPos = positionsByChar.get(r.characterSlug);
@@ -11066,6 +11130,7 @@ function renderCanonicalFallbackHybrid(refs, letterForUrl) {
11066
11130
  phrases.push(shared.roleToPhrase(shared.resolveDefaultRole(r.defaultRole, r.defaultUsageMode, r.source), binding));
11067
11131
  const lock = buildIdentityLockLine(r, binding);
11068
11132
  if (lock) lockLines.push(lock);
11133
+ pushOverrideDirective(elementDirectives, r, binding);
11069
11134
  const inject = r.elementInjection?.trim();
11070
11135
  if (inject) elementDirectives.push(inject);
11071
11136
  }
@@ -11080,7 +11145,7 @@ function renderExtraRefsHybrid(extraRefs, letterForUrl, seedLetterByChar) {
11080
11145
  if (!r.url) continue;
11081
11146
  const letter = letterForUrl(r.url);
11082
11147
  const binding = `reference image ${letter}`;
11083
- const description = (r.description ?? r.variantDescription ?? "").trim();
11148
+ const description = (r.descriptionOverride ?? "").trim() || (r.description ?? r.variantDescription ?? "").trim();
11084
11149
  if (r.source === "wired-character" && r.characterSlug) {
11085
11150
  const earlier = firstLetterByChar.get(r.characterSlug);
11086
11151
  if (earlier !== void 0 && earlier !== letter) {
@@ -11117,10 +11182,11 @@ function renderLocationCanonicalHybrid(nonCharacterRefs, finalIndexByUrl, covere
11117
11182
  phrases.push(shared.roleToPhrase(resolveLocationRole(null, null, r), binding));
11118
11183
  const lock = buildIdentityLockLine(r, binding);
11119
11184
  if (lock) lockLines.push(lock);
11185
+ pushOverrideDirective(elementDirectives, r, binding);
11120
11186
  const inject = r.elementInjection?.trim();
11121
11187
  if (inject) elementDirectives.push(inject);
11122
11188
  }
11123
- return { phrases, lockLines, elementDirectives };
11189
+ return { phrases, lockLines, elementDirectives, renderedUrls: seenUrls };
11124
11190
  }
11125
11191
  function renderObjectCreatureCanonicalHybrid(nonCharacterRefs, finalIndexByUrl, coveredUrls) {
11126
11192
  const phrases = [];
@@ -11137,10 +11203,24 @@ function renderObjectCreatureCanonicalHybrid(nonCharacterRefs, finalIndexByUrl,
11137
11203
  phrases.push(shared.roleToPhrase(shared.defaultRoleForSource(r.source), binding));
11138
11204
  const lock = buildIdentityLockLine(r, binding);
11139
11205
  if (lock) lockLines.push(lock);
11206
+ pushOverrideDirective(elementDirectives, r, binding);
11140
11207
  const inject = r.elementInjection?.trim();
11141
11208
  if (inject) elementDirectives.push(inject);
11142
11209
  }
11143
- return { phrases, lockLines, elementDirectives };
11210
+ return { phrases, lockLines, elementDirectives, renderedUrls: seenUrls };
11211
+ }
11212
+ function renderUntoldOverridesHybrid(nonCharacterRefs, finalIndexByUrl, toldUrls) {
11213
+ const lines = [];
11214
+ const sweptUrls = /* @__PURE__ */ new Set();
11215
+ for (const r of nonCharacterRefs) {
11216
+ if (!r.url || toldUrls.has(r.url) || sweptUrls.has(r.url)) continue;
11217
+ const slot = finalIndexByUrl.get(r.url);
11218
+ if (!slot) continue;
11219
+ const before = lines.length;
11220
+ pushOverrideDirective(lines, r, `reference image ${slotToLetter(slot)}`);
11221
+ if (lines.length > before) sweptUrls.add(r.url);
11222
+ }
11223
+ return lines;
11144
11224
  }
11145
11225
  function buildTileIdForUrl(urls, refs, prompt, sourceNodeIdById) {
11146
11226
  const knownSlugs = Array.from(
@@ -11313,8 +11393,10 @@ function buildImagePromptInternal(config2, marks) {
11313
11393
  );
11314
11394
  const suppressedCanonicalLocationIds = config2.suppressedCanonicalLocationIds ?? [];
11315
11395
  const isHybrid = config2.referenceFormat === "hybrid";
11396
+ const describedLines = renderDescribedReferenceLines(config2.describedReferences);
11316
11397
  let hybridBodyConverged = false;
11317
11398
  const mentionedEntityUrls = /* @__PURE__ */ new Set();
11399
+ const mentionedImageUrls = /* @__PURE__ */ new Set();
11318
11400
  if (config2.skipCharacterMentions) {
11319
11401
  config2 = {
11320
11402
  ...config2,
@@ -11432,6 +11514,7 @@ function buildImagePromptInternal(config2, marks) {
11432
11514
  resolved.additionalUrls = [...resolved.additionalUrls, ...hi.additionalUrls];
11433
11515
  hybridImageLockLines = hi.lockLines;
11434
11516
  hybridImageElementDirectives = hi.elementDirectives;
11517
+ for (const u of hi.mentionedUrls) mentionedImageUrls.add(u);
11435
11518
  if (hi.additionalUrls.length > 0) hybridBodyConverged = true;
11436
11519
  }
11437
11520
  }
@@ -11562,9 +11645,13 @@ ${promptForNext}`;
11562
11645
  referenceImageUrls = config2.referenceImageUrls || [];
11563
11646
  }
11564
11647
  }
11565
- if (connectedReferences) {
11648
+ if (connectedReferences || describedLines.length > 0) {
11649
+ const structuredRefs = connectedReferences ?? [];
11566
11650
  let prompt2 = config2.prompt;
11567
- const nonCharacterRefs = connectedReferences.filter(
11651
+ if (!isHybrid && describedLines.length > 0) {
11652
+ prompt2 = appendReferenceLines(prompt2, describedLines, "legacy");
11653
+ }
11654
+ const nonCharacterRefs = structuredRefs.filter(
11568
11655
  (r) => r.source !== "wired-character" && r.isExtraRef !== true
11569
11656
  );
11570
11657
  const nonCharacterUrls = nonCharacterRefs.map((r) => r.url).filter((u) => Boolean(u));
@@ -11594,11 +11681,23 @@ ${promptForNext}`;
11594
11681
  const canonLockBlock = canonLockLines.length > 0 ? `${canonLockLines.join("\n")}
11595
11682
 
11596
11683
  ` : "";
11684
+ const toldOverrideUrls = /* @__PURE__ */ new Set([
11685
+ ...locCanon.renderedUrls,
11686
+ ...objCanon.renderedUrls,
11687
+ ...mentionedEntityUrls,
11688
+ ...mentionedImageUrls
11689
+ ]);
11690
+ const untoldOverrides = renderUntoldOverridesHybrid(
11691
+ nonCharacterRefs,
11692
+ finalIndexByUrl,
11693
+ toldOverrideUrls
11694
+ );
11597
11695
  const canonTrailingLines = [
11598
11696
  ...locCanon.phrases,
11599
11697
  ...objCanon.phrases,
11600
11698
  ...locCanon.elementDirectives,
11601
- ...objCanon.elementDirectives
11699
+ ...objCanon.elementDirectives,
11700
+ ...untoldOverrides
11602
11701
  ];
11603
11702
  const composedScene = `${canonLockBlock}${insertBeforeStyleSection(scene, canonTrailingLines)}`;
11604
11703
  prompt2 = config2.referenceLockSnippet ? `${config2.referenceLockSnippet}
@@ -11625,6 +11724,9 @@ ${directives}`;
11625
11724
  }
11626
11725
  }
11627
11726
  }
11727
+ if (isHybrid && describedLines.length > 0) {
11728
+ prompt2 = appendReferenceLines(prompt2, describedLines, "hybrid");
11729
+ }
11628
11730
  const styleText2 = style?.trim();
11629
11731
  const styleLine2 = styleText2 && !isDeniedStyleId(styleText2) ? `Style: ${getStylePromptHint(styleText2) || styleText2}` : "";
11630
11732
  const negPrompt2 = negativePrompt?.trim();
@@ -11665,7 +11767,7 @@ ${directives}`;
11665
11767
  const reordered = applyReferenceOrder(
11666
11768
  assembledUrls,
11667
11769
  prompt2,
11668
- connectedReferences,
11770
+ structuredRefs,
11669
11771
  referenceOrder,
11670
11772
  sourceNodeIdById
11671
11773
  );
@@ -11790,7 +11892,10 @@ function collectIdentities(prompt, refs, meta3) {
11790
11892
  label,
11791
11893
  fidelity: m?.fidelity ?? defaultFidelityForSource(ref?.source),
11792
11894
  customText: m?.customText?.trim() || void 0,
11793
- description: ref?.description,
11895
+ // Per-use override ahead of the ref's own description; the directive
11896
+ // builder's location-canonical fallback only fires when BOTH are absent,
11897
+ // so an override outranks the stored location wording too.
11898
+ description: ref?.descriptionOverride?.trim() || ref?.description,
11794
11899
  source: ref?.source,
11795
11900
  locationCanonicalDescription: ref?.locationCanonicalDescription,
11796
11901
  locationSlug: ref?.locationSlug,
@@ -11898,7 +12003,7 @@ function buildNonCharacterDirectives(prompt, nonCharacterRefs, identityMeta, fin
11898
12003
  // wired-object falls through to the strict "match exactly" verb.
11899
12004
  label: ref.source === "wired-location" ? "location" : ref.source === "wired-creature" ? "creature" : "object",
11900
12005
  fidelity: defaultFidelityForSource(ref.source),
11901
- description: ref.description?.trim() || void 0,
12006
+ description: ref.descriptionOverride?.trim() || ref.description?.trim() || void 0,
11902
12007
  source: ref.source,
11903
12008
  locationCanonicalDescription: ref.locationCanonicalDescription,
11904
12009
  locationSlug: ref.locationSlug,
@@ -12425,6 +12530,8 @@ function resolveVideoCharacterMentionsHybrid(prompt, tokens, refs, offset) {
12425
12530
  const binding = bindingFor(m.url);
12426
12531
  const lock = buildIdentityLockLine(withForcedIdentityLock(ref, lockOverrideByUrl.get(m.url)), binding);
12427
12532
  if (lock) lockLines.push(lock);
12533
+ const overrideLine = referenceDescriptionLine(binding, ref.descriptionOverride);
12534
+ if (overrideLine) elementDirectives.push(overrideLine);
12428
12535
  const inject = ref.elementInjection?.trim();
12429
12536
  if (inject) elementDirectives.push(inject);
12430
12537
  }
@@ -12450,6 +12557,13 @@ function resolveVideoReferenceCore(args) {
12450
12557
  video: args.videoRefCount ?? 0,
12451
12558
  audio: args.audioRefCount ?? 0
12452
12559
  });
12560
+ const describedAndCaptionLines = [
12561
+ ...renderDescribedReferenceLines(args.describedReferences),
12562
+ ...renderReferenceCaptionLines(args.videoCaptions, args.audioCaptions, {
12563
+ video: args.videoRefCount ?? 0,
12564
+ audio: args.audioRefCount ?? 0
12565
+ })
12566
+ ];
12453
12567
  let wiredCharRefs = [...args.wiredCharRefs];
12454
12568
  const suppressedSlugs = new Set(args.suppressedCanonicalCharacterIds ?? []);
12455
12569
  if (suppressedSlugs.size > 0) {
@@ -12474,14 +12588,15 @@ function resolveVideoReferenceCore(args) {
12474
12588
  const slotByRefId = /* @__PURE__ */ new Map();
12475
12589
  if (wiredCharRefs.length === 0 && !hasExtras) {
12476
12590
  const counts = tokenCounts(leadingRefUrls.length);
12591
+ const resolved2 = resolveReferenceTokens(
12592
+ resolveRefIdTokens(args.prompt, { slotById: slotByRefId, nameById: nameByRefId, imageCount: counts.image }),
12593
+ counts
12594
+ );
12477
12595
  return {
12478
- // tokenCounts(leadingRefUrls.length) → image count == offset (no assets here):
12479
- // leadingRefUrls mode counts the leading refs; ordinalOffset mode counts the
12480
- // caller-owned leading refs the offset stands in for.
12481
- prompt: resolveReferenceTokens(
12482
- resolveRefIdTokens(args.prompt, { slotById: slotByRefId, nameById: nameByRefId, imageCount: counts.image }),
12483
- counts
12484
- ),
12596
+ // Described references and rail captions are the ONE thing this branch can
12597
+ // still contribute: they need no seat, so a request that carries only them
12598
+ // (a story landing before any entity exists) reaches the model here.
12599
+ prompt: describedAndCaptionLines.length > 0 ? appendReferenceLines(resolved2 ?? "", describedAndCaptionLines, args.hybridRoles === true ? "hybrid" : "legacy") : resolved2,
12485
12600
  additionalUrls: [...leadingRefUrls]
12486
12601
  };
12487
12602
  }
@@ -12539,6 +12654,8 @@ function resolveVideoReferenceCore(args) {
12539
12654
  canonicalPhrases.push(shared.roleToPhrase(shared.resolveDefaultRole(r.defaultRole, r.defaultUsageMode, r.source), binding));
12540
12655
  const lock = buildIdentityLockLine(r, binding);
12541
12656
  if (lock) canonicalLockLines.push(lock);
12657
+ const overrideLine = referenceDescriptionLine(binding, r.descriptionOverride);
12658
+ if (overrideLine) canonicalElementDirectives.push(overrideLine);
12542
12659
  const inject = r.elementInjection?.trim();
12543
12660
  if (inject) canonicalElementDirectives.push(inject);
12544
12661
  continue;
@@ -12555,7 +12672,10 @@ function resolveVideoReferenceCore(args) {
12555
12672
  const directive = shared.usageModeDirective(effectiveMode);
12556
12673
  const includeCanonicalDesc = effectiveMode === "identical" || effectiveMode === "face-pose";
12557
12674
  const descBodyParts = [];
12558
- if (includeCanonicalDesc && r.characterCanonicalDescription?.trim()) {
12675
+ const canonicalOverride = r.descriptionOverride?.trim();
12676
+ if (canonicalOverride) {
12677
+ descBodyParts.push(canonicalOverride);
12678
+ } else if (includeCanonicalDesc && r.characterCanonicalDescription?.trim()) {
12559
12679
  descBodyParts.push(r.characterCanonicalDescription.trim());
12560
12680
  }
12561
12681
  if (r.elementInjection?.trim()) descBodyParts.push(r.elementInjection.trim());
@@ -12572,7 +12692,7 @@ function resolveVideoReferenceCore(args) {
12572
12692
  if (!ex.url) continue;
12573
12693
  position += 1;
12574
12694
  if (ex.id && !slotByRefId.has(ex.id)) slotByRefId.set(ex.id, position);
12575
- const desc = (ex.description ?? "").trim();
12695
+ const desc = (ex.descriptionOverride ?? "").trim() || (ex.description ?? "").trim();
12576
12696
  if (ex.characterSlug) {
12577
12697
  const meta3 = args.lookupCharacterBySlug?.(ex.characterSlug);
12578
12698
  const charDefaultMode = meta3?.defaultUsageMode;
@@ -12716,6 +12836,11 @@ ${finalPrompt}` : block;
12716
12836
  merged.push(u);
12717
12837
  }
12718
12838
  }
12839
+ finalPrompt = appendReferenceLines(
12840
+ finalPrompt,
12841
+ describedAndCaptionLines,
12842
+ hybrid ? "hybrid" : "legacy"
12843
+ );
12719
12844
  finalPrompt = resolveRefIdTokens(finalPrompt, {
12720
12845
  slotById: slotByRefId,
12721
12846
  nameById: nameByRefId,
@@ -12930,6 +13055,7 @@ function assembleImageInput(input) {
12930
13055
  prompt: composePromptText(input.userPrompt, pieces, keptHintClauses),
12931
13056
  provider: input.provider,
12932
13057
  ...input.connectedReferences !== void 0 ? { connectedReferences: input.connectedReferences } : {},
13058
+ ...input.describedReferences !== void 0 ? { describedReferences: input.describedReferences } : {},
12933
13059
  // Manual uploads / direct refs ride the builder's reference-URL channel so
12934
13060
  // the per-provider reference gate filters them alongside bound entities.
12935
13061
  // Omit the field entirely when absent so the builder's default ([]) kicks
@@ -34866,6 +34992,7 @@ exports.WARDROBE_FIELD_BY_DIMENSION = WARDROBE_FIELD_BY_DIMENSION;
34866
34992
  exports.__resetCatalogIdGuardForTests = __resetCatalogIdGuardForTests;
34867
34993
  exports.appendField = appendField;
34868
34994
  exports.appendMusicMeta = appendMusicMeta;
34995
+ exports.appendReferenceLines = appendReferenceLines;
34869
34996
  exports.applyMinorAgeFloorToPickerValues = applyMinorAgeFloorToPickerValues;
34870
34997
  exports.applyPickerJson = applyPickerJson;
34871
34998
  exports.applyPromptAffixes = applyPromptAffixes;
@@ -35192,11 +35319,14 @@ exports.promptPartSeparator = promptPartSeparator;
35192
35319
  exports.readDirectionFields = readDirectionFields;
35193
35320
  exports.readStructuredFields = readStructuredFields;
35194
35321
  exports.readSubjectFields = readSubjectFields;
35322
+ exports.referenceDescriptionLine = referenceDescriptionLine;
35195
35323
  exports.referenceRulesBlock = referenceRulesBlock;
35196
35324
  exports.registerCatalogPack = registerCatalogPack;
35197
35325
  exports.registerPersonPack = registerPersonPack;
35326
+ exports.renderDescribedReferenceLines = renderDescribedReferenceLines;
35198
35327
  exports.renderDirectionHintClauses = renderDirectionHintClauses;
35199
35328
  exports.renderDirectionHints = renderDirectionHints;
35329
+ exports.renderReferenceCaptionLines = renderReferenceCaptionLines;
35200
35330
  exports.renderStructuredFields = renderStructuredFields;
35201
35331
  exports.renderStyleSection = renderStyleSection;
35202
35332
  exports.renderSubjectHints = renderSubjectHints;