@ai-react-markdown/engine 2.10.0 → 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,8 +4958,22 @@ function escapeLatexPipesInUnclosed(text) {
4899
4958
  const tail = text.substring(unclosedStart + delimLen);
4900
4959
  return before + delim + replaceUnescapedPipes(tail);
4901
4960
  }
4902
- function truncateUnclosedLatexBlock(text, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
4961
+ function opensMathFlow(text, pos, runStartsAtLineStart) {
4962
+ let i = pos;
4963
+ let spaces = 0;
4964
+ while (i > 0) {
4965
+ const prev = text[i - 1];
4966
+ if (prev === "\n" || prev === "\r") return true;
4967
+ i -= 1;
4968
+ if (text[i] !== " ") return false;
4969
+ spaces += 1;
4970
+ if (spaces > 3) return false;
4971
+ }
4972
+ return runStartsAtLineStart;
4973
+ }
4974
+ function truncateUnclosedLatexBlock(text, runStartsAtLineStart, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
4903
4975
  if (unclosedStart === -1) return text;
4976
+ if (!opensMathFlow(text, unclosedStart, runStartsAtLineStart)) return text;
4904
4977
  return text.substring(0, unclosedStart).trimEnd();
4905
4978
  }
4906
4979
  function escapeTextUnderscores(text) {
@@ -4945,7 +5018,8 @@ function convertSingleToDoubleDollar(text) {
4945
5018
  }
4946
5019
  function preprocessLaTeX(str) {
4947
5020
  if (!hasLatexTrigger(str)) return str;
4948
- 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;
4949
5023
  }
4950
5024
  function hasLatexTrigger(str) {
4951
5025
  return str.includes("$") || str.includes("\\[") || str.includes("\\(");
@@ -4974,42 +5048,240 @@ function hasUnclosedTextCommand(text) {
4974
5048
  }
4975
5049
  var RESIDUAL_OPEN_BRACKET_RE = /(?<!!)\\\[/;
4976
5050
  var LEADING_DOUBLE_DOLLAR_RE = /^\s*\$\$/;
4977
- 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) {
4978
5119
  const segments = splitByProtectedRegions(slice);
4979
5120
  const parts = [];
4980
5121
  let quiescent = true;
4981
5122
  let truncatedAtSeamStart = false;
4982
5123
  for (let index = 0; index < segments.length; index++) {
4983
5124
  const segment = segments[index];
4984
- if (segment.isCode) {
5125
+ if (segment.kind !== "text") {
4985
5126
  parts.push(segment.text);
4986
5127
  continue;
4987
5128
  }
4988
- let text = segment.text;
4989
- text = escapeMhchemCommands(text);
4990
- text = escapeCurrencyDollarSigns(text);
4991
- text = convertLatexDelimiters(text);
4992
- if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) quiescent = false;
4993
- text = escapeLatexPipes(text);
4994
- if (probe && findUnclosedDelimiterStart(text, "both") !== -1) quiescent = false;
4995
- text = escapeLatexPipesInUnclosed(text);
4996
- if (probe && hasUnclosedTextCommand(text)) quiescent = false;
4997
- text = escapeTextUnderscores(text);
4998
- text = convertSingleToDoubleDollar(text);
4999
- let unclosedDouble;
5000
- if (probe || index === 0 && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
5001
- unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
5002
- if (unclosedDouble !== -1) {
5003
- quiescent = false;
5004
- if (index === 0 && text.slice(0, unclosedDouble).trim() === "") {
5005
- 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 });
5006
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 });
5007
5176
  }
5177
+ continue;
5008
5178
  }
5009
- text = truncateUnclosedLatexBlock(text, unclosedDouble);
5010
- 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
+ );
5011
5238
  }
5012
- 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);
5013
5285
  }
