@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.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,8 +4977,22 @@ function escapeLatexPipesInUnclosed(text) {
4918
4977
  const tail = text.substring(unclosedStart + delimLen);
4919
4978
  return before + delim + replaceUnescapedPipes(tail);
4920
4979
  }
4921
- function truncateUnclosedLatexBlock(text, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
4980
+ function opensMathFlow(text, pos, runStartsAtLineStart) {
4981
+ let i = pos;
4982
+ let spaces = 0;
4983
+ while (i > 0) {
4984
+ const prev = text[i - 1];
4985
+ if (prev === "\n" || prev === "\r") return true;
4986
+ i -= 1;
4987
+ if (text[i] !== " ") return false;
4988
+ spaces += 1;
4989
+ if (spaces > 3) return false;
4990
+ }
4991
+ return runStartsAtLineStart;
4992
+ }
4993
+ function truncateUnclosedLatexBlock(text, runStartsAtLineStart, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
4922
4994
  if (unclosedStart === -1) return text;
4995
+ if (!opensMathFlow(text, unclosedStart, runStartsAtLineStart)) return text;
4923
4996
  return text.substring(0, unclosedStart).trimEnd();
4924
4997
  }
4925
4998
  function escapeTextUnderscores(text) {
@@ -4964,7 +5037,8 @@ function convertSingleToDoubleDollar(text) {
4964
5037
  }
4965
5038
  function preprocessLaTeX(str) {
4966
5039
  if (!hasLatexTrigger(str)) return str;
4967
- 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;
4968
5042
  }
4969
5043
  function hasLatexTrigger(str) {
4970
5044
  return str.includes("$") || str.includes("\\[") || str.includes("\\(");
@@ -4993,42 +5067,240 @@ function hasUnclosedTextCommand(text) {
4993
5067
  }
4994
5068
  var RESIDUAL_OPEN_BRACKET_RE = /(?<!!)\\\[/;
4995
5069
  var LEADING_DOUBLE_DOLLAR_RE = /^\s*\$\$/;
4996
- 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) {
4997
5138
  const segments = splitByProtectedRegions(slice);
4998
5139
  const parts = [];
4999
5140
  let quiescent = true;
5000
5141
  let truncatedAtSeamStart = false;
5001
5142
  for (let index = 0; index < segments.length; index++) {
5002
5143
  const segment = segments[index];
5003
- if (segment.isCode) {
5144
+ if (segment.kind !== "text") {
5004
5145
  parts.push(segment.text);
5005
5146
  continue;
5006
5147
  }
5007
- let text = segment.text;
5008
- text = escapeMhchemCommands(text);
5009
- text = escapeCurrencyDollarSigns(text);
5010
- text = convertLatexDelimiters(text);
5011
- if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) quiescent = false;
5012
- text = escapeLatexPipes(text);
5013
- if (probe && findUnclosedDelimiterStart(text, "both") !== -1) quiescent = false;
5014
- text = escapeLatexPipesInUnclosed(text);
5015
- if (probe && hasUnclosedTextCommand(text)) quiescent = false;
5016
- text = escapeTextUnderscores(text);
5017
- text = convertSingleToDoubleDollar(text);
5018
- let unclosedDouble;
5019
- if (probe || index === 0 && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
5020
- unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
5021
- if (unclosedDouble !== -1) {
5022
- quiescent = false;
5023
- if (index === 0 && text.slice(0, unclosedDouble).trim() === "") {
5024
- 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 });
5025
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 });
5026
5195
  }
5196
+ continue;
5027
5197
  }
5028
- text = truncateUnclosedLatexBlock(text, unclosedDouble);
5029
- 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
+ );
5030
5257
  }
5031
- 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);
5032
5304
  }
