@ai-react-markdown/engine 2.10.1 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -31,6 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
33
  DEFAULT_PAYLOAD: () => DEFAULT_PAYLOAD,
34
+ ENGINE_PLACEHOLDER_TAGS: () => ENGINE_PLACEHOLDER_TAGS,
35
+ ENGINE_PROVENANCE_PROPERTY: () => ENGINE_PROVENANCE_PROPERTY,
34
36
  PIPELINE_STAGES: () => PIPELINE_STAGES,
35
37
  SENTINEL_FN_CONTENT: () => SENTINEL_FN_CONTENT,
36
38
  SENTINEL_LINK_URL: () => SENTINEL_LINK_URL,
@@ -75,7 +77,9 @@ __export(src_exports, {
75
77
  preprocessLaTeX: () => preprocessLaTeX,
76
78
  rehypeFooterAdorn: () => rehypeFooterAdorn,
77
79
  rehypeRebaseHashLinks: () => rehypeRebaseHashLinks_default,
80
+ rehypeVerifyEngineTags: () => rehypeVerifyEngineTags,
78
81
  removeComments: () => removeComments,
82
+ resolveCrossChunkReference: () => resolveCrossChunkReference,
79
83
  sanitizeCrossChunkUrl: () => sanitizeCrossChunkUrl,
80
84
  sanitizeSchema: () => sanitizeSchema,
81
85
  shortenDocumentId: () => shortenDocumentId,
@@ -2220,6 +2224,38 @@ function normalizeForMatch(s) {
2220
2224
  return (0, import_micromark_util_normalize_identifier3.normalizeIdentifier)(s);
2221
2225
  }
2222
2226
 
2227
+ // src/components/blankLineScanner.ts
2228
+ function createBlankLineScanner() {
2229
+ let end = 0;
2230
+ let newline = false;
2231
+ let cr = false;
2232
+ return (source, from = 0) => {
2233
+ if (from === 0) {
2234
+ end = 0;
2235
+ newline = false;
2236
+ cr = false;
2237
+ }
2238
+ for (let i = from; i < source.length; i++) {
2239
+ const c = source[i];
2240
+ if (c === "\n") {
2241
+ if (newline) {
2242
+ end = i + 1;
2243
+ newline = false;
2244
+ } else newline = true;
2245
+ cr = false;
2246
+ } else if (newline) {
2247
+ if ((c === " " || c === " ") && !cr) continue;
2248
+ if (c === "\r" && !cr) cr = true;
2249
+ else {
2250
+ newline = false;
2251
+ cr = false;
2252
+ }
2253
+ }
2254
+ }
2255
+ return end;
2256
+ };
2257
+ }
2258
+
2223
2259
  // src/components/collectDefLabels.ts
2224
2260
  var SCANNER_BOUNDARY_PROFILE = { defListEnabled: false, mathFlow: false, referenceTaint: false };
2225
2261
  function buildProcessor() {
@@ -2251,19 +2287,12 @@ var setsEqual = (a, b) => {
2251
2287
  for (const v of a) if (!b.has(v)) return false;
2252
2288
  return true;
2253
2289
  };
2254
- var BLANK_LINE_RE = /\r?\n[ \t]*\r?\n/g;
2255
- function lastRegionStart(source) {
2256
- BLANK_LINE_RE.lastIndex = 0;
2257
- let start = 0;
2258
- for (let m = BLANK_LINE_RE.exec(source); m !== null; m = BLANK_LINE_RE.exec(source)) {
2259
- start = m.index + m[0].length;
2260
- }
2261
- return start;
2262
- }
2263
2290
  var DEF_LINE_START_RE = /^[ \t>*+\d.)-]*\[(?:[^\]\\]|\\[\s\S])*\]:/m;
2264
2291
  function createDefLabelScanner(parse = collectDefLabels) {
2265
2292
  let prevSource = null;
2266
2293
  let prevLabels = null;
2294
+ const scanBlankLines = createBlankLineScanner();
2295
+ let regionStart = 0;
2267
2296
  let frozenEnd = 0;
2268
2297
  let frozenFootnotes = /* @__PURE__ */ new Set();
2269
2298
  let frozenLinks = /* @__PURE__ */ new Set();
@@ -2276,13 +2305,16 @@ function createDefLabelScanner(parse = collectDefLabels) {
2276
2305
  };
2277
2306
  return {
2278
2307
  scan(source) {
2308
+ if (source === prevSource && prevLabels !== null) return prevLabels;
2309
+ const previousRegionStart = regionStart;
2310
+ const appended = prevSource !== null && source.startsWith(prevSource);
2311
+ regionStart = scanBlankLines(source, appended ? prevSource.length : 0);
2279
2312
  let isAppend = false;
2280
2313
  if (prevSource !== null && prevLabels !== null) {
2281
2314
  if (source === prevSource) return prevLabels;
2282
2315
  if (source.startsWith(prevSource)) {
2283
2316
  isAppend = true;
2284
- const regionStart = lastRegionStart(prevSource);
2285
- const region = prevSource.slice(regionStart) + source.slice(prevSource.length);
2317
+ const region = source.slice(previousRegionStart);
2286
2318
  if (!DEF_LINE_START_RE.test(region)) {
2287
2319
  prevSource = source;
2288
2320
  return prevLabels;
@@ -2515,8 +2547,47 @@ function* extractContributions(mdast, options = {}) {
2515
2547
  for (const c of out) yield c;
2516
2548
  }
2517
2549
 
2550
+ // src/components/registryIndex.ts
2551
+ function buildRegistryIndex(registry) {
2552
+ const index = {
2553
+ footnotes: /* @__PURE__ */ new Map(),
2554
+ links: /* @__PURE__ */ new Map(),
2555
+ numbers: /* @__PURE__ */ new Map(),
2556
+ counts: /* @__PURE__ */ new Map(),
2557
+ occurrences: /* @__PURE__ */ new Map()
2558
+ };
2559
+ for (const sym of registry.chunkOrder) {
2560
+ const data = registry.chunkData.get(sym);
2561
+ if (!data) continue;
2562
+ for (const label of data.defs.keys()) if (!index.footnotes.has(label)) index.footnotes.set(label, sym);
2563
+ for (const label of data.linkDefs.keys()) if (!index.links.has(label)) index.links.set(label, sym);
2564
+ const local = /* @__PURE__ */ new Map();
2565
+ index.occurrences.set(sym, local);
2566
+ for (const ref of data.refs) {
2567
+ if (ref.kind !== "footnote") continue;
2568
+ const label = ref.label;
2569
+ if (!index.numbers.has(label)) index.numbers.set(label, index.numbers.size + 1);
2570
+ const total = (index.counts.get(label) ?? 0) + 1;
2571
+ index.counts.set(label, total);
2572
+ const prior = local.get(label);
2573
+ if (prior) prior.count++;
2574
+ else local.set(label, { start: total, count: 1 });
2575
+ }
2576
+ }
2577
+ return index;
2578
+ }
2579
+
2518
2580
  // src/components/documentRegistry.ts
2519
2581
  function createRegistry(onEmpty) {
2582
+ let index;
2583
+ let indexedVersion = -1;
2584
+ const getIndex = () => {
2585
+ if (!index || indexedVersion !== reg.version) {
2586
+ index = buildRegistryIndex(reg);
2587
+ indexedVersion = reg.version;
2588
+ }
2589
+ return index;
2590
+ };
2520
2591
  const reg = {
2521
2592
  chunkOrder: [],
2522
2593
  chunkData: /* @__PURE__ */ new Map(),
@@ -2668,72 +2739,25 @@ function createRegistry(onEmpty) {
2668
2739
  };
2669
2740
  },
2670
2741
  canonicalFootnoteFor(label) {
2671
- const id = normalizeId(label);
2672
- for (const sym of this.chunkOrder) {
2673
- const data = this.chunkData.get(sym);
2674
- if (data?.defs.has(id)) return sym;
2675
- }
2676
- return null;
2742
+ return getIndex().footnotes.get(normalizeId(label)) ?? null;
2677
2743
  },
2678
2744
  canonicalLinkFor(label) {
2679
- const id = normalizeId(label);
2680
- for (const sym of this.chunkOrder) {
2681
- const data = this.chunkData.get(sym);
2682
- if (data?.linkDefs.has(id)) return sym;
2683
- }
2684
- return null;
2745
+ return getIndex().links.get(normalizeId(label)) ?? null;
2685
2746
  },
2686
2747
  globalNumber(label) {
2687
- const id = normalizeId(label);
2688
- let n = 0;
2689
- const seen = /* @__PURE__ */ new Set();
2690
- for (const sym of this.chunkOrder) {
2691
- const data = this.chunkData.get(sym);
2692
- if (!data) continue;
2693
- for (const ref of data.refs) {
2694
- if (ref.kind !== "footnote") continue;
2695
- if (!seen.has(ref.label)) {
2696
- seen.add(ref.label);
2697
- n++;
2698
- if (ref.label === id) return n;
2699
- }
2700
- }
2701
- }
2702
- return null;
2748
+ return getIndex().numbers.get(normalizeId(label)) ?? null;
2703
2749
  },
2704
2750
  resolveLinkDef(label) {
2705
- const sym = this.canonicalLinkFor(label);
2706
- if (!sym) return null;
2707
- return this.chunkData.get(sym)?.linkDefs.get(normalizeId(label)) ?? null;
2751
+ const id = normalizeId(label);
2752
+ const sym = getIndex().links.get(id);
2753
+ return sym ? this.chunkData.get(sym)?.linkDefs.get(id) ?? null : null;
2708
2754
  },
2709
2755
  getRefsForLabel(label) {
2710
- const id = normalizeId(label);
2711
- let n = 0;
2712
- for (const sym of this.chunkOrder) {
2713
- const data = this.chunkData.get(sym);
2714
- if (!data) continue;
2715
- for (const ref of data.refs) {
2716
- if (ref.kind === "footnote" && ref.label === id) n++;
2717
- }
2718
- }
2719
- return n;
2756
+ return getIndex().counts.get(normalizeId(label)) ?? 0;
2720
2757
  },
2721
2758
  globalOccurrenceForRef(chunkSym, label, localOccurrence) {
2722
- const id = normalizeId(label);
2723
- let global2 = 0;
2724
- for (const sym of this.chunkOrder) {
2725
- const data = this.chunkData.get(sym);
2726
- if (!data) continue;
2727
- let localCount = 0;
2728
- for (const ref of data.refs) {
2729
- if (ref.kind !== "footnote") continue;
2730
- if (ref.label !== id) continue;
2731
- localCount++;
2732
- global2++;
2733
- if (sym === chunkSym && localCount === localOccurrence) return global2;
2734
- }
2735
- }
2736
- return null;
2759
+ const range = getIndex().occurrences.get(chunkSym)?.get(normalizeId(label));
2760
+ return range && Number.isInteger(localOccurrence) && localOccurrence > 0 && localOccurrence <= range.count ? range.start + localOccurrence - 1 : null;
2737
2761
  },
2738
2762
  _notify() {
2739
2763
  this.version++;
@@ -2790,6 +2814,55 @@ function rehypeUnwrapCrossChunkImages() {
2790
2814
 
2791
2815
  // src/components/pluginChain.ts
2792
2816
  var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
2817
+
2818
+ // src/components/rehypeVerifyEngineTags.ts
2819
+ var ENGINE_PLACEHOLDER_TAGS = /* @__PURE__ */ new Set([
2820
+ "footnote-sup",
2821
+ "cross-chunk-link",
2822
+ "cross-chunk-image"
2823
+ ]);
2824
+ var ENGINE_PROVENANCE_PROPERTY = "engineProvenance";
2825
+ function walk(parent, provenance, ancestors) {
2826
+ const children = parent.children;
2827
+ let i = 0;
2828
+ while (i < children.length) {
2829
+ const node = children[i];
2830
+ if (node.type === "element" && ENGINE_PLACEHOLDER_TAGS.has(node.tagName)) {
2831
+ const props = node.properties ?? {};
2832
+ const stamped = props[ENGINE_PROVENANCE_PROPERTY];
2833
+ const genuine = provenance !== "" && typeof stamped === "string" && stamped === provenance;
2834
+ if (genuine) {
2835
+ delete props[ENGINE_PROVENANCE_PROPERTY];
2836
+ if (ancestors && (node.tagName === "cross-chunk-link" || node.tagName === "cross-chunk-image")) {
2837
+ (node.data ??= {}).referenceAncestors = ancestors.slice();
2838
+ }
2839
+ ancestors?.push(
2840
+ node.tagName === "cross-chunk-link" ? "a" : node.tagName === "cross-chunk-image" ? "img" : node.tagName
2841
+ );
2842
+ walk(node, provenance, ancestors);
2843
+ ancestors?.pop();
2844
+ i += 1;
2845
+ } else {
2846
+ children.splice(i, 1, ...node.children);
2847
+ }
2848
+ continue;
2849
+ }
2850
+ if (node.type === "element") {
2851
+ ancestors?.push(node.tagName);
2852
+ walk(node, provenance, ancestors);
2853
+ ancestors?.pop();
2854
+ }
2855
+ i += 1;
2856
+ }
2857
+ }
2858
+ function rehypeVerifyEngineTags(options) {
2859
+ const provenance = options?.provenance ?? "";
2860
+ return function transformer(tree) {
2861
+ walk(tree, provenance, options?.referenceAncestors ? [] : void 0);
2862
+ };
2863
+ }
2864
+
2865
+ // src/components/pluginChain.ts
2793
2866
  var import_remark_breaks = __toESM(require("remark-breaks"), 1);
2794
2867
  var import_remark_cjk_friendly = __toESM(require("remark-cjk-friendly"), 1);
2795
2868
  var import_remark_cjk_friendly_gfm_strikethrough = __toESM(require("remark-cjk-friendly-gfm-strikethrough"), 1);
@@ -2806,16 +2879,18 @@ var import_remark_remove_comments = __toESM(require("remark-remove-comments"), 1
2806
2879
  // src/components/rehypeRebaseHashLinks.ts
2807
2880
  var import_unist_util_visit5 = require("unist-util-visit");
2808
2881
  var DEFAULT_PREFIX = "user-content-";
2882
+ function rebaseHashHref(href, prefix) {
2883
+ const hashPrefix = "#" + prefix;
2884
+ return href.startsWith("#") && !href.startsWith(hashPrefix) ? hashPrefix + href.slice(1) : href;
2885
+ }
2809
2886
  var rehypeRebaseHashLinks = (options) => {
2810
2887
  const prefix = options?.prefix ?? DEFAULT_PREFIX;
2811
- const hashPrefix = "#" + prefix;
2812
2888
  return (tree) => {
2813
2889
  (0, import_unist_util_visit5.visit)(tree, "element", (node) => {
2814
2890
  if (node.tagName !== "a") return;
2815
2891
  const href = node.properties?.href;
2816
2892
  if (typeof href !== "string" || !href.startsWith("#")) return;
2817
- if (href.startsWith(hashPrefix)) return;
2818
- node.properties.href = hashPrefix + href.slice(1);
2893
+ node.properties.href = rebaseHashHref(href, prefix);
2819
2894
  });
2820
2895
  };
2821
2896
  };
@@ -2895,10 +2970,21 @@ function buildCoreRemarkPlugins(enginePlugins) {
2895
2970
  ...DISPLAY_OPTIMIZE_CHAIN.filter(([name]) => selected.has(name)).map(([, plugin]) => plugin)
2896
2971
  ];
2897
2972
  }
2898
- function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix) {
2973
+ function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix, options) {
2899
2974
  return [
2900
2975
  // Allow raw HTML through so rehype-sanitize can handle it.
2901
2976
  [import_rehype_raw.default, { passThrough: [] }],
2977
+ // Unwrap forged engine placeholders BEFORE sanitize admits their tag
2978
+ // names. Only when the caller holds a credential (see the option's doc).
2979
+ ...options ? [
2980
+ [
2981
+ rehypeVerifyEngineTags,
2982
+ {
2983
+ provenance: options.provenance,
2984
+ ...sanitizeSchema2.ancestors?.a || sanitizeSchema2.ancestors?.img ? { referenceAncestors: true } : {}
2985
+ }
2986
+ ]
2987
+ ] : [],
2902
2988
  // Sanitize HTML while allowing <mark> (highlight), KaTeX class names,
2903
2989
  // and any extra protocols the caller permitted via the `sanitizeSchema`
2904
2990
  // prop. Override `clobberPrefix` with the instance-scoped value — the
@@ -2938,6 +3024,10 @@ function buildCoreRemarkRehypeOptions(enableDefinitionList) {
2938
3024
  }
2939
3025
 
2940
3026
  // src/components/customMdastHandlers.ts
3027
+ function provenanceProps(s) {
3028
+ const provenance = s.options.provenance;
3029
+ return typeof provenance === "string" ? { engineProvenance: provenance } : {};
3030
+ }
2941
3031
  function localDefProps(s, id) {
2942
3032
  const def = s.definitionById.get(id);
2943
3033
  if (!def || typeof def.url !== "string" || def.url === SENTINEL_LINK_URL) return {};
@@ -2968,15 +3058,14 @@ function buildCrossChunkHandlers() {
2968
3058
  type: "element",
2969
3059
  tagName: "cross-chunk-link",
2970
3060
  properties: {
2971
- // `label` is the ORIGINAL source text (mdast's `label` field), NOT
2972
- // the normalized `identifier`. The placeholder uses it to construct
2973
- // hrefs that line up with mdast-util-to-hast's default `<li id>`
2974
- // which also preserves source case. Registry lookups normalize
2975
- // internally, so cross-chunk case-insensitive matching still works.
3061
+ // Display labels decode escapes; registry identifiers must retain
3062
+ // those bytes. Never use the display label as the lookup key.
3063
+ identifier: node.identifier,
2976
3064
  label: node.label ?? node.identifier,
2977
3065
  referenceType: node.referenceType,
2978
3066
  documentId: s.options.documentId,
2979
- ...localDefProps(s, id)
3067
+ ...localDefProps(s, id),
3068
+ ...provenanceProps(s)
2980
3069
  },
2981
3070
  children: s.all(node)
2982
3071
  };
@@ -2990,11 +3079,13 @@ function buildCrossChunkHandlers() {
2990
3079
  type: "element",
2991
3080
  tagName: "cross-chunk-image",
2992
3081
  properties: {
3082
+ identifier: node.identifier,
2993
3083
  label: node.label ?? node.identifier,
2994
3084
  referenceType: node.referenceType,
2995
3085
  alt: node.alt ?? "",
2996
3086
  documentId: s.options.documentId,
2997
- ...localDefProps(s, id)
3087
+ ...localDefProps(s, id),
3088
+ ...provenanceProps(s)
2998
3089
  },
2999
3090
  children: []
3000
3091
  };
@@ -3011,7 +3102,8 @@ function buildCrossChunkHandlers() {
3011
3102
  properties: {
3012
3103
  label: node.identifier,
3013
3104
  localOccurrence,
3014
- documentId: s.options.documentId
3105
+ documentId: s.options.documentId,
3106
+ ...provenanceProps(s)
3015
3107
  },
3016
3108
  children: []
3017
3109
  };
@@ -3029,7 +3121,8 @@ function buildCrossChunkHandlers() {
3029
3121
  // first client frame), where the local synthetic footer is what
3030
3122
  // renders, so marks and footer agree (core-render-02).
3031
3123
  localNumber: s.footnoteOrder.indexOf(id) + 1,
3032
- documentId: s.options.documentId
3124
+ documentId: s.options.documentId,
3125
+ ...provenanceProps(s)
3033
3126
  },
3034
3127
  children: []
3035
3128
  };
@@ -4249,8 +4342,8 @@ var sanitizeSchema = deepFreeze(
4249
4342
  attributes: {
4250
4343
  ...import_rehype_sanitize2.defaultSchema.attributes,
4251
4344
  code: mergeClassNameAllowlist(import_rehype_sanitize2.defaultSchema.attributes?.code, ["math-inline", "math-display"]),
4252
- "cross-chunk-link": ["label", "referenceType", "documentId", "localUrl", "localTitle"],
4253
- "cross-chunk-image": ["label", "referenceType", "documentId", "alt", "localUrl", "localTitle"],
4345
+ "cross-chunk-link": ["identifier", "label", "referenceType", "documentId", "localUrl", "localTitle"],
4346
+ "cross-chunk-image": ["identifier", "label", "referenceType", "documentId", "alt", "localUrl", "localTitle"],
4254
4347
  "footnote-sup": ["label", "localOccurrence", "localNumber", "documentId"]
4255
4348
  },
4256
4349
  strip: [.../* @__PURE__ */ new Set([...import_rehype_sanitize2.defaultSchema.strip || [], ...STRIPPED_TAGS])]
@@ -4286,6 +4379,49 @@ function sanitizeCrossChunkUrl(rawUrl, key, tagName, urlTransform, schema) {
4286
4379
  return String(transformed);
4287
4380
  }
4288
4381
 
4382
+ // src/components/resolveCrossChunkReference.ts
4383
+ var import_rehype_sanitize3 = __toESM(require("rehype-sanitize"), 1);
4384
+ var import_micromark_util_sanitize_uri3 = require("micromark-util-sanitize-uri");
4385
+ function resolveCrossChunkReference(input, schema, urlTransform, clobberPrefix) {
4386
+ const key = input.tagName === "a" ? "href" : "src";
4387
+ const element = {
4388
+ type: "element",
4389
+ tagName: input.tagName,
4390
+ properties: {
4391
+ [key]: (0, import_micromark_util_sanitize_uri3.normalizeUri)(input.url),
4392
+ ...input.tagName === "img" ? { alt: input.alt ?? "" } : {},
4393
+ ...input.title !== void 0 ? { title: input.title } : {}
4394
+ },
4395
+ children: input.tagName === "a" ? [{ type: "text", value: "__reference_children__" }] : []
4396
+ };
4397
+ const requiredAncestors = schema.ancestors?.[input.tagName];
4398
+ const recordedAncestors = input.node?.data?.referenceAncestors;
4399
+ let finalSchema = schema;
4400
+ if (requiredAncestors && Array.isArray(recordedAncestors) && requiredAncestors.some((tag) => recordedAncestors.includes(tag))) {
4401
+ const ancestors = { ...schema.ancestors };
4402
+ delete ancestors[input.tagName];
4403
+ finalSchema = { ...schema, ancestors };
4404
+ }
4405
+ const root2 = (0, import_rehype_sanitize3.default)({ ...finalSchema, clobberPrefix })({ type: "root", children: [element] });
4406
+ const node = root2.children[0];
4407
+ if (node?.type !== "element") return { element: null, keepChildren: node?.type === "text" };
4408
+ if (node.tagName === "a" && typeof node.properties.href === "string") {
4409
+ node.properties.href = rebaseHashHref(node.properties.href, clobberPrefix);
4410
+ }
4411
+ node.children = input.node?.children ?? [];
4412
+ if (input.node?.position) node.position = input.node.position;
4413
+ buildTransform({
4414
+ allowedElements: void 0,
4415
+ disallowedElements: void 0,
4416
+ allowElement: void 0,
4417
+ skipHtml: void 0,
4418
+ unwrapDisallowed: void 0,
4419
+ urlTransform
4420
+ })(node, 0, root2);
4421
+ node.children = [];
4422
+ return { element: node, keepChildren: false };
4423
+ }
4424
+
4289
4425
  // src/plugins/defs.ts
4290
4426
  function getEnginePluginInternals(plugin) {
4291
4427
  const candidate = plugin;
@@ -4490,6 +4626,8 @@ var createSmoothStreamController = (options = {}) => {
4490
4626
  let source = "";
4491
4627
  let visibleEnd = 0;
4492
4628
  let pending = [];
4629
+ let pendingHead = 0;
4630
+ const pendingCount = () => pending.length - pendingHead;
4493
4631
  let tentativeEnd = 0;
4494
4632
  let finished = false;
4495
4633
  let seam;
@@ -4534,7 +4672,7 @@ var createSmoothStreamController = (options = {}) => {
4534
4672
  cancelFrame = void 0;
4535
4673
  };
4536
4674
  const ensureScheduled = () => {
4537
- if (disposed || cancelFrame || pending.length === 0) return;
4675
+ if (disposed || cancelFrame || pendingCount() === 0) return;
4538
4676
  lastTickAt = now();
4539
4677
  credit = 0;
4540
4678
  cancelFrame = schedule(tick);
@@ -4548,31 +4686,38 @@ var createSmoothStreamController = (options = {}) => {
4548
4686
  const params = resolveParams();
4549
4687
  let rate;
4550
4688
  if (finished && drainDeadlineAt !== void 0) {
4551
- rate = Math.max(params.minCharsPerSecond, pending.length * 1e3 / Math.max(1, drainDeadlineAt - t));
4689
+ rate = Math.max(params.minCharsPerSecond, pendingCount() * 1e3 / Math.max(1, drainDeadlineAt - t));
4552
4690
  } else if (gapWindow.length > 0 && lastArrivalAt !== void 0) {
4553
4691
  const gaps = gapWindow.map((s) => s.gap).sort((a, b) => a - b);
4554
4692
  const intervalQ = gaps[Math.min(gaps.length - 1, Math.floor(gaps.length * INTERVAL_QUANTILE))];
4555
4693
  const horizon = Math.max(16, Math.min(params.bufferFactor * intervalQ + HORIZON_PAD_MS, params.maxLagMs));
4556
4694
  const deadline = Math.max(lastArrivalAt + horizon, t + DEADLINE_FLOOR_MS);
4557
- rate = Math.max(params.minCharsPerSecond, pending.length * 1e3 / Math.max(1, deadline - t));
4695
+ rate = Math.max(params.minCharsPerSecond, pendingCount() * 1e3 / Math.max(1, deadline - t));
4558
4696
  } else {
4559
- rate = Math.max(params.minCharsPerSecond, pending.length * 1e3 / Math.max(1, params.correctionTauMs));
4697
+ rate = Math.max(params.minCharsPerSecond, pendingCount() * 1e3 / Math.max(1, params.correctionTauMs));
4560
4698
  }
4561
4699
  credit += rate * dt / 1e3;
4562
- const reveal = Math.min(Math.floor(credit), pending.length);
4700
+ const reveal = Math.min(Math.floor(credit), pendingCount());
4563
4701
  if (reveal > 0) {
4564
- visibleEnd = pending[reveal - 1];
4565
- pending = pending.slice(reveal);
4566
- credit = pending.length > 0 ? credit - reveal : 0;
4702
+ visibleEnd = pending[pendingHead + reveal - 1];
4703
+ pendingHead += reveal;
4704
+ if (pendingHead === pending.length) {
4705
+ pending = [];
4706
+ pendingHead = 0;
4707
+ } else if (pendingHead >= 1024 && pendingHead * 2 >= pending.length) {
4708
+ pending = pending.slice(pendingHead);
4709
+ pendingHead = 0;
4710
+ }
4711
+ credit = pendingCount() > 0 ? credit - reveal : 0;
4567
4712
  notify();
4568
4713
  }
4569
- if (!disposed && !cancelFrame && pending.length > 0) cancelFrame = schedule(tick);
4714
+ if (!disposed && !cancelFrame && pendingCount() > 0) cancelFrame = schedule(tick);
4570
4715
  };
4571
4716
  const resegmentTail = () => {
4572
- if (seam !== void 0 && seam < source.length && pending[pending.length - 1] === seam) {
4717
+ if (pendingCount() > 0 && seam !== void 0 && seam < source.length && pending[pending.length - 1] === seam) {
4573
4718
  pending.pop();
4574
4719
  }
4575
- const from = pending.length > 0 ? pending[pending.length - 1] : visibleEnd;
4720
+ const from = pendingCount() > 0 ? pending[pending.length - 1] : visibleEnd;
4576
4721
  let anchor = from;
4577
4722
  if (seam !== void 0 && from <= seam) {
4578
4723
  anchor = Math.max(0, from - RESUME_LOOKBACK);
@@ -4606,6 +4751,7 @@ var createSmoothStreamController = (options = {}) => {
4606
4751
  visibleEnd = next.length;
4607
4752
  tentativeEnd = next.length;
4608
4753
  pending = [];
4754
+ pendingHead = 0;
4609
4755
  seam = next.length;
4610
4756
  credit = 0;
4611
4757
  cancelScheduled();
@@ -4637,7 +4783,7 @@ var createSmoothStreamController = (options = {}) => {
4637
4783
  if (finished) return;
4638
4784
  finished = true;
4639
4785
  lastArrivalAt = void 0;
4640
- if (tentativeEnd > (pending.length > 0 ? pending[pending.length - 1] : visibleEnd)) {
4786
+ if (tentativeEnd > (pendingCount() > 0 ? pending[pending.length - 1] : visibleEnd)) {
4641
4787
  pending.push(tentativeEnd);
4642
4788
  }
4643
4789
  const params = resolveParams();
@@ -4663,10 +4809,11 @@ var createSmoothStreamController = (options = {}) => {
4663
4809
  snap,
4664
4810
  flush() {
4665
4811
  disposed = false;
4666
- const target = finished ? source.length : pending.length > 0 ? pending[pending.length - 1] : visibleEnd;
4812
+ const target = finished ? source.length : pendingCount() > 0 ? pending[pending.length - 1] : visibleEnd;
4667
4813
  if (target <= visibleEnd) return;
4668
4814
  visibleEnd = target;
4669
4815
  pending = [];
4816
+ pendingHead = 0;
4670
4817
  credit = 0;
4671
4818
  cancelScheduled();
4672
4819
  notify();
@@ -4689,6 +4836,12 @@ var createSmoothStreamController = (options = {}) => {
4689
4836
  };
4690
4837
 
4691
4838
  // src/preprocessors/latex.ts
4839
+ function isHardBoundary(kind) {
4840
+ return kind === "code" || kind === "literal" || kind === "multilineTag";
4841
+ }
4842
+ function hasLineEnding(text) {
4843
+ return text.includes("\n") || text.includes("\r");
4844
+ }
4692
4845
  function getRepeatedMarkerLength(content, start, marker) {
4693
4846
  let end = start;
4694
4847
  while (end < content.length && content[end] === marker) {
@@ -4756,11 +4909,11 @@ function splitByProtectedRegions(content) {
4756
4909
  let multilineFenceMarker = null;
4757
4910
  let multilineFenceLength = 0;
4758
4911
  let multilineFenceIndent = 0;
4759
- function pushProtected(start, end) {
4912
+ function pushProtected(start, end, kind) {
4760
4913
  if (start > lastIndex) {
4761
- segments.push({ text: content.substring(lastIndex, start), isCode: false });
4914
+ segments.push({ kind: "text", text: content.substring(lastIndex, start) });
4762
4915
  }
4763
- segments.push({ text: content.substring(start, end), isCode: true });
4916
+ segments.push({ kind, text: content.substring(start, end) });
4764
4917
  lastIndex = end;
4765
4918
  }
4766
4919
  let i = 0;
@@ -4771,7 +4924,7 @@ function splitByProtectedRegions(content) {
4771
4924
  const runLen = getRepeatedMarkerLength(content, i, multilineFenceMarker);
4772
4925
  const closerIndent = lineIndentBefore(content, i);
4773
4926
  if (runLen >= multilineFenceLength && closerIndent !== -1 && closerIndent <= multilineFenceIndent + 3 && restOfLineIsBlank(content, i + runLen)) {
4774
- pushProtected(multilineStart, i + runLen);
4927
+ pushProtected(multilineStart, i + runLen, "code");
4775
4928
  multilineStart = -1;
4776
4929
  multilineFenceMarker = null;
4777
4930
  multilineFenceLength = 0;
@@ -4798,7 +4951,7 @@ function splitByProtectedRegions(content) {
4798
4951
  if (char === "`") {
4799
4952
  const closeIdx = findClosingBacktickRun(content, i + runLen, runLen);
4800
4953
  if (closeIdx !== -1) {
4801
- pushProtected(i, closeIdx + runLen);
4954
+ pushProtected(i, closeIdx + runLen, "code");
4802
4955
  i = closeIdx + runLen;
4803
4956
  continue;
4804
4957
  }
@@ -4823,7 +4976,11 @@ function splitByProtectedRegions(content) {
4823
4976
  endIndex = content.length;
4824
4977
  }
4825
4978
  }
4826
- pushProtected(i, endIndex);
4979
+ pushProtected(
4980
+ i,
4981
+ endIndex,
4982
+ isOpeningPairedTag ? "literal" : hasLineEnding(content.substring(i, endIndex)) ? "multilineTag" : "tag"
4983
+ );
4827
4984
  i = endIndex;
4828
4985
  continue;
4829
4986
  }
@@ -4831,10 +4988,10 @@ function splitByProtectedRegions(content) {
4831
4988
  i += 1;
4832
4989
  }
4833
4990
  if (multilineStart !== -1) {
4834
- pushProtected(multilineStart, content.length);
4991
+ pushProtected(multilineStart, content.length, "code");
4835
4992
  }
4836
4993
  if (lastIndex < content.length) {
4837
- segments.push({ text: content.substring(lastIndex), isCode: false });
4994
+ segments.push({ kind: "text", text: content.substring(lastIndex) });
4838
4995
  }
4839
4996
  return segments;
4840
4997
  }
@@ -4988,20 +5145,22 @@ function escapeLatexPipesInUnclosed(text) {
4988
5145
  const tail = text.substring(unclosedStart + delimLen);
4989
5146
  return before + delim + replaceUnescapedPipes(tail);
4990
5147
  }
4991
- function opensMathFlow(text, pos) {
5148
+ function opensMathFlow(text, pos, runStartsAtLineStart) {
4992
5149
  let i = pos;
4993
5150
  let spaces = 0;
4994
- while (i > 0 && text[i - 1] !== "\n") {
5151
+ while (i > 0) {
5152
+ const prev = text[i - 1];
5153
+ if (prev === "\n" || prev === "\r") return true;
4995
5154
  i -= 1;
4996
5155
  if (text[i] !== " ") return false;
4997
5156
  spaces += 1;
4998
5157
  if (spaces > 3) return false;
4999
5158
  }
5000
- return true;
5159
+ return runStartsAtLineStart;
5001
5160
  }
5002
- function truncateUnclosedLatexBlock(text, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
5161
+ function truncateUnclosedLatexBlock(text, runStartsAtLineStart, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
5003
5162
  if (unclosedStart === -1) return text;
5004
- if (!opensMathFlow(text, unclosedStart)) return text;
5163
+ if (!opensMathFlow(text, unclosedStart, runStartsAtLineStart)) return text;
5005
5164
  return text.substring(0, unclosedStart).trimEnd();
5006
5165
  }
5007
5166
  function escapeTextUnderscores(text) {
@@ -5046,7 +5205,8 @@ function convertSingleToDoubleDollar(text) {
5046
5205
  }
5047
5206
  function preprocessLaTeX(str) {
5048
5207
  if (!hasLatexTrigger(str)) return str;
5049
- return processSlice(str, false).out;
5208
+ const mask = selectMask(str);
5209
+ return (mask === null ? processSlice(str, { legacy: true, probe: false }) : processSlice(str, { probe: false, mask })).out;
5050
5210
  }
5051
5211
  function hasLatexTrigger(str) {
5052
5212
  return str.includes("$") || str.includes("\\[") || str.includes("\\(");
@@ -5075,42 +5235,240 @@ function hasUnclosedTextCommand(text) {
5075
5235
  }
5076
5236
  var RESIDUAL_OPEN_BRACKET_RE = /(?<!!)\\\[/;
5077
5237
  var LEADING_DOUBLE_DOLLAR_RE = /^\s*\$\$/;
5078
- function processSlice(slice, probe = true) {
5238
+ var PUA_START = 57344;
5239
+ var PUA_END = 63743;
5240
+ var PUA_SIZE = PUA_END - PUA_START + 1;
5241
+ function selectMask(source) {
5242
+ const first = String.fromCharCode(PUA_START);
5243
+ if (source.indexOf(first) === -1) return first;
5244
+ const seen = new Uint8Array(PUA_SIZE);
5245
+ for (let i = 0; i < source.length; i++) {
5246
+ const c = source.charCodeAt(i);
5247
+ if (c >= PUA_START && c <= PUA_END) seen[c - PUA_START] = 1;
5248
+ }
5249
+ for (let k = 0; k < PUA_SIZE; k++) if (seen[k] === 0) return String.fromCharCode(PUA_START + k);
5250
+ return null;
5251
+ }
5252
+ var PuaPresence = class {
5253
+ bits = new Uint8Array(PUA_SIZE);
5254
+ distinct = 0;
5255
+ reset() {
5256
+ this.bits.fill(0);
5257
+ this.distinct = 0;
5258
+ }
5259
+ add(text, from) {
5260
+ for (let i = from; i < text.length; i++) {
5261
+ const c = text.charCodeAt(i);
5262
+ if (c >= PUA_START && c <= PUA_END) {
5263
+ const k = c - PUA_START;
5264
+ if (this.bits[k] === 0) {
5265
+ this.bits[k] = 1;
5266
+ this.distinct += 1;
5267
+ }
5268
+ }
5269
+ }
5270
+ }
5271
+ select() {
5272
+ if (this.distinct === 0) return String.fromCharCode(PUA_START);
5273
+ if (this.distinct >= PUA_SIZE) return null;
5274
+ for (let k = 0; k < PUA_SIZE; k++) if (this.bits[k] === 0) return String.fromCharCode(PUA_START + k);
5275
+ return null;
5276
+ }
5277
+ };
5278
+ function transformRun(input, probe, runStartsAtLineStart, seamEligible) {
5279
+ let text = input;
5280
+ let tailSensitive = false;
5281
+ let truncatedAtSeamStart = false;
5282
+ text = escapeMhchemCommands(text);
5283
+ text = escapeCurrencyDollarSigns(text);
5284
+ text = convertLatexDelimiters(text);
5285
+ if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) tailSensitive = true;
5286
+ text = escapeLatexPipes(text);
5287
+ if (probe && findUnclosedDelimiterStart(text, "both") !== -1) tailSensitive = true;
5288
+ text = escapeLatexPipesInUnclosed(text);
5289
+ if (probe && hasUnclosedTextCommand(text)) tailSensitive = true;
5290
+ text = escapeTextUnderscores(text);
5291
+ text = convertSingleToDoubleDollar(text);
5292
+ let unclosedDouble;
5293
+ if (probe || seamEligible && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
5294
+ unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
5295
+ if (unclosedDouble !== -1) {
5296
+ tailSensitive = true;
5297
+ if (seamEligible && opensMathFlow(text, unclosedDouble, runStartsAtLineStart) && text.slice(0, unclosedDouble).trim() === "") {
5298
+ truncatedAtSeamStart = true;
5299
+ }
5300
+ }
5301
+ }
5302
+ text = truncateUnclosedLatexBlock(text, runStartsAtLineStart, unclosedDouble);
5303
+ return { out: text, tailSensitive, truncatedAtSeamStart };
5304
+ }
5305
+ function processSliceLegacy(slice, probe) {
5079
5306
  const segments = splitByProtectedRegions(slice);
5080
5307
  const parts = [];
5081
5308
  let quiescent = true;
5082
5309
  let truncatedAtSeamStart = false;
5083
5310
  for (let index = 0; index < segments.length; index++) {
5084
5311
  const segment = segments[index];
5085
- if (segment.isCode) {
5312
+ if (segment.kind !== "text") {
5086
5313
  parts.push(segment.text);
5087
5314
  continue;
5088
5315
  }
5089
- let text = segment.text;
5090
- text = escapeMhchemCommands(text);
5091
- text = escapeCurrencyDollarSigns(text);
5092
- text = convertLatexDelimiters(text);
5093
- if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) quiescent = false;
5094
- text = escapeLatexPipes(text);
5095
- if (probe && findUnclosedDelimiterStart(text, "both") !== -1) quiescent = false;
5096
- text = escapeLatexPipesInUnclosed(text);
5097
- if (probe && hasUnclosedTextCommand(text)) quiescent = false;
5098
- text = escapeTextUnderscores(text);
5099
- text = convertSingleToDoubleDollar(text);
5100
- let unclosedDouble;
5101
- if (probe || index === 0 && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
5102
- unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
5103
- if (unclosedDouble !== -1) {
5104
- quiescent = false;
5105
- if (index === 0 && opensMathFlow(text, unclosedDouble) && text.slice(0, unclosedDouble).trim() === "") {
5106
- truncatedAtSeamStart = true;
5316
+ const r = transformRun(segment.text, probe, true, index === 0);
5317
+ if (r.tailSensitive) quiescent = false;
5318
+ if (r.truncatedAtSeamStart) truncatedAtSeamStart = true;
5319
+ parts.push(r.out);
5320
+ }
5321
+ return { out: parts.join(""), quiescent, truncatedAtSeamStart, degradedReason: null };
5322
+ }
5323
+ var SCOPE_DEPTH_CAP = 8;
5324
+ var VOID_TAGS2 = /* @__PURE__ */ new Set(["br", "hr", "img", "wbr", "input", "source"]);
5325
+ var TAG_NAME_RE = /^<(\/?)([A-Za-z][A-Za-z0-9]*)/;
5326
+ function tagInfo(tag) {
5327
+ const m = TAG_NAME_RE.exec(tag);
5328
+ const closing = m?.[1] === "/";
5329
+ const name = (m?.[2] ?? "").toLowerCase();
5330
+ const opensScope = !closing && !tag.endsWith("/>") && !VOID_TAGS2.has(name);
5331
+ return { name, closing, opensScope };
5332
+ }
5333
+ function buildRunTree(segments) {
5334
+ const root2 = [];
5335
+ const stack = [];
5336
+ const current = () => stack.length > 0 ? stack[stack.length - 1].children : root2;
5337
+ const unwind = () => {
5338
+ while (stack.length > 0) {
5339
+ const frame = stack.pop();
5340
+ current().push({ type: "atom", text: frame.open }, ...frame.children);
5341
+ }
5342
+ };
5343
+ for (const segment of segments) {
5344
+ if (segment.kind === "tag") {
5345
+ const info = tagInfo(segment.text);
5346
+ if (info.closing) {
5347
+ const top = stack[stack.length - 1];
5348
+ if (top !== void 0 && top.name === info.name) {
5349
+ stack.pop();
5350
+ const parent = current();
5351
+ if (top.suppressed) {
5352
+ parent.push({ type: "atom", text: top.open }, ...top.children, { type: "atom", text: segment.text });
5353
+ } else {
5354
+ parent.push({ type: "scope", open: top.open, close: segment.text, children: top.children });
5355
+ }
5356
+ } else {
5357
+ current().push({ type: "atom", text: segment.text });
5107
5358
  }
5359
+ } else if (info.opensScope) {
5360
+ stack.push({ name: info.name, open: segment.text, suppressed: stack.length >= SCOPE_DEPTH_CAP, children: [] });
5361
+ } else {
5362
+ current().push({ type: "atom", text: segment.text });
5108
5363
  }
5364
+ continue;
5365
+ }
5366
+ const t = segment.text;
5367
+ let start = 0;
5368
+ for (let i = 0; i < t.length; i++) {
5369
+ const c = t.charCodeAt(i);
5370
+ if (c !== 10 && c !== 13) continue;
5371
+ if (i > start) current().push({ type: "text", text: t.slice(start, i) });
5372
+ const end = c === 13 && t.charCodeAt(i + 1) === 10 ? i + 2 : i + 1;
5373
+ unwind();
5374
+ root2.push({ type: "text", text: t.slice(i, end) });
5375
+ start = end;
5376
+ i = end - 1;
5377
+ }
5378
+ if (start < t.length) current().push({ type: "text", text: t.slice(start) });
5379
+ }
5380
+ unwind();
5381
+ return root2;
5382
+ }
5383
+ var restoreFailureInjector = null;
5384
+ function restore(out, atoms, mask) {
5385
+ if (restoreFailureInjector !== null && restoreFailureInjector(atoms)) return null;
5386
+ let result = "";
5387
+ let k = 0;
5388
+ let last = 0;
5389
+ for (; ; ) {
5390
+ const idx = out.indexOf(mask, last);
5391
+ if (idx === -1) break;
5392
+ if (k >= atoms.length) return null;
5393
+ result += out.slice(last, idx) + atoms[k];
5394
+ k += 1;
5395
+ last = idx + 1;
5396
+ }
5397
+ return result + out.slice(last);
5398
+ }
5399
+ function emitRun(nodes, mask) {
5400
+ let text = "";
5401
+ const atoms = [];
5402
+ for (const node of nodes) {
5403
+ if (node.type === "text") {
5404
+ text += node.text;
5405
+ } else if (node.type === "atom") {
5406
+ atoms.push(node.text);
5407
+ text += mask;
5408
+ } else {
5409
+ const inner = emitRun(node.children, mask);
5410
+ if (inner === null) return null;
5411
+ const transformed = transformRun(inner.text, false, false, false);
5412
+ const restored = restore(transformed.out, inner.atoms, mask);
5413
+ if (restored === null) return null;
5414
+ atoms.push(node.open + restored + node.close);
5415
+ text += mask;
5109
5416
  }
5110
- text = truncateUnclosedLatexBlock(text, unclosedDouble);
5111
- parts.push(text);
5112
5417
  }
5113
- return { out: parts.join(""), quiescent, truncatedAtSeamStart };
5418
+ return { text, atoms };
5419
+ }
5420
+ function reportRestoreViolation() {
5421
+ if (false) {
5422
+ console.error(
5423
+ "[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."
5424
+ );
5425
+ }
5426
+ }
5427
+ function processSliceDefault(slice, probe, mask) {
5428
+ const segments = splitByProtectedRegions(slice);
5429
+ const parts = [];
5430
+ let quiescent = true;
5431
+ let truncatedAtSeamStart = false;
5432
+ let offset = 0;
5433
+ let i = 0;
5434
+ while (i < segments.length) {
5435
+ const segment = segments[i];
5436
+ if (isHardBoundary(segment.kind)) {
5437
+ parts.push(segment.text);
5438
+ offset += segment.text.length;
5439
+ i += 1;
5440
+ continue;
5441
+ }
5442
+ const runStart = offset;
5443
+ const runSegments = [];
5444
+ while (i < segments.length && !isHardBoundary(segments[i].kind)) {
5445
+ runSegments.push(segments[i]);
5446
+ offset += segments[i].text.length;
5447
+ i += 1;
5448
+ }
5449
+ const before = runStart === 0 ? -1 : slice.charCodeAt(runStart - 1);
5450
+ const runStartsAtLineStart = before === -1 || before === 10 || before === 13;
5451
+ const seamEligible = runStart === 0;
5452
+ const emitted = emitRun(buildRunTree(runSegments), mask);
5453
+ if (emitted === null) {
5454
+ reportRestoreViolation();
5455
+ return { ...processSliceLegacy(slice, probe), degradedReason: "restore-invariant" };
5456
+ }
5457
+ const r = transformRun(emitted.text, probe, runStartsAtLineStart, seamEligible);
5458
+ const restored = restore(r.out, emitted.atoms, mask);
5459
+ if (restored === null) {
5460
+ reportRestoreViolation();
5461
+ return { ...processSliceLegacy(slice, probe), degradedReason: "restore-invariant" };
5462
+ }
5463
+ if (r.tailSensitive) quiescent = false;
5464
+ if (r.truncatedAtSeamStart) truncatedAtSeamStart = true;
5465
+ parts.push(restored);
5466
+ }
5467
+ return { out: parts.join(""), quiescent, truncatedAtSeamStart, degradedReason: null };
5468
+ }
5469
+ function processSlice(slice, options) {
5470
+ if (options.legacy === true) return processSliceLegacy(slice, options.probe);
5471
+ return processSliceDefault(slice, options.probe, options.mask);
5114
5472
  }
5115
5473
  function isBlankRawLine(text, from, to) {
5116
5474
  for (let i = from; i < to; i++) {
@@ -5126,7 +5484,7 @@ function findRawSafeCut(active) {
5126
5484
  let backtickHazard = false;
5127
5485
  let latentLt = false;
5128
5486
  for (const segment of segments) {
5129
- if (segment.isCode) {
5487
+ if (segment.kind !== "text") {
5130
5488
  if (segment.text.includes(">")) latentLt = false;
5131
5489
  offset += segment.text.length;
5132
5490
  continue;
@@ -5161,12 +5519,28 @@ function createIncrementalLatexPreprocessor(options) {
5161
5519
  const freezeThreshold = options?.freezeThreshold ?? DEFAULT_FREEZE_ATTEMPT_THRESHOLD;
5162
5520
  const onAttempt = options?.onAttempt;
5163
5521
  const backoff = options?.backoff ?? true;
5522
+ const onDegrade = options?.onDegrade;
5164
5523
  let prevSource = "";
5165
5524
  let prevOutput = "";
5166
5525
  let frozenSrcEnd = 0;
5167
5526
  let frozenOut = "";
5168
5527
  let triggered = false;
5169
5528
  let nextAttemptLen = 0;
5529
+ let lineageDegraded = false;
5530
+ const presence = new PuaPresence();
5531
+ const commit = (source, out) => {
5532
+ prevSource = source;
5533
+ prevOutput = out;
5534
+ return out;
5535
+ };
5536
+ const legacyWhole = (source) => processSlice(source, { legacy: true, probe: false }).out;
5537
+ const degrade = (source, reason) => {
5538
+ lineageDegraded = true;
5539
+ frozenSrcEnd = 0;
5540
+ frozenOut = "";
5541
+ onDegrade?.(reason);
5542
+ return commit(source, legacyWhole(source));
5543
+ };
5170
5544
  return function incrementalPreprocessLaTeX(source) {
5171
5545
  if (source === prevSource) return prevOutput;
5172
5546
  const isAppend = source.length > prevSource.length && source.startsWith(prevSource);
@@ -5175,16 +5549,20 @@ function createIncrementalLatexPreprocessor(options) {
5175
5549
  frozenOut = "";
5176
5550
  triggered = false;
5177
5551
  nextAttemptLen = 0;
5552
+ lineageDegraded = false;
5553
+ presence.reset();
5554
+ presence.add(source, 0);
5555
+ } else {
5556
+ presence.add(source, prevSource.length);
5178
5557
  }
5179
5558
  if (!triggered) {
5180
5559
  const checkFrom = isAppend ? Math.max(0, prevSource.length - 1) : 0;
5181
- if (!hasLatexTrigger(source.slice(checkFrom))) {
5182
- prevSource = source;
5183
- prevOutput = source;
5184
- return source;
5185
- }
5560
+ if (!hasLatexTrigger(source.slice(checkFrom))) return commit(source, source);
5186
5561
  triggered = true;
5187
5562
  }
5563
+ if (lineageDegraded) return commit(source, legacyWhole(source));
5564
+ const mask = presence.select();
5565
+ if (mask === null) return degrade(source, "mask-exhausted");
5188
5566
  let active = source.slice(frozenSrcEnd);
5189
5567
  if (active.length > freezeThreshold && active.length >= nextAttemptLen) {
5190
5568
  const activeLength = active.length;
@@ -5199,18 +5577,20 @@ function createIncrementalLatexPreprocessor(options) {
5199
5577
  };
5200
5578
  const cut = findRawSafeCut(active);
5201
5579
  if (cut > 0) {
5202
- const candidate = processSlice(active.slice(0, cut));
5580
+ const candidate = processSlice(active.slice(0, cut), { probe: true, mask });
5581
+ if (candidate.degradedReason !== null) {
5582
+ onAttempt?.({ activeLength, frozenBytes: 0 });
5583
+ return degrade(source, candidate.degradedReason);
5584
+ }
5203
5585
  if (candidate.quiescent) freeze(cut, candidate);
5204
5586
  }
5205
5587
  nextAttemptLen = advanced || !backoff ? 0 : active.length * 2;
5206
5588
  onAttempt?.({ activeLength, frozenBytes });
5207
5589
  }
5208
- const tail = processSlice(active, false);
5590
+ const tail = processSlice(active, { probe: false, mask });
5591
+ if (tail.degradedReason !== null) return degrade(source, tail.degradedReason);
5209
5592
  const head = tail.truncatedAtSeamStart ? frozenOut.replace(/\s+$/, "") : frozenOut;
5210
- const out = head + tail.out;
5211
- prevSource = source;
5212
- prevOutput = out;
5213
- return out;
5593
+ return commit(source, head + tail.out);
5214
5594
  };
5215
5595
  }
5216
5596
 
@@ -5236,6 +5616,8 @@ function createRemendPreprocessor(options) {
5236
5616
  // Annotate the CommonJS export names for ESM import in node:
5237
5617
  0 && (module.exports = {
5238
5618
  DEFAULT_PAYLOAD,
5619
+ ENGINE_PLACEHOLDER_TAGS,
5620
+ ENGINE_PROVENANCE_PROPERTY,
5239
5621
  PIPELINE_STAGES,
5240
5622
  SENTINEL_FN_CONTENT,
5241
5623
  SENTINEL_LINK_URL,
@@ -5280,7 +5662,9 @@ function createRemendPreprocessor(options) {
5280
5662
  preprocessLaTeX,
5281
5663
  rehypeFooterAdorn,
5282
5664
  rehypeRebaseHashLinks,
5665
+ rehypeVerifyEngineTags,
5283
5666
  removeComments,
5667
+ resolveCrossChunkReference,
5284
5668
  sanitizeCrossChunkUrl,
5285
5669
  sanitizeSchema,
5286
5670
  shortenDocumentId,