@ai-react-markdown/engine 2.10.1 → 2.11.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.js CHANGED
@@ -2701,6 +2701,44 @@ function rehypeUnwrapCrossChunkImages() {
2701
2701
 
2702
2702
  // src/components/pluginChain.ts
2703
2703
  import rehypeSanitize from "rehype-sanitize";
2704
+
2705
+ // src/components/rehypeVerifyEngineTags.ts
2706
+ var ENGINE_PLACEHOLDER_TAGS = /* @__PURE__ */ new Set([
2707
+ "footnote-sup",
2708
+ "cross-chunk-link",
2709
+ "cross-chunk-image"
2710
+ ]);
2711
+ var ENGINE_PROVENANCE_PROPERTY = "engineProvenance";
2712
+ function walk(parent, provenance) {
2713
+ const children = parent.children;
2714
+ let i = 0;
2715
+ while (i < children.length) {
2716
+ const node = children[i];
2717
+ if (node.type === "element" && ENGINE_PLACEHOLDER_TAGS.has(node.tagName)) {
2718
+ const props = node.properties ?? {};
2719
+ const stamped = props[ENGINE_PROVENANCE_PROPERTY];
2720
+ const genuine = provenance !== "" && typeof stamped === "string" && stamped === provenance;
2721
+ if (genuine) {
2722
+ delete props[ENGINE_PROVENANCE_PROPERTY];
2723
+ walk(node, provenance);
2724
+ i += 1;
2725
+ } else {
2726
+ children.splice(i, 1, ...node.children);
2727
+ }
2728
+ continue;
2729
+ }
2730
+ if (node.type === "element") walk(node, provenance);
2731
+ i += 1;
2732
+ }
2733
+ }
2734
+ function rehypeVerifyEngineTags(options) {
2735
+ const provenance = options?.provenance ?? "";
2736
+ return function transformer(tree) {
2737
+ walk(tree, provenance);
2738
+ };
2739
+ }
2740
+
2741
+ // src/components/pluginChain.ts
2704
2742
  import remarkBreaks from "remark-breaks";
2705
2743
  import remarkCjkFriendly from "remark-cjk-friendly";
2706
2744
  import remarkCjkFriendlyGfmStrikethrough from "remark-cjk-friendly-gfm-strikethrough";
@@ -2806,10 +2844,13 @@ function buildCoreRemarkPlugins(enginePlugins) {
2806
2844
  ...DISPLAY_OPTIMIZE_CHAIN.filter(([name]) => selected.has(name)).map(([, plugin]) => plugin)
2807
2845
  ];
2808
2846
  }
2809
- function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix) {
2847
+ function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix, options) {
2810
2848
  return [
2811
2849
  // Allow raw HTML through so rehype-sanitize can handle it.
2812
2850
  [rehypeRaw, { passThrough: [] }],
2851
+ // Unwrap forged engine placeholders BEFORE sanitize admits their tag
2852
+ // names. Only when the caller holds a credential (see the option's doc).
2853
+ ...options ? [[rehypeVerifyEngineTags, { provenance: options.provenance }]] : [],
2813
2854
  // Sanitize HTML while allowing <mark> (highlight), KaTeX class names,
2814
2855
  // and any extra protocols the caller permitted via the `sanitizeSchema`
2815
2856
  // prop. Override `clobberPrefix` with the instance-scoped value — the
@@ -2849,6 +2890,10 @@ function buildCoreRemarkRehypeOptions(enableDefinitionList) {
2849
2890
  }
2850
2891
 
2851
2892
  // src/components/customMdastHandlers.ts
2893
+ function provenanceProps(s) {
2894
+ const provenance = s.options.provenance;
2895
+ return typeof provenance === "string" ? { engineProvenance: provenance } : {};
2896
+ }
2852
2897
  function localDefProps(s, id) {
2853
2898
  const def = s.definitionById.get(id);
2854
2899
  if (!def || typeof def.url !== "string" || def.url === SENTINEL_LINK_URL) return {};
@@ -2887,7 +2932,8 @@ function buildCrossChunkHandlers() {
2887
2932
  label: node.label ?? node.identifier,
2888
2933
  referenceType: node.referenceType,
2889
2934
  documentId: s.options.documentId,
2890
- ...localDefProps(s, id)
2935
+ ...localDefProps(s, id),
2936
+ ...provenanceProps(s)
2891
2937
  },
2892
2938
  children: s.all(node)
2893
2939
  };
@@ -2905,7 +2951,8 @@ function buildCrossChunkHandlers() {
2905
2951
  referenceType: node.referenceType,
2906
2952
  alt: node.alt ?? "",
2907
2953
  documentId: s.options.documentId,
2908
- ...localDefProps(s, id)
2954
+ ...localDefProps(s, id),
2955
+ ...provenanceProps(s)
2909
2956
  },
2910
2957
  children: []
2911
2958
  };
@@ -2922,7 +2969,8 @@ function buildCrossChunkHandlers() {
2922
2969
  properties: {
2923
2970
  label: node.identifier,
2924
2971
  localOccurrence,
2925
- documentId: s.options.documentId
2972
+ documentId: s.options.documentId,
2973
+ ...provenanceProps(s)
2926
2974
  },
2927
2975
  children: []
2928
2976
  };
@@ -2940,7 +2988,8 @@ function buildCrossChunkHandlers() {
2940
2988
  // first client frame), where the local synthetic footer is what
2941
2989
  // renders, so marks and footer agree (core-render-02).
2942
2990
  localNumber: s.footnoteOrder.indexOf(id) + 1,
2943
- documentId: s.options.documentId
2991
+ documentId: s.options.documentId,
2992
+ ...provenanceProps(s)
2944
2993
  },
2945
2994
  children: []
2946
2995
  };