5014
5286
  function isBlankRawLine(text, from, to) {
5015
5287
  for (let i = from; i < to; i++) {
@@ -5025,7 +5297,7 @@ function findRawSafeCut(active) {
5025
5297
  let backtickHazard = false;
5026
5298
  let latentLt = false;
5027
5299
  for (const segment of segments) {
5028
- if (segment.isCode) {
5300
+ if (segment.kind !== "text") {
5029
5301
  if (segment.text.includes(">")) latentLt = false;
5030
5302
  offset += segment.text.length;
5031
5303
  continue;
@@ -5060,12 +5332,28 @@ function createIncrementalLatexPreprocessor(options) {
5060
5332
  const freezeThreshold = options?.freezeThreshold ?? DEFAULT_FREEZE_ATTEMPT_THRESHOLD;
5061
5333
  const onAttempt = options?.onAttempt;
5062
5334
  const backoff = options?.backoff ?? true;
5335
+ const onDegrade = options?.onDegrade;
5063
5336
  let prevSource = "";
5064
5337
  let prevOutput = "";
5065
5338
  let frozenSrcEnd = 0;
5066
5339
  let frozenOut = "";
5067
5340
  let triggered = false;
5068
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
+ };
5069
5357
  return function incrementalPreprocessLaTeX(source) {
5070
5358
  if (source === prevSource) return prevOutput;
5071
5359
  const isAppend = source.length > prevSource.length && source.startsWith(prevSource);
@@ -5074,16 +5362,20 @@ function createIncrementalLatexPreprocessor(options) {
5074
5362
  frozenOut = "";
5075
5363
  triggered = false;
5076
5364
  nextAttemptLen = 0;
5365
+ lineageDegraded = false;
5366
+ presence.reset();
5367
+ presence.add(source, 0);
5368
+ } else {
5369
+ presence.add(source, prevSource.length);
5077
5370
  }
5078
5371
  if (!triggered) {
5079
5372
  const checkFrom = isAppend ? Math.max(0, prevSource.length - 1) : 0;
5080
- if (!hasLatexTrigger(source.slice(checkFrom))) {
5081
- prevSource = source;
5082
- prevOutput = source;
5083
- return source;
5084
- }
5373
+ if (!hasLatexTrigger(source.slice(checkFrom))) return commit(source, source);
5085
5374
  triggered = true;
5086
5375
  }
5376
+ if (lineageDegraded) return commit(source, legacyWhole(source));
5377
+ const mask = presence.select();
5378
+ if (mask === null) return degrade(source, "mask-exhausted");
5087
5379
  let active = source.slice(frozenSrcEnd);
5088
5380
  if (active.length > freezeThreshold && active.length >= nextAttemptLen) {
5089
5381
  const activeLength = active.length;
@@ -5098,18 +5390,20 @@ function createIncrementalLatexPreprocessor(options) {
5098
5390
  };
5099
5391
  const cut = findRawSafeCut(active);
5100
5392
  if (cut > 0) {
5101
- 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
+ }
5102
5398
  if (candidate.quiescent) freeze(cut, candidate);
5103
5399
  }
5104
5400
  nextAttemptLen = advanced || !backoff ? 0 : active.length * 2;
5105
5401
  onAttempt?.({ activeLength, frozenBytes });
5106
5402
  }
5107
- const tail = processSlice(active, false);
5403
+ const tail = processSlice(active, { probe: false, mask });
5404
+ if (tail.degradedReason !== null) return degrade(source, tail.degradedReason);
5108
5405
  const head = tail.truncatedAtSeamStart ? frozenOut.replace(/\s+$/, "") : frozenOut;
5109
- const out = head + tail.out;
5110
- prevSource = source;
5111
- prevOutput = out;
5112
- return out;
5406
+ return commit(source, head + tail.out);
5113
5407
  };
5114
5408
  }
5115
5409
 
@@ -5134,6 +5428,8 @@ function createRemendPreprocessor(options) {
5134
5428
  }
5135
5429
  export {
5136
5430
  DEFAULT_PAYLOAD,
5431
+ ENGINE_PLACEHOLDER_TAGS,
5432
+ ENGINE_PROVENANCE_PROPERTY,
5137
5433
  PIPELINE_STAGES,
5138
5434
  SENTINEL_FN_CONTENT,
5139
5435
  SENTINEL_LINK_URL,
@@ -5178,6 +5474,7 @@ export {
5178
5474
  preprocessLaTeX,
5179
5475
  rehypeFooterAdorn,
5180
5476
  rehypeRebaseHashLinks_default as rehypeRebaseHashLinks,
5477
+ rehypeVerifyEngineTags,
5181
5478
  removeComments,
5182
5479
  sanitizeCrossChunkUrl,
5183
5480
  sanitizeSchema,