5033
5305
  function isBlankRawLine(text, from, to) {
5034
5306
  for (let i = from; i < to; i++) {
@@ -5044,7 +5316,7 @@ function findRawSafeCut(active) {
5044
5316
  let backtickHazard = false;
5045
5317
  let latentLt = false;
5046
5318
  for (const segment of segments) {
5047
- if (segment.isCode) {
5319
+ if (segment.kind !== "text") {
5048
5320
  if (segment.text.includes(">")) latentLt = false;
5049
5321
  offset += segment.text.length;
5050
5322
  continue;
@@ -5079,12 +5351,28 @@ function createIncrementalLatexPreprocessor(options) {
5079
5351
  const freezeThreshold = options?.freezeThreshold ?? DEFAULT_FREEZE_ATTEMPT_THRESHOLD;
5080
5352
  const onAttempt = options?.onAttempt;
5081
5353
  const backoff = options?.backoff ?? true;
5354
+ const onDegrade = options?.onDegrade;
5082
5355
  let prevSource = "";
5083
5356
  let prevOutput = "";
5084
5357
  let frozenSrcEnd = 0;
5085
5358
  let frozenOut = "";
5086
5359
  let triggered = false;
5087
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
+ };
5088
5376
  return function incrementalPreprocessLaTeX(source) {
5089
5377
  if (source === prevSource) return prevOutput;
5090
5378
  const isAppend = source.length > prevSource.length && source.startsWith(prevSource);
@@ -5093,16 +5381,20 @@ function createIncrementalLatexPreprocessor(options) {
5093
5381
  frozenOut = "";
5094
5382
  triggered = false;
5095
5383
  nextAttemptLen = 0;
5384
+ lineageDegraded = false;
5385
+ presence.reset();
5386
+ presence.add(source, 0);
5387
+ } else {
5388
+ presence.add(source, prevSource.length);
5096
5389
  }
5097
5390
  if (!triggered) {
5098
5391
  const checkFrom = isAppend ? Math.max(0, prevSource.length - 1) : 0;
5099
- if (!hasLatexTrigger(source.slice(checkFrom))) {
5100
- prevSource = source;
5101
- prevOutput = source;
5102
- return source;
5103
- }
5392
+ if (!hasLatexTrigger(source.slice(checkFrom))) return commit(source, source);
5104
5393
  triggered = true;
5105
5394
  }
5395
+ if (lineageDegraded) return commit(source, legacyWhole(source));
5396
+ const mask = presence.select();
5397
+ if (mask === null) return degrade(source, "mask-exhausted");
5106
5398
  let active = source.slice(frozenSrcEnd);
5107
5399
  if (active.length > freezeThreshold && active.length >= nextAttemptLen) {
5108
5400
  const activeLength = active.length;
@@ -5117,18 +5409,20 @@ function createIncrementalLatexPreprocessor(options) {
5117
5409
  };
5118
5410
  const cut = findRawSafeCut(active);
5119
5411
  if (cut > 0) {
5120
- 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
+ }
5121
5417
  if (candidate.quiescent) freeze(cut, candidate);
5122
5418
  }
5123
5419
  nextAttemptLen = advanced || !backoff ? 0 : active.length * 2;
5124
5420
  onAttempt?.({ activeLength, frozenBytes });
5125
5421
  }
5126
- const tail = processSlice(active, false);
5422
+ const tail = processSlice(active, { probe: false, mask });
5423
+ if (tail.degradedReason !== null) return degrade(source, tail.degradedReason);
5127
5424
  const head = tail.truncatedAtSeamStart ? frozenOut.replace(/\s+$/, "") : frozenOut;
5128
- const out = head + tail.out;
5129
- prevSource = source;
5130
- prevOutput = out;
5131
- return out;
5425
+ return commit(source, head + tail.out);
5132
5426
  };
5133
5427
  }
5134
5428
 
@@ -5153,6 +5447,8 @@ function createRemendPreprocessor(options) {
5153
5447
  }
5154
5448
  export {
5155
5449
  DEFAULT_PAYLOAD,
5450
+ ENGINE_PLACEHOLDER_TAGS,
5451
+ ENGINE_PROVENANCE_PROPERTY,
5156
5452
  PIPELINE_STAGES,
5157
5453
  SENTINEL_FN_CONTENT,
5158
5454
  SENTINEL_LINK_URL,
@@ -5197,6 +5493,7 @@ export {
5197
5493
  preprocessLaTeX,
5198
5494
  rehypeFooterAdorn,
5199
5495
  rehypeRebaseHashLinks_default as rehypeRebaseHashLinks,
5496
+ rehypeVerifyEngineTags,
5200
5497
  removeComments,
5201
5498
  sanitizeCrossChunkUrl,
5202
5499
  sanitizeSchema,