@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.dev.js CHANGED
@@ -2708,6 +2708,44 @@ function rehypeUnwrapCrossChunkImages() {
2708
2708
 
2709
2709
  // src/components/pluginChain.ts
2710
2710
  import rehypeSanitize from "rehype-sanitize";
2711
+
2712
+ // src/components/rehypeVerifyEngineTags.ts
2713
+ var ENGINE_PLACEHOLDER_TAGS = /* @__PURE__ */ new Set([
2714
+ "footnote-sup",
2715
+ "cross-chunk-link",
2716
+ "cross-chunk-image"
2717
+ ]);
2718
+ var ENGINE_PROVENANCE_PROPERTY = "engineProvenance";
2719
+ function walk(parent, provenance) {
2720
+ const children = parent.children;
2721
+ let i = 0;
2722
+ while (i < children.length) {
2723
+ const node = children[i];
2724
+ if (node.type === "element" && ENGINE_PLACEHOLDER_TAGS.has(node.tagName)) {
2725
+ const props = node.properties ?? {};
2726
+ const stamped = props[ENGINE_PROVENANCE_PROPERTY];
2727
+ const genuine = provenance !== "" && typeof stamped === "string" && stamped === provenance;
2728
+ if (genuine) {
2729
+ delete props[ENGINE_PROVENANCE_PROPERTY];
2730
+ walk(node, provenance);
2731
+ i += 1;
2732
+ } else {
2733
+ children.splice(i, 1, ...node.children);
2734
+ }
2735
+ continue;
2736
+ }
2737
+ if (node.type === "element") walk(node, provenance);
2738
+ i += 1;
2739
+ }
2740
+ }
2741
+ function rehypeVerifyEngineTags(options) {
2742
+ const provenance = options?.provenance ?? "";
2743
+ return function transformer(tree) {
2744
+ walk(tree, provenance);
2745
+ };
2746
+ }
2747
+
2748
+ // src/components/pluginChain.ts
2711
2749
  import remarkBreaks from "remark-breaks";
2712
2750
  import remarkCjkFriendly from "remark-cjk-friendly";
2713
2751
  import remarkCjkFriendlyGfmStrikethrough from "remark-cjk-friendly-gfm-strikethrough";
@@ -2813,10 +2851,13 @@ function buildCoreRemarkPlugins(enginePlugins) {
2813
2851
  ...DISPLAY_OPTIMIZE_CHAIN.filter(([name]) => selected.has(name)).map(([, plugin]) => plugin)
2814
2852
  ];
2815
2853
  }
2816
- function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix) {
2854
+ function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix, options) {
2817
2855
  return [
2818
2856
  // Allow raw HTML through so rehype-sanitize can handle it.
2819
2857
  [rehypeRaw, { passThrough: [] }],
2858
+ // Unwrap forged engine placeholders BEFORE sanitize admits their tag
2859
+ // names. Only when the caller holds a credential (see the option's doc).
2860
+ ...options ? [[rehypeVerifyEngineTags, { provenance: options.provenance }]] : [],
2820
2861
  // Sanitize HTML while allowing <mark> (highlight), KaTeX class names,
2821
2862
  // and any extra protocols the caller permitted via the `sanitizeSchema`
2822
2863
  // prop. Override `clobberPrefix` with the instance-scoped value — the
@@ -2856,6 +2897,10 @@ function buildCoreRemarkRehypeOptions(enableDefinitionList) {
2856
2897
  }
2857
2898
 
2858
2899
  // src/components/customMdastHandlers.ts
2900
+ function provenanceProps(s) {
2901
+ const provenance = s.options.provenance;
2902
+ return typeof provenance === "string" ? { engineProvenance: provenance } : {};
2903
+ }
2859
2904
  function localDefProps(s, id) {
2860
2905
  const def = s.definitionById.get(id);
2861
2906
  if (!def || typeof def.url !== "string" || def.url === SENTINEL_LINK_URL) return {};
@@ -2894,7 +2939,8 @@ function buildCrossChunkHandlers() {
2894
2939
  label: node.label ?? node.identifier,
2895
2940
  referenceType: node.referenceType,
2896
2941
  documentId: s.options.documentId,
2897
- ...localDefProps(s, id)
2942
+ ...localDefProps(s, id),
2943
+ ...provenanceProps(s)
2898
2944
  },
2899
2945
  children: s.all(node)
2900
2946
  };
@@ -2912,7 +2958,8 @@ function buildCrossChunkHandlers() {
2912
2958
  referenceType: node.referenceType,
2913
2959
  alt: node.alt ?? "",
2914
2960
  documentId: s.options.documentId,
2915
- ...localDefProps(s, id)
2961
+ ...localDefProps(s, id),
2962
+ ...provenanceProps(s)
2916
2963
  },
2917
2964
  children: []
2918
2965
  };
@@ -2929,7 +2976,8 @@ function buildCrossChunkHandlers() {
2929
2976
  properties: {
2930
2977
  label: node.identifier,
2931
2978
  localOccurrence,
2932
- documentId: s.options.documentId
2979
+ documentId: s.options.documentId,
2980
+ ...provenanceProps(s)
2933
2981
  },
2934
2982
  children: []
2935
2983
  };
@@ -2947,7 +2995,8 @@ function buildCrossChunkHandlers() {
2947
2995
  // first client frame), where the local synthetic footer is what
2948
2996
  // renders, so marks and footer agree (core-render-02).
2949
2997
  localNumber: s.footnoteOrder.indexOf(id) + 1,
2950
- documentId: s.options.documentId
2998
+ documentId: s.options.documentId,
2999
+ ...provenanceProps(s)
2951
3000
  },
2952
3001
  children: []
2953
3002
  };
