@character-foundry/character-foundry 0.4.2 → 0.4.3-dev.1766019473

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 (57) hide show
  1. package/dist/charx.cjs +15 -36
  2. package/dist/charx.cjs.map +1 -1
  3. package/dist/charx.d.cts +18 -9
  4. package/dist/charx.d.ts +18 -9
  5. package/dist/charx.js +15 -36
  6. package/dist/charx.js.map +1 -1
  7. package/dist/exporter.cjs +34 -38
  8. package/dist/exporter.cjs.map +1 -1
  9. package/dist/exporter.d.cts +18 -9
  10. package/dist/exporter.d.ts +18 -9
  11. package/dist/exporter.js +34 -38
  12. package/dist/exporter.js.map +1 -1
  13. package/dist/federation.cjs +104 -36
  14. package/dist/federation.cjs.map +1 -1
  15. package/dist/federation.d.cts +53 -9
  16. package/dist/federation.d.ts +53 -9
  17. package/dist/federation.js +104 -36
  18. package/dist/federation.js.map +1 -1
  19. package/dist/index.cjs +34 -38
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +42 -21
  22. package/dist/index.d.ts +42 -21
  23. package/dist/index.js +34 -38
  24. package/dist/index.js.map +1 -1
  25. package/dist/loader.cjs +101 -15
  26. package/dist/loader.cjs.map +1 -1
  27. package/dist/loader.d.cts +42 -14
  28. package/dist/loader.d.ts +42 -14
  29. package/dist/loader.js +101 -15
  30. package/dist/loader.js.map +1 -1
  31. package/dist/lorebook.d.cts +34 -17
  32. package/dist/lorebook.d.ts +34 -17
  33. package/dist/normalizer.cjs +2 -2
  34. package/dist/normalizer.cjs.map +1 -1
  35. package/dist/normalizer.d.cts +60 -30
  36. package/dist/normalizer.d.ts +60 -30
  37. package/dist/normalizer.js +2 -2
  38. package/dist/normalizer.js.map +1 -1
  39. package/dist/png.cjs +2 -2
  40. package/dist/png.cjs.map +1 -1
  41. package/dist/png.d.cts +32 -16
  42. package/dist/png.d.ts +32 -16
  43. package/dist/png.js +2 -2
  44. package/dist/png.js.map +1 -1
  45. package/dist/schemas.cjs +7 -7
  46. package/dist/schemas.cjs.map +1 -1
  47. package/dist/schemas.d.cts +96 -48
  48. package/dist/schemas.d.ts +96 -48
  49. package/dist/schemas.js +7 -7
  50. package/dist/schemas.js.map +1 -1
  51. package/dist/voxta.cjs +21 -4
  52. package/dist/voxta.cjs.map +1 -1
  53. package/dist/voxta.d.cts +28 -14
  54. package/dist/voxta.d.ts +28 -14
  55. package/dist/voxta.js +21 -4
  56. package/dist/voxta.js.map +1 -1
  57. package/package.json +6 -6
