@ai-react-markdown/engine 2.3.2 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -72,6 +72,7 @@ __export(src_exports, {
72
72
  normalizeId: () => normalizeId,
73
73
  pangu: () => pangu,
74
74
  parseStage: () => parseStage,
75
+ phantomSuffixCloser: () => phantomSuffixCloser,
75
76
  preprocessAIMDContent: () => preprocessAIMDContent,
76
77
  preprocessLaTeX: () => preprocessLaTeX,
77
78
  rehypeFooterAdorn: () => rehypeFooterAdorn,
@@ -289,7 +290,7 @@ var DEF_LIST_DD_RE = /^ {0,3}:[ \t]/;
289
290
  var DEF_RE = /^ {0,3}\[((?:[^[\]\\]|\\.)+)\]:/;
290
291
  var FENCE_RE = /^ {0,3}(`{3,}|~{3,})/;
291
292
  var MATH_RUN_RE = /^ {0,3}(\$\$+)/;
292
- var TAG_OR_COMMENT_RE = /<(\/?)([A-Za-z][A-Za-z0-9-]*)(?=[\s/>])([^>]*)>|<!--|-->/g;
293
+ var TAG_OR_COMMENT_RE = /<(\/?)([A-Za-z][A-Za-z0-9-]*)(?=[\s/>])([^>]*)>|<!--|-->|--!>/g;
293
294
  var TRUNCATED_TAG_RE = /<(\/?)([A-Za-z][A-Za-z0-9-]*)([^<>]*)$/;
294
295
  var REF_RE = /!?\[((?:[^[\]\\]|\\.)*)\]/g;
295
296
  var BACKTICK_RUN_RE = /`+/g;
@@ -371,6 +372,7 @@ function freshCheckpoint(defListEnabled, mathFlow, referenceTaint) {
371
372
  fenceLen: 0,
372
373
  inMath: false,
373
374
  mathFenceLen: 0,
375
+ openIndent: 0,
374
376
  blankRun: 0,
375
377
  lastBlankStart: -1,
376
378
  hazardVerdict: false,
@@ -381,21 +383,15 @@ function freshCheckpoint(defListEnabled, mathFlow, referenceTaint) {
381
383
  paragraphHasUnpairedRun: false,
382
384
  htmlFlowSinceBlank: false,
383
385
  htmlSeamPending: false,
384
- phasePoisonedAt: Infinity
386
+ phasePoisonedAt: Infinity,
387
+ pendingTruncatedTags: []
385
388
  };
386
389
  }
387
390
  function isPlausibleLinkDefRest(rest) {
388
391
  const t = rest.trim();
389
392
  if (t === "") return false;
390
- let destEnd;
391
- if (t.startsWith("<")) {
392
- const close = t.indexOf(">");
393
- if (close === -1) return false;
394
- destEnd = close + 1;
395
- } else {
396
- const ws = t.search(/[ \t]/);
397
- destEnd = ws === -1 ? t.length : ws;
398
- }
393
+ const destEnd = linkDestinationEnd(t);
394
+ if (destEnd === -1) return false;
399
395
  const after = t.slice(destEnd).trim();
400
396
  if (after === "") return true;
401
397
  const opener = after[0];
@@ -410,6 +406,39 @@ function isPlausibleLinkDefRest(rest) {
410
406
  }
411
407
  return false;
412
408
  }
409
+ function linkDestinationEnd(t) {
410
+ if (t.startsWith("<")) {
411
+ for (let i2 = 1; i2 < t.length; i2++) {
412
+ const ch = t[i2];
413
+ if (ch === "\\" && (t[i2 + 1] === "<" || t[i2 + 1] === ">" || t[i2 + 1] === "\\")) {
414
+ i2 += 1;
415
+ continue;
416
+ }
417
+ if (ch === ">") return i2 + 1;
418
+ if (ch === "<") return -1;
419
+ }
420
+ return -1;
421
+ }
422
+ let balance = 0;
423
+ let i = 0;
424
+ for (; i < t.length; i++) {
425
+ const code = t.charCodeAt(i);
426
+ if (code === 32 || code === 9) break;
427
+ if (code < 32 || code === 127) return -1;
428
+ const ch = t[i];
429
+ if (ch === "\\" && (t[i + 1] === "(" || t[i + 1] === ")" || t[i + 1] === "\\")) {
430
+ i += 1;
431
+ continue;
432
+ }
433
+ if (ch === "(") balance += 1;
434
+ else if (ch === ")") {
435
+ if (balance === 0) break;
436
+ balance -= 1;
437
+ }
438
+ }
439
+ if (balance !== 0 || i === 0) return -1;
440
+ return i;
441
+ }
413
442
  function classifyBlockStart(text, indent, defListEnabled) {
414
443
  if (indent >= 4) return true;
415
444
  if (LIST_MARKER_RE.test(text) || FOOTNOTE_DEF_RE.test(text)) return true;
@@ -471,6 +500,48 @@ function computeFreezeBoundary(text, options, resume) {
471
500
  }
472
501
  return { boundary, checkpoint: cp };
473
502
  }
503
+ function floatingResidue(text, commentOpenAtStart) {
504
+ let out = "";
505
+ let open = commentOpenAtStart;
506
+ let last = 0;
507
+ TAG_OR_COMMENT_RE.lastIndex = 0;
508
+ let m;
509
+ while ((m = TAG_OR_COMMENT_RE.exec(text)) !== null) {
510
+ if (m[0] === "<!--") {
511
+ const next = text.slice(m.index + 4, m.index + 6);
512
+ const overlapLen = next.startsWith(">") ? 5 : next === "->" ? 6 : 0;
513
+ if (open) {
514
+ if (overlapLen) {
515
+ open = false;
516
+ last = m.index + overlapLen;
517
+ TAG_OR_COMMENT_RE.lastIndex = last;
518
+ }
519
+ continue;
520
+ }
521
+ out += text.slice(last, m.index);
522
+ if (overlapLen) {
523
+ last = m.index + overlapLen;
524
+ TAG_OR_COMMENT_RE.lastIndex = last;
525
+ continue;
526
+ }
527
+ open = true;
528
+ continue;
529
+ }
530
+ if (m[0] === "-->") {
531
+ if (open) {
532
+ open = false;
533
+ last = m.index + 3;
534
+ }
535
+ continue;
536
+ }
537
+ if (m[0] === "--!>") continue;
538
+ if (open) continue;
539
+ out += text.slice(last, m.index);
540
+ last = m.index + m[0].length;
541
+ }
542
+ if (!open) out += text.slice(last);
543
+ return out;
544
+ }
474
545
  function processConfirmedLine(cp, ln, text) {
475
546
  const newest = cp.candidates[cp.candidates.length - 1];
476
547
  if (newest && newest.defListSettled === null) {
@@ -525,6 +596,7 @@ function processConfirmedLine(cp, ln, text) {
525
596
  cp.inFence = true;
526
597
  cp.fenceChar = open[1][0];
527
598
  cp.fenceLen = open[1].length;
599
+ cp.openIndent = ln.indent;
528
600
  cp.blankRun = 0;
529
601
  cp.paragraphHasUnpairedRun = false;
530
602
  cp.prevLineBlank = false;
@@ -559,6 +631,7 @@ function processConfirmedLine(cp, ln, text) {
559
631
  }
560
632
  cp.inMath = true;
561
633
  cp.mathFenceLen = mathRun[1].length;
634
+ cp.openIndent = ln.indent;
562
635
  cp.blankRun = 0;
563
636
  cp.paragraphHasUnpairedRun = false;
564
637
  cp.prevLineBlank = false;
@@ -569,6 +642,10 @@ function processConfirmedLine(cp, ln, text) {
569
642
  }
570
643
  }
571
644
  if (ln.blank) {
645
+ if (cp.pendingTruncatedTags.length > 0) {
646
+ for (const tag of cp.pendingTruncatedTags) applyTag(tag, true);
647
+ cp.pendingTruncatedTags = [];
648
+ }
572
649
  cp.blankRun += 1;
573
650
  cp.lastBlankStart = ln.start;
574
651
  cp.candidates.push({
@@ -598,6 +675,7 @@ function processConfirmedLine(cp, ln, text) {
598
675
  if (!TYPE6_NAMES.has(tagStart[1].toLowerCase())) cp.hazardVerdict = true;
599
676
  }
600
677
  const inRawText = cp.htmlFlowSinceBlank || rawOpenAtLineStart;
678
+ const rawFlowStart = ln.indent <= 3 && /^<(?:!--|\?|![A-Za-z]|!\[CDATA\[)/.test(ln.text.trimStart());
601
679
  const { masked, unpaired } = inRawText ? { masked: null, unpaired: false } : maskIntraLineCodeSpans(ln.text, cp.paragraphHasUnpairedRun);
602
680
  if (unpaired) cp.paragraphHasUnpairedRun = true;
603
681
  const scanText = masked ?? ln.text;
@@ -642,9 +720,14 @@ function processConfirmedLine(cp, ln, text) {
642
720
  const rawOpenAtStart = cp.piOpen || cp.declOpen || cp.cdataOpen;
643
721
  const rawSpans = [];
644
722
  let pos = 0;
723
+ const poisonRawDivergence = () => {
724
+ cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start);
725
+ };
645
726
  while (pos < scanText.length) {
646
727
  if (cp.piOpen) {
647
728
  const c = scanText.indexOf("?>", pos);
729
+ const gt = scanText.indexOf(">", pos);
730
+ if (gt !== -1 && (c === -1 || gt !== c + 1)) poisonRawDivergence();
648
731
  if (c === -1) {
649
732
  rawSpans.push([pos, scanText.length]);
650
733
  break;
@@ -656,6 +739,8 @@ function processConfirmedLine(cp, ln, text) {
656
739
  }
657
740
  if (cp.cdataOpen) {
658
741
  const c = scanText.indexOf("]]>", pos);
742
+ const gt = scanText.indexOf(">", pos);
743
+ if (gt !== -1 && (c === -1 || gt !== c + 2)) poisonRawDivergence();
659
744
  if (c === -1) {
660
745
  rawSpans.push([pos, scanText.length]);
661
746
  break;
@@ -688,6 +773,12 @@ function processConfirmedLine(cp, ln, text) {
688
773
  cp.cdataOpen = true;
689
774
  pos = cd + 9;
690
775
  } else if (first === pi) {
776
+ if (scanText[pi + 2] === ">") {
777
+ rawSpans.push([pi, pi + 3]);
778
+ pos = pi + 3;
779
+ if (scanText.slice(0, pi).trim() !== "" || ln.indent > 3) poisonRawDivergence();
780
+ continue;
781
+ }
691
782
  rawSpans.push([pi, pi + 2]);
692
783
  cp.piOpen = true;
693
784
  pos = pi + 2;
@@ -704,6 +795,15 @@ function processConfirmedLine(cp, ln, text) {
704
795
  let lastCommentOpenerIdx = -1;
705
796
  while ((m = TAG_OR_COMMENT_RE.exec(scanText)) !== null) {
706
797
  if (m[0] === "<!--") {
798
+ const next = scanText.slice(m.index + 4, m.index + 6);
799
+ if (cp.commentOpen) {
800
+ if (next.startsWith(">") || next === "->") cp.commentOpen = false;
801
+ else if (next === "!>" || next === "-!") poisonRawDivergence();
802
+ continue;
803
+ }
804
+ if (next.startsWith(">") || next === "->") {
805
+ continue;
806
+ }
707
807
  cp.commentOpen = true;
708
808
  lastCommentOpenerIdx = m.index;
709
809
  continue;
@@ -712,6 +812,10 @@ function processConfirmedLine(cp, ln, text) {
712
812
  cp.commentOpen = false;
713
813
  continue;
714
814
  }
815
+ if (m[0] === "--!>") {
816
+ if (cp.commentOpen) poisonRawDivergence();
817
+ continue;
818
+ }
715
819
  if (cp.commentOpen) continue;
716
820
  const closing = m[1] === "/";
717
821
  const tag = m[2].toLowerCase();
@@ -724,6 +828,9 @@ function processConfirmedLine(cp, ln, text) {
724
828
  cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + lastCommentOpenerIdx);
725
829
  }
726
830
  }
831
+ if (cp.pendingTruncatedTags.length > 0 && scanText.includes(">")) {
832
+ cp.pendingTruncatedTags = [];
833
+ }
727
834
  if (!cp.commentOpen) {
728
835
  const lastLt = scanText.lastIndexOf("<");
729
836
  if (lastLt !== -1 && !scanText.includes(">", lastLt)) {
@@ -731,22 +838,20 @@ function processConfirmedLine(cp, ln, text) {
731
838
  if (m2) {
732
839
  const closing = m2[1] === "/";
733
840
  const tag = m2[2].toLowerCase();
734
- if (!VOID_TAGS.has(tag)) applyTag(tag, closing);
841
+ if (!VOID_TAGS.has(tag)) {
842
+ applyTag(tag, closing);
843
+ if (!closing && !inRawText) cp.pendingTruncatedTags.push(tag);
844
+ }
735
845
  }
736
846
  }
737
847
  }
738
848
  }
739
- if (inRawText && cp.openTotal === 0) {
740
- let residue = scanText;
849
+ if ((inRawText || rawFlowStart) && cp.openTotal === 0) {
850
+ let masked2 = scanText;
741
851
  for (const [from, to] of rawSpans) {
742
- residue = residue.slice(0, from) + " ".repeat(to - from) + residue.slice(to);
743
- }
744
- residue = residue.replace(/<!--[\s\S]*?-->/g, " ");
745
- if (commentOpenAtLineStart) {
746
- residue = residue.includes("-->") ? residue.replace(/[\s\S]*?-->/, " ") : "";
852
+ masked2 = masked2.slice(0, from) + " ".repeat(to - from) + masked2.slice(to);
747
853
  }
748
- residue = residue.replace(TAG_OR_COMMENT_RE, "");
749
- if (residue.trim() !== "") {
854
+ if (floatingResidue(masked2, commentOpenAtLineStart).trim() !== "") {
750
855
  cp.htmlSeamPending = true;
751
856
  }
752
857
  }
@@ -756,6 +861,9 @@ function processConfirmedLine(cp, ln, text) {
756
861
  cp.prevLineWasValidDef = validDef && !def[1].startsWith("^");
757
862
  }
758
863
 
864
+ // src/components/incrementalParse/spliceParse.ts
865
+ var import_micromark_util_normalize_identifier2 = require("micromark-util-normalize-identifier");
866
+
759
867
  // src/components/hastPredicates.ts
760
868
  function isFootnoteSection(node) {
761
869
  if (node.tagName !== "section") return false;
@@ -850,16 +958,20 @@ function collectPrefixInjection(mdast, content, boundary, resume) {
850
958
  for (const child of children) visit6(child, true);
851
959
  }
852
960
  };
961
+ let lastStart = -1;
853
962
  for (const child of mdast.children) {
854
963
  const start = child.position?.start?.offset;
964
+ if (start !== void 0) {
965
+ if (start < lastStart) return { events: [], uninjectable: true, cacheable: false };
966
+ lastStart = start;
967
+ }
855
968
  if (start === void 0) {
856
969
  if (resumeAt > 0) return collectPrefixInjection(mdast, content, boundary, null);
857
970
  cacheable = false;
858
971
  visit6(child, false);
859
972
  continue;
860
973
  }
861
- if (start >= boundary) break;
862
- if (start < resumeAt) continue;
974
+ if (start >= boundary || start < resumeAt) continue;
863
975
  visit6(child, false);
864
976
  }
865
977
  return { events, uninjectable, cacheable };
@@ -873,7 +985,7 @@ function cloneEventsForAppend(events) {
873
985
  var TERMINATOR_LABEL = "__aimd_injection_terminator__";
874
986
  var INJECTION_TERMINATOR = `[${TERMINATOR_LABEL}]: __aimd_sentinel_link__`;
875
987
  function tailMentionsTerminator(tailSource) {
876
- return tailSource.includes(`[${TERMINATOR_LABEL}`);
988
+ return (0, import_micromark_util_normalize_identifier2.normalizeIdentifier)(tailSource).includes(`[${(0, import_micromark_util_normalize_identifier2.normalizeIdentifier)(TERMINATOR_LABEL)}`);
877
989
  }
878
990
  function buildInjectionPrefix(events) {
879
991
  if (events.length === 0) return { text: "", segments: [] };
@@ -1321,11 +1433,12 @@ var import_remark_gfm = __toESM(require("remark-gfm"), 1);
1321
1433
  var import_unist_util_visit = require("unist-util-visit");
1322
1434
 
1323
1435
  // src/components/normalizeId.ts
1436
+ var import_micromark_util_normalize_identifier3 = require("micromark-util-normalize-identifier");
1324
1437
  function normalizeId(s) {
1325
- return s.replace(/\s+/g, " ").toUpperCase();
1438
+ return (0, import_micromark_util_normalize_identifier3.normalizeIdentifier)(s);
1326
1439
  }
1327
1440
  function normalizeForMatch(s) {
1328
- return s.replace(/\\(.)/g, "$1").replace(/\s+/g, " ").toUpperCase();
1441
+ return (0, import_micromark_util_normalize_identifier3.normalizeIdentifier)(s.replace(/\\(.)/g, "$1"));
1329
1442
  }
1330
1443
 
1331
1444
  // src/components/collectDefLabels.ts
@@ -1580,6 +1693,18 @@ function buildPhantomSuffix(phantoms) {
1580
1693
  }
1581
1694
  return suffix;
1582
1695
  }
1696
+ function phantomSuffixCloser(content) {
1697
+ if (content === "") return "";
1698
+ const endsWithNewline = content.endsWith("\n");
1699
+ const confirmed = endsWithNewline ? content : content + "\n";
1700
+ const { checkpoint } = computeFreezeBoundary(confirmed, { defListEnabled: false, referenceTaint: false });
1701
+ if (checkpoint.phasePoisonedAt !== Infinity) return "";
1702
+ const nl = endsWithNewline ? "" : "\n";
1703
+ const indent = " ".repeat(checkpoint.openIndent);
1704
+ if (checkpoint.inFence) return `${nl}${indent}${checkpoint.fenceChar.repeat(checkpoint.fenceLen)}`;
1705
+ if (checkpoint.inMath) return `${nl}${indent}${"$".repeat(checkpoint.mathFenceLen)}`;
1706
+ return "";
1707
+ }
1583
1708
 
1584
1709
  // src/components/extractContributions.ts
1585
1710
  function fakeAnchorElement(url) {
@@ -1658,6 +1783,14 @@ function createRegistry(onEmpty) {
1658
1783
  releaseSymbol(reactId) {
1659
1784
  const entry = this._reactIdMap.get(reactId);
1660
1785
  if (!entry) return;
1786
+ if (entry.refcount <= 0) {
1787
+ if (false) {
1788
+ console.warn(
1789
+ `[ai-react-markdown] Registry.releaseSymbol("${reactId}") called with no matching allocateSymbol \u2014 ignoring (unbalanced release).`
1790
+ );
1791
+ }
1792
+ return;
1793
+ }
1661
1794
  entry.refcount--;
1662
1795
  if (entry.refcount === 0) {
1663
1796
  queueMicrotask(() => {
@@ -1954,6 +2087,11 @@ function buildCoreRemarkRehypeOptions(enableDefinitionList) {
1954
2087
  }
1955
2088
 
1956
2089
  // src/components/customMdastHandlers.ts
2090
+ function localDefProps(s, id) {
2091
+ const def = s.definitionById.get(id);
2092
+ if (!def || typeof def.url !== "string" || def.url === SENTINEL_LINK_URL) return {};
2093
+ return def.title ? { localUrl: def.url, localTitle: def.title } : { localUrl: def.url };
2094
+ }
1957
2095
  function buildCrossChunkHandlers() {
1958
2096
  return {
1959
2097
  footnoteDefinition: (state, node) => {
@@ -1964,6 +2102,7 @@ function buildCrossChunkHandlers() {
1964
2102
  }
1965
2103
  if (s.options.preserveOrphan && !s.footnoteOrder.includes(id)) {
1966
2104
  s.footnoteOrder.push(id);
2105
+ if (!s.footnoteCounts.has(id)) s.footnoteCounts.set(id, 0);
1967
2106
  }
1968
2107
  return void 0;
1969
2108
  },
@@ -1985,7 +2124,8 @@ function buildCrossChunkHandlers() {
1985
2124
  // internally, so cross-chunk case-insensitive matching still works.
1986
2125
  label: node.label ?? node.identifier,
1987
2126
  referenceType: node.referenceType,
1988
- documentId: s.options.documentId
2127
+ documentId: s.options.documentId,
2128
+ ...localDefProps(s, id)
1989
2129
  },
1990
2130
  children: s.all(node)
1991
2131
  };
@@ -2002,7 +2142,8 @@ function buildCrossChunkHandlers() {
2002
2142
  label: node.label ?? node.identifier,
2003
2143
  referenceType: node.referenceType,
2004
2144
  alt: node.alt ?? "",
2005
- documentId: s.options.documentId
2145
+ documentId: s.options.documentId,
2146
+ ...localDefProps(s, id)
2006
2147
  },
2007
2148
  children: []
2008
2149
  };
@@ -2031,6 +2172,12 @@ function buildCrossChunkHandlers() {
2031
2172
  properties: {
2032
2173
  label: node.identifier,
2033
2174
  localOccurrence,
2175
+ // The number mdast-util-to-hast would give this reference in a
2176
+ // standalone render (footnoteOrder position) — the placeholder's
2177
+ // fallback while the registry has no global number (server render /
2178
+ // first client frame), where the local synthetic footer is what
2179
+ // renders, so marks and footer agree (core-render-02).
2180
+ localNumber: s.footnoteOrder.indexOf(id) + 1,
2034
2181
  documentId: s.options.documentId
2035
2182
  },
2036
2183
  children: []
@@ -3239,9 +3386,9 @@ var sanitizeSchema = cloneDeep_default({
3239
3386
  attributes: {
3240
3387
  ...import_rehype_sanitize2.defaultSchema.attributes,
3241
3388
  code: mergeClassNameAllowlist(import_rehype_sanitize2.defaultSchema.attributes?.code, ["math-inline", "math-display"]),
3242
- "cross-chunk-link": ["label", "referenceType", "documentId"],
3243
- "cross-chunk-image": ["label", "referenceType", "documentId", "alt"],
3244
- "footnote-sup": ["label", "localOccurrence", "documentId"]
3389
+ "cross-chunk-link": ["label", "referenceType", "documentId", "localUrl", "localTitle"],
3390
+ "cross-chunk-image": ["label", "referenceType", "documentId", "alt", "localUrl", "localTitle"],
3391
+ "footnote-sup": ["label", "localOccurrence", "localNumber", "documentId"]
3245
3392
  }
3246
3393
  });
3247
3394
 
@@ -3610,9 +3757,9 @@ var createSmoothStreamController = (options = {}) => {
3610
3757
  snap,
3611
3758
  flush() {
3612
3759
  disposed = false;
3613
- if (visibleEnd === source.length) return;
3614
- visibleEnd = source.length;
3615
- tentativeEnd = source.length;
3760
+ const target = finished ? source.length : pending.length > 0 ? pending[pending.length - 1] : visibleEnd;
3761
+ if (target <= visibleEnd) return;
3762
+ visibleEnd = target;
3616
3763
  pending = [];
3617
3764
  credit = 0;
3618
3765
  cancelScheduled();
@@ -4139,6 +4286,7 @@ function createRemendPreprocessor(options) {
4139
4286
  normalizeId,
4140
4287
  pangu,
4141
4288
  parseStage,
4289
+ phantomSuffixCloser,
4142
4290
  preprocessAIMDContent,
4143
4291
  preprocessLaTeX,
4144
4292
  rehypeFooterAdorn,