@pixerate/editor 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +101 -1
  2. package/THIRD_PARTY_NOTICES.md +40 -0
  3. package/dist/agent/index.cjs +175 -11
  4. package/dist/agent/index.cjs.map +1 -1
  5. package/dist/agent/index.d.cts +18 -4
  6. package/dist/agent/index.d.ts +18 -4
  7. package/dist/agent/index.js +168 -4
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/{arrangeShortcuts-B8O3f8-P.d.cts → arrange-BICutYPk.d.cts} +243 -29
  10. package/dist/{arrangeShortcuts-sLlD2rSX.d.ts → arrange-BICutYPk.d.ts} +243 -29
  11. package/dist/builtins-CdPcLwwF.d.cts +176 -0
  12. package/dist/builtins-Chj6zv_O.d.ts +176 -0
  13. package/dist/canvas/index.cjs +8588 -1857
  14. package/dist/canvas/index.cjs.map +1 -1
  15. package/dist/canvas/index.d.cts +1847 -9
  16. package/dist/canvas/index.d.ts +1847 -9
  17. package/dist/canvas/index.js +8577 -1846
  18. package/dist/canvas/index.js.map +1 -1
  19. package/dist/chunk-3CC7OPUQ.cjs +831 -0
  20. package/dist/chunk-3CC7OPUQ.cjs.map +1 -0
  21. package/dist/chunk-4BVP64JJ.cjs +1406 -0
  22. package/dist/chunk-4BVP64JJ.cjs.map +1 -0
  23. package/dist/chunk-BVP3TSLH.cjs +69 -0
  24. package/dist/chunk-BVP3TSLH.cjs.map +1 -0
  25. package/dist/{chunk-OLZJXL4R.cjs → chunk-CS6NZWWA.cjs} +59 -25
  26. package/dist/chunk-CS6NZWWA.cjs.map +1 -0
  27. package/dist/{chunk-MCSJFWAO.cjs → chunk-EWCOFSCQ.cjs} +685 -100
  28. package/dist/chunk-EWCOFSCQ.cjs.map +1 -0
  29. package/dist/chunk-HDYMLS3X.cjs +409 -0
  30. package/dist/chunk-HDYMLS3X.cjs.map +1 -0
  31. package/dist/chunk-HRMEDT2L.js +1406 -0
  32. package/dist/chunk-HRMEDT2L.js.map +1 -0
  33. package/dist/{chunk-D27YFC46.cjs → chunk-IFHKKVXH.cjs} +382 -63
  34. package/dist/chunk-IFHKKVXH.cjs.map +1 -0
  35. package/dist/{chunk-PGNZKFQS.js → chunk-JOIREKL7.js} +137 -58
  36. package/dist/chunk-JOIREKL7.js.map +1 -0
  37. package/dist/chunk-KRGXUQ2Y.js +1696 -0
  38. package/dist/chunk-KRGXUQ2Y.js.map +1 -0
  39. package/dist/{chunk-3LV6RRPH.js → chunk-MHEPLBFB.js} +2 -2
  40. package/dist/chunk-MR2764X2.js +831 -0
  41. package/dist/chunk-MR2764X2.js.map +1 -0
  42. package/dist/{chunk-XNORY7M6.cjs → chunk-NFVBT3UZ.cjs} +166 -87
  43. package/dist/chunk-NFVBT3UZ.cjs.map +1 -0
  44. package/dist/chunk-NQF32I6Z.cjs +6478 -0
  45. package/dist/chunk-NQF32I6Z.cjs.map +1 -0
  46. package/dist/chunk-O5RYH2PG.js +69 -0
  47. package/dist/chunk-O5RYH2PG.js.map +1 -0
  48. package/dist/{chunk-ITBSKI26.js → chunk-Q2OHWD2A.js} +1064 -360
  49. package/dist/chunk-Q2OHWD2A.js.map +1 -0
  50. package/dist/{chunk-FODNLX5N.js → chunk-QJSOZFCZ.js} +38 -4
  51. package/dist/chunk-QJSOZFCZ.js.map +1 -0
  52. package/dist/{chunk-SML64VJG.cjs → chunk-RRREAOVM.cjs} +4 -4
  53. package/dist/{chunk-SML64VJG.cjs.map → chunk-RRREAOVM.cjs.map} +1 -1
  54. package/dist/{chunk-5S3Z4PPF.js → chunk-SWE3NPOU.js} +347 -28
  55. package/dist/chunk-SWE3NPOU.js.map +1 -0
  56. package/dist/chunk-TXXNBDQX.cjs +1696 -0
  57. package/dist/chunk-TXXNBDQX.cjs.map +1 -0
  58. package/dist/chunk-UBCFIABU.js +409 -0
  59. package/dist/chunk-UBCFIABU.js.map +1 -0
  60. package/dist/chunk-X7HXL2SB.js +6478 -0
  61. package/dist/chunk-X7HXL2SB.js.map +1 -0
  62. package/dist/{chunk-SBCZ6RN5.cjs → chunk-YOMYSFJU.cjs} +1163 -459
  63. package/dist/chunk-YOMYSFJU.cjs.map +1 -0
  64. package/dist/{chunk-3CN2WD3X.js → chunk-ZRNHGAQT.js} +661 -76
  65. package/dist/chunk-ZRNHGAQT.js.map +1 -0
  66. package/dist/commands/index.cjs +425 -4
  67. package/dist/commands/index.cjs.map +1 -1
  68. package/dist/commands/index.d.cts +39 -77
  69. package/dist/commands/index.d.ts +39 -77
  70. package/dist/commands/index.js +424 -3
  71. package/dist/commands/index.js.map +1 -1
  72. package/dist/compare/index.cjs +3 -3
  73. package/dist/compare/index.d.cts +1 -1
  74. package/dist/compare/index.d.ts +1 -1
  75. package/dist/compare/index.js +2 -2
  76. package/dist/controller-CxXGOEmy.d.cts +2038 -0
  77. package/dist/controller-CxXGOEmy.d.ts +2038 -0
  78. package/dist/{documents-BQjzJNe2.d.ts → documents-CHT1U5Vs.d.ts} +10 -0
  79. package/dist/{documents-KRTPhvst.d.cts → documents-Gp7wb0Me.d.cts} +10 -0
  80. package/dist/export/index.cjs +36 -0
  81. package/dist/export/index.cjs.map +1 -0
  82. package/dist/export/index.d.cts +243 -0
  83. package/dist/export/index.d.ts +243 -0
  84. package/dist/export/index.js +36 -0
  85. package/dist/export/index.js.map +1 -0
  86. package/dist/extensions/index.cjs +101 -6
  87. package/dist/extensions/index.cjs.map +1 -1
  88. package/dist/extensions/index.d.cts +18 -5
  89. package/dist/extensions/index.d.ts +18 -5
  90. package/dist/extensions/index.js +101 -6
  91. package/dist/generation/index.cjs +1013 -69
  92. package/dist/generation/index.cjs.map +1 -1
  93. package/dist/generation/index.d.cts +474 -13
  94. package/dist/generation/index.d.ts +474 -13
  95. package/dist/generation/index.js +976 -32
  96. package/dist/generation/index.js.map +1 -1
  97. package/dist/image-editor/index.cjs +3385 -1275
  98. package/dist/image-editor/index.cjs.map +1 -1
  99. package/dist/image-editor/index.d.cts +693 -128
  100. package/dist/image-editor/index.d.ts +693 -128
  101. package/dist/image-editor/index.js +3325 -1215
  102. package/dist/image-editor/index.js.map +1 -1
  103. package/dist/{index-fJrlkJCd.d.ts → index-B273Zllk.d.ts} +500 -7
  104. package/dist/{index-BhJBSm2d.d.cts → index-jcJwmBYt.d.cts} +500 -7
  105. package/dist/index.cjs +1603 -45
  106. package/dist/index.cjs.map +1 -1
  107. package/dist/index.d.cts +154 -27
  108. package/dist/index.d.ts +154 -27
  109. package/dist/index.js +1584 -26
  110. package/dist/index.js.map +1 -1
  111. package/dist/mention-text-CDnCFd3c.d.cts +53 -0
  112. package/dist/mention-text-CDnCFd3c.d.ts +53 -0
  113. package/dist/model-C1zApQM8.d.cts +219 -0
  114. package/dist/model-C1zApQM8.d.ts +219 -0
  115. package/dist/persistence/index.d.cts +3 -120
  116. package/dist/persistence/index.d.ts +3 -120
  117. package/dist/registry-CED5C0lA.d.cts +204 -0
  118. package/dist/registry-CED5C0lA.d.ts +204 -0
  119. package/dist/{registry-GJI4gToU.d.cts → registry-CEgwXED1.d.cts} +12 -1
  120. package/dist/{registry-GJI4gToU.d.ts → registry-CEgwXED1.d.ts} +12 -1
  121. package/dist/{registry-D32LpIc4.d.cts → registry-D0GADKc7.d.cts} +1 -1
  122. package/dist/{registry-evKTP4ls.d.ts → registry-DhVUZioj.d.ts} +1 -1
  123. package/dist/serializers/index.cjs +12 -4
  124. package/dist/serializers/index.cjs.map +1 -1
  125. package/dist/serializers/index.d.cts +41 -13
  126. package/dist/serializers/index.d.ts +41 -13
  127. package/dist/serializers/index.js +12 -4
  128. package/dist/spreadsheet/index.cjs +157 -3
  129. package/dist/spreadsheet/index.cjs.map +1 -1
  130. package/dist/spreadsheet/index.d.cts +534 -11
  131. package/dist/spreadsheet/index.d.ts +534 -11
  132. package/dist/spreadsheet/index.js +157 -3
  133. package/dist/storage-BpFfspHm.d.cts +120 -0
  134. package/dist/storage-BpFfspHm.d.ts +120 -0
  135. package/dist/tokens/index.cjs +115 -0
  136. package/dist/tokens/index.cjs.map +1 -0
  137. package/dist/tokens/index.d.cts +212 -0
  138. package/dist/tokens/index.d.ts +212 -0
  139. package/dist/tokens/index.js +115 -0
  140. package/dist/tokens/index.js.map +1 -0
  141. package/dist/{track-changes-BK5sVf4_.d.cts → track-changes-D_VAPELi.d.cts} +95 -11
  142. package/dist/{track-changes-CDjCrwKe.d.ts → track-changes-Db_fGEvx.d.ts} +95 -11
  143. package/package.json +15 -1
  144. package/dist/chunk-3CN2WD3X.js.map +0 -1
  145. package/dist/chunk-5S3Z4PPF.js.map +0 -1
  146. package/dist/chunk-D27YFC46.cjs.map +0 -1
  147. package/dist/chunk-FODNLX5N.js.map +0 -1
  148. package/dist/chunk-ITBSKI26.js.map +0 -1
  149. package/dist/chunk-MA2F5KHF.cjs +0 -3223
  150. package/dist/chunk-MA2F5KHF.cjs.map +0 -1
  151. package/dist/chunk-MCSJFWAO.cjs.map +0 -1
  152. package/dist/chunk-OLZJXL4R.cjs.map +0 -1
  153. package/dist/chunk-PGNZKFQS.js.map +0 -1
  154. package/dist/chunk-SBCZ6RN5.cjs.map +0 -1
  155. package/dist/chunk-U5KWZU4X.js +0 -3223
  156. package/dist/chunk-U5KWZU4X.js.map +0 -1
  157. package/dist/chunk-XNORY7M6.cjs.map +0 -1
  158. package/dist/controller-BhLLAoi8.d.cts +0 -1000
  159. package/dist/controller-BhLLAoi8.d.ts +0 -1000
  160. package/dist/types-DX4Mh4SL.d.cts +0 -217
  161. package/dist/types-DX4Mh4SL.d.ts +0 -217
  162. /package/dist/{chunk-3LV6RRPH.js.map → chunk-MHEPLBFB.js.map} +0 -0
