@character-foundry/character-foundry 0.4.3 → 0.5.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 (63) hide show
  1. package/dist/app-framework.cjs +12 -3
  2. package/dist/app-framework.cjs.map +1 -1
  3. package/dist/app-framework.d.cts +9 -1
  4. package/dist/app-framework.d.ts +9 -1
  5. package/dist/app-framework.js +12 -3
  6. package/dist/app-framework.js.map +1 -1
  7. package/dist/charx.cjs +85 -52
  8. package/dist/charx.cjs.map +1 -1
  9. package/dist/charx.d.cts +22 -22
  10. package/dist/charx.d.ts +22 -22
  11. package/dist/charx.js +85 -52
  12. package/dist/charx.js.map +1 -1
  13. package/dist/exporter.cjs +104 -54
  14. package/dist/exporter.cjs.map +1 -1
  15. package/dist/exporter.d.cts +19 -19
  16. package/dist/exporter.d.ts +19 -19
  17. package/dist/exporter.js +104 -54
  18. package/dist/exporter.js.map +1 -1
  19. package/dist/federation.cjs +104 -36
  20. package/dist/federation.cjs.map +1 -1
  21. package/dist/federation.d.cts +54 -19
  22. package/dist/federation.d.ts +54 -19
  23. package/dist/federation.js +104 -36
  24. package/dist/federation.js.map +1 -1
  25. package/dist/index.cjs +104 -54
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +29 -29
  28. package/dist/index.d.ts +29 -29
  29. package/dist/index.js +104 -54
  30. package/dist/index.js.map +1 -1
  31. package/dist/loader.cjs +171 -31
  32. package/dist/loader.cjs.map +1 -1
  33. package/dist/loader.d.cts +37 -23
  34. package/dist/loader.d.ts +37 -23
  35. package/dist/loader.js +171 -31
  36. package/dist/loader.js.map +1 -1
  37. package/dist/lorebook.d.cts +23 -23
  38. package/dist/lorebook.d.ts +23 -23
  39. package/dist/normalizer.cjs +72 -18
  40. package/dist/normalizer.cjs.map +1 -1
  41. package/dist/normalizer.d.cts +37 -37
  42. package/dist/normalizer.d.ts +37 -37
  43. package/dist/normalizer.js +72 -18
  44. package/dist/normalizer.js.map +1 -1
  45. package/dist/png.cjs +72 -18
  46. package/dist/png.cjs.map +1 -1
  47. package/dist/png.d.cts +25 -25
  48. package/dist/png.d.ts +25 -25
  49. package/dist/png.js +72 -18
  50. package/dist/png.js.map +1 -1
  51. package/dist/schemas.cjs +80 -23
  52. package/dist/schemas.cjs.map +1 -1
  53. package/dist/schemas.d.cts +85 -67
  54. package/dist/schemas.d.ts +85 -67
  55. package/dist/schemas.js +80 -23
  56. package/dist/schemas.js.map +1 -1
  57. package/dist/voxta.cjs +91 -20
  58. package/dist/voxta.cjs.map +1 -1
  59. package/dist/voxta.d.cts +23 -23
  60. package/dist/voxta.d.ts +23 -23
  61. package/dist/voxta.js +91 -20
  62. package/dist/voxta.js.map +1 -1
  63. package/package.json +24 -24