@@ -4619,6 +4668,12 @@ var createSmoothStreamController = (options = {}) => {
4619
4668
  };
4620
4669
 
4621
4670
  // src/preprocessors/latex.ts
4671
+ function isHardBoundary(kind) {
4672
+ return kind === "code" || kind === "literal" || kind === "multilineTag";
4673
+ }
4674
+ function hasLineEnding(text) {
4675
+ return text.includes("\n") || text.includes("\r");
4676
+ }
4622
4677
  function getRepeatedMarkerLength(content, start, marker) {
4623
4678
  let end = start;
4624
4679
  while (end < content.length && content[end] === marker) {
@@ -4686,11 +4741,11 @@ function splitByProtectedRegions(content) {
4686
4741
  let multilineFenceMarker = null;
4687
4742
  let multilineFenceLength = 0;
4688
4743
  let multilineFenceIndent = 0;
4689
- function pushProtected(start, end) {
4744
+ function pushProtected(start, end, kind) {
4690
4745
  if (start > lastIndex) {
4691
- segments.push({ text: content.substring(lastIndex, start), isCode: false });
4746
+ segments.push({ kind: "text", text: content.substring(lastIndex, start) });
4692
4747
  }
4693
- segments.push({ text: content.substring(start, end), isCode: true });
4748
+ segments.push({ kind, text: content.substring(start, end) });
4694
4749
  lastIndex = end;
4695
4750
  }
4696
4751
  let i = 0;
@@ -4701,7 +4756,7 @@ function splitByProtectedRegions(content) {
4701
4756
  const runLen = getRepeatedMarkerLength(content, i, multilineFenceMarker);
4702
4757
  const closerIndent = lineIndentBefore(content, i);
4703
4758
  if (runLen >= multilineFenceLength && closerIndent !== -1 && closerIndent <= multilineFenceIndent + 3 && restOfLineIsBlank(content, i + runLen)) {
4704
- pushProtected(multilineStart, i + runLen);
4759
+ pushProtected(multilineStart, i + runLen, "code");
4705
4760
  multilineStart = -1;
4706
4761
  multilineFenceMarker = null;
4707
4762
  multilineFenceLength = 0;
@@ -4728,7 +4783,7 @@ function splitByProtectedRegions(content) {
4728
4783
  if (char === "`") {
4729
4784
  const closeIdx = findClosingBacktickRun(content, i + runLen, runLen);
4730
4785
  if (closeIdx !== -1) {
4731
- pushProtected(i, closeIdx + runLen);
4786
+ pushProtected(i, closeIdx + runLen, "code");
4732
4787
  i = closeIdx + runLen;
4733
4788
  continue;
4734
4789
  }
@@ -4753,7 +4808,11 @@ function splitByProtectedRegions(content) {
4753
4808
  endIndex = content.length;
4754
4809
  }
4755
4810
  }
4756
- pushProtected(i, endIndex);
4811
+ pushProtected(
4812
+ i,
4813
+ endIndex,
4814
+ isOpeningPairedTag ? "literal" : hasLineEnding(content.substring(i, endIndex)) ? "multilineTag" : "tag"
4815
+ );
4757
4816
  i = endIndex;
4758
4817
  continue;
4759
4818
  }
@@ -4761,10 +4820,10 @@ function splitByProtectedRegions(content) {
4761
4820
  i += 1;
4762
4821
  }
4763
4822
  if (multilineStart !== -1) {
4764
- pushProtected(multilineStart, content.length);
4823
+ pushProtected(multilineStart, content.length, "code");
4765
4824
  }
4766
4825
  if (lastIndex < content.length) {
4767
- segments.push({ text: content.substring(lastIndex), isCode: false });
4826
+ segments.push({ kind: "text", text: content.substring(lastIndex) });
4768
4827
  }
4769
4828
  return segments;
4770
4829
  }
@@ -4918,20 +4977,22 @@ function escapeLatexPipesInUnclosed(text) {
4918
4977
  const tail = text.substring(unclosedStart + delimLen);
4919
4978
  return before + delim + replaceUnescapedPipes(tail);
4920
4979
  }
4921
- function opensMathFlow(text, pos) {
4980
+ function opensMathFlow(text, pos, runStartsAtLineStart) {
4922
4981
  let i = pos;
4923
4982
  let spaces = 0;
4924
- while (i > 0 && text[i - 1] !== "\n") {
4983
+ while (i > 0) {
4984
+ const prev = text[i - 1];
4985
+ if (prev === "\n" || prev === "\r") return true;
4925
4986
  i -= 1;
4926
4987
  if (text[i] !== " ") return false;
4927
4988
  spaces += 1;
4928
4989
  if (spaces > 3) return false;
4929
4990
  }
4930
- return true;
4991
+ return runStartsAtLineStart;
4931
4992
  }
4932
- function truncateUnclosedLatexBlock(text, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
4993
+ function truncateUnclosedLatexBlock(text, runStartsAtLineStart, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
4933
4994
  if (unclosedStart === -1) return text;
4934
- if (!opensMathFlow(text, unclosedStart)) return text;
4995
+ if (!opensMathFlow(text, unclosedStart, runStartsAtLineStart)) return text;
4935
4996
  return text.substring(0, unclosedStart).trimEnd();
4936
4997
  }
4937
4998
  function escapeTextUnderscores(text) {
@@ -4976,7 +5037,8 @@ function convertSingleToDoubleDollar(text) {
4976
5037
  }
4977
5038
  function preprocessLaTeX(str) {
4978
5039
  if (!hasLatexTrigger(str)) return str;
4979
- return processSlice(str, false).out;
5040
+ const mask = selectMask(str);
5041
+ return (mask === null ? processSlice(str, { legacy: true, probe: false }) : processSlice(str, { probe: false, mask })).out;
4980
5042
  }
4981
5043
  function hasLatexTrigger(str) {
4982
5044
  return str.includes("$") || str.includes("\\[") || str.includes("\\(");
@@ -5005,42 +5067,240 @@ function hasUnclosedTextCommand(text) {
5005
5067
  }
5006
5068
  var RESIDUAL_OPEN_BRACKET_RE = /(?<!!)\\\[/;
5007
5069
  var LEADING_DOUBLE_DOLLAR_RE = /^\s*\$\$/;
5008
- function processSlice(slice, probe = true) {
5070
+ var PUA_START = 57344;
5071
+ var PUA_END = 63743;
5072
+ var PUA_SIZE = PUA_END - PUA_START + 1;
5073
+ function selectMask(source) {
5074
+ const first = String.fromCharCode(PUA_START);
5075
+ if (source.indexOf(first) === -1) return first;
5076
+ const seen = new Uint8Array(PUA_SIZE);
5077
+ for (let i = 0; i < source.length; i++) {
5078
+ const c = source.charCodeAt(i);
5079
+ if (c >= PUA_START && c <= PUA_END) seen[c - PUA_START] = 1;
5080
+ }
5081
+ for (let k = 0; k < PUA_SIZE; k++) if (seen[k] === 0) return String.fromCharCode(PUA_START + k);
5082
+ return null;
5083
+ }
5084
+ var PuaPresence = class {
5085
+ bits = new Uint8Array(PUA_SIZE);
5086
+ distinct = 0;
5087
+ reset() {
5088
+ this.bits.fill(0);
5089
+ this.distinct = 0;
5090
+ }
5091
+ add(text, from) {
5092
+ for (let i = from; i < text.length; i++) {
5093
+ const c = text.charCodeAt(i);
5094
+ if (c >= PUA_START && c <= PUA_END) {
5095
+ const k = c - PUA_START;
5096
+ if (this.bits[k] === 0) {
5097
+ this.bits[k] = 1;
5098
+ this.distinct += 1;
5099
+ }
5100
+ }
5101
+ }
5102
+ }
5103
+ select() {
5104
+ if (this.distinct === 0) return String.fromCharCode(PUA_START);
5105
+ if (this.distinct >= PUA_SIZE) return null;
5106
+ for (let k = 0; k < PUA_SIZE; k++) if (this.bits[k] === 0) return String.fromCharCode(PUA_START + k);
5107
+ return null;
5108
+ }
5109
+ };
5110
+ function transformRun(input, probe, runStartsAtLineStart, seamEligible) {
5111
+ let text = input;
5112
+ let tailSensitive = false;
5113
+ let truncatedAtSeamStart = false;
5114
+ text = escapeMhchemCommands(text);
5115
+ text = escapeCurrencyDollarSigns(text);
5116
+ text = convertLatexDelimiters(text);
5117
+ if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) tailSensitive = true;
5118
+ text = escapeLatexPipes(text);
5119
+ if (probe && findUnclosedDelimiterStart(text, "both") !== -1) tailSensitive = true;
5120
+ text = escapeLatexPipesInUnclosed(text);
5121
+ if (probe && hasUnclosedTextCommand(text)) tailSensitive = true;
5122
+ text = escapeTextUnderscores(text);
5123
+ text = convertSingleToDoubleDollar(text);
5124
+ let unclosedDouble;
5125
+ if (probe || seamEligible && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
5126
+ unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
5127
+ if (unclosedDouble !== -1) {
5128
+ tailSensitive = true;
5129
+ if (seamEligible && opensMathFlow(text, unclosedDouble, runStartsAtLineStart) && text.slice(0, unclosedDouble).trim() === "") {
5130
+ truncatedAtSeamStart = true;
5131
+ }
5132
+ }
5133
+ }
5134
+ text = truncateUnclosedLatexBlock(text, runStartsAtLineStart, unclosedDouble);
5135
+ return { out: text, tailSensitive, truncatedAtSeamStart };
5136
+ }
5137
+ function processSliceLegacy(slice, probe) {
5009
5138
  const segments = splitByProtectedRegions(slice);
5010
5139
  const parts = [];
5011
5140
  let quiescent = true;
5012
5141
  let truncatedAtSeamStart = false;
5013
5142
  for (let index = 0; index < segments.length; index++) {
5014
5143
  const segment = segments[index];
5015
- if (segment.isCode) {
5144
+ if (segment.kind !== "text") {
5016
5145
  parts.push(segment.text);
5017
5146
  continue;
5018
5147
  }
5019
- let text = segment.text;
5020
- text = escapeMhchemCommands(text);
5021
- text = escapeCurrencyDollarSigns(text);
5022
- text = convertLatexDelimiters(text);
5023
- if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) quiescent = false;
5024
- text = escapeLatexPipes(text);
5025
- if (probe && findUnclosedDelimiterStart(text, "both") !== -1) quiescent = false;
5026
- text = escapeLatexPipesInUnclosed(text);
5027
- if (probe && hasUnclosedTextCommand(text)) quiescent = false;
5028
- text = escapeTextUnderscores(text);
5029
- text = convertSingleToDoubleDollar(text);
5030
- let unclosedDouble;
5031
- if (probe || index === 0 && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
5032
- unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
5033
- if (unclosedDouble !== -1) {
5034
- quiescent = false;
5035
- if (index === 0 && opensMathFlow(text, unclosedDouble) && text.slice(0, unclosedDouble).trim() === "") {
5036
- truncatedAtSeamStart = true;
5148
+ const r = transformRun(segment.text, probe, true, index === 0);
5149
+ if (r.tailSensitive) quiescent = false;
5150
+ if (r.truncatedAtSeamStart) truncatedAtSeamStart = true;
5151
+ parts.push(r.out);
5152
+ }
5153
+ return { out: parts.join(""), quiescent, truncatedAtSeamStart, degradedReason: null };
5154
+ }
5155
+ var SCOPE_DEPTH_CAP = 8;
5156
+ var VOID_TAGS2 = /* @__PURE__ */ new Set(["br", "hr", "img", "wbr", "input", "source"]);
5157
+ var TAG_NAME_RE = /^<(\/?)([A-Za-z][A-Za-z0-9]*)/;
5158
+ function tagInfo(tag) {
5159
+ const m = TAG_NAME_RE.exec(tag);
5160
+ const closing = m?.[1] === "/";
5161
+ const name = (m?.[2] ?? "").toLowerCase();
5162
+ const opensScope = !closing && !tag.endsWith("/>") && !VOID_TAGS2.has(name);
5163
+ return { name, closing, opensScope };
5164
+ }
5165
+ function buildRunTree(segments) {
5166
+ const root2 = [];
5167
+ const stack = [];
5168
+ const current = () => stack.length > 0 ? stack[stack.length - 1].children : root2;
5169
+ const unwind = () => {
5170
+ while (stack.length > 0) {
5171
+ const frame = stack.pop();
5172
+ current().push({ type: "atom", text: frame.open }, ...frame.children);
5173
+ }
5174
+ };
5175
+ for (const segment of segments) {
5176
+ if (segment.kind === "tag") {
5177
+ const info = tagInfo(segment.text);
5178
+ if (info.closing) {
5179
+ const top = stack[stack.length - 1];
5180
+ if (top !== void 0 && top.name === info.name) {
5181
+ stack.pop();
5182
+ const parent = current();
5183
+ if (top.suppressed) {
5184
+ parent.push({ type: "atom", text: top.open }, ...top.children, { type: "atom", text: segment.text });
5185
+ } else {
5186
+ parent.push({ type: "scope", open: top.open, close: segment.text, children: top.children });
5187
+ }
5188
+ } else {
5189
+ current().push({ type: "atom", text: segment.text });
5037
5190
  }
5191
+ } else if (info.opensScope) {
5192
+ stack.push({ name: info.name, open: segment.text, suppressed: stack.length >= SCOPE_DEPTH_CAP, children: [] });
5193
+ } else {
5194
+ current().push({ type: "atom", text: segment.text });
5038
5195
  }
5196
+ continue;
5039
5197
  }
5040
- text = truncateUnclosedLatexBlock(text, unclosedDouble);
5041
- parts.push(text);
5198
+ const t = segment.text;
5199
+ let start = 0;
5200
+ for (let i = 0; i < t.length; i++) {
5201
+ const c = t.charCodeAt(i);
5202
+ if (c !== 10 && c !== 13) continue;
5203
+ if (i > start) current().push({ type: "text", text: t.slice(start, i) });
5204
+ const end = c === 13 && t.charCodeAt(i + 1) === 10 ? i + 2 : i + 1;
5205
+ unwind();
5206
+ root2.push({ type: "text", text: t.slice(i, end) });
5207
+ start = end;
5208
+ i = end - 1;
5209
+ }
5210
+ if (start < t.length) current().push({ type: "text", text: t.slice(start) });
5211
+ }
5212
+ unwind();
5213
+ return root2;
5214
+ }
5215
+ var restoreFailureInjector = null;
5216
+ function restore(out, atoms, mask) {
5217
+ if (restoreFailureInjector !== null && restoreFailureInjector(atoms)) return null;
5218
+ let result = "";
5219
+ let k = 0;
5220
+ let last = 0;
5221
+ for (; ; ) {
5222
+ const idx = out.indexOf(mask, last);
5223
+ if (idx === -1) break;
5224
+ if (k >= atoms.length) return null;
5225
+ result += out.slice(last, idx) + atoms[k];
5226
+ k += 1;
5227
+ last = idx + 1;
5228
+ }
5229
+ return result + out.slice(last);
5230
+ }
5231
+ function emitRun(nodes, mask) {
5232
+ let text = "";
5233
+ const atoms = [];
5234
+ for (const node of nodes) {
5235
+ if (node.type === "text") {
5236
+ text += node.text;
5237
+ } else if (node.type === "atom") {
5238
+ atoms.push(node.text);
5239
+ text += mask;
5240
+ } else {
5241
+ const inner = emitRun(node.children, mask);
5242
+ if (inner === null) return null;
5243
+ const transformed = transformRun(inner.text, false, false, false);
5244
+ const restored = restore(transformed.out, inner.atoms, mask);
5245
+ if (restored === null) return null;
5246
+ atoms.push(node.open + restored + node.close);
5247
+ text += mask;
5248
+ }
5249
+ }
5250
+ return { text, atoms };
5251
+ }
5252
+ function reportRestoreViolation() {
5253
+ if (true) {
5254
+ console.error(
5255
+ "[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."
5256
+ );
5042
5257
  }
5043
- return { out: parts.join(""), quiescent, truncatedAtSeamStart };
5258
+ }
5259
+ function processSliceDefault(slice, probe, mask) {
5260
+ const segments = splitByProtectedRegions(slice);
5261
+ const parts = [];
5262
+ let quiescent = true;
5263
+ let truncatedAtSeamStart = false;
5264
+ let offset = 0;
5265
+ let i = 0;
5266
+ while (i < segments.length) {
5267
+ const segment = segments[i];
5268
+ if (isHardBoundary(segment.kind)) {
5269
+ parts.push(segment.text);
5270
+ offset += segment.text.length;
5271
+ i += 1;
5272
+ continue;
5273
+ }
5274
+ const runStart = offset;
5275
+ const runSegments = [];
5276
+ while (i < segments.length && !isHardBoundary(segments[i].kind)) {
5277
+ runSegments.push(segments[i]);
5278
+ offset += segments[i].text.length;
5279
+ i += 1;
5280
+ }
5281
+ const before = runStart === 0 ? -1 : slice.charCodeAt(runStart - 1);
5282
+ const runStartsAtLineStart = before === -1 || before === 10 || before === 13;
5283
+ const seamEligible = runStart === 0;
5284
+ const emitted = emitRun(buildRunTree(runSegments), mask);
5285
+ if (emitted === null) {
5286
+ reportRestoreViolation();
5287
+ return { ...processSliceLegacy(slice, probe), degradedReason: "restore-invariant" };
5288
+ }
5289
+ const r = transformRun(emitted.text, probe, runStartsAtLineStart, seamEligible);
5290
+ const restored = restore(r.out, emitted.atoms, mask);
5291
+ if (restored === null) {
5292
+ reportRestoreViolation();
5293
+ return { ...processSliceLegacy(slice, probe), degradedReason: "restore-invariant" };
5294
+ }
5295
+ if (r.tailSensitive) quiescent = false;
5296
+ if (r.truncatedAtSeamStart) truncatedAtSeamStart = true;
5297
+ parts.push(restored);
5298
+ }
5299
+ return { out: parts.join(""), quiescent, truncatedAtSeamStart, degradedReason: null };
5300
+ }
5301
+ function processSlice(slice, options) {
5302
+ if (options.legacy === true) return processSliceLegacy(slice, options.probe);
5303
+ return processSliceDefault(slice, options.probe, options.mask);
5044
5304
  }
5045
5305
  function isBlankRawLine(text, from, to) {
5046
5306
  for (let i = from; i < to; i++) {
@@ -5056,7 +5316,7 @@ function findRawSafeCut(active) {
5056
5316
  let backtickHazard = false;
5057
5317
  let latentLt = false;
5058
5318
  for (const segment of segments) {
5059
- if (segment.isCode) {
5319
+ if (segment.kind !== "text") {
5060
5320
  if (segment.text.includes(">")) latentLt = false;
5061
5321
  offset += segment.text.length;
5062
5322
  continue;
@@ -5091,12 +5351,28 @@ function createIncrementalLatexPreprocessor(options) {
5091
5351
  const freezeThreshold = options?.freezeThreshold ?? DEFAULT_FREEZE_ATTEMPT_THRESHOLD;
5092
5352
  const onAttempt = options?.onAttempt;
5093
5353
  const backoff = options?.backoff ?? true;
5354
+ const onDegrade = options?.onDegrade;
5094
5355
  let prevSource = "";
5095
5356
  let prevOutput = "";
5096
5357
  let frozenSrcEnd = 0;
5097
5358
  let frozenOut = "";
5098
5359
  let triggered = false;
5099
5360
  let nextAttemptLen = 0;
5361
+ let lineageDegraded = false;
5362
+ const presence = new PuaPresence();
5363
+ const commit = (source, out) => {
5364
+ prevSource = source;
5365
+ prevOutput = out;
5366
+ return out;
5367
+ };
5368
+ const legacyWhole = (source) => processSlice(source, { legacy: true, probe: false }).out;
5369
+ const degrade = (source, reason) => {
5370
+ lineageDegraded = true;
5371
+ frozenSrcEnd = 0;
5372
+ frozenOut = "";
5373
+ onDegrade?.(reason);
5374
+ return commit(source, legacyWhole(source));
5375
+ };
5100
5376
  return function incrementalPreprocessLaTeX(source) {
5101
5377
  if (source === prevSource) return prevOutput;
5102
5378
  const isAppend = source.length > prevSource.length && source.startsWith(prevSource);
@@ -5105,16 +5381,20 @@ function createIncrementalLatexPreprocessor(options) {
5105
5381
  frozenOut = "";
5106
5382
  triggered = false;
5107
5383
  nextAttemptLen = 0;
5384
+ lineageDegraded = false;
5385
+ presence.reset();
5386
+ presence.add(source, 0);
5387
+ } else {
5388
+ presence.add(source, prevSource.length);
5108
5389
  }
5109
5390
  if (!triggered) {
5110
5391
  const checkFrom = isAppend ? Math.max(0, prevSource.length - 1) : 0;
5111
- if (!hasLatexTrigger(source.slice(checkFrom))) {
5112
- prevSource = source;
5113
- prevOutput = source;
5114
- return source;
5115
- }
5392
+ if (!hasLatexTrigger(source.slice(checkFrom))) return commit(source, source);
5116
5393
  triggered = true;
5117
5394
  }
5395
+ if (lineageDegraded) return commit(source, legacyWhole(source));
5396
+ const mask = presence.select();
5397
+ if (mask === null) return degrade(source, "mask-exhausted");
5118
5398
  let active = source.slice(frozenSrcEnd);
5119
5399
  if (active.length > freezeThreshold && active.length >= nextAttemptLen) {
5120
5400
  const activeLength = active.length;
@@ -5129,18 +5409,20 @@ function createIncrementalLatexPreprocessor(options) {
5129
5409
  };
5130
5410
  const cut = findRawSafeCut(active);
5131
5411
  if (cut > 0) {
5132
- const candidate = processSlice(active.slice(0, cut));
5412
+ const candidate = processSlice(active.slice(0, cut), { probe: true, mask });
5413
+ if (candidate.degradedReason !== null) {
5414
+ onAttempt?.({ activeLength, frozenBytes: 0 });
5415
+ return degrade(source, candidate.degradedReason);
5416
+ }
5133
5417
  if (candidate.quiescent) freeze(cut, candidate);
5134
5418
  }
5135
5419
  nextAttemptLen = advanced || !backoff ? 0 : active.length * 2;
5136
5420
  onAttempt?.({ activeLength, frozenBytes });
5137
5421
  }
5138
- const tail = processSlice(active, false);
5422
+ const tail = processSlice(active, { probe: false, mask });
5423
+ if (tail.degradedReason !== null) return degrade(source, tail.degradedReason);
5139
5424
  const head = tail.truncatedAtSeamStart ? frozenOut.replace(/\s+$/, "") : frozenOut;
5140
- const out = head + tail.out;
5141
- prevSource = source;
5142
- prevOutput = out;
5143
- return out;
5425
+ return commit(source, head + tail.out);
5144
5426
  };
5145
5427
  }
5146
5428
 
@@ -5165,6 +5447,8 @@ function createRemendPreprocessor(options) {
5165
5447
  }
5166
5448
  export {
5167
5449
  DEFAULT_PAYLOAD,
5450
+ ENGINE_PLACEHOLDER_TAGS,
5451
+ ENGINE_PROVENANCE_PROPERTY,
5168
5452
  PIPELINE_STAGES,
5169
5453
  SENTINEL_FN_CONTENT,
5170
5454
  SENTINEL_LINK_URL,
@@ -5209,6 +5493,7 @@ export {
5209
5493
  preprocessLaTeX,
5210
5494
  rehypeFooterAdorn,
5211
5495
  rehypeRebaseHashLinks_default as rehypeRebaseHashLinks,
5496
+ rehypeVerifyEngineTags,
5212
5497
  removeComments,
5213
5498
  sanitizeCrossChunkUrl,
5214
5499
  sanitizeSchema,