@@ -8,8 +8,17 @@ import "../chunk-P6ODPZ4W.js";
8
8
  import {
9
9
  validateJsonSchema
10
10
  } from "../chunk-ARRTP33I.js";
11
+ import {
12
+ findTextMentions,
13
+ formatMentionText,
14
+ setMentionRoleInText
15
+ } from "../chunk-O5RYH2PG.js";
11
16
 
12
17
  // src/generation/capabilities.ts
18
+ var REFERENCE_ROLE_NAME_REGEX = /^[A-Za-z](?:[A-Za-z0-9_-]*[A-Za-z0-9])?$/;
19
+ function isReferenceRoleName(value) {
20
+ return typeof value === "string" && value.length <= 64 && REFERENCE_ROLE_NAME_REGEX.test(value);
21
+ }
13
22
  var REFERENCE_ROLES = [
14
23
  "start_frame",
15
24
  "end_frame",
@@ -32,6 +41,56 @@ var REFERENCE_ROLE_LABELS = {
32
41
  video_ref: "Video reference",
33
42
  audio_ref: "Audio reference"
34
43
  };
44
+ var REFERENCE_ROLE_ALIASES = {
45
+ reference: "image_ref",
46
+ image: "image_ref",
47
+ style: "style_ref",
48
+ character: "character_ref",
49
+ background: "background_ref",
50
+ "start-frame": "start_frame",
51
+ "first-frame": "start_frame",
52
+ first_frame: "start_frame",
53
+ "end-frame": "end_frame",
54
+ "last-frame": "end_frame",
55
+ last_frame: "end_frame",
56
+ "style-ref": "style_ref",
57
+ "character-ref": "character_ref",
58
+ "background-ref": "background_ref",
59
+ "image-ref": "image_ref",
60
+ "video-ref": "video_ref",
61
+ "audio-ref": "audio_ref",
62
+ video: "video_ref",
63
+ audio: "audio_ref"
64
+ };
65
+ var isBuiltinRole = (role) => REFERENCE_ROLES.includes(role);
66
+ function referenceRoleLabel(role, caps) {
67
+ const declared = caps?.references?.roles[role]?.label;
68
+ if (declared) return declared;
69
+ if (isBuiltinRole(role)) return REFERENCE_ROLE_LABELS[role];
70
+ const words = String(role).replace(/[_-]+/g, " ").trim();
71
+ return words.charAt(0).toUpperCase() + words.slice(1);
72
+ }
73
+ function normalizeReferenceRole(role, caps) {
74
+ const name = String(role).trim();
75
+ if (caps ? !!caps.references?.roles[name] : isBuiltinRole(name)) return name;
76
+ const lower = name.toLowerCase();
77
+ if (caps?.references?.roles[lower]) return lower;
78
+ if (!caps && isBuiltinRole(lower)) return lower;
79
+ return REFERENCE_ROLE_ALIASES[lower] ?? (isBuiltinRole(lower) ? lower : name);
80
+ }
81
+ function mediaTypeMatches(mediaType, accepts) {
82
+ if (!accepts || accepts.length === 0) return true;
83
+ if (!mediaType) return false;
84
+ const type = mediaType.split(";")[0].trim().toLowerCase();
85
+ const kind = type.split("/")[0];
86
+ return accepts.some((entry) => {
87
+ const pattern = entry.trim().toLowerCase();
88
+ if (pattern === "*" || pattern === "*/*" || pattern === type) return true;
89
+ if (!pattern.includes("/")) return pattern === kind;
90
+ if (pattern.endsWith("/*")) return pattern.slice(0, -2) === kind;
91
+ return false;
92
+ });
93
+ }
35
94
  var GENERATION_REQUEST_FIELDS = [
36
95
  "model",
37
96
  "prompt",
@@ -57,7 +116,11 @@ function seedCapability(value) {
57
116
  }
58
117
  function supportedReferenceRoles(caps) {
59
118
  const roles = caps.references?.roles ?? {};
60
- return REFERENCE_ROLES.filter((role) => (roles[role]?.max ?? 0) > 0);
119
+ const builtin = REFERENCE_ROLES.filter((role) => (roles[role]?.max ?? 0) > 0);
120
+ const custom = Object.keys(roles).filter(
121
+ (role) => !isBuiltinRole(role) && isReferenceRoleName(role) && (roles[role]?.max ?? 0) > 0
122
+ );
123
+ return [...builtin, ...custom];
61
124
  }
62
125
  function supportedModes(caps) {
63
126
  const modes = ["generate"];
@@ -356,19 +419,80 @@ var Planner = class {
356
419
  if (role === "mask" && this.caps.inpainting) return { max: 1 };
357
420
  return void 0;
358
421
  }
422
+ /** Turns `options.mentions` into references (appended after the request's own). */
423
+ addMentions() {
424
+ const mentions = this.options.mentions;
425
+ if (!mentions?.length) return;
426
+ const refs = [...this.request.references ?? []];
427
+ for (const mention of mentions) {
428
+ if (!mention || typeof mention.id !== "string" || mention.id === "") continue;
429
+ let role = mention.role ?? void 0;
430
+ if (!role) {
431
+ role = this.options.defaultMentionRole ?? this.inferRole(mention.mediaType);
432
+ this.adjust(
433
+ `references.${role}`,
434
+ void 0,
435
+ role,
436
+ "defaulted",
437
+ `@${mention.label ?? mention.id} has no role; using ${role}.`
438
+ );
439
+ }
440
+ refs.push({
441
+ id: mention.id,
442
+ role,
443
+ ...mention.url !== void 0 ? { url: mention.url } : {},
444
+ ...mention.mediaType !== void 0 ? { mediaType: mention.mediaType } : {},
445
+ ...mention.contentHash !== void 0 ? { contentHash: mention.contentHash } : {}
446
+ });
447
+ }
448
+ this.request.references = refs;
449
+ }
450
+ /** First role of the model (other than `mask`) whose `accepts` matches `mediaType`. */
451
+ inferRole(mediaType) {
452
+ for (const role of supportedReferenceRoles(this.caps)) {
453
+ if (role === "mask") continue;
454
+ const cap = this.roleCapability(role);
455
+ if (cap && (mediaType === void 0 || mediaTypeMatches(mediaType, cap.accepts))) return role;
456
+ }
457
+ return "image_ref";
458
+ }
359
459
  planReferences() {
360
460
  const refs = this.request.references;
361
461
  const allowDrop = !this.strict && (this.options.dropUnsupportedReferences ?? (this.strategy === "nearest" || this.strategy === "downgrade"));
362
462
  const byRole = /* @__PURE__ */ new Map();
363
463
  const kept = [];
364
464
  const unsupported = /* @__PURE__ */ new Map();
365
- for (const [index, ref] of (refs ?? []).entries()) {
366
- if (!ref || typeof ref.id !== "string" || ref.id === "" || !REFERENCE_ROLES.includes(ref.role)) {
367
- this.error(`references.${index}`, ref, "Each reference needs an `id` and a known `role`.");
465
+ for (const [index, original] of (refs ?? []).entries()) {
466
+ let ref = original;
467
+ if (!ref || typeof ref.id !== "string" || ref.id === "" || !isReferenceRoleName(ref.role)) {
468
+ this.error(`references.${index}`, ref, "Each reference needs an `id` and a valid `role`.");
368
469
  continue;
369
470
  }
370
471
  if (!this.roleCapability(ref.role)) {
371
- unsupported.set(ref.role, (unsupported.get(ref.role) ?? 0) + 1);
472
+ const alias = normalizeReferenceRole(ref.role, this.caps);
473
+ if (alias !== ref.role && this.roleCapability(alias)) {
474
+ if (this.strict) {
475
+ this.error(`references.${ref.role}`, ref.role, `The model calls this role ${describe(alias)}.`);
476
+ continue;
477
+ }
478
+ this.adjust(
479
+ `references.${ref.role}`,
480
+ ref.role,
481
+ alias,
482
+ "snapped",
483
+ `Using the model's ${alias} role for ${describe(ref.role)}.`
484
+ );
485
+ ref = { ...ref, role: alias };
486
+ } else {
487
+ unsupported.set(ref.role, (unsupported.get(ref.role) ?? 0) + 1);
488
+ continue;
489
+ }
490
+ }
491
+ const cap = this.roleCapability(ref.role);
492
+ if (ref.mediaType !== void 0 && !mediaTypeMatches(ref.mediaType, cap.accepts)) {
493
+ const message = `${describe(ref.id)} is ${describe(ref.mediaType)}; ${ref.role} accepts ${list(cap.accepts ?? [])}.`;
494
+ if (allowDrop) this.adjust(`references.${ref.role}`, ref.id, void 0, "dropped", message);
495
+ else this.error(`references.${ref.role}`, ref.id, message);
372
496
  continue;
373
497
  }
374
498
  const group = byRole.get(ref.role) ?? [];
@@ -386,7 +510,10 @@ var Planner = class {
386
510
  else this.error(`references.${role}`, count, message);
387
511
  }
388
512
  const removed = /* @__PURE__ */ new Set();
389
- for (const role of REFERENCE_ROLES) {
513
+ const custom = supportedReferenceRoles(this.caps).filter(
514
+ (role) => !REFERENCE_ROLES.includes(role)
515
+ );
516
+ for (const role of [...REFERENCE_ROLES, ...custom]) {
390
517
  const cap = this.roleCapability(role);
391
518
  const group = byRole.get(role) ?? [];
392
519
  if (!cap) continue;
@@ -525,6 +652,7 @@ var Planner = class {
525
652
  this.planDuration();
526
653
  this.planSeed();
527
654
  this.planMode();
655
+ this.addMentions();
528
656
  this.planReferences();
529
657
  this.planParams();
530
658
  }
@@ -642,7 +770,8 @@ function capabilitiesToJsonSchema(caps) {
642
770
  const limits = roles.map((role) => {
643
771
  const cap = caps.references?.roles[role] ?? { max: 1 };
644
772
  const min = cap.min ? `${cap.min}\u2013` : "up to ";
645
- return `${role} (${cap.label ?? REFERENCE_ROLE_LABELS[role]}): ${min}${cap.max}`;
773
+ const accepts = "accepts" in cap && cap.accepts?.length ? `, ${cap.accepts.join("/")} only` : "";
774
+ return `${role} (${referenceRoleLabel(role, caps)}): ${min}${cap.max}${accepts}`;
646
775
  });
647
776
  const perRoleTotal = roles.reduce((sum, role) => sum + (caps.references?.roles[role]?.max ?? 1), 0);
648
777
  const maxItems = Math.min(caps.references?.maxTotal ?? Infinity, perRoleTotal);
@@ -655,7 +784,9 @@ function capabilitiesToJsonSchema(caps) {
655
784
  properties: {
656
785
  id: { type: "string", minLength: 1, description: "Asset id." },
657
786
  role: { type: "string", enum: roles },
658
- url: { type: "string" }
787
+ url: { type: "string" },
788
+ mediaType: { type: "string", description: 'MIME type or media kind of the asset, e.g. "image/png".' },
789
+ contentHash: { type: "string" }
659
790
  },
660
791
  required: ["id", "role"],
661
792
  additionalProperties: false
@@ -682,9 +813,58 @@ function capabilitiesToJsonSchema(caps) {
682
813
  };
683
814
  }
684
815
 
816
+ // src/generation/hash.ts
817
+ function stableStringify(value) {
818
+ if (Array.isArray(value)) return `[${value.map((item) => item === void 0 ? "null" : stableStringify(item)).join(",")}]`;
819
+ if (value && typeof value === "object") {
820
+ const entries = Object.keys(value).filter((key) => value[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`);
821
+ return `{${entries.join(",")}}`;
822
+ }
823
+ return JSON.stringify(value) ?? "null";
824
+ }
825
+ var FNV_PRIME = 16777619;
826
+ function hashText(text) {
827
+ let a = 2166136261;
828
+ let b = 789454171;
829
+ for (let i = 0; i < text.length; i++) {
830
+ const unit = text.charCodeAt(i);
831
+ a = Math.imul(a ^ unit & 255, FNV_PRIME);
832
+ a = Math.imul(a ^ unit >>> 8, FNV_PRIME);
833
+ b = Math.imul(b ^ unit >>> 8, FNV_PRIME);
834
+ b = Math.imul(b ^ unit & 255, FNV_PRIME);
835
+ b ^= b >>> 15;
836
+ }
837
+ a ^= a >>> 16;
838
+ a = Math.imul(a, 2246822507);
839
+ a ^= a >>> 13;
840
+ b ^= b >>> 16;
841
+ b = Math.imul(b, 3266489909);
842
+ b ^= b >>> 13;
843
+ const hex = (n) => (n >>> 0).toString(16).padStart(8, "0");
844
+ return `h1-${hex(a)}${hex(b)}`;
845
+ }
846
+ function hashValue(value) {
847
+ return hashText(stableStringify(value));
848
+ }
849
+ var PRESENTATION_ONLY = /* @__PURE__ */ new Set(["name", "description", "badges", "icon", "group", "timing"]);
850
+ function hashCapabilities(caps) {
851
+ const relevant = {};
852
+ for (const key of Object.keys(caps)) {
853
+ if (!PRESENTATION_ONLY.has(key)) relevant[key] = caps[key];
854
+ }
855
+ return hashValue(relevant);
856
+ }
857
+
685
858
  // src/generation/tools.ts
686
859
  var STRATEGIES = ["nearest", "upgrade", "downgrade", "error"];
687
- var requestSchema = {
860
+ function knownRoles(models) {
861
+ const roles = [...REFERENCE_ROLES];
862
+ for (const model of models) {
863
+ for (const role of supportedReferenceRoles(model)) if (!roles.includes(role)) roles.push(role);
864
+ }
865
+ return roles;
866
+ }
867
+ var requestSchemaFor = (roles) => ({
688
868
  type: "object",
689
869
  description: "Generation request. Call list_models for the fields each model supports; unsupported values are adjusted per `strategy`.",
690
870
  properties: {
@@ -704,8 +884,14 @@ var requestSchema = {
704
884
  type: "object",
705
885
  properties: {
706
886
  id: { type: "string", minLength: 1 },
707
- role: { type: "string", enum: [...REFERENCE_ROLES] },
708
- url: { type: "string" }
887
+ role: {
888
+ type: "string",
889
+ enum: [...roles],
890
+ description: "What the asset is used for. Each model lists the roles it accepts (list_models)."
891
+ },
892
+ url: { type: "string" },
893
+ mediaType: { type: "string", description: 'MIME type or media kind, e.g. "image/png".' },
894
+ contentHash: { type: "string" }
709
895
  },
710
896
  required: ["id", "role"],
711
897
  additionalProperties: false
@@ -714,20 +900,12 @@ var requestSchema = {
714
900
  params: { type: "object", additionalProperties: { type: ["string", "number", "boolean"] } }
715
901
  },
716
902
  additionalProperties: false
717
- };
903
+ });
718
904
  var strategySchema = {
719
905
  type: "string",
720
906
  enum: STRATEGIES,
721
907
  description: "How to handle unsupported values: nearest (snap/clamp, ties up), downgrade (ties down), upgrade (ties up, never drops references), error (change nothing)."
722
908
  };
723
- function stableStringify(value) {
724
- if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
725
- if (value && typeof value === "object") {
726
- const entries = Object.keys(value).filter((key) => value[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`);
727
- return `{${entries.join(",")}}`;
728
- }
729
- return JSON.stringify(value) ?? "null";
730
- }
731
909
  function planFailure(plan) {
732
910
  return {
733
911
  isError: true,
@@ -746,13 +924,13 @@ function createGenerationToolProvider(options) {
746
924
  const defaultStrategy = options.defaultStrategy ?? "nearest";
747
925
  const maxKeys = Math.max(1, options.maxIdempotencyKeys ?? 500);
748
926
  const calls = /* @__PURE__ */ new Map();
749
- const findModel = (id) => {
927
+ const findModel2 = (id) => {
750
928
  const model = readModels().find((m) => m.id === id);
751
929
  if (!model) throw new Error(`Unknown model "${String(id)}". Call list_models for the available ids.`);
752
930
  return model;
753
931
  };
754
932
  const plan = (args) => {
755
- const model = findModel(args.model);
933
+ const model = findModel2(args.model);
756
934
  const request = { ...args.request ?? {}, model: model.id };
757
935
  return {
758
936
  model,
@@ -763,12 +941,13 @@ function createGenerationToolProvider(options) {
763
941
  };
764
942
  };
765
943
  const planInput = () => {
766
- const ids = readModels().map((m) => m.id);
944
+ const models = readModels();
945
+ const ids = models.map((m) => m.id);
767
946
  return {
768
947
  type: "object",
769
948
  properties: {
770
949
  model: { type: "string", ...ids.length ? { enum: ids } : {}, description: "Model id from list_models." },
771
- request: requestSchema,
950
+ request: requestSchemaFor(knownRoles(models)),
772
951
  strategy: strategySchema,
773
952
  resolvedPrompt: {
774
953
  type: "string",
@@ -1046,12 +1225,6 @@ function parseProvenance(input) {
1046
1225
  values: isRecord(data.values) ? data.values : null
1047
1226
  };
1048
1227
  }
1049
- function recreateRequest(provenance) {
1050
- const request = clone(provenance.request);
1051
- request.model = provenance.modelId;
1052
- if (request.prompt === void 0) request.prompt = provenance.resolvedPrompt;
1053
- return request;
1054
- }
1055
1228
  function replayProvenancePrompt(provenance) {
1056
1229
  const result = resolvePrompt(provenance.rawPrompt, pinnedTemplatesToTemplates(provenance.templates), {
1057
1230
  bindings: provenance.bindings,
@@ -1311,8 +1484,9 @@ function getReferenceSlots(caps, request = {}) {
1311
1484
  const references = refs.filter((ref) => ref.role === role);
1312
1485
  return {
1313
1486
  role,
1314
- label: cap.label ?? REFERENCE_ROLE_LABELS[role],
1487
+ label: referenceRoleLabel(role, caps),
1315
1488
  ...cap.description ? { description: cap.description } : {},
1489
+ ..."accepts" in cap && cap.accepts?.length ? { accepts: [...cap.accepts] } : {},
1316
1490
  min: cap.min ?? 0,
1317
1491
  max: cap.max,
1318
1492
  references,
@@ -1329,7 +1503,747 @@ function removeReference(request, role, id) {
1329
1503
  if (references.length === 0) delete next.references;
1330
1504
  return next;
1331
1505
  }
1506
+
1507
+ // src/generation/assets.ts
1508
+ function toAssetResolver(lookup) {
1509
+ if (!lookup) return void 0;
1510
+ if (typeof lookup === "function") return (id) => lookup(id) ?? void 0;
1511
+ if (lookup instanceof Map) return (id) => lookup.get(id);
1512
+ const byId = new Map(lookup.map((asset) => [asset.id, asset]));
1513
+ return (id) => byId.get(id);
1514
+ }
1515
+ function roleCap(caps, role) {
1516
+ const cap = caps.references?.roles[role];
1517
+ if (cap && cap.max > 0) return cap;
1518
+ if (role === "mask" && caps.inpainting) return { max: 1 };
1519
+ return void 0;
1520
+ }
1521
+ function getAssetRoleOptions(caps) {
1522
+ const roles = supportedReferenceRoles(caps);
1523
+ if (caps.inpainting && !roles.includes("mask")) roles.push("mask");
1524
+ return roles.map((role) => {
1525
+ const cap = roleCap(caps, role) ?? { max: 1 };
1526
+ return {
1527
+ role,
1528
+ label: referenceRoleLabel(role, caps),
1529
+ ...cap.description ? { description: cap.description } : {},
1530
+ min: cap.min ?? 0,
1531
+ max: cap.max,
1532
+ ...cap.accepts?.length ? { accepts: [...cap.accepts] } : {}
1533
+ };
1534
+ });
1535
+ }
1536
+ function validateAssetMentions(mentions, caps, options = {}) {
1537
+ const resolve = toAssetResolver(options.assets);
1538
+ const issues = [];
1539
+ const counts = {};
1540
+ mentions.forEach((mention, index) => {
1541
+ const id = mention.id;
1542
+ const label = `@${mention.label ?? id}`;
1543
+ if (!mention.role) {
1544
+ issues.push({ index, id, code: "missing-role", severity: "warning", message: `${label} has no role.` });
1545
+ return;
1546
+ }
1547
+ let role = mention.role;
1548
+ if (!roleCap(caps, role)) {
1549
+ const alias = normalizeReferenceRole(role, caps);
1550
+ if (alias !== role && roleCap(caps, alias)) {
1551
+ issues.push({
1552
+ index,
1553
+ id,
1554
+ role,
1555
+ code: "renamed-role",
1556
+ severity: "warning",
1557
+ suggestion: alias,
1558
+ message: `${label}: this model calls "${role}" ${referenceRoleLabel(alias, caps)} (${alias}).`
1559
+ });
1560
+ role = alias;
1561
+ } else {
1562
+ issues.push({
1563
+ index,
1564
+ id,
1565
+ role,
1566
+ code: "unsupported-role",
1567
+ severity: "error",
1568
+ message: `${label}: ${caps.name ?? caps.id} does not accept ${referenceRoleLabel(role, caps).toLowerCase()} assets.`
1569
+ });
1570
+ return;
1571
+ }
1572
+ }
1573
+ const cap = roleCap(caps, role);
1574
+ const mediaType = mention.mediaType ?? resolve?.(id)?.mediaType;
1575
+ if (mediaType !== void 0 && !mediaTypeMatches(mediaType, cap.accepts)) {
1576
+ issues.push({
1577
+ index,
1578
+ id,
1579
+ role,
1580
+ code: "media-mismatch",
1581
+ severity: "error",
1582
+ message: `${label} is ${mediaType}; ${referenceRoleLabel(role, caps)} accepts ${(cap.accepts ?? []).join(", ")}.`
1583
+ });
1584
+ return;
1585
+ }
1586
+ counts[role] = (counts[role] ?? 0) + 1;
1587
+ if (counts[role] > cap.max) {
1588
+ issues.push({
1589
+ index,
1590
+ id,
1591
+ role,
1592
+ code: "too-many",
1593
+ severity: "warning",
1594
+ message: `${label}: at most ${cap.max} ${referenceRoleLabel(role, caps).toLowerCase()} asset(s); extra ones are left out.`
1595
+ });
1596
+ }
1597
+ });
1598
+ const missing = getAssetRoleOptions(caps).filter((option) => (counts[option.role] ?? 0) < option.min).map((option) => ({ role: option.role, min: option.min, count: counts[option.role] ?? 0 }));
1599
+ return { ok: !issues.some((issue) => issue.severity === "error") && missing.length === 0, issues, counts, missing };
1600
+ }
1601
+ function resolveAssetMentions(mentions, assets) {
1602
+ const byId = new Map(assets.map((asset) => [asset.id, asset]));
1603
+ const byLabel = new Map(assets.filter((a) => a.label).map((asset) => [asset.label.toLowerCase(), asset]));
1604
+ const result = [];
1605
+ for (const mention of mentions) {
1606
+ const asset = byId.get(mention.handle) ?? byLabel.get(mention.handle.toLowerCase());
1607
+ if (!asset) continue;
1608
+ result.push({
1609
+ id: asset.id,
1610
+ label: mention.handle,
1611
+ ...mention.role ? { role: mention.role } : {},
1612
+ ...asset.mediaType !== void 0 ? { mediaType: asset.mediaType } : {},
1613
+ ...asset.contentHash !== void 0 ? { contentHash: asset.contentHash } : {},
1614
+ ...asset.url !== void 0 ? { url: asset.url } : {}
1615
+ });
1616
+ }
1617
+ return result;
1618
+ }
1619
+ function defaultAssetRoleOptions() {
1620
+ return REFERENCE_ROLES.map((role) => ({ role, label: referenceRoleLabel(role), min: 0, max: 1 }));
1621
+ }
1622
+ function describeAssetMentions(mentions, assets, caps) {
1623
+ const byId = new Map(assets.map((asset) => [asset.id, asset]));
1624
+ const byLabel = new Map(assets.filter((a) => a.label).map((asset) => [asset.label.toLowerCase(), asset]));
1625
+ const chips = [];
1626
+ for (const mention of mentions) {
1627
+ const asset = byId.get(mention.handle) ?? byLabel.get(mention.handle.toLowerCase());
1628
+ if (!asset) continue;
1629
+ const role = mention.role || void 0;
1630
+ chips.push({
1631
+ index: chips.length,
1632
+ ...mention.start !== void 0 ? { start: mention.start } : {},
1633
+ handle: mention.handle,
1634
+ asset,
1635
+ ...role ? { role, roleLabel: referenceRoleLabel(caps ? normalizeReferenceRole(role, caps) : role, caps) } : {},
1636
+ issues: []
1637
+ });
1638
+ }
1639
+ if (caps) {
1640
+ const validation = validateAssetMentions(
1641
+ chips.map((chip) => ({
1642
+ id: chip.asset.id,
1643
+ label: chip.handle,
1644
+ ...chip.role ? { role: chip.role } : {},
1645
+ ...chip.asset.mediaType ? { mediaType: chip.asset.mediaType } : {}
1646
+ })),
1647
+ caps
1648
+ );
1649
+ for (const issue of validation.issues) chips[issue.index].issues.push(issue);
1650
+ }
1651
+ return chips;
1652
+ }
1653
+
1654
+ // src/generation/record.ts
1655
+ var GENERATION_RECORD_FORMAT = "pixerate.generation-record";
1656
+ var GENERATION_RECORD_VERSION = 1;
1657
+ var idCounter = 0;
1658
+ function createGenerationRecordId() {
1659
+ const uuid = globalThis.crypto?.randomUUID?.();
1660
+ if (typeof uuid === "string") return `gen_${uuid}`;
1661
+ idCounter = (idCounter + 1) % 1679616;
1662
+ const random = Math.floor(Math.random() * 2 ** 48).toString(36).padStart(10, "0");
1663
+ return `gen_${Date.now().toString(36)}${idCounter.toString(36).padStart(4, "0")}${random}`;
1664
+ }
1665
+ var clone2 = (value) => value === void 0 ? value : JSON.parse(JSON.stringify(value));
1666
+ function recordTemplates(pinned) {
1667
+ return pinned.map((p) => ({
1668
+ name: p.name,
1669
+ ...p.templateId !== void 0 ? { templateId: p.templateId } : {},
1670
+ ...p.versionId !== void 0 ? { versionId: p.versionId, version: p.version } : {},
1671
+ hash: hashText(p.body)
1672
+ }));
1673
+ }
1674
+ function recordAssets(references, lookup) {
1675
+ const resolve = toAssetResolver(lookup);
1676
+ return (references ?? []).map((ref) => {
1677
+ const info = resolve?.(ref.id);
1678
+ const contentHash = ref.contentHash ?? info?.contentHash;
1679
+ const mediaType = ref.mediaType ?? info?.mediaType;
1680
+ const url = ref.url ?? info?.url;
1681
+ return {
1682
+ id: ref.id,
1683
+ role: ref.role,
1684
+ ...contentHash !== void 0 ? { contentHash } : {},
1685
+ ...mediaType !== void 0 ? { mediaType } : {},
1686
+ ...url !== void 0 ? { url } : {}
1687
+ };
1688
+ });
1689
+ }
1690
+ function createGenerationRecord(options) {
1691
+ const {
1692
+ model,
1693
+ request: settingsInput,
1694
+ plan: planInput,
1695
+ strategy: strategyInput,
1696
+ mentions,
1697
+ assets,
1698
+ seed: seedInput,
1699
+ id,
1700
+ parentId,
1701
+ kind,
1702
+ ...provenanceOptions
1703
+ } = options;
1704
+ const caps = typeof model === "string" ? void 0 : model;
1705
+ const settings = clone2(settingsInput ?? planInput?.request ?? {});
1706
+ let plan = planInput;
1707
+ if (!plan && caps) {
1708
+ const resolved = resolvePrompt(options.rawPrompt, options.templates ?? [], {
1709
+ bindings: options.bindings,
1710
+ values: options.values,
1711
+ maxDepth: options.maxDepth
1712
+ });
1713
+ plan = planGenerationRequest(settings, caps, {
1714
+ strategy: strategyInput ?? "nearest",
1715
+ resolvedPrompt: resolved.text,
1716
+ ...mentions ? { mentions } : {}
1717
+ });
1718
+ }
1719
+ const provenance = createProvenance({
1720
+ ...provenanceOptions,
1721
+ model,
1722
+ ...plan ? { plan } : { request: settings }
1723
+ });
1724
+ const seed = seedInput ?? provenance.request.seed;
1725
+ return {
1726
+ format: GENERATION_RECORD_FORMAT,
1727
+ version: GENERATION_RECORD_VERSION,
1728
+ id: id ?? createGenerationRecordId(),
1729
+ ...parentId !== void 0 ? { parentId } : {},
1730
+ kind: kind ?? (parentId !== void 0 ? "recreate" : "generate"),
1731
+ provenance,
1732
+ templates: recordTemplates(provenance.templates),
1733
+ promptHash: hashText(provenance.resolvedPrompt),
1734
+ ...caps ? { capabilityHash: hashCapabilities(caps) } : {},
1735
+ settings,
1736
+ strategy: strategyInput ?? plan?.strategy ?? "nearest",
1737
+ ...seed !== void 0 ? { seed } : {},
1738
+ assets: recordAssets(provenance.request.references, assets),
1739
+ ...mentions?.length ? { mentions: clone2([...mentions]) } : {},
1740
+ createdAt: provenance.createdAt
1741
+ };
1742
+ }
1743
+ function completeGenerationRecord(record, update) {
1744
+ const next = clone2(record);
1745
+ const now = new Date(update.completedAt ?? Date.now()).toISOString();
1746
+ if (update.outputIds) next.provenance.outputIds = [...update.outputIds];
1747
+ next.provenance.completedAt = now;
1748
+ if (update.meta) next.provenance.meta = { ...next.provenance.meta ?? {}, ...clone2(update.meta) };
1749
+ if (update.seed !== void 0) next.seed = update.seed;
1750
+ next.updatedAt = now;
1751
+ return next;
1752
+ }
1753
+ function serializeGenerationRecord(record) {
1754
+ return JSON.stringify(record);
1755
+ }
1756
+ var isObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
1757
+ function parseGenerationRecord(input) {
1758
+ let data = input;
1759
+ if (typeof input === "string") {
1760
+ try {
1761
+ data = JSON.parse(input);
1762
+ } catch {
1763
+ throw new ProvenanceParseError("Generation record is not valid JSON.");
1764
+ }
1765
+ }
1766
+ if (!isObject(data) || data.format !== GENERATION_RECORD_FORMAT) {
1767
+ throw new ProvenanceParseError("Not a generation record.");
1768
+ }
1769
+ if (typeof data.version !== "number" || data.version > GENERATION_RECORD_VERSION) {
1770
+ throw new ProvenanceParseError(`Unsupported generation record version ${JSON.stringify(data.version)}.`);
1771
+ }
1772
+ const checks = [
1773
+ ["id", typeof data.id === "string" && data.id !== ""],
1774
+ ["templates", Array.isArray(data.templates) && data.templates.every((t) => isObject(t) && typeof t.name === "string" && typeof t.hash === "string")],
1775
+ ["assets", Array.isArray(data.assets) && data.assets.every((a) => isObject(a) && typeof a.id === "string" && typeof a.role === "string")],
1776
+ ["settings", isObject(data.settings)],
1777
+ ["promptHash", typeof data.promptHash === "string"],
1778
+ ["createdAt", typeof data.createdAt === "string"]
1779
+ ];
1780
+ const bad = checks.find(([, ok]) => !ok);
1781
+ if (bad) throw new ProvenanceParseError(`Invalid generation record field "${bad[0]}".`);
1782
+ const provenance = parseProvenance(data.provenance);
1783
+ return {
1784
+ ...data,
1785
+ kind: data.kind === "recreate" || data.kind === "variation" ? data.kind : "generate",
1786
+ strategy: ["nearest", "upgrade", "downgrade", "error"].includes(data.strategy) ? data.strategy : "nearest",
1787
+ provenance
1788
+ };
1789
+ }
1790
+ function isGenerationRecord(value) {
1791
+ return isObject(value) && value.format === GENERATION_RECORD_FORMAT;
1792
+ }
1793
+ var SKIPPED_SETTING_FIELDS = /* @__PURE__ */ new Set(["model", "prompt", "references", "params", "seed"]);
1794
+ function findModel(capabilities, modelId) {
1795
+ if (!capabilities) return { given: false };
1796
+ const list2 = Array.isArray(capabilities) ? capabilities : [capabilities];
1797
+ return { given: true, caps: list2.find((m) => m.id === modelId) };
1798
+ }
1799
+ var sameValue = (a, b) => stableStringify(a) === stableStringify(b);
1800
+ var show = (value) => value === void 0 ? "unset" : typeof value === "string" ? `"${value}"` : JSON.stringify(value);
1801
+ var refKey = (ref) => `${ref.role}:${ref.id}`;
1802
+ function templateDrift(record, current, mode) {
1803
+ if (!current) return [];
1804
+ const byName = new Map(current.map((t) => [t.name, t]));
1805
+ const drift = [];
1806
+ for (const recorded of record.templates) {
1807
+ const template = byName.get(recorded.name);
1808
+ const fromVersion = recorded.version ?? recorded.versionId;
1809
+ if (!template) {
1810
+ drift.push({
1811
+ type: "template-missing",
1812
+ severity: "warning",
1813
+ template: recorded.name,
1814
+ from: fromVersion,
1815
+ applied: false,
1816
+ message: `Template "${recorded.name}" no longer exists; the recorded body is used.`
1817
+ });
1818
+ continue;
1819
+ }
1820
+ const body = getTemplateBody(template, {});
1821
+ const hash = body === void 0 ? void 0 : hashText(body);
1822
+ if (hash === recorded.hash) continue;
1823
+ const latest = getLatestVersion(template);
1824
+ const toVersion = template.versions?.length ? latest?.version ?? latest?.id : void 0;
1825
+ const versions = fromVersion !== void 0 || toVersion !== void 0 ? ` (${fromVersion ?? "unversioned"} \u2192 ${toVersion ?? "unversioned"})` : "";
1826
+ drift.push({
1827
+ type: "template-changed",
1828
+ severity: mode === "latest" ? "info" : "warning",
1829
+ template: recorded.name,
1830
+ from: { version: fromVersion, hash: recorded.hash },
1831
+ to: { version: toVersion, hash },
1832
+ applied: mode === "latest",
1833
+ message: mode === "latest" ? `Template "${recorded.name}" changed since this generation${versions}; using the current version.` : `Template "${recorded.name}" changed since this generation${versions}; recreating with the recorded version.`
1834
+ });
1835
+ }
1836
+ return drift;
1837
+ }
1838
+ function randomSeed(caps, previous, random) {
1839
+ const cap = caps ? seedCapability(caps.seed) : {};
1840
+ if (!cap) return void 0;
1841
+ const min = Math.ceil(cap.min ?? 0);
1842
+ const max = Math.floor(cap.max ?? 2 ** 31 - 1);
1843
+ if (max < min) return min;
1844
+ let seed = previous;
1845
+ for (let attempt = 0; attempt < 8 && (seed === void 0 || seed === previous); attempt++) {
1846
+ seed = min + Math.floor(random() * (max - min + 1));
1847
+ }
1848
+ if (seed === previous && max > min) seed = previous === max ? min : previous + 1;
1849
+ return seed;
1850
+ }
1851
+ function rebuild(record, options, kind, overrides) {
1852
+ const mode = options.mode ?? "exact";
1853
+ const provenance = record.provenance;
1854
+ const drift = templateDrift(record, options.templates, mode);
1855
+ const rawPrompt = overrides?.rawPrompt ?? provenance.rawPrompt;
1856
+ const values = overrides?.values !== void 0 ? { ...provenance.values ?? {}, ...overrides.values } : provenance.values ? clone2(provenance.values) : null;
1857
+ const pinned = pinnedTemplatesToTemplates(provenance.templates);
1858
+ let templates;
1859
+ let bindings;
1860
+ if (mode === "latest" && options.templates) {
1861
+ const currentNames = new Set(options.templates.map((t) => t.name));
1862
+ const fallback = pinned.filter((t) => !currentNames.has(t.name));
1863
+ templates = [...options.templates.map((t) => ({ ...t })), ...fallback];
1864
+ bindings = {};
1865
+ for (const t of fallback) {
1866
+ if (provenance.bindings[t.name] !== void 0) bindings[t.name] = provenance.bindings[t.name];
1867
+ }
1868
+ } else {
1869
+ templates = pinned;
1870
+ bindings = { ...provenance.bindings };
1871
+ }
1872
+ const resolved = resolvePrompt(rawPrompt, templates, { bindings, ...values ? { values } : {} });
1873
+ const sourceMap = resolved.map.map((segment) => {
1874
+ const { locked: _locked, ...rest } = segment;
1875
+ return rest;
1876
+ });
1877
+ if (resolved.text !== provenance.resolvedPrompt && !overrides) {
1878
+ drift.push({
1879
+ type: "prompt-changed",
1880
+ severity: "info",
1881
+ from: provenance.resolvedPrompt,
1882
+ to: resolved.text,
1883
+ applied: true,
1884
+ message: "The resolved prompt differs from the recorded one."
1885
+ });
1886
+ }
1887
+ const base = clone2(mode === "exact" ? provenance.request : record.settings);
1888
+ const recordedPlanned = provenance.request;
1889
+ if (base.prompt === void 0 || base.prompt === provenance.resolvedPrompt) base.prompt = resolved.text;
1890
+ else if (base.prompt === provenance.rawPrompt) base.prompt = rawPrompt;
1891
+ if (mode === "latest" && record.mentions?.length) {
1892
+ const plannedRole = new Map((recordedPlanned.references ?? []).map((ref) => [ref.id, ref.role]));
1893
+ base.references = [
1894
+ ...base.references ?? [],
1895
+ ...record.mentions.map((m) => ({
1896
+ id: m.id,
1897
+ role: m.role || plannedRole.get(m.id) || "image_ref",
1898
+ ...m.url !== void 0 ? { url: m.url } : {},
1899
+ ...m.mediaType !== void 0 ? { mediaType: m.mediaType } : {},
1900
+ ...m.contentHash !== void 0 ? { contentHash: m.contentHash } : {}
1901
+ }))
1902
+ ];
1903
+ } else if (mode === "latest" && base.references === void 0 && recordedPlanned.references) {
1904
+ base.references = clone2(recordedPlanned.references);
1905
+ }
1906
+ if (record.seed !== void 0) base.seed = record.seed;
1907
+ base.model = provenance.modelId;
1908
+ const { given, caps } = findModel(options.capabilities, provenance.modelId);
1909
+ if (overrides) {
1910
+ if (overrides.settings) {
1911
+ const { params, ...rest } = overrides.settings;
1912
+ Object.assign(base, clone2(rest));
1913
+ if (params) base.params = { ...base.params ?? {}, ...clone2(params) };
1914
+ }
1915
+ if (overrides.seed === null) delete base.seed;
1916
+ else base.seed = overrides.seed ?? randomSeed(caps, record.seed, overrides.random ?? Math.random);
1917
+ if (base.seed === void 0) delete base.seed;
1918
+ }
1919
+ const resolveAsset = toAssetResolver(options.assets);
1920
+ if (resolveAsset && base.references) {
1921
+ const recordedById = new Map(record.assets.map((a) => [refKey(a), a]));
1922
+ const kept = [];
1923
+ for (const ref of base.references) {
1924
+ const info = resolveAsset(ref.id);
1925
+ if (!info) {
1926
+ drift.push({
1927
+ type: "asset-missing",
1928
+ severity: "error",
1929
+ assetId: ref.id,
1930
+ role: ref.role,
1931
+ message: `Asset "${ref.id}" (${ref.role}) is no longer available.`
1932
+ });
1933
+ continue;
1934
+ }
1935
+ const recordedHash = recordedById.get(refKey(ref))?.contentHash ?? ref.contentHash;
1936
+ if (recordedHash !== void 0 && info.contentHash !== void 0 && recordedHash !== info.contentHash) {
1937
+ drift.push({
1938
+ type: "asset-changed",
1939
+ severity: "warning",
1940
+ assetId: ref.id,
1941
+ role: ref.role,
1942
+ from: recordedHash,
1943
+ to: info.contentHash,
1944
+ applied: true,
1945
+ message: `Asset "${ref.id}" (${ref.role}) has changed since this generation.`
1946
+ });
1947
+ }
1948
+ const next = { ...ref };
1949
+ if (info.contentHash !== void 0) next.contentHash = info.contentHash;
1950
+ if (next.mediaType === void 0 && info.mediaType !== void 0) next.mediaType = info.mediaType;
1951
+ kept.push(next);
1952
+ }
1953
+ if (kept.length) base.references = kept;
1954
+ else delete base.references;
1955
+ }
1956
+ let plan;
1957
+ let request = base;
1958
+ if (given && !caps) {
1959
+ drift.push({
1960
+ type: "model-missing",
1961
+ severity: "error",
1962
+ modelId: provenance.modelId,
1963
+ message: `Model "${provenance.modelId}" is no longer available.`
1964
+ });
1965
+ } else if (caps) {
1966
+ const capabilityHash = hashCapabilities(caps);
1967
+ if (record.capabilityHash !== void 0 && record.capabilityHash !== capabilityHash) {
1968
+ drift.push({
1969
+ type: "model-changed",
1970
+ severity: "warning",
1971
+ modelId: caps.id,
1972
+ from: record.capabilityHash,
1973
+ to: capabilityHash,
1974
+ message: `The capabilities of "${caps.name ?? caps.id}" changed since this generation.`
1975
+ });
1976
+ }
1977
+ plan = planGenerationRequest(base, caps, {
1978
+ strategy: options.strategy ?? record.strategy,
1979
+ resolvedPrompt: resolved.text
1980
+ });
1981
+ request = plan.request;
1982
+ const severity = mode === "exact" && !overrides ? "warning" : "info";
1983
+ const type = mode === "exact" && !overrides ? "setting-unsupported" : "setting-changed";
1984
+ const describeChange = (field, from, to) => {
1985
+ const adjustment = plan.adjustments.find((a) => a.field === field || a.field.startsWith(`${field}.`));
1986
+ drift.push({
1987
+ type,
1988
+ severity,
1989
+ field,
1990
+ from,
1991
+ to,
1992
+ applied: true,
1993
+ message: type === "setting-unsupported" ? `${field} ${show(from)} is no longer supported; using ${show(to)}.${adjustment ? ` ${adjustment.reason}` : ""}` : `${field} was ${show(from)}, now ${show(to)}.${adjustment ? ` ${adjustment.reason}` : ""}`
1994
+ });
1995
+ };
1996
+ if (!overrides) {
1997
+ for (const field of GENERATION_REQUEST_FIELDS) {
1998
+ if (SKIPPED_SETTING_FIELDS.has(field)) continue;
1999
+ const from = recordedPlanned[field];
2000
+ const to = request[field];
2001
+ if (!sameValue(from, to)) describeChange(field, from, to);
2002
+ }
2003
+ const paramNames = /* @__PURE__ */ new Set([...Object.keys(recordedPlanned.params ?? {}), ...Object.keys(request.params ?? {})]);
2004
+ for (const name of [...paramNames].sort()) {
2005
+ const from = recordedPlanned.params?.[name];
2006
+ const to = request.params?.[name];
2007
+ if (!sameValue(from, to)) describeChange(`params.${name}`, from, to);
2008
+ }
2009
+ if (!sameValue(record.seed ?? recordedPlanned.seed, request.seed)) describeChange("seed", record.seed ?? recordedPlanned.seed, request.seed);
2010
+ const missing = new Set(drift.filter((d) => d.type === "asset-missing").map((d) => `${d.role}:${d.assetId}`));
2011
+ const fromRefs = (recordedPlanned.references ?? []).map(refKey).filter((key) => !missing.has(key));
2012
+ const toRefs = (request.references ?? []).map(refKey);
2013
+ if (!sameValue(fromRefs, toRefs)) describeChange("references", fromRefs, toRefs);
2014
+ }
2015
+ for (const error of plan.errors) {
2016
+ drift.push({
2017
+ type: "setting-unsupported",
2018
+ severity: "error",
2019
+ field: error.field,
2020
+ from: error.value,
2021
+ message: error.message
2022
+ });
2023
+ }
2024
+ }
2025
+ const ok = !drift.some((d) => d.severity === "error") && (plan ? plan.ok : true);
2026
+ const recordOptions = {
2027
+ rawPrompt,
2028
+ templates,
2029
+ bindings,
2030
+ ...values ? { values } : {},
2031
+ model: caps ?? provenance.modelId,
2032
+ request,
2033
+ ...plan ? { plan } : {},
2034
+ strategy: options.strategy ?? record.strategy,
2035
+ parentId: record.id,
2036
+ kind
2037
+ };
2038
+ return {
2039
+ mode,
2040
+ ok,
2041
+ request,
2042
+ ...plan ? { plan } : {},
2043
+ rawPrompt,
2044
+ resolvedPrompt: resolved.text,
2045
+ sourceMap,
2046
+ values,
2047
+ templates,
2048
+ bindings,
2049
+ drift,
2050
+ parentId: record.id,
2051
+ kind,
2052
+ recordOptions
2053
+ };
2054
+ }
2055
+ function recreateRequest(input, options) {
2056
+ if (isGenerationRecord(input)) return rebuild(input, options ?? {}, "recreate");
2057
+ const provenance = input;
2058
+ const request = clone2(provenance.request);
2059
+ request.model = provenance.modelId;
2060
+ if (request.prompt === void 0) request.prompt = provenance.resolvedPrompt;
2061
+ return request;
2062
+ }
2063
+ function variationRequest(record, overrides = {}, options = {}) {
2064
+ return rebuild(record, options, "variation", overrides);
2065
+ }
2066
+ var DRIFT_TYPE_LABELS = {
2067
+ "template-changed": "Template changed",
2068
+ "template-missing": "Template deleted",
2069
+ "prompt-changed": "Prompt changed",
2070
+ "model-missing": "Model unavailable",
2071
+ "model-changed": "Model changed",
2072
+ "setting-unsupported": "Setting unsupported",
2073
+ "setting-changed": "Setting changed",
2074
+ "asset-missing": "Asset missing",
2075
+ "asset-changed": "Asset changed"
2076
+ };
2077
+ function blockingDrift(drift) {
2078
+ return drift.filter((d) => d.severity === "error");
2079
+ }
2080
+
2081
+ // src/generation/store.ts
2082
+ var INDEX_VERSION = 1;
2083
+ var time = (value) => new Date(value).getTime();
2084
+ function summarizeGenerationRecord(record) {
2085
+ const unique = (values) => [...new Set(values)];
2086
+ return {
2087
+ id: record.id,
2088
+ modelId: record.provenance.modelId,
2089
+ kind: record.kind,
2090
+ ...record.parentId !== void 0 ? { parentId: record.parentId } : {},
2091
+ createdAt: record.createdAt,
2092
+ templates: unique(record.templates.map((t) => t.name)),
2093
+ templateIds: unique(record.templates.flatMap((t) => t.templateId !== void 0 ? [t.templateId] : [])),
2094
+ assets: unique(record.assets.map((a) => a.id)),
2095
+ roles: unique(record.assets.map((a) => a.role)),
2096
+ preview: record.provenance.resolvedPrompt.slice(0, 160)
2097
+ };
2098
+ }
2099
+ function matchesRecordFilter(summary, filter = {}) {
2100
+ if (filter.model !== void 0) {
2101
+ const models = typeof filter.model === "string" ? [filter.model] : filter.model;
2102
+ if (!models.includes(summary.modelId)) return false;
2103
+ }
2104
+ if (filter.template !== void 0 && !summary.templates.includes(filter.template) && !summary.templateIds.includes(filter.template)) {
2105
+ return false;
2106
+ }
2107
+ if (filter.asset !== void 0 && !summary.assets.includes(filter.asset)) return false;
2108
+ if (filter.role !== void 0 && !summary.roles.includes(filter.role)) return false;
2109
+ if (filter.kind !== void 0 && summary.kind !== filter.kind) return false;
2110
+ if (filter.parentId !== void 0 && summary.parentId !== filter.parentId) return false;
2111
+ const created = time(summary.createdAt);
2112
+ if (filter.since !== void 0 && created < time(filter.since)) return false;
2113
+ if (filter.until !== void 0 && created >= time(filter.until)) return false;
2114
+ if (filter.text !== void 0 && !summary.preview.toLowerCase().includes(filter.text.toLowerCase())) return false;
2115
+ return true;
2116
+ }
2117
+ var newestFirst = (a, b) => time(b.createdAt) - time(a.createdAt) || (a.id < b.id ? 1 : a.id > b.id ? -1 : 0);
2118
+ function createGenerationRecordStore(options) {
2119
+ const storage = options.storage;
2120
+ const ns = options.namespace ?? "pixerate.generation-records";
2121
+ const indexKey = `${ns}:index`;
2122
+ const recordKey = (id) => `${ns}:record:${id}`;
2123
+ const listeners = /* @__PURE__ */ new Set();
2124
+ let index = null;
2125
+ let queue = Promise.resolve();
2126
+ const run = (task) => {
2127
+ const result = queue.then(task, task);
2128
+ queue = result.catch(() => void 0);
2129
+ return result;
2130
+ };
2131
+ const notify = () => listeners.forEach((listener) => listener());
2132
+ const loadIndex = async () => {
2133
+ if (index) return index;
2134
+ const raw = await storage.get(indexKey);
2135
+ let items = [];
2136
+ if (raw) {
2137
+ try {
2138
+ const data = JSON.parse(raw);
2139
+ if (data && Array.isArray(data.items)) {
2140
+ items = data.items.filter((item) => item && typeof item.id === "string" && typeof item.createdAt === "string");
2141
+ }
2142
+ } catch {
2143
+ items = [];
2144
+ }
2145
+ }
2146
+ index = items;
2147
+ return index;
2148
+ };
2149
+ const saveIndex = async (items) => {
2150
+ await storage.set(indexKey, JSON.stringify({ version: INDEX_VERSION, items }));
2151
+ index = items;
2152
+ };
2153
+ const select = (items, filter = {}) => {
2154
+ const matched = items.filter((s) => matchesRecordFilter(s, filter)).sort(newestFirst);
2155
+ const offset = Math.max(0, filter.offset ?? 0);
2156
+ return matched.slice(offset, filter.limit !== void 0 ? offset + Math.max(0, filter.limit) : void 0);
2157
+ };
2158
+ const pruneLocked = async (prune) => {
2159
+ const items = [...await loadIndex()].sort(newestFirst);
2160
+ const removed = [];
2161
+ const kept = [];
2162
+ let candidates = 0;
2163
+ const cutoff = prune.olderThan !== void 0 ? time(prune.olderThan) : void 0;
2164
+ for (const item of items) {
2165
+ if (prune.keep?.(item)) {
2166
+ kept.push(item);
2167
+ candidates++;
2168
+ continue;
2169
+ }
2170
+ const tooOld = cutoff !== void 0 && time(item.createdAt) < cutoff;
2171
+ const overLimit = prune.maxRecords !== void 0 && candidates >= Math.max(0, prune.maxRecords);
2172
+ if (tooOld || overLimit) removed.push(item);
2173
+ else {
2174
+ kept.push(item);
2175
+ candidates++;
2176
+ }
2177
+ }
2178
+ if (removed.length === 0) return [];
2179
+ await saveIndex(kept);
2180
+ for (const item of removed) await storage.remove(recordKey(item.id));
2181
+ return removed.map((item) => item.id);
2182
+ };
2183
+ return {
2184
+ put: (record) => run(async () => {
2185
+ const serialized = serializeGenerationRecord(record);
2186
+ await storage.set(recordKey(record.id), serialized);
2187
+ const items = (await loadIndex()).filter((item) => item.id !== record.id);
2188
+ items.push(summarizeGenerationRecord(record));
2189
+ await saveIndex(items);
2190
+ if (options.maxRecords !== void 0) await pruneLocked({ maxRecords: options.maxRecords });
2191
+ notify();
2192
+ }),
2193
+ get: (id) => run(async () => {
2194
+ const raw = await storage.get(recordKey(id));
2195
+ if (!raw) return null;
2196
+ try {
2197
+ return parseGenerationRecord(raw);
2198
+ } catch {
2199
+ return null;
2200
+ }
2201
+ }),
2202
+ summaries: (filter) => run(async () => select(await loadIndex(), filter)),
2203
+ list: (filter) => run(async () => {
2204
+ const records = [];
2205
+ for (const summary of select(await loadIndex(), filter)) {
2206
+ const raw = await storage.get(recordKey(summary.id));
2207
+ if (!raw) continue;
2208
+ try {
2209
+ records.push(parseGenerationRecord(raw));
2210
+ } catch {
2211
+ }
2212
+ }
2213
+ return records;
2214
+ }),
2215
+ remove: (id) => run(async () => {
2216
+ const items = await loadIndex();
2217
+ const next = items.filter((item) => item.id !== id);
2218
+ if (next.length === items.length) return false;
2219
+ await saveIndex(next);
2220
+ await storage.remove(recordKey(id));
2221
+ notify();
2222
+ return true;
2223
+ }),
2224
+ prune: (prune) => run(async () => {
2225
+ const removed = await pruneLocked(prune);
2226
+ if (removed.length) notify();
2227
+ return removed;
2228
+ }),
2229
+ clear: () => run(async () => {
2230
+ const items = await loadIndex();
2231
+ await saveIndex([]);
2232
+ for (const item of items) await storage.remove(recordKey(item.id));
2233
+ notify();
2234
+ }),
2235
+ subscribe(listener) {
2236
+ listeners.add(listener);
2237
+ return () => {
2238
+ listeners.delete(listener);
2239
+ };
2240
+ }
2241
+ };
2242
+ }
1332
2243
  export {
2244
+ DRIFT_TYPE_LABELS,
2245
+ GENERATION_RECORD_FORMAT,
2246
+ GENERATION_RECORD_VERSION,
1333
2247
  GENERATION_REQUEST_FIELDS,
1334
2248
  MAX_PENDING_PERCENT,
1335
2249
  PROVENANCE_FORMAT,
@@ -1337,37 +2251,67 @@ export {
1337
2251
  ProvenanceParseError,
1338
2252
  QUALITY_LADDER,
1339
2253
  REFERENCE_ROLES,
2254
+ REFERENCE_ROLE_ALIASES,
1340
2255
  REFERENCE_ROLE_LABELS,
2256
+ REFERENCE_ROLE_NAME_REGEX,
1341
2257
  addReference,
1342
2258
  aspectRatioRank,
2259
+ blockingDrift,
1343
2260
  capabilitiesToJsonSchema,
2261
+ completeGenerationRecord,
1344
2262
  createBatchResolver,
2263
+ createGenerationRecord,
2264
+ createGenerationRecordId,
2265
+ createGenerationRecordStore,
1345
2266
  createGenerationToolProvider,
1346
2267
  createProvenance,
2268
+ defaultAssetRoleOptions,
2269
+ describeAssetMentions,
1347
2270
  estimateJobProgress,
1348
2271
  expectedDurationFor,
2272
+ findTextMentions,
2273
+ formatMentionText,
1349
2274
  formatRemaining,
2275
+ getAssetRoleOptions,
1350
2276
  getGenerationControls,
1351
2277
  getReferenceSlots,
1352
2278
  getRequestValue,
2279
+ hashCapabilities,
2280
+ hashText,
2281
+ hashValue,
2282
+ isGenerationRecord,
2283
+ isReferenceRoleName,
2284
+ matchesRecordFilter,
2285
+ mediaTypeMatches,
1353
2286
  mergeCapabilities,
2287
+ normalizeReferenceRole,
1354
2288
  parseControlValue,
2289
+ parseGenerationRecord,
1355
2290
  parseProvenance,
1356
2291
  pinnedTemplatesToTemplates,
1357
2292
  planGenerationRequest,
1358
2293
  qualityRank,
1359
2294
  recreateRequest,
2295
+ referenceRoleLabel,
1360
2296
  removeReference,
1361
2297
  replayProvenancePrompt,
1362
2298
  resolutionMegapixels,
1363
2299
  resolutionRank,
2300
+ resolveAssetMentions,
1364
2301
  seedCapability,
2302
+ serializeGenerationRecord,
1365
2303
  serializeProvenance,
2304
+ setMentionRoleInText,
1366
2305
  setRequestValue,
1367
2306
  snapToOption,
2307
+ stableStringify,
2308
+ summarizeGenerationRecord,
1368
2309
  supportedModes,
1369
2310
  supportedReferenceRoles,
1370
2311
  textFieldCapability,
1371
- validateJsonSchema
2312
+ toAssetResolver,
2313
+ validateAssetMentions,
2314
+ validateJsonSchema,
2315
+ variationRequest
1372
2316
  };
1373
2317
  //# sourceMappingURL=index.js.map