package/dist/loader.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var loader_exports = {};
22
22
  __export(loader_exports, {
23
23
  computeContentHash: () => computeContentHash,
24
+ computeContentHashV2: () => computeContentHashV2,
24
25
  detectFormat: () => detectFormat,
25
26
  getContainerFormat: () => getContainerFormat,
26
27
  mightBeCard: () => mightBeCard,
@@ -443,6 +444,58 @@ var import_zod = require("zod");
443
444
  var import_zod2 = require("zod");
444
445
  var import_zod3 = require("zod");
445
446
  var import_zod4 = require("zod");
447
+ function preprocessTimestamp(val) {
448
+ if (val === null || val === void 0) return void 0;
449
+ let num;
450
+ if (typeof val === "number") {
451
+ num = val;
452
+ } else if (typeof val === "string") {
453
+ const trimmed = val.trim();
454
+ if (!trimmed) return void 0;
455
+ const parsed = Number(trimmed);
456
+ if (!isNaN(parsed)) {
457
+ num = parsed;
458
+ } else {
459
+ const date = new Date(trimmed);
460
+ if (isNaN(date.getTime())) return void 0;
461
+ num = Math.floor(date.getTime() / 1e3);
462
+ }
463
+ } else {
464
+ return void 0;
465
+ }
466
+ if (num > 1e10) {
467
+ num = Math.floor(num / 1e3);
468
+ }
469
+ if (num < 0) return void 0;
470
+ return num;
471
+ }
472
+ function preprocessNumeric(val) {
473
+ if (val === null || val === void 0) return void 0;
474
+ if (typeof val === "number") {
475
+ return isNaN(val) ? void 0 : val;
476
+ }
477
+ if (typeof val === "string") {
478
+ const trimmed = val.trim();
479
+ if (!trimmed) return void 0;
480
+ const parsed = Number(trimmed);
481
+ return isNaN(parsed) ? void 0 : parsed;
482
+ }
483
+ return void 0;
484
+ }
485
+ var KNOWN_ASSET_TYPES = /* @__PURE__ */ new Set([
486
+ "icon",
487
+ "background",
488
+ "emotion",
489
+ "user_icon",
490
+ "sound",
491
+ "video",
492
+ "custom",
493
+ "x-risu-asset"
494
+ ]);
495
+ function preprocessAssetType(val) {
496
+ if (typeof val !== "string") return "custom";
497
+ return KNOWN_ASSET_TYPES.has(val) ? val : "custom";
498
+ }
446
499
  var ISO8601Schema = import_zod.z.string().datetime();
447
500
  var UUIDSchema = import_zod.z.string().uuid();
448
501
  var SpecSchema = import_zod.z.enum(["v2", "v3"]);
@@ -465,16 +518,19 @@ var SourceFormatSchema = import_zod.z.enum([
465
518
  // VoxPkg format
466
519
  ]);
467
520
  var OriginalShapeSchema = import_zod.z.enum(["wrapped", "unwrapped", "legacy"]);
468
- var AssetTypeSchema = import_zod.z.enum([
469
- "icon",
470
- "background",
471
- "emotion",
472
- "user_icon",
473
- "sound",
474
- "video",
475
- "custom",
476
- "x-risu-asset"
477
- ]);
521
+ var AssetTypeSchema = import_zod.z.preprocess(
522
+ preprocessAssetType,
523
+ import_zod.z.enum([
524
+ "icon",
525
+ "background",
526
+ "emotion",
527
+ "user_icon",
528
+ "sound",
529
+ "video",
530
+ "custom",
531
+ "x-risu-asset"
532
+ ])
533
+ );
478
534
  var AssetDescriptorSchema = import_zod.z.object({
479
535
  type: AssetTypeSchema,
480
536
  uri: import_zod.z.string(),
@@ -508,8 +564,8 @@ var CCv2LorebookEntrySchema = import_zod2.z.object({
508
564
  var CCv2CharacterBookSchema = import_zod2.z.object({
509
565
  name: import_zod2.z.string().optional(),
510
566
  description: import_zod2.z.string().optional(),
511
- scan_depth: import_zod2.z.number().int().nonnegative().optional(),
512
- token_budget: import_zod2.z.number().int().nonnegative().optional(),
567
+ scan_depth: import_zod2.z.preprocess(preprocessNumeric, import_zod2.z.number().int().nonnegative().optional()),
568
+ token_budget: import_zod2.z.preprocess(preprocessNumeric, import_zod2.z.number().int().nonnegative().optional()),
513
569
  recursive_scanning: import_zod2.z.boolean().optional(),
514
570
  extensions: import_zod2.z.record(import_zod2.z.unknown()).optional(),
515
571
  entries: import_zod2.z.array(CCv2LorebookEntrySchema)
@@ -583,8 +639,8 @@ var CCv3LorebookEntrySchema = import_zod3.z.object({
583
639
  var CCv3CharacterBookSchema = import_zod3.z.object({
584
640
  name: import_zod3.z.string().optional(),
585
641
  description: import_zod3.z.string().optional(),
586
- scan_depth: import_zod3.z.number().int().nonnegative().optional(),
587
- token_budget: import_zod3.z.number().int().nonnegative().optional(),
642
+ scan_depth: import_zod3.z.preprocess(preprocessNumeric, import_zod3.z.number().int().nonnegative().optional()),
643
+ token_budget: import_zod3.z.preprocess(preprocessNumeric, import_zod3.z.number().int().nonnegative().optional()),
588
644
  recursive_scanning: import_zod3.z.boolean().optional(),
589
645
  extensions: import_zod3.z.record(import_zod3.z.unknown()).optional(),
590
646
  entries: import_zod3.z.array(CCv3LorebookEntrySchema)
@@ -616,10 +672,9 @@ var CCv3DataInnerSchema = import_zod3.z.object({
616
672
  nickname: import_zod3.z.string().optional(),
617
673
  creator_notes_multilingual: import_zod3.z.record(import_zod3.z.string()).optional(),
618
674
  source: import_zod3.z.array(import_zod3.z.string()).optional(),
619
- creation_date: import_zod3.z.number().int().nonnegative().optional(),
620
- // Unix timestamp in seconds
621
- modification_date: import_zod3.z.number().int().nonnegative().optional()
622
- // Unix timestamp in seconds
675
+ // Unix timestamps - preprocess to handle ISO strings, numeric strings, milliseconds
676
+ creation_date: import_zod3.z.preprocess(preprocessTimestamp, import_zod3.z.number().int().nonnegative().optional()),
677
+ modification_date: import_zod3.z.preprocess(preprocessTimestamp, import_zod3.z.number().int().nonnegative().optional())
623
678
  });
624
679
  var CCv3DataSchema = import_zod3.z.object({
625
680
  spec: import_zod3.z.literal("chara_card_v3"),
@@ -8727,7 +8782,7 @@ function defaultTokenCounter(_card) {
8727
8782
  total: 0
8728
8783
  };
8729
8784
  }
8730
- function getCanonicalContent(card) {
8785
+ function getCanonicalContentV1(card) {
8731
8786
  const normalized = {
8732
8787
  name: card.data.name,
8733
8788
  description: card.data.description || "",
@@ -8749,6 +8804,69 @@ function getCanonicalContent(card) {
8749
8804
  };
8750
8805
  return JSON.stringify(normalized, Object.keys(normalized).sort());
8751
8806
  }
8807
+ function stableStringify(value) {
8808
+ if (value === null) return "null";
8809
+ switch (typeof value) {
8810
+ case "string":
8811
+ return JSON.stringify(value);
8812
+ case "number":
8813
+ return Number.isFinite(value) ? String(value) : "null";
8814
+ case "boolean":
8815
+ return value ? "true" : "false";
8816
+ case "bigint":
8817
+ return JSON.stringify(value.toString());
8818
+ case "undefined":
8819
+ case "function":
8820
+ case "symbol":
8821
+ return "null";
8822
+ case "object": {
8823
+ if (Array.isArray(value)) {
8824
+ const parts2 = value.map((item) => {
8825
+ if (item === void 0 || typeof item === "function" || typeof item === "symbol") {
8826
+ return "null";
8827
+ }
8828
+ return stableStringify(item);
8829
+ });
8830
+ return `[${parts2.join(",")}]`;
8831
+ }
8832
+ const obj = value;
8833
+ const keys = Object.keys(obj).sort();
8834
+ const parts = [];
8835
+ for (const key of keys) {
8836
+ const v = obj[key];
8837
+ if (v === void 0 || typeof v === "function" || typeof v === "symbol") {
8838
+ continue;
8839
+ }
8840
+ parts.push(`${JSON.stringify(key)}:${stableStringify(v)}`);
8841
+ }
8842
+ return `{${parts.join(",")}}`;
8843
+ }
8844
+ default:
8845
+ return "null";
8846
+ }
8847
+ }
8848
+ function getCanonicalContentV2(card) {
8849
+ const normalized = {
8850
+ name: card.data.name,
8851
+ description: card.data.description || "",
8852
+ personality: card.data.personality || "",
8853
+ scenario: card.data.scenario || "",
8854
+ first_mes: card.data.first_mes || "",
8855
+ mes_example: card.data.mes_example || "",
8856
+ system_prompt: card.data.system_prompt || "",
8857
+ post_history_instructions: card.data.post_history_instructions || "",
8858
+ alternate_greetings: card.data.alternate_greetings || [],
8859
+ character_book: card.data.character_book ? {
8860
+ entries: (card.data.character_book.entries || []).map((e) => ({
8861
+ keys: e.keys,
8862
+ content: e.content,
8863
+ enabled: e.enabled
8864
+ }))
8865
+ } : null,
8866
+ creator_notes: card.data.creator_notes || ""
8867
+ };
8868
+ return stableStringify(normalized);
8869
+ }
8752
8870
  function isWithinTolerance(clientValue, computedValue, tolerance) {
8753
8871
  if (clientValue === void 0) return false;
8754
8872
  if (computedValue === 0) return clientValue === 0;
@@ -8769,15 +8887,18 @@ async function validateClientMetadata(clientMetadata, parseResult, options = {})
8769
8887
  const warnings = [];
8770
8888
  const errors = [];
8771
8889
  const computedTokens = countTokens(card);
8772
- const canonicalContent = getCanonicalContent(card);
8773
- const computedHash = await computeHash(canonicalContent);
8890
+ const canonicalContentV1 = getCanonicalContentV1(card);
8891
+ const canonicalContentV2 = getCanonicalContentV2(card);
8892
+ const computedHashV1 = await computeHash(canonicalContentV1);
8893
+ const computedHashV2 = await computeHash(canonicalContentV2);
8774
8894
  const entries = card.data.character_book?.entries || [];
8775
8895
  const computedHasLorebook = entries.length > 0;
8776
8896
  const computedLorebookCount = entries.length;
8777
8897
  const authoritative = {
8778
8898
  name: card.data.name,
8779
8899
  tokens: computedTokens,
8780
- contentHash: computedHash,
8900
+ contentHash: computedHashV1,
8901
+ contentHashV2: computedHashV2,
8781
8902
  hasLorebook: computedHasLorebook,
8782
8903
  lorebookEntriesCount: computedLorebookCount
8783
8904
  };
@@ -8789,21 +8910,27 @@ async function validateClientMetadata(clientMetadata, parseResult, options = {})
8789
8910
  withinTolerance: false
8790
8911
  });
8791
8912
  }
8792
- if (clientMetadata.contentHash !== computedHash) {
8913
+ const matchesV1 = clientMetadata.contentHash === computedHashV1;
8914
+ const matchesV2 = clientMetadata.contentHash === computedHashV2;
8915
+ if (!matchesV1 && !matchesV2) {
8793
8916
  const disc = {
8794
8917
  field: "contentHash",
8795
8918
  clientValue: clientMetadata.contentHash,
8796
- computedValue: computedHash,
8919
+ computedValue: computedHashV1,
8797
8920
  withinTolerance: false
8798
8921
  };
8799
8922
  discrepancies.push(disc);
8800
8923
  if (allowHashMismatch) {
8801
8924
  warnings.push(
8802
- `Content hash mismatch: client=${clientMetadata.contentHash.substring(0, 8)}..., server=${computedHash.substring(0, 8)}...`
8925
+ `Content hash mismatch: client=${clientMetadata.contentHash.substring(0, 8)}..., server(v1)=${computedHashV1.substring(0, 8)}..., server(v2)=${computedHashV2.substring(0, 8)}...`
8803
8926
  );
8804
8927
  } else {
8805
8928
  errors.push("Content hash mismatch - possible tampering or encoding difference");
8806
8929
  }
8930
+ } else if (matchesV1 && !matchesV2) {
8931
+ warnings.push(
8932
+ "Client contentHash matches legacy v1 canonicalization. Prefer authoritative.contentHashV2 for new storage."
8933
+ );
8807
8934
  }
8808
8935
  const tokenFields = [
8809
8936
  "description",
@@ -8884,7 +9011,11 @@ async function validateClientMetadata(clientMetadata, parseResult, options = {})
8884
9011
  };
8885
9012
  }
8886
9013
  async function computeContentHash(card) {
8887
- const content = getCanonicalContent(card);
9014
+ const content = getCanonicalContentV1(card);
9015
+ return sha256Hash(content);
9016
+ }
9017
+ async function computeContentHashV2(card) {
9018
+ const content = getCanonicalContentV2(card);
8888
9019
  return sha256Hash(content);
8889
9020
  }
8890
9021
  function validateClientMetadataSync(clientMetadata, parseResult, options) {
@@ -8900,15 +9031,18 @@ function validateClientMetadataSync(clientMetadata, parseResult, options) {
8900
9031
  const warnings = [];
8901
9032
  const errors = [];
8902
9033
  const computedTokens = countTokens(card);
8903
- const canonicalContent = getCanonicalContent(card);
8904
- const computedHash = computeHash(canonicalContent);
9034
+ const canonicalContentV1 = getCanonicalContentV1(card);
9035
+ const canonicalContentV2 = getCanonicalContentV2(card);
9036
+ const computedHashV1 = computeHash(canonicalContentV1);
9037
+ const computedHashV2 = computeHash(canonicalContentV2);
8905
9038
  const entries = card.data.character_book?.entries || [];
8906
9039
  const computedHasLorebook = entries.length > 0;
8907
9040
  const computedLorebookCount = entries.length;
8908
9041
  const authoritative = {
8909
9042
  name: card.data.name,
8910
9043
  tokens: computedTokens,
8911
- contentHash: computedHash,
9044
+ contentHash: computedHashV1,
9045
+ contentHashV2: computedHashV2,
8912
9046
  hasLorebook: computedHasLorebook,
8913
9047
  lorebookEntriesCount: computedLorebookCount
8914
9048
  };
@@ -8920,11 +9054,13 @@ function validateClientMetadataSync(clientMetadata, parseResult, options) {
8920
9054
  withinTolerance: false
8921
9055
  });
8922
9056
  }
8923
- if (clientMetadata.contentHash !== computedHash) {
9057
+ const matchesV1 = clientMetadata.contentHash === computedHashV1;
9058
+ const matchesV2 = clientMetadata.contentHash === computedHashV2;
9059
+ if (!matchesV1 && !matchesV2) {
8924
9060
  discrepancies.push({
8925
9061
  field: "contentHash",
8926
9062
  clientValue: clientMetadata.contentHash,
8927
- computedValue: computedHash,
9063
+ computedValue: computedHashV1,
8928
9064
  withinTolerance: false
8929
9065
  });
8930
9066
  if (allowHashMismatch) {
@@ -8932,6 +9068,10 @@ function validateClientMetadataSync(clientMetadata, parseResult, options) {
8932
9068
  } else {
8933
9069
  errors.push("Content hash mismatch");
8934
9070
  }
9071
+ } else if (matchesV1 && !matchesV2) {
9072
+ warnings.push(
9073
+ "Client contentHash matches legacy v1 canonicalization. Prefer authoritative.contentHashV2 for new storage."
9074
+ );
8935
9075
  }
8936
9076
  const tokenFields = [
8937
9077
  "description",