@@ -4600,6 +4649,12 @@ var createSmoothStreamController = (options = {}) => {
4600
4649
  };
4601
4650
 
4602
4651
  // src/preprocessors/latex.ts
4652
+ function isHardBoundary(kind) {
4653
+ return kind === "code" || kind === "literal" || kind === "multilineTag";
4654
+ }
4655
+ function hasLineEnding(text) {
4656
+ return text.includes("\n") || text.includes("\r");
4657
+ }
4603
4658
  function getRepeatedMarkerLength(content, start, marker) {
4604
4659
  let end = start;
4605
4660
  while (end < content.length && content[end] === marker) {
@@ -4667,11 +4722,11 @@ function splitByProtectedRegions(content) {
4667
4722
  let multilineFenceMarker = null;
4668
4723
  let multilineFenceLength = 0;
4669
4724
  let multilineFenceIndent = 0;
4670
- function pushProtected(start, end) {
4725
+ function pushProtected(start, end, kind) {
4671
4726
  if (start > lastIndex) {
4672
- segments.push({ text: content.substring(lastIndex, start), isCode: false });
4727
+ segments.push({ kind: "text", text: content.substring(lastIndex, start) });
4673
4728
  }
4674
- segments.push({ text: content.substring(start, end), isCode: true });
4729
+ segments.push({ kind, text: content.substring(start, end) });
4675
4730
  lastIndex = end;
4676
4731
  }
4677
4732
  let i = 0;
@@ -4682,7 +4737,7 @@ function splitByProtectedRegions(content) {
4682
4737
  const runLen = getRepeatedMarkerLength(content, i, multilineFenceMarker);
4683
4738
  const closerIndent = lineIndentBefore(content, i);
4684
4739
  if (runLen >= multilineFenceLength && closerIndent !== -1 && closerIndent <= multilineFenceIndent + 3 && restOfLineIsBlank(content, i + runLen)) {
4685
- pushProtected(multilineStart, i + runLen);
4740
+ pushProtected(multilineStart, i + runLen, "code");
4686
4741
  multilineStart = -1;
4687
4742
  multilineFenceMarker = null;
4688
4743
  multilineFenceLength = 0;
@@ -4709,7 +4764,7 @@ function splitByProtectedRegions(content) {
4709
4764
  if (char === "`") {
4710
4765
  const closeIdx = findClosingBacktickRun(content, i + runLen, runLen);
4711
4766
  if (closeIdx !== -1) {
4712
- pushProtected(i, closeIdx + runLen);
4767
+ pushProtected(i, closeIdx + runLen, "code");
4713
4768
  i = closeIdx + runLen;
4714
4769
  continue;
4715
4770
  }
@@ -4734,7 +4789,11 @@ function splitByProtectedRegions(content) {
4734
4789
  endIndex = content.length;
4735
4790
  }
4736
4791
  }
4737
- pushProtected(i, endIndex);
4792
+ pushProtected(
4793
+ i,
4794
+ endIndex,
4795
+ isOpeningPairedTag ? "literal" : hasLineEnding(content.substring(i, endIndex)) ? "multilineTag" : "tag"
4796
+ );
4738
4797
  i = endIndex;
4739
4798
  continue;
4740
4799
  }
@@ -4742,10 +4801,10 @@ function splitByProtectedRegions(content) {
4742
4801
  i += 1;
4743
4802
  }
4744
4803
  if (multilineStart !== -1) {
4745
- pushProtected(multilineStart, content.length);
4804
+ pushProtected(multilineStart, content.length, "code");
4746
4805
  }
4747
4806
  if (lastIndex < content.length) {
4748
- segments.push({ text: content.substring(lastIndex), isCode: false });
4807
+ segments.push({ kind: "text", text: content.substring(lastIndex) });
4749
4808
  }
4750
4809
  return segments;
4751
4810
  }
@@ -4899,20 +4958,22 @@ function escapeLatexPipesInUnclosed(text) {
4899
4958
  const tail = text.substring(unclosedStart + delimLen);
4900
4959
  return before + delim + replaceUnescapedPipes(tail);
4901
4960
  }
4902
- function opensMathFlow(text, pos) {
4961
+ function opensMathFlow(text, pos, runStartsAtLineStart) {
4903
4962
  let i = pos;
4904
4963
  let spaces = 0;
4905
- while (i > 0 && text[i - 1] !== "\n") {
4964
+ while (i > 0) {
4965
+ const prev = text[i - 1];
4966
+ if (prev === "\n" || prev === "\r") return true;
4906
4967
  i -= 1;
4907
4968
  if (text[i] !== " ") return false;
4908
4969
  spaces += 1;
4909
4970
  if (spaces > 3) return false;
4910
4971
  }
4911
- return true;
4972
+ return runStartsAtLineStart;
4912
4973
  }
4913
- function truncateUnclosedLatexBlock(text, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
4974
+ function truncateUnclosedLatexBlock(text, runStartsAtLineStart, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
4914
4975
  if (unclosedStart === -1) return text;
4915
- if (!opensMathFlow(text, unclosedStart)) return text;
4976
+ if (!opensMathFlow(text, unclosedStart, runStartsAtLineStart)) return text;
4916
4977
  return text.substring(0, unclosedStart).trimEnd();
4917
4978
  }
4918
4979
  function escapeTextUnderscores(text) {
@@ -4957,7 +5018,8 @@ function convertSingleToDoubleDollar(text) {
4957
5018
  }
4958
5019
  function preprocessLaTeX(str) {
4959
5020
  if (!hasLatexTrigger(str)) return str;
4960
- return processSlice(str, false).out;
5021
+ const mask = selectMask(str);
5022
+ return (mask === null ? processSlice(str, { legacy: true, probe: false }) : processSlice(str, { probe: false, mask })).out;
4961
5023
  }
4962
5024
  function hasLatexTrigger(str) {
4963
5025
  return str.includes("$") || str.includes("\\[") || str.includes("\\(");
@@ -4986,42 +5048,240 @@ function hasUnclosedTextCommand(text) {
4986
5048
  }
4987
5049
  var RESIDUAL_OPEN_BRACKET_RE = /(?<!!)\\\[/;
4988
5050
  var LEADING_DOUBLE_DOLLAR_RE = /^\s*\$\$/;
4989
- function processSlice(slice, probe = true) {
5051
+ var PUA_START = 57344;
5052
+ var PUA_END = 63743;
5053
+ var PUA_SIZE = PUA_END - PUA_START + 1;
5054
+ function selectMask(source) {
5055
+ const first = String.fromCharCode(PUA_START);
5056
+ if (source.indexOf(first) === -1) return first;
5057
+ const seen = new Uint8Array(PUA_SIZE);
5058
+ for (let i = 0; i < source.length; i++) {
5059
+ const c = source.charCodeAt(i);
5060
+ if (c >= PUA_START && c <= PUA_END) seen[c - PUA_START] = 1;
5061
+ }
5062
+ for (let k = 0; k < PUA_SIZE; k++) if (seen[k] === 0) return String.fromCharCode(PUA_START + k);
5063
+ return null;
5064
+ }
5065
+ var PuaPresence = class {
5066
+ bits = new Uint8Array(PUA_SIZE);
5067
+ distinct = 0;
5068
+ reset() {
5069
+ this.bits.fill(0);
5070
+ this.distinct = 0;
5071
+ }
5072
+ add(text, from) {
5073
+ for (let i = from; i < text.length; i++) {
5074
+ const c = text.charCodeAt(i);
5075
+ if (c >= PUA_START && c <= PUA_END) {
5076
+ const k = c - PUA_START;
5077
+ if (this.bits[k] === 0) {
5078
+ this.bits[k] = 1;
5079
+ this.distinct += 1;
5080
+ }
5081
+ }
5082
+ }
5083
+ }
5084
+ select() {
5085
+ if (this.distinct === 0) return String.fromCharCode(PUA_START);
5086
+ if (this.distinct >= PUA_SIZE) return null;
5087
+ for (let k = 0; k < PUA_SIZE; k++) if (this.bits[k] === 0) return String.fromCharCode(PUA_START + k);
5088
+ return null;
5089
+ }
5090
+ };
5091
+ function transformRun(input, probe, runStartsAtLineStart, seamEligible) {
5092
+ let text = input;
5093
+ let tailSensitive = false;
5094
+ let truncatedAtSeamStart = false;
5095
+ text = escapeMhchemCommands(text);
5096
+ text = escapeCurrencyDollarSigns(text);
5097
+ text = convertLatexDelimiters(text);
5098
+ if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) tailSensitive = true;
5099
+ text = escapeLatexPipes(text);
5100
+ if (probe && findUnclosedDelimiterStart(text, "both") !== -1) tailSensitive = true;
5101
+ text = escapeLatexPipesInUnclosed(text);
5102
+ if (probe && hasUnclosedTextCommand(text)) tailSensitive = true;
5103
+ text = escapeTextUnderscores(text);
5104
+ text = convertSingleToDoubleDollar(text);
5105
+ let unclosedDouble;
5106
+ if (probe || seamEligible && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
5107
+ unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
5108
+ if (unclosedDouble !== -1) {
5109
+ tailSensitive = true;
5110
+ if (seamEligible && opensMathFlow(text, unclosedDouble, runStartsAtLineStart) && text.slice(0, unclosedDouble).trim() === "") {
5111
+ truncatedAtSeamStart = true;
5112
+ }
5113
+ }
5114
+ }
5115
+ text = truncateUnclosedLatexBlock(text, runStartsAtLineStart, unclosedDouble);
5116
+ return { out: text, tailSensitive, truncatedAtSeamStart };
5117
+ }
5118
+ function processSliceLegacy(slice, probe) {
4990
5119
  const segments = splitByProtectedRegions(slice);
4991
5120
  const parts = [];
4992
5121
  let quiescent = true;
4993
5122
  let truncatedAtSeamStart = false;
4994
5123
  for (let index = 0; index < segments.length; index++) {
4995
5124
  const segment = segments[index];
4996
- if (segment.isCode) {
5125
+ if (segment.kind !== "text") {
4997
5126
  parts.push(segment.text);
4998
5127
  continue;
4999
5128
  }
5000
- let text = segment.text;
5001
- text = escapeMhchemCommands(text);
5002
- text = escapeCurrencyDollarSigns(text);
5003
- text = convertLatexDelimiters(text);
5004
- if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) quiescent = false;
5005
- text = escapeLatexPipes(text);
5006
- if (probe && findUnclosedDelimiterStart(text, "both") !== -1) quiescent = false;
5007
- text = escapeLatexPipesInUnclosed(text);
5008
- if (probe && hasUnclosedTextCommand(text)) quiescent = false;
5009
- text = escapeTextUnderscores(text);
5010
- text = convertSingleToDoubleDollar(text);
5011
- let unclosedDouble;
5012
- if (probe || index === 0 && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
5013
- unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
5014
- if (unclosedDouble !== -1) {
5015
- quiescent = false;
5016
- if (index === 0 && opensMathFlow(text, unclosedDouble) && text.slice(0, unclosedDouble).trim() === "") {
5017
- truncatedAtSeamStart = true;
5129
+ const r = transformRun(segment.text, probe, true, index === 0);
5130
+ if (r.tailSensitive) quiescent = false;
5131
+ if (r.truncatedAtSeamStart) truncatedAtSeamStart = true;
5132
+ parts.push(r.out);
5133
+ }
5134
+ return { out: parts.join(""), quiescent, truncatedAtSeamStart, degradedReason: null };
5135
+ }
5136
+ var SCOPE_DEPTH_CAP = 8;
5137
+ var VOID_TAGS2 = /* @__PURE__ */ new Set(["br", "hr", "img", "wbr", "input", "source"]);
5138
+ var TAG_NAME_RE = /^<(\/?)([A-Za-z][A-Za-z0-9]*)/;
5139
+ function tagInfo(tag) {
5140
+ const m = TAG_NAME_RE.exec(tag);
5141
+ const closing = m?.[1] === "/";
5142
+ const name = (m?.[2] ?? "").toLowerCase();
5143
+ const opensScope = !closing && !tag.endsWith("/>") && !VOID_TAGS2.has(name);
5144
+ return { name, closing, opensScope };
5145
+ }
5146
+ function buildRunTree(segments) {
5147
+ const root2 = [];
5148
+ const stack = [];
5149
+ const current = () => stack.length > 0 ? stack[stack.length - 1].children : root2;
5150
+ const unwind = () => {
5151
+ while (stack.length > 0) {
5152
+ const frame = stack.pop();
5153
+ current().push({ type: "atom", text: frame.open }, ...frame.children);
5154
+ }
5155
+ };
5156
+ for (const segment of segments) {
5157
+ if (segment.kind === "tag") {
5158
+ const info = tagInfo(segment.text);
5159
+ if (info.closing) {
5160
+ const top = stack[stack.length - 1];
5161
+ if (top !== void 0 && top.name === info.name) {
5162
+ stack.pop();
5163
+ const parent = current();
5164
+ if (top.suppressed) {
5165
+ parent.push({ type: "atom", text: top.open }, ...top.children, { type: "atom", text: segment.text });
5166
+ } else {
5167
+ parent.push({ type: "scope", open: top.open, close: segment.text, children: top.children });
5168
+ }
5169
+ } else {
5170
+ current().push({ type: "atom", text: segment.text });
5018
5171
  }
5172
+ } else if (info.opensScope) {
5173
+ stack.push({ name: info.name, open: segment.text, suppressed: stack.length >= SCOPE_DEPTH_CAP, children: [] });
5174
+ } else {
5175
+ current().push({ type: "atom", text: segment.text });
5019
5176
  }
5177
+ continue;
5020
5178
  }
5021
- text = truncateUnclosedLatexBlock(text, unclosedDouble);
5022
- parts.push(text);
5179
+ const t = segment.text;
5180
+ let start = 0;
5181
+ for (let i = 0; i < t.length; i++) {
5182
+ const c = t.charCodeAt(i);
5183
+ if (c !== 10 && c !== 13) continue;
5184
+ if (i > start) current().push({ type: "text", text: t.slice(start, i) });
5185
+ const end = c === 13 && t.charCodeAt(i + 1) === 10 ? i + 2 : i + 1;
5186
+ unwind();
5187
+ root2.push({ type: "text", text: t.slice(i, end) });
5188
+ start = end;
5189
+ i = end - 1;
5190
+ }
5191
+ if (start < t.length) current().push({ type: "text", text: t.slice(start) });
5192
+ }
5193
+ unwind();
5194
+ return root2;
5195
+ }
5196
+ var restoreFailureInjector = null;
5197
+ function restore(out, atoms, mask) {
5198
+ if (restoreFailureInjector !== null && restoreFailureInjector(atoms)) return null;
5199
+ let result = "";
5200
+ let k = 0;
5201
+ let last = 0;
5202
+ for (; ; ) {
5203
+ const idx = out.indexOf(mask, last);
5204
+ if (idx === -1) break;
5205
+ if (k >= atoms.length) return null;
5206
+ result += out.slice(last, idx) + atoms[k];
5207
+ k += 1;
5208
+ last = idx + 1;
5209
+ }
5210
+ return result + out.slice(last);
5211
+ }
5212
+ function emitRun(nodes, mask) {
5213
+ let text = "";
5214
+ const atoms = [];
5215
+ for (const node of nodes) {
5216
+ if (node.type === "text") {
5217
+ text += node.text;
5218
+ } else if (node.type === "atom") {
5219
+ atoms.push(node.text);
5220
+ text += mask;
5221
+ } else {
5222
+ const inner = emitRun(node.children, mask);
5223
+ if (inner === null) return null;
5224
+ const transformed = transformRun(inner.text, false, false, false);
5225
+ const restored = restore(transformed.out, inner.atoms, mask);
5226
+ if (restored === null) return null;
5227
+ atoms.push(node.open + restored + node.close);
5228
+ text += mask;
5229
+ }
5230
+ }
5231
+ return { text, atoms };
5232
+ }
5233
+ function reportRestoreViolation() {
5234
+ if (false) {
5235
+ console.error(
5236
+ "[ai-react-markdown] LaTeX preprocessor: atom restoration violated its invariant (more masks in the output than atoms); the slice was re-processed on the legacy path. This is an engine defect \u2014 please report it."
5237
+ );
5023
5238
  }
5024
- return { out: parts.join(""), quiescent, truncatedAtSeamStart };
5239
+ }
5240
+ function processSliceDefault(slice, probe, mask) {
5241
+ const segments = splitByProtectedRegions(slice);
5242
+ const parts = [];
5243
+ let quiescent = true;
5244
+ let truncatedAtSeamStart = false;
5245
+ let offset = 0;
5246
+ let i = 0;
5247
+ while (i < segments.length) {
5248
+ const segment = segments[i];
5249
+ if (isHardBoundary(segment.kind)) {
5250
+ parts.push(segment.text);
5251
+ offset += segment.text.length;
5252
+ i += 1;
5253
+ continue;
5254
+ }
5255
+ const runStart = offset;
5256
+ const runSegments = [];
5257
+ while (i < segments.length && !isHardBoundary(segments[i].kind)) {
5258
+ runSegments.push(segments[i]);
5259
+ offset += segments[i].text.length;
5260
+ i += 1;
5261
+ }
5262
+ const before = runStart === 0 ? -1 : slice.charCodeAt(runStart - 1);
5263
+ const runStartsAtLineStart = before === -1 || before === 10 || before === 13;
5264
+ const seamEligible = runStart === 0;
5265
+ const emitted = emitRun(buildRunTree(runSegments), mask);
5266
+ if (emitted === null) {
5267
+ reportRestoreViolation();
5268
+ return { ...processSliceLegacy(slice, probe), degradedReason: "restore-invariant" };
5269
+ }
5270
+ const r = transformRun(emitted.text, probe, runStartsAtLineStart, seamEligible);
5271
+ const restored = restore(r.out, emitted.atoms, mask);
5272
+ if (restored === null) {
5273
+ reportRestoreViolation();
5274
+ return { ...processSliceLegacy(slice, probe), degradedReason: "restore-invariant" };
5275
+ }
5276
+ if (r.tailSensitive) quiescent = false;
5277
+ if (r.truncatedAtSeamStart) truncatedAtSeamStart = true;
5278
+ parts.push(restored);
5279
+ }
5280
+ return { out: parts.join(""), quiescent, truncatedAtSeamStart, degradedReason: null };
5281
+ }
5282
+ function processSlice(slice, options) {
5283
+ if (options.legacy === true) return processSliceLegacy(slice, options.probe);
5284
+ return processSliceDefault(slice, options.probe, options.mask);
5025
5285
  }
5026
5286
  function isBlankRawLine(text, from, to) {
5027
5287
  for (let i = from; i < to; i++) {
@@ -5037,7 +5297,7 @@ function findRawSafeCut(active) {
5037
5297
  let backtickHazard = false;
5038
5298
  let latentLt = false;
5039
5299
  for (const segment of segments) {
5040
- if (segment.isCode) {
5300
+ if (segment.kind !== "text") {
5041
5301
  if (segment.text.includes(">")) latentLt = false;
5042
5302
  offset += segment.text.length;
5043
5303
  continue;
@@ -5072,12 +5332,28 @@ function createIncrementalLatexPreprocessor(options) {
5072
5332
  const freezeThreshold = options?.freezeThreshold ?? DEFAULT_FREEZE_ATTEMPT_THRESHOLD;
5073
5333
  const onAttempt = options?.onAttempt;
5074
5334
  const backoff = options?.backoff ?? true;
5335
+ const onDegrade = options?.onDegrade;
5075
5336
  let prevSource = "";
5076
5337
  let prevOutput = "";
5077
5338
  let frozenSrcEnd = 0;
5078
5339
  let frozenOut = "";
5079
5340
  let triggered = false;
5080
5341
  let nextAttemptLen = 0;
5342
+ let lineageDegraded = false;
5343
+ const presence = new PuaPresence();
5344
+ const commit = (source, out) => {
5345
+ prevSource = source;
5346
+ prevOutput = out;
5347
+ return out;
5348
+ };
5349
+ const legacyWhole = (source) => processSlice(source, { legacy: true, probe: false }).out;
5350
+ const degrade = (source, reason) => {
5351
+ lineageDegraded = true;
5352
+ frozenSrcEnd = 0;
5353
+ frozenOut = "";
5354
+ onDegrade?.(reason);
5355
+ return commit(source, legacyWhole(source));
5356
+ };
5081
5357
  return function incrementalPreprocessLaTeX(source) {
5082
5358
  if (source === prevSource) return prevOutput;
5083
5359
  const isAppend = source.length > prevSource.length && source.startsWith(prevSource);
@@ -5086,16 +5362,20 @@ function createIncrementalLatexPreprocessor(options) {
5086
5362
  frozenOut = "";
5087
5363
  triggered = false;
5088
5364
  nextAttemptLen = 0;
5365
+ lineageDegraded = false;
5366
+ presence.reset();
5367
+ presence.add(source, 0);
5368
+ } else {
5369
+ presence.add(source, prevSource.length);
5089
5370
  }
5090
5371
  if (!triggered) {
5091
5372
  const checkFrom = isAppend ? Math.max(0, prevSource.length - 1) : 0;
5092
- if (!hasLatexTrigger(source.slice(checkFrom))) {
5093
- prevSource = source;
5094
- prevOutput = source;
5095
- return source;
5096
- }
5373
+ if (!hasLatexTrigger(source.slice(checkFrom))) return commit(source, source);
5097
5374
  triggered = true;
5098
5375
  }
5376
+ if (lineageDegraded) return commit(source, legacyWhole(source));
5377
+ const mask = presence.select();
5378
+ if (mask === null) return degrade(source, "mask-exhausted");
5099
5379
  let active = source.slice(frozenSrcEnd);
5100
5380
  if (active.length > freezeThreshold && active.length >= nextAttemptLen) {
5101
5381
  const activeLength = active.length;
@@ -5110,18 +5390,20 @@ function createIncrementalLatexPreprocessor(options) {
5110
5390
  };
5111
5391
  const cut = findRawSafeCut(active);
5112
5392
  if (cut > 0) {
5113
- const candidate = processSlice(active.slice(0, cut));
5393
+ const candidate = processSlice(active.slice(0, cut), { probe: true, mask });
5394
+ if (candidate.degradedReason !== null) {
5395
+ onAttempt?.({ activeLength, frozenBytes: 0 });
5396
+ return degrade(source, candidate.degradedReason);
5397
+ }
5114
5398
  if (candidate.quiescent) freeze(cut, candidate);
5115
5399
  }
5116
5400
  nextAttemptLen = advanced || !backoff ? 0 : active.length * 2;
5117
5401
  onAttempt?.({ activeLength, frozenBytes });
5118
5402
  }
5119
- const tail = processSlice(active, false);
5403
+ const tail = processSlice(active, { probe: false, mask });
5404
+ if (tail.degradedReason !== null) return degrade(source, tail.degradedReason);
5120
5405
  const head = tail.truncatedAtSeamStart ? frozenOut.replace(/\s+$/, "") : frozenOut;
5121
- const out = head + tail.out;
5122
- prevSource = source;
5123
- prevOutput = out;
5124
- return out;
5406
+ return commit(source, head + tail.out);
5125
5407
  };
5126
5408
  }
5127
5409
 
@@ -5146,6 +5428,8 @@ function createRemendPreprocessor(options) {
5146
5428
  }
5147
5429
  export {
5148
5430
  DEFAULT_PAYLOAD,
5431
+ ENGINE_PLACEHOLDER_TAGS,
5432
+ ENGINE_PROVENANCE_PROPERTY,
5149
5433
  PIPELINE_STAGES,
5150
5434
  SENTINEL_FN_CONTENT,
5151
5435
  SENTINEL_LINK_URL,
@@ -5190,6 +5474,7 @@ export {
5190
5474
  preprocessLaTeX,
5191
5475
  rehypeFooterAdorn,
5192
5476
  rehypeRebaseHashLinks_default as rehypeRebaseHashLinks,
5477
+ rehypeVerifyEngineTags,
5193
5478
  removeComments,
5194
5479
  sanitizeCrossChunkUrl,
5195
5480
  sanitizeSchema,