package/dist/loader.d.cts CHANGED
@@ -33,7 +33,8 @@ declare const CCv3CharacterBookSchema: z.ZodObject<{
33
33
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
34
34
  z.ZodEnum<[
35
35
  "before_char",
36
- "after_char"
36
+ "after_char",
37
+ "in_chat"
37
38
  ]>,
38
39
  z.ZodNumber,
39
40
  z.ZodLiteral<"">
@@ -76,7 +77,8 @@ declare const CCv3CharacterBookSchema: z.ZodObject<{
76
77
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
77
78
  z.ZodEnum<[
78
79
  "before_char",
79
- "after_char"
80
+ "after_char",
81
+ "in_chat"
80
82
  ]>,
81
83
  z.ZodNumber,
82
84
  z.ZodLiteral<"">
@@ -119,7 +121,8 @@ declare const CCv3CharacterBookSchema: z.ZodObject<{
119
121
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
120
122
  z.ZodEnum<[
121
123
  "before_char",
122
- "after_char"
124
+ "after_char",
125
+ "in_chat"
123
126
  ]>,
124
127
  z.ZodNumber,
125
128
  z.ZodLiteral<"">
@@ -164,7 +167,8 @@ declare const CCv3CharacterBookSchema: z.ZodObject<{
164
167
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
165
168
  z.ZodEnum<[
166
169
  "before_char",
167
- "after_char"
170
+ "after_char",
171
+ "in_chat"
168
172
  ]>,
169
173
  z.ZodNumber,
170
174
  z.ZodLiteral<"">
@@ -215,7 +219,8 @@ declare const CCv3CharacterBookSchema: z.ZodObject<{
215
219
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
216
220
  z.ZodEnum<[
217
221
  "before_char",
218
- "after_char"
222
+ "after_char",
223
+ "in_chat"
219
224
  ]>,
220
225
  z.ZodNumber,
221
226
  z.ZodLiteral<"">
@@ -291,7 +296,8 @@ declare const CCv3DataSchema: z.ZodObject<{
291
296
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
292
297
  z.ZodEnum<[
293
298
  "before_char",
294
- "after_char"
299
+ "after_char",
300
+ "in_chat"
295
301
  ]>,
296
302
  z.ZodNumber,
297
303
  z.ZodLiteral<"">
@@ -334,7 +340,8 @@ declare const CCv3DataSchema: z.ZodObject<{
334
340
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
335
341
  z.ZodEnum<[
336
342
  "before_char",
337
- "after_char"
343
+ "after_char",
344
+ "in_chat"
338
345
  ]>,
339
346
  z.ZodNumber,
340
347
  z.ZodLiteral<"">
@@ -377,7 +384,8 @@ declare const CCv3DataSchema: z.ZodObject<{
377
384
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
378
385
  z.ZodEnum<[
379
386
  "before_char",
380
- "after_char"
387
+ "after_char",
388
+ "in_chat"
381
389
  ]>,
382
390
  z.ZodNumber,
383
391
  z.ZodLiteral<"">
@@ -422,7 +430,8 @@ declare const CCv3DataSchema: z.ZodObject<{
422
430
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
423
431
  z.ZodEnum<[
424
432
  "before_char",
425
- "after_char"
433
+ "after_char",
434
+ "in_chat"
426
435
  ]>,
427
436
  z.ZodNumber,
428
437
  z.ZodLiteral<"">
@@ -473,7 +482,8 @@ declare const CCv3DataSchema: z.ZodObject<{
473
482
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
474
483
  z.ZodEnum<[
475
484
  "before_char",
476
- "after_char"
485
+ "after_char",
486
+ "in_chat"
477
487
  ]>,
478
488
  z.ZodNumber,
479
489
  z.ZodLiteral<"">
@@ -572,7 +582,8 @@ declare const CCv3DataSchema: z.ZodObject<{
572
582
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
573
583
  z.ZodEnum<[
574
584
  "before_char",
575
- "after_char"
585
+ "after_char",
586
+ "in_chat"
576
587
  ]>,
577
588
  z.ZodNumber,
578
589
  z.ZodLiteral<"">
@@ -647,7 +658,8 @@ declare const CCv3DataSchema: z.ZodObject<{
647
658
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
648
659
  z.ZodEnum<[
649
660
  "before_char",
650
- "after_char"
661
+ "after_char",
662
+ "in_chat"
651
663
  ]>,
652
664
  z.ZodNumber,
653
665
  z.ZodLiteral<"">
@@ -732,7 +744,8 @@ declare const CCv3DataSchema: z.ZodObject<{
732
744
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
733
745
  z.ZodEnum<[
734
746
  "before_char",
735
- "after_char"
747
+ "after_char",
748
+ "in_chat"
736
749
  ]>,
737
750
  z.ZodNumber,
738
751
  z.ZodLiteral<"">
@@ -811,7 +824,8 @@ declare const CCv3DataSchema: z.ZodObject<{
811
824
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
812
825
  z.ZodEnum<[
813
826
  "before_char",
814
- "after_char"
827
+ "after_char",
828
+ "in_chat"
815
829
  ]>,
816
830
  z.ZodNumber,
817
831
  z.ZodLiteral<"">
@@ -1180,6 +1194,13 @@ export interface AuthoritativeMetadata {
1180
1194
  tokens: TokenCounts;
1181
1195
  /** Content hash computed server-side */
1182
1196
  contentHash: string;
1197
+ /**
1198
+ * Content hash v2 computed server-side.
1199
+ *
1200
+ * @remarks
1201
+ * v1 is preserved for backwards compatibility. Prefer v2 for new storage/deduplication.
1202
+ */
1203
+ contentHashV2?: string;
1183
1204
  /** Whether the card has a lorebook */
1184
1205
  hasLorebook: boolean;
1185
1206
  /** Number of lorebook entries */
@@ -1279,6 +1300,13 @@ export declare function validateClientMetadata(clientMetadata: ClientMetadata, p
1279
1300
  * @returns SHA-256 hash of canonical content
1280
1301
  */
1281
1302
  export declare function computeContentHash(card: CCv3Data): Promise<string>;
1303
+ /**
1304
+ * Compute content hash v2 for a card (standalone utility)
1305
+ *
1306
+ * @param card - CCv3 card data
1307
+ * @returns SHA-256 hash of canonical content v2
1308
+ */
1309
+ export declare function computeContentHashV2(card: CCv3Data): Promise<string>;
1282
1310
  /**
1283
1311
  * Options for synchronous metadata validation
1284
1312
  */
package/dist/loader.d.ts CHANGED
@@ -33,7 +33,8 @@ declare const CCv3CharacterBookSchema: z.ZodObject<{
33
33
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
34
34
  z.ZodEnum<[
35
35
  "before_char",
36
- "after_char"
36
+ "after_char",
37
+ "in_chat"
37
38
  ]>,
38
39
  z.ZodNumber,
39
40
  z.ZodLiteral<"">
@@ -76,7 +77,8 @@ declare const CCv3CharacterBookSchema: z.ZodObject<{
76
77
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
77
78
  z.ZodEnum<[
78
79
  "before_char",
79
- "after_char"
80
+ "after_char",
81
+ "in_chat"
80
82
  ]>,
81
83
  z.ZodNumber,
82
84
  z.ZodLiteral<"">
@@ -119,7 +121,8 @@ declare const CCv3CharacterBookSchema: z.ZodObject<{
119
121
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
120
122
  z.ZodEnum<[
121
123
  "before_char",
122
- "after_char"
124
+ "after_char",
125
+ "in_chat"
123
126
  ]>,
124
127
  z.ZodNumber,
125
128
  z.ZodLiteral<"">
@@ -164,7 +167,8 @@ declare const CCv3CharacterBookSchema: z.ZodObject<{
164
167
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
165
168
  z.ZodEnum<[
166
169
  "before_char",
167
- "after_char"
170
+ "after_char",
171
+ "in_chat"
168
172
  ]>,
169
173
  z.ZodNumber,
170
174
  z.ZodLiteral<"">
@@ -215,7 +219,8 @@ declare const CCv3CharacterBookSchema: z.ZodObject<{
215
219
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
216
220
  z.ZodEnum<[
217
221
  "before_char",
218
- "after_char"
222
+ "after_char",
223
+ "in_chat"
219
224
  ]>,
220
225
  z.ZodNumber,
221
226
  z.ZodLiteral<"">
@@ -291,7 +296,8 @@ declare const CCv3DataSchema: z.ZodObject<{
291
296
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
292
297
  z.ZodEnum<[
293
298
  "before_char",
294
- "after_char"
299
+ "after_char",
300
+ "in_chat"
295
301
  ]>,
296
302
  z.ZodNumber,
297
303
  z.ZodLiteral<"">
@@ -334,7 +340,8 @@ declare const CCv3DataSchema: z.ZodObject<{
334
340
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
335
341
  z.ZodEnum<[
336
342
  "before_char",
337
- "after_char"
343
+ "after_char",
344
+ "in_chat"
338
345
  ]>,
339
346
  z.ZodNumber,
340
347
  z.ZodLiteral<"">
@@ -377,7 +384,8 @@ declare const CCv3DataSchema: z.ZodObject<{
377
384
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
378
385
  z.ZodEnum<[
379
386
  "before_char",
380
- "after_char"
387
+ "after_char",
388
+ "in_chat"
381
389
  ]>,
382
390
  z.ZodNumber,
383
391
  z.ZodLiteral<"">
@@ -422,7 +430,8 @@ declare const CCv3DataSchema: z.ZodObject<{
422
430
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
423
431
  z.ZodEnum<[
424
432
  "before_char",
425
- "after_char"
433
+ "after_char",
434
+ "in_chat"
426
435
  ]>,
427
436
  z.ZodNumber,
428
437
  z.ZodLiteral<"">
@@ -473,7 +482,8 @@ declare const CCv3DataSchema: z.ZodObject<{
473
482
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
474
483
  z.ZodEnum<[
475
484
  "before_char",
476
- "after_char"
485
+ "after_char",
486
+ "in_chat"
477
487
  ]>,
478
488
  z.ZodNumber,
479
489
  z.ZodLiteral<"">
@@ -572,7 +582,8 @@ declare const CCv3DataSchema: z.ZodObject<{
572
582
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
573
583
  z.ZodEnum<[
574
584
  "before_char",
575
- "after_char"
585
+ "after_char",
586
+ "in_chat"
576
587
  ]>,
577
588
  z.ZodNumber,
578
589
  z.ZodLiteral<"">
@@ -647,7 +658,8 @@ declare const CCv3DataSchema: z.ZodObject<{
647
658
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
648
659
  z.ZodEnum<[
649
660
  "before_char",
650
- "after_char"
661
+ "after_char",
662
+ "in_chat"
651
663
  ]>,
652
664
  z.ZodNumber,
653
665
  z.ZodLiteral<"">
@@ -732,7 +744,8 @@ declare const CCv3DataSchema: z.ZodObject<{
732
744
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
733
745
  z.ZodEnum<[
734
746
  "before_char",
735
- "after_char"
747
+ "after_char",
748
+ "in_chat"
736
749
  ]>,
737
750
  z.ZodNumber,
738
751
  z.ZodLiteral<"">
@@ -811,7 +824,8 @@ declare const CCv3DataSchema: z.ZodObject<{
811
824
  position: z.ZodOptional<z.ZodNullable<z.ZodUnion<[
812
825
  z.ZodEnum<[
813
826
  "before_char",
814
- "after_char"
827
+ "after_char",
828
+ "in_chat"
815
829
  ]>,
816
830
  z.ZodNumber,
817
831
  z.ZodLiteral<"">
@@ -1180,6 +1194,13 @@ export interface AuthoritativeMetadata {
1180
1194
  tokens: TokenCounts;
1181
1195
  /** Content hash computed server-side */
1182
1196
  contentHash: string;
1197
+ /**
1198
+ * Content hash v2 computed server-side.
1199
+ *
1200
+ * @remarks
1201
+ * v1 is preserved for backwards compatibility. Prefer v2 for new storage/deduplication.
1202
+ */
1203
+ contentHashV2?: string;
1183
1204
  /** Whether the card has a lorebook */
1184
1205
  hasLorebook: boolean;
1185
1206
  /** Number of lorebook entries */
@@ -1279,6 +1300,13 @@ export declare function validateClientMetadata(clientMetadata: ClientMetadata, p
1279
1300
  * @returns SHA-256 hash of canonical content
1280
1301
  */
1281
1302
  export declare function computeContentHash(card: CCv3Data): Promise<string>;
1303
+ /**
1304
+ * Compute content hash v2 for a card (standalone utility)
1305
+ *
1306
+ * @param card - CCv3 card data
1307
+ * @returns SHA-256 hash of canonical content v2
1308
+ */
1309
+ export declare function computeContentHashV2(card: CCv3Data): Promise<string>;
1282
1310
  /**
1283
1311
  * Options for synchronous metadata validation
1284
1312
  */
package/dist/loader.js CHANGED
@@ -473,7 +473,7 @@ var CCv2LorebookEntrySchema = z2.object({
473
473
  selective: z2.boolean().nullable().optional(),
474
474
  secondary_keys: z2.array(z2.string()).nullable().optional(),
475
475
  constant: z2.boolean().nullable().optional(),
476
- position: z2.union([z2.enum(["before_char", "after_char"]), z2.number().int(), z2.literal("")]).nullable().optional()
476
+ position: z2.union([z2.enum(["before_char", "after_char", "in_chat"]), z2.number().int(), z2.literal("")]).nullable().optional()
477
477
  }).passthrough();
478
478
  var CCv2CharacterBookSchema = z2.object({
479
479
  name: z2.string().optional(),
@@ -537,7 +537,7 @@ var CCv3LorebookEntrySchema = z3.object({
537
537
  selective: z3.boolean().nullable().optional(),
538
538
  secondary_keys: z3.array(z3.string()).nullable().optional(),
539
539
  constant: z3.boolean().nullable().optional(),
540
- position: z3.union([z3.enum(["before_char", "after_char"]), z3.number().int(), z3.literal("")]).nullable().optional(),
540
+ position: z3.union([z3.enum(["before_char", "after_char", "in_chat"]), z3.number().int(), z3.literal("")]).nullable().optional(),
541
541
  extensions: z3.record(z3.unknown()).optional(),
542
542
  // v3 specific - also lenient with types since SillyTavern uses numbers for enums
543
543
  automation_id: z3.string().optional(),
@@ -8697,7 +8697,7 @@ function defaultTokenCounter(_card) {
8697
8697
  total: 0
8698
8698
  };
8699
8699
  }
8700
- function getCanonicalContent(card) {
8700
+ function getCanonicalContentV1(card) {
8701
8701
  const normalized = {
8702
8702
  name: card.data.name,
8703
8703
  description: card.data.description || "",
@@ -8719,6 +8719,69 @@ function getCanonicalContent(card) {
8719
8719
  };
8720
8720
  return JSON.stringify(normalized, Object.keys(normalized).sort());
8721
8721
  }
8722
+ function stableStringify(value) {
8723
+ if (value === null) return "null";
8724
+ switch (typeof value) {
8725
+ case "string":
8726
+ return JSON.stringify(value);
8727
+ case "number":
8728
+ return Number.isFinite(value) ? String(value) : "null";
8729
+ case "boolean":
8730
+ return value ? "true" : "false";
8731
+ case "bigint":
8732
+ return JSON.stringify(value.toString());
8733
+ case "undefined":
8734
+ case "function":
8735
+ case "symbol":
8736
+ return "null";
8737
+ case "object": {
8738
+ if (Array.isArray(value)) {
8739
+ const parts2 = value.map((item) => {
8740
+ if (item === void 0 || typeof item === "function" || typeof item === "symbol") {
8741
+ return "null";
8742
+ }
8743
+ return stableStringify(item);
8744
+ });
8745
+ return `[${parts2.join(",")}]`;
8746
+ }
8747
+ const obj = value;
8748
+ const keys = Object.keys(obj).sort();
8749
+ const parts = [];
8750
+ for (const key of keys) {
8751
+ const v = obj[key];
8752
+ if (v === void 0 || typeof v === "function" || typeof v === "symbol") {
8753
+ continue;
8754
+ }
8755
+ parts.push(`${JSON.stringify(key)}:${stableStringify(v)}`);
8756
+ }
8757
+ return `{${parts.join(",")}}`;
8758
+ }
8759
+ default:
8760
+ return "null";
8761
+ }
8762
+ }
8763
+ function getCanonicalContentV2(card) {
8764
+ const normalized = {
8765
+ name: card.data.name,
8766
+ description: card.data.description || "",
8767
+ personality: card.data.personality || "",
8768
+ scenario: card.data.scenario || "",
8769
+ first_mes: card.data.first_mes || "",
8770
+ mes_example: card.data.mes_example || "",
8771
+ system_prompt: card.data.system_prompt || "",
8772
+ post_history_instructions: card.data.post_history_instructions || "",
8773
+ alternate_greetings: card.data.alternate_greetings || [],
8774
+ character_book: card.data.character_book ? {
8775
+ entries: (card.data.character_book.entries || []).map((e) => ({
8776
+ keys: e.keys,
8777
+ content: e.content,
8778
+ enabled: e.enabled
8779
+ }))
8780
+ } : null,
8781
+ creator_notes: card.data.creator_notes || ""
8782
+ };
8783
+ return stableStringify(normalized);
8784
+ }
8722
8785
  function isWithinTolerance(clientValue, computedValue, tolerance) {
8723
8786
  if (clientValue === void 0) return false;
8724
8787
  if (computedValue === 0) return clientValue === 0;
@@ -8739,15 +8802,18 @@ async function validateClientMetadata(clientMetadata, parseResult, options = {})
8739
8802
  const warnings = [];
8740
8803
  const errors = [];
8741
8804
  const computedTokens = countTokens(card);
8742
- const canonicalContent = getCanonicalContent(card);
8743
- const computedHash = await computeHash(canonicalContent);
8805
+ const canonicalContentV1 = getCanonicalContentV1(card);
8806
+ const canonicalContentV2 = getCanonicalContentV2(card);
8807
+ const computedHashV1 = await computeHash(canonicalContentV1);
8808
+ const computedHashV2 = await computeHash(canonicalContentV2);
8744
8809
  const entries = card.data.character_book?.entries || [];
8745
8810
  const computedHasLorebook = entries.length > 0;
8746
8811
  const computedLorebookCount = entries.length;
8747
8812
  const authoritative = {
8748
8813
  name: card.data.name,
8749
8814
  tokens: computedTokens,
8750
- contentHash: computedHash,
8815
+ contentHash: computedHashV1,
8816
+ contentHashV2: computedHashV2,
8751
8817
  hasLorebook: computedHasLorebook,
8752
8818
  lorebookEntriesCount: computedLorebookCount
8753
8819
  };
@@ -8759,21 +8825,27 @@ async function validateClientMetadata(clientMetadata, parseResult, options = {})
8759
8825
  withinTolerance: false
8760
8826
  });
8761
8827
  }
8762
- if (clientMetadata.contentHash !== computedHash) {
8828
+ const matchesV1 = clientMetadata.contentHash === computedHashV1;
8829
+ const matchesV2 = clientMetadata.contentHash === computedHashV2;
8830
+ if (!matchesV1 && !matchesV2) {
8763
8831
  const disc = {
8764
8832
  field: "contentHash",
8765
8833
  clientValue: clientMetadata.contentHash,
8766
- computedValue: computedHash,
8834
+ computedValue: computedHashV1,
8767
8835
  withinTolerance: false
8768
8836
  };
8769
8837
  discrepancies.push(disc);
8770
8838
  if (allowHashMismatch) {
8771
8839
  warnings.push(
8772
- `Content hash mismatch: client=${clientMetadata.contentHash.substring(0, 8)}..., server=${computedHash.substring(0, 8)}...`
8840
+ `Content hash mismatch: client=${clientMetadata.contentHash.substring(0, 8)}..., server(v1)=${computedHashV1.substring(0, 8)}..., server(v2)=${computedHashV2.substring(0, 8)}...`
8773
8841
  );
8774
8842
  } else {
8775
8843
  errors.push("Content hash mismatch - possible tampering or encoding difference");
8776
8844
  }
8845
+ } else if (matchesV1 && !matchesV2) {
8846
+ warnings.push(
8847
+ "Client contentHash matches legacy v1 canonicalization. Prefer authoritative.contentHashV2 for new storage."
8848
+ );
8777
8849
  }
8778
8850
  const tokenFields = [
8779
8851
  "description",
@@ -8854,7 +8926,11 @@ async function validateClientMetadata(clientMetadata, parseResult, options = {})
8854
8926
  };
8855
8927
  }
8856
8928
  async function computeContentHash(card) {
8857
- const content = getCanonicalContent(card);
8929
+ const content = getCanonicalContentV1(card);
8930
+ return sha256Hash(content);
8931
+ }
8932
+ async function computeContentHashV2(card) {
8933
+ const content = getCanonicalContentV2(card);
8858
8934
  return sha256Hash(content);
8859
8935
  }
8860
8936
  function validateClientMetadataSync(clientMetadata, parseResult, options) {
@@ -8870,15 +8946,18 @@ function validateClientMetadataSync(clientMetadata, parseResult, options) {
8870
8946
  const warnings = [];
8871
8947
  const errors = [];
8872
8948
  const computedTokens = countTokens(card);
8873
- const canonicalContent = getCanonicalContent(card);
8874
- const computedHash = computeHash(canonicalContent);
8949
+ const canonicalContentV1 = getCanonicalContentV1(card);
8950
+ const canonicalContentV2 = getCanonicalContentV2(card);
8951
+ const computedHashV1 = computeHash(canonicalContentV1);
8952
+ const computedHashV2 = computeHash(canonicalContentV2);
8875
8953
  const entries = card.data.character_book?.entries || [];
8876
8954
  const computedHasLorebook = entries.length > 0;
8877
8955
  const computedLorebookCount = entries.length;
8878
8956
  const authoritative = {
8879
8957
  name: card.data.name,
8880
8958
  tokens: computedTokens,
8881
- contentHash: computedHash,
8959
+ contentHash: computedHashV1,
8960
+ contentHashV2: computedHashV2,
8882
8961
  hasLorebook: computedHasLorebook,
8883
8962
  lorebookEntriesCount: computedLorebookCount
8884
8963
  };
@@ -8890,11 +8969,13 @@ function validateClientMetadataSync(clientMetadata, parseResult, options) {
8890
8969
  withinTolerance: false
8891
8970
  });
8892
8971
  }
8893
- if (clientMetadata.contentHash !== computedHash) {
8972
+ const matchesV1 = clientMetadata.contentHash === computedHashV1;
8973
+ const matchesV2 = clientMetadata.contentHash === computedHashV2;
8974
+ if (!matchesV1 && !matchesV2) {
8894
8975
  discrepancies.push({
8895
8976
  field: "contentHash",
8896
8977
  clientValue: clientMetadata.contentHash,
8897
- computedValue: computedHash,
8978
+ computedValue: computedHashV1,
8898
8979
  withinTolerance: false
8899
8980
  });
8900
8981
  if (allowHashMismatch) {
@@ -8902,6 +8983,10 @@ function validateClientMetadataSync(clientMetadata, parseResult, options) {
8902
8983
  } else {
8903
8984
  errors.push("Content hash mismatch");
8904
8985
  }
8986
+ } else if (matchesV1 && !matchesV2) {
8987
+ warnings.push(
8988
+ "Client contentHash matches legacy v1 canonicalization. Prefer authoritative.contentHashV2 for new storage."
8989
+ );
8905
8990
  }
8906
8991
  const tokenFields = [
8907
8992
  "description",
@@ -8970,6 +9055,7 @@ function validateClientMetadataSync(clientMetadata, parseResult, options) {
8970
9055
  }
8971
9056
  export {
8972
9057
  computeContentHash,
9058
+ computeContentHashV2,
8973
9059
  detectFormat,
8974
9060
  getContainerFormat,
8975
9061
  mightBeCard,