@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.
@@ -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,
@@ -2227,6 +2231,38 @@ function normalizeForMatch(s) {
2227
2231
  return (0, import_micromark_util_normalize_identifier3.normalizeIdentifier)(s);
2228
2232
  }
2229
2233
 
2234
+ // src/components/blankLineScanner.ts
2235
+ function createBlankLineScanner() {
2236
+ let end = 0;
2237
+ let newline = false;
2238
+ let cr = false;
2239
+ return (source, from = 0) => {
2240
+ if (from === 0) {
2241
+ end = 0;
2242
+ newline = false;
2243
+ cr = false;
2244
+ }
2245
+ for (let i = from; i < source.length; i++) {
2246
+ const c = source[i];
2247
+ if (c === "\n") {
2248
+ if (newline) {
2249
+ end = i + 1;
2250
+ newline = false;
2251
+ } else newline = true;
2252
+ cr = false;
2253
+ } else if (newline) {
2254
+ if ((c === " " || c === " ") && !cr) continue;
2255
+ if (c === "\r" && !cr) cr = true;
2256
+ else {
2257
+ newline = false;
2258
+ cr = false;
2259
+ }
2260
+ }
2261
+ }
2262
+ return end;
2263
+ };
2264
+ }
2265
+
2230
2266
  // src/components/collectDefLabels.ts
2231
2267
  var SCANNER_BOUNDARY_PROFILE = { defListEnabled: false, mathFlow: false, referenceTaint: false };
2232
2268
  function buildProcessor() {
@@ -2258,19 +2294,12 @@ var setsEqual = (a, b) => {
2258
2294
  for (const v of a) if (!b.has(v)) return false;
2259
2295
  return true;
2260
2296
  };
2261
- var BLANK_LINE_RE = /\r?\n[ \t]*\r?\n/g;
2262
- function lastRegionStart(source) {
2263
- BLANK_LINE_RE.lastIndex = 0;
2264
- let start = 0;
2265
- for (let m = BLANK_LINE_RE.exec(source); m !== null; m = BLANK_LINE_RE.exec(source)) {
2266
- start = m.index + m[0].length;
2267
- }
2268
- return start;
2269
- }
2270
2297
  var DEF_LINE_START_RE = /^[ \t>*+\d.)-]*\[(?:[^\]\\]|\\[\s\S])*\]:/m;
2271
2298
  function createDefLabelScanner(parse = collectDefLabels) {
2272
2299
  let prevSource = null;
2273
2300
  let prevLabels = null;
2301
+ const scanBlankLines = createBlankLineScanner();
2302
+ let regionStart = 0;
2274
2303
  let frozenEnd = 0;
2275
2304
  let frozenFootnotes = /* @__PURE__ */ new Set();
2276
2305
  let frozenLinks = /* @__PURE__ */ new Set();
@@ -2283,13 +2312,16 @@ function createDefLabelScanner(parse = collectDefLabels) {
2283
2312
  };
2284
2313
  return {
2285
2314
  scan(source) {
2315
+ if (source === prevSource && prevLabels !== null) return prevLabels;
2316
+ const previousRegionStart = regionStart;
2317
+ const appended = prevSource !== null && source.startsWith(prevSource);
2318
+ regionStart = scanBlankLines(source, appended ? prevSource.length : 0);
2286
2319
  let isAppend = false;
2287
2320
  if (prevSource !== null && prevLabels !== null) {
2288
2321
  if (source === prevSource) return prevLabels;
2289
2322
  if (source.startsWith(prevSource)) {
2290
2323
  isAppend = true;
2291
- const regionStart = lastRegionStart(prevSource);
2292
- const region = prevSource.slice(regionStart) + source.slice(prevSource.length);
2324
+ const region = source.slice(previousRegionStart);
2293
2325
  if (!DEF_LINE_START_RE.test(region)) {
2294
2326
  prevSource = source;
2295
2327
  return prevLabels;
@@ -2522,8 +2554,47 @@ function* extractContributions(mdast, options = {}) {
2522
2554
  for (const c of out) yield c;
2523
2555
  }
2524
2556
 
2557
+ // src/components/registryIndex.ts
2558
+ function buildRegistryIndex(registry) {
2559
+ const index = {
2560
+ footnotes: /* @__PURE__ */ new Map(),
2561
+ links: /* @__PURE__ */ new Map(),
2562
+ numbers: /* @__PURE__ */ new Map(),
2563
+ counts: /* @__PURE__ */ new Map(),
2564
+ occurrences: /* @__PURE__ */ new Map()
2565
+ };
2566
+ for (const sym of registry.chunkOrder) {
2567
+ const data = registry.chunkData.get(sym);
2568
+ if (!data) continue;
2569
+ for (const label of data.defs.keys()) if (!index.footnotes.has(label)) index.footnotes.set(label, sym);
2570
+ for (const label of data.linkDefs.keys()) if (!index.links.has(label)) index.links.set(label, sym);
2571
+ const local = /* @__PURE__ */ new Map();
2572
+ index.occurrences.set(sym, local);
2573
+ for (const ref of data.refs) {
2574
+ if (ref.kind !== "footnote") continue;
2575
+ const label = ref.label;
2576
+ if (!index.numbers.has(label)) index.numbers.set(label, index.numbers.size + 1);
2577
+ const total = (index.counts.get(label) ?? 0) + 1;
2578
+ index.counts.set(label, total);
2579
+ const prior = local.get(label);
2580
+ if (prior) prior.count++;
2581
+ else local.set(label, { start: total, count: 1 });
2582
+ }
2583
+ }
2584
+ return index;
2585
+ }
2586
+
2525
2587
  // src/components/documentRegistry.ts
2526
2588
  function createRegistry(onEmpty) {
2589
+ let index;
2590
+ let indexedVersion = -1;
2591
+ const getIndex = () => {
2592
+ if (!index || indexedVersion !== reg.version) {
2593
+ index = buildRegistryIndex(reg);
2594
+ indexedVersion = reg.version;
2595
+ }
2596
+ return index;
2597
+ };
2527
2598
  const reg = {
2528
2599
  chunkOrder: [],
2529
2600
  chunkData: /* @__PURE__ */ new Map(),
@@ -2675,72 +2746,25 @@ function createRegistry(onEmpty) {
2675
2746
  };
2676
2747
  },
2677
2748
  canonicalFootnoteFor(label) {
2678
- const id = normalizeId(label);
2679
- for (const sym of this.chunkOrder) {
2680
- const data = this.chunkData.get(sym);
2681
- if (data?.defs.has(id)) return sym;
2682
- }
2683
- return null;
2749
+ return getIndex().footnotes.get(normalizeId(label)) ?? null;
2684
2750
  },
2685
2751
  canonicalLinkFor(label) {
2686
- const id = normalizeId(label);
2687
- for (const sym of this.chunkOrder) {
2688
- const data = this.chunkData.get(sym);
2689
- if (data?.linkDefs.has(id)) return sym;
2690
- }
2691
- return null;
2752
+ return getIndex().links.get(normalizeId(label)) ?? null;
2692
2753
  },
2693
2754
  globalNumber(label) {
2694
- const id = normalizeId(label);
2695
- let n = 0;
2696
- const seen = /* @__PURE__ */ new Set();
2697
- for (const sym of this.chunkOrder) {
2698
- const data = this.chunkData.get(sym);
2699
- if (!data) continue;
2700
- for (const ref of data.refs) {
2701
- if (ref.kind !== "footnote") continue;
2702
- if (!seen.has(ref.label)) {
2703
- seen.add(ref.label);
2704
- n++;
2705
- if (ref.label === id) return n;
2706
- }
2707
- }
2708
- }
2709
- return null;
2755
+ return getIndex().numbers.get(normalizeId(label)) ?? null;
2710
2756
  },
2711
2757
  resolveLinkDef(label) {
2712
- const sym = this.canonicalLinkFor(label);
2713
- if (!sym) return null;
2714
- return this.chunkData.get(sym)?.linkDefs.get(normalizeId(label)) ?? null;
2758
+ const id = normalizeId(label);
2759
+ const sym = getIndex().links.get(id);
2760
+ return sym ? this.chunkData.get(sym)?.linkDefs.get(id) ?? null : null;
2715
2761
  },
2716
2762
  getRefsForLabel(label) {
2717
- const id = normalizeId(label);
2718
- let n = 0;
2719
- for (const sym of this.chunkOrder) {
2720
- const data = this.chunkData.get(sym);
2721
- if (!data) continue;
2722
- for (const ref of data.refs) {
2723
- if (ref.kind === "footnote" && ref.label === id) n++;
2724
- }
2725
- }
2726
- return n;
2763
+ return getIndex().counts.get(normalizeId(label)) ?? 0;
2727
2764
  },
2728
2765
  globalOccurrenceForRef(chunkSym, label, localOccurrence) {
2729
- const id = normalizeId(label);
2730
- let global2 = 0;
2731
- for (const sym of this.chunkOrder) {
2732
- const data = this.chunkData.get(sym);
2733
- if (!data) continue;
2734
- let localCount = 0;
2735
- for (const ref of data.refs) {
2736
- if (ref.kind !== "footnote") continue;
2737
- if (ref.label !== id) continue;
2738
- localCount++;
2739
- global2++;
2740
- if (sym === chunkSym && localCount === localOccurrence) return global2;
2741
- }
2742
- }
2743
- return null;
2766
+ const range = getIndex().occurrences.get(chunkSym)?.get(normalizeId(label));
2767
+ return range && Number.isInteger(localOccurrence) && localOccurrence > 0 && localOccurrence <= range.count ? range.start + localOccurrence - 1 : null;
2744
2768
  },
2745
2769
  _notify() {
2746
2770
  this.version++;
@@ -2797,6 +2821,55 @@ function rehypeUnwrapCrossChunkImages() {
2797
2821
 
2798
2822
  // src/components/pluginChain.ts
2799
2823
  var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
2824
+
2825
+ // src/components/rehypeVerifyEngineTags.ts
2826
+ var ENGINE_PLACEHOLDER_TAGS = /* @__PURE__ */ new Set([
2827
+ "footnote-sup",
2828
+ "cross-chunk-link",
2829
+ "cross-chunk-image"
2830
+ ]);
2831
+ var ENGINE_PROVENANCE_PROPERTY = "engineProvenance";
2832
+ function walk(parent, provenance, ancestors) {
2833
+ const children = parent.children;
2834
+ let i = 0;
2835
+ while (i < children.length) {
2836
+ const node = children[i];
2837
+ if (node.type === "element" && ENGINE_PLACEHOLDER_TAGS.has(node.tagName)) {
2838
+ const props = node.properties ?? {};
2839
+ const stamped = props[ENGINE_PROVENANCE_PROPERTY];
2840
+ const genuine = provenance !== "" && typeof stamped === "string" && stamped === provenance;
2841
+ if (genuine) {
2842
+ delete props[ENGINE_PROVENANCE_PROPERTY];
2843
+ if (ancestors && (node.tagName === "cross-chunk-link" || node.tagName === "cross-chunk-image")) {
2844
+ (node.data ??= {}).referenceAncestors = ancestors.slice();
2845
+ }
2846
+ ancestors?.push(
2847
+ node.tagName === "cross-chunk-link" ? "a" : node.tagName === "cross-chunk-image" ? "img" : node.tagName
2848
+ );
2849
+ walk(node, provenance, ancestors);
2850
+ ancestors?.pop();
2851
+ i += 1;
2852
+ } else {
2853
+ children.splice(i, 1, ...node.children);
2854
+ }
2855
+ continue;
2856
+ }
2857
+ if (node.type === "element") {
2858
+ ancestors?.push(node.tagName);
2859
+ walk(node, provenance, ancestors);
2860
+ ancestors?.pop();
2861
+ }
2862
+ i += 1;
2863
+ }
2864
+ }
2865
+ function rehypeVerifyEngineTags(options) {
2866
+ const provenance = options?.provenance ?? "";
2867
+ return function transformer(tree) {
2868
+ walk(tree, provenance, options?.referenceAncestors ? [] : void 0);
2869
+ };
2870
+ }
2871
+
2872
+ // src/components/pluginChain.ts
2800
2873
  var import_remark_breaks = __toESM(require("remark-breaks"), 1);
2801
2874
  var import_remark_cjk_friendly = __toESM(require("remark-cjk-friendly"), 1);
2802
2875
  var import_remark_cjk_friendly_gfm_strikethrough = __toESM(require("remark-cjk-friendly-gfm-strikethrough"), 1);
@@ -2813,16 +2886,18 @@ var import_remark_remove_comments = __toESM(require("remark-remove-comments"), 1
2813
2886
  // src/components/rehypeRebaseHashLinks.ts
2814
2887
  var import_unist_util_visit5 = require("unist-util-visit");
2815
2888
  var DEFAULT_PREFIX = "user-content-";
2889
+ function rebaseHashHref(href, prefix) {
2890
+ const hashPrefix = "#" + prefix;
2891
+ return href.startsWith("#") && !href.startsWith(hashPrefix) ? hashPrefix + href.slice(1) : href;
2892
+ }
2816
2893
  var rehypeRebaseHashLinks = (options) => {
2817
2894
  const prefix = options?.prefix ?? DEFAULT_PREFIX;
2818
- const hashPrefix = "#" + prefix;
2819
2895
  return (tree) => {
2820
2896
  (0, import_unist_util_visit5.visit)(tree, "element", (node) => {
2821
2897
  if (node.tagName !== "a") return;
2822
2898
  const href = node.properties?.href;
2823
2899
  if (typeof href !== "string" || !href.startsWith("#")) return;
2824
- if (href.startsWith(hashPrefix)) return;
2825
- node.properties.href = hashPrefix + href.slice(1);
2900
+ node.properties.href = rebaseHashHref(href, prefix);
2826
2901
  });
2827
2902
  };
2828
2903
  };
@@ -2902,10 +2977,21 @@ function buildCoreRemarkPlugins(enginePlugins) {
2902
2977
  ...DISPLAY_OPTIMIZE_CHAIN.filter(([name]) => selected.has(name)).map(([, plugin]) => plugin)
2903
2978
  ];
2904
2979
  }
2905
- function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix) {
2980
+ function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix, options) {
2906
2981
  return [
2907
2982
  // Allow raw HTML through so rehype-sanitize can handle it.
2908
2983
  [import_rehype_raw.default, { passThrough: [] }],
2984
+ // Unwrap forged engine placeholders BEFORE sanitize admits their tag
2985
+ // names. Only when the caller holds a credential (see the option's doc).
2986
+ ...options ? [
2987
+ [
2988
+ rehypeVerifyEngineTags,
2989
+ {
2990
+ provenance: options.provenance,
2991
+ ...sanitizeSchema2.ancestors?.a || sanitizeSchema2.ancestors?.img ? { referenceAncestors: true } : {}
2992
+ }
2993
+ ]
2994
+ ] : [],
2909
2995
  // Sanitize HTML while allowing <mark> (highlight), KaTeX class names,
2910
2996
  // and any extra protocols the caller permitted via the `sanitizeSchema`
2911
2997
  // prop. Override `clobberPrefix` with the instance-scoped value — the
@@ -2945,6 +3031,10 @@ function buildCoreRemarkRehypeOptions(enableDefinitionList) {
2945
3031
  }
2946
3032
 
2947
3033
  // src/components/customMdastHandlers.ts
3034
+ function provenanceProps(s) {
3035
+ const provenance = s.options.provenance;
3036
+ return typeof provenance === "string" ? { engineProvenance: provenance } : {};
3037
+ }
2948
3038
  function localDefProps(s, id) {
2949
3039
  const def = s.definitionById.get(id);
2950
3040
  if (!def || typeof def.url !== "string" || def.url === SENTINEL_LINK_URL) return {};
@@ -2975,15 +3065,14 @@ function buildCrossChunkHandlers() {
2975
3065
  type: "element",
2976
3066
  tagName: "cross-chunk-link",
2977
3067
  properties: {
2978
- // `label` is the ORIGINAL source text (mdast's `label` field), NOT
2979
- // the normalized `identifier`. The placeholder uses it to construct
2980
- // hrefs that line up with mdast-util-to-hast's default `<li id>`
2981
- // which also preserves source case. Registry lookups normalize
2982
- // internally, so cross-chunk case-insensitive matching still works.
3068
+ // Display labels decode escapes; registry identifiers must retain
3069
+ // those bytes. Never use the display label as the lookup key.
3070
+ identifier: node.identifier,
2983
3071
  label: node.label ?? node.identifier,
2984
3072
  referenceType: node.referenceType,
2985
3073
  documentId: s.options.documentId,
2986
- ...localDefProps(s, id)
3074
+ ...localDefProps(s, id),
3075
+ ...provenanceProps(s)
2987
3076
  },
2988
3077
  children: s.all(node)
2989
3078
  };
@@ -2997,11 +3086,13 @@ function buildCrossChunkHandlers() {
2997
3086
  type: "element",
2998
3087
  tagName: "cross-chunk-image",
2999
3088
  properties: {
3089
+ identifier: node.identifier,
3000
3090
  label: node.label ?? node.identifier,
3001
3091
  referenceType: node.referenceType,
3002
3092
  alt: node.alt ?? "",
3003
3093
  documentId: s.options.documentId,
3004
- ...localDefProps(s, id)
3094
+ ...localDefProps(s, id),
3095
+ ...provenanceProps(s)
3005
3096
  },
3006
3097
  children: []
3007
3098
  };
@@ -3018,7 +3109,8 @@ function buildCrossChunkHandlers() {
3018
3109
  properties: {
3019
3110
  label: node.identifier,
3020
3111
  localOccurrence,
3021
- documentId: s.options.documentId
3112
+ documentId: s.options.documentId,
3113
+ ...provenanceProps(s)
3022
3114
  },
3023
3115
  children: []
3024
3116
  };
@@ -3036,7 +3128,8 @@ function buildCrossChunkHandlers() {
3036
3128
  // first client frame), where the local synthetic footer is what
3037
3129
  // renders, so marks and footer agree (core-render-02).
3038
3130
  localNumber: s.footnoteOrder.indexOf(id) + 1,
3039
- documentId: s.options.documentId
3131
+ documentId: s.options.documentId,
3132
+ ...provenanceProps(s)
3040
3133
  },
3041
3134
  children: []
3042
3135
  };
@@ -4256,8 +4349,8 @@ var sanitizeSchema = deepFreeze(
4256
4349
  attributes: {
4257
4350
  ...import_rehype_sanitize2.defaultSchema.attributes,
4258
4351
  code: mergeClassNameAllowlist(import_rehype_sanitize2.defaultSchema.attributes?.code, ["math-inline", "math-display"]),
4259
- "cross-chunk-link": ["label", "referenceType", "documentId", "localUrl", "localTitle"],
4260
- "cross-chunk-image": ["label", "referenceType", "documentId", "alt", "localUrl", "localTitle"],
4352
+ "cross-chunk-link": ["identifier", "label", "referenceType", "documentId", "localUrl", "localTitle"],
4353
+ "cross-chunk-image": ["identifier", "label", "referenceType", "documentId", "alt", "localUrl", "localTitle"],
4261
4354
  "footnote-sup": ["label", "localOccurrence", "localNumber", "documentId"]
4262
4355
  },
4263
4356
  strip: [.../* @__PURE__ */ new Set([...import_rehype_sanitize2.defaultSchema.strip || [], ...STRIPPED_TAGS])]
@@ -4293,6 +4386,49 @@ function sanitizeCrossChunkUrl(rawUrl, key, tagName, urlTransform, schema) {
4293
4386
  return String(transformed);
4294
4387
  }
4295
4388
 
4389
+ // src/components/resolveCrossChunkReference.ts
4390
+ var import_rehype_sanitize3 = __toESM(require("rehype-sanitize"), 1);
4391
+ var import_micromark_util_sanitize_uri3 = require("micromark-util-sanitize-uri");
4392
+ function resolveCrossChunkReference(input, schema, urlTransform, clobberPrefix) {
4393
+ const key = input.tagName === "a" ? "href" : "src";
4394
+ const element = {
4395
+ type: "element",
4396
+ tagName: input.tagName,
4397
+ properties: {
4398
+ [key]: (0, import_micromark_util_sanitize_uri3.normalizeUri)(input.url),
4399
+ ...input.tagName === "img" ? { alt: input.alt ?? "" } : {},
4400
+ ...input.title !== void 0 ? { title: input.title } : {}
4401
+ },
4402
+ children: input.tagName === "a" ? [{ type: "text", value: "__reference_children__" }] : []
4403
+ };
4404
+ const requiredAncestors = schema.ancestors?.[input.tagName];
4405
+ const recordedAncestors = input.node?.data?.referenceAncestors;
4406
+ let finalSchema = schema;
4407
+ if (requiredAncestors && Array.isArray(recordedAncestors) && requiredAncestors.some((tag) => recordedAncestors.includes(tag))) {
4408
+ const ancestors = { ...schema.ancestors };
4409
+ delete ancestors[input.tagName];
4410
+ finalSchema = { ...schema, ancestors };
4411
+ }
4412
+ const root2 = (0, import_rehype_sanitize3.default)({ ...finalSchema, clobberPrefix })({ type: "root", children: [element] });
4413
+ const node = root2.children[0];
4414
+ if (node?.type !== "element") return { element: null, keepChildren: node?.type === "text" };
4415
+ if (node.tagName === "a" && typeof node.properties.href === "string") {
4416
+ node.properties.href = rebaseHashHref(node.properties.href, clobberPrefix);
4417
+ }
4418
+ node.children = input.node?.children ?? [];
4419
+ if (input.node?.position) node.position = input.node.position;
4420
+ buildTransform({
4421
+ allowedElements: void 0,
4422
+ disallowedElements: void 0,
4423
+ allowElement: void 0,
4424
+ skipHtml: void 0,
4425
+ unwrapDisallowed: void 0,
4426
+ urlTransform
4427
+ })(node, 0, root2);
4428
+ node.children = [];
4429
+ return { element: node, keepChildren: false };
4430
+ }
4431
+
4296
4432
  // src/plugins/defs.ts
4297
4433
  function getEnginePluginInternals(plugin) {
4298
4434
  const candidate = plugin;
@@ -4509,6 +4645,8 @@ var createSmoothStreamController = (options = {}) => {
4509
4645
  let source = "";
4510
4646
  let visibleEnd = 0;
4511
4647
  let pending = [];
4648
+ let pendingHead = 0;
4649
+ const pendingCount = () => pending.length - pendingHead;
4512
4650
  let tentativeEnd = 0;
4513
4651
  let finished = false;
4514
4652
  let seam;
@@ -4553,7 +4691,7 @@ var createSmoothStreamController = (options = {}) => {
4553
4691
  cancelFrame = void 0;
4554
4692
  };
4555
4693
  const ensureScheduled = () => {
4556
- if (disposed || cancelFrame || pending.length === 0) return;
4694
+ if (disposed || cancelFrame || pendingCount() === 0) return;
4557
4695
  lastTickAt = now();
4558
4696
  credit = 0;
4559
4697
  cancelFrame = schedule(tick);
@@ -4567,31 +4705,38 @@ var createSmoothStreamController = (options = {}) => {
4567
4705
  const params = resolveParams();
4568
4706
  let rate;
4569
4707
  if (finished && drainDeadlineAt !== void 0) {
4570
- rate = Math.max(params.minCharsPerSecond, pending.length * 1e3 / Math.max(1, drainDeadlineAt - t));
4708
+ rate = Math.max(params.minCharsPerSecond, pendingCount() * 1e3 / Math.max(1, drainDeadlineAt - t));
4571
4709
  } else if (gapWindow.length > 0 && lastArrivalAt !== void 0) {
4572
4710
  const gaps = gapWindow.map((s) => s.gap).sort((a, b) => a - b);
4573
4711
  const intervalQ = gaps[Math.min(gaps.length - 1, Math.floor(gaps.length * INTERVAL_QUANTILE))];
4574
4712
  const horizon = Math.max(16, Math.min(params.bufferFactor * intervalQ + HORIZON_PAD_MS, params.maxLagMs));
4575
4713
  const deadline = Math.max(lastArrivalAt + horizon, t + DEADLINE_FLOOR_MS);
4576
- rate = Math.max(params.minCharsPerSecond, pending.length * 1e3 / Math.max(1, deadline - t));
4714
+ rate = Math.max(params.minCharsPerSecond, pendingCount() * 1e3 / Math.max(1, deadline - t));
4577
4715
  } else {
4578
- rate = Math.max(params.minCharsPerSecond, pending.length * 1e3 / Math.max(1, params.correctionTauMs));
4716
+ rate = Math.max(params.minCharsPerSecond, pendingCount() * 1e3 / Math.max(1, params.correctionTauMs));
4579
4717
  }
4580
4718
  credit += rate * dt / 1e3;
4581
- const reveal = Math.min(Math.floor(credit), pending.length);
4719
+ const reveal = Math.min(Math.floor(credit), pendingCount());
4582
4720
  if (reveal > 0) {
4583
- visibleEnd = pending[reveal - 1];
4584
- pending = pending.slice(reveal);
4585
- credit = pending.length > 0 ? credit - reveal : 0;
4721
+ visibleEnd = pending[pendingHead + reveal - 1];
4722
+ pendingHead += reveal;
4723
+ if (pendingHead === pending.length) {
4724
+ pending = [];
4725
+ pendingHead = 0;
4726
+ } else if (pendingHead >= 1024 && pendingHead * 2 >= pending.length) {
4727
+ pending = pending.slice(pendingHead);
4728
+ pendingHead = 0;
4729
+ }
4730
+ credit = pendingCount() > 0 ? credit - reveal : 0;
4586
4731
  notify();
4587
4732
  }
4588
- if (!disposed && !cancelFrame && pending.length > 0) cancelFrame = schedule(tick);
4733
+ if (!disposed && !cancelFrame && pendingCount() > 0) cancelFrame = schedule(tick);
4589
4734
  };
4590
4735
  const resegmentTail = () => {
4591
- if (seam !== void 0 && seam < source.length && pending[pending.length - 1] === seam) {
4736
+ if (pendingCount() > 0 && seam !== void 0 && seam < source.length && pending[pending.length - 1] === seam) {
4592
4737
  pending.pop();
4593
4738
  }
4594
- const from = pending.length > 0 ? pending[pending.length - 1] : visibleEnd;
4739
+ const from = pendingCount() > 0 ? pending[pending.length - 1] : visibleEnd;
4595
4740
  let anchor = from;
4596
4741
  if (seam !== void 0 && from <= seam) {
4597
4742
  anchor = Math.max(0, from - RESUME_LOOKBACK);
@@ -4625,6 +4770,7 @@ var createSmoothStreamController = (options = {}) => {
4625
4770
  visibleEnd = next.length;
4626
4771
  tentativeEnd = next.length;
4627
4772
  pending = [];
4773
+ pendingHead = 0;
4628
4774
  seam = next.length;
4629
4775
  credit = 0;
4630
4776
  cancelScheduled();
@@ -4656,7 +4802,7 @@ var createSmoothStreamController = (options = {}) => {
4656
4802
  if (finished) return;
4657
4803
  finished = true;
4658
4804
  lastArrivalAt = void 0;
4659
- if (tentativeEnd > (pending.length > 0 ? pending[pending.length - 1] : visibleEnd)) {
4805
+ if (tentativeEnd > (pendingCount() > 0 ? pending[pending.length - 1] : visibleEnd)) {
4660
4806
  pending.push(tentativeEnd);
4661
4807
  }
4662
4808
  const params = resolveParams();
@@ -4682,10 +4828,11 @@ var createSmoothStreamController = (options = {}) => {
4682
4828
  snap,
4683
4829
  flush() {
4684
4830
  disposed = false;
4685
- const target = finished ? source.length : pending.length > 0 ? pending[pending.length - 1] : visibleEnd;
4831
+ const target = finished ? source.length : pendingCount() > 0 ? pending[pending.length - 1] : visibleEnd;
4686
4832
  if (target <= visibleEnd) return;
4687
4833
  visibleEnd = target;
4688
4834
  pending = [];
4835
+ pendingHead = 0;
4689
4836
  credit = 0;
4690
4837
  cancelScheduled();
4691
4838
  notify();
@@ -4708,6 +4855,12 @@ var createSmoothStreamController = (options = {}) => {
4708
4855
  };
4709
4856
 
4710
4857
  // src/preprocessors/latex.ts
4858
+ function isHardBoundary(kind) {
4859
+ return kind === "code" || kind === "literal" || kind === "multilineTag";
4860
+ }
4861
+ function hasLineEnding(text) {
4862
+ return text.includes("\n") || text.includes("\r");
4863
+ }
4711
4864
  function getRepeatedMarkerLength(content, start, marker) {
4712
4865
  let end = start;
4713
4866
  while (end < content.length && content[end] === marker) {
@@ -4775,11 +4928,11 @@ function splitByProtectedRegions(content) {
4775
4928
  let multilineFenceMarker = null;
4776
4929
  let multilineFenceLength = 0;
4777
4930
  let multilineFenceIndent = 0;
4778
- function pushProtected(start, end) {
4931
+ function pushProtected(start, end, kind) {
4779
4932
  if (start > lastIndex) {
4780
- segments.push({ text: content.substring(lastIndex, start), isCode: false });
4933
+ segments.push({ kind: "text", text: content.substring(lastIndex, start) });
4781
4934
  }
4782
- segments.push({ text: content.substring(start, end), isCode: true });
4935
+ segments.push({ kind, text: content.substring(start, end) });
4783
4936
  lastIndex = end;
4784
4937
  }
4785
4938
  let i = 0;
@@ -4790,7 +4943,7 @@ function splitByProtectedRegions(content) {
4790
4943
  const runLen = getRepeatedMarkerLength(content, i, multilineFenceMarker);
4791
4944
  const closerIndent = lineIndentBefore(content, i);
4792
4945
  if (runLen >= multilineFenceLength && closerIndent !== -1 && closerIndent <= multilineFenceIndent + 3 && restOfLineIsBlank(content, i + runLen)) {
4793
- pushProtected(multilineStart, i + runLen);
4946
+ pushProtected(multilineStart, i + runLen, "code");
4794
4947
  multilineStart = -1;
4795
4948
  multilineFenceMarker = null;
4796
4949
  multilineFenceLength = 0;
@@ -4817,7 +4970,7 @@ function splitByProtectedRegions(content) {
4817
4970
  if (char === "`") {
4818
4971
  const closeIdx = findClosingBacktickRun(content, i + runLen, runLen);
4819
4972
  if (closeIdx !== -1) {
4820
- pushProtected(i, closeIdx + runLen);
4973
+ pushProtected(i, closeIdx + runLen, "code");
4821
4974
  i = closeIdx + runLen;
4822
4975
  continue;
4823
4976
  }
@@ -4842,7 +4995,11 @@ function splitByProtectedRegions(content) {
4842
4995
  endIndex = content.length;
4843
4996
  }
4844
4997
  }
4845
- pushProtected(i, endIndex);
4998
+ pushProtected(
4999
+ i,
5000
+ endIndex,
5001
+ isOpeningPairedTag ? "literal" : hasLineEnding(content.substring(i, endIndex)) ? "multilineTag" : "tag"
5002
+ );
4846
5003
  i = endIndex;
4847
5004
  continue;
4848
5005
  }
@@ -4850,10 +5007,10 @@ function splitByProtectedRegions(content) {
4850
5007
  i += 1;
4851
5008
  }
4852
5009
  if (multilineStart !== -1) {
4853
- pushProtected(multilineStart, content.length);
5010
+ pushProtected(multilineStart, content.length, "code");
4854
5011
  }
4855
5012
  if (lastIndex < content.length) {
4856
- segments.push({ text: content.substring(lastIndex), isCode: false });
5013
+ segments.push({ kind: "text", text: content.substring(lastIndex) });
4857
5014
  }
4858
5015
  return segments;
4859
5016
  }
@@ -5007,20 +5164,22 @@ function escapeLatexPipesInUnclosed(text) {
5007
5164
  const tail = text.substring(unclosedStart + delimLen);
5008
5165
  return before + delim + replaceUnescapedPipes(tail);
5009
5166
  }
5010
- function opensMathFlow(text, pos) {
5167
+ function opensMathFlow(text, pos, runStartsAtLineStart) {
5011
5168
  let i = pos;
5012
5169
  let spaces = 0;
5013
- while (i > 0 && text[i - 1] !== "\n") {
5170
+ while (i > 0) {
5171
+ const prev = text[i - 1];
5172
+ if (prev === "\n" || prev === "\r") return true;
5014
5173
  i -= 1;
5015
5174
  if (text[i] !== " ") return false;
5016
5175
  spaces += 1;
5017
5176
  if (spaces > 3) return false;
5018
5177
  }
5019
- return true;
5178
+ return runStartsAtLineStart;
5020
5179
  }
5021
- function truncateUnclosedLatexBlock(text, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
5180
+ function truncateUnclosedLatexBlock(text, runStartsAtLineStart, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
5022
5181
  if (unclosedStart === -1) return text;
5023
- if (!opensMathFlow(text, unclosedStart)) return text;
5182
+ if (!opensMathFlow(text, unclosedStart, runStartsAtLineStart)) return text;
5024
5183
  return text.substring(0, unclosedStart).trimEnd();
5025
5184
  }
5026
5185
  function escapeTextUnderscores(text) {
@@ -5065,7 +5224,8 @@ function convertSingleToDoubleDollar(text) {
5065
5224
  }
5066
5225
  function preprocessLaTeX(str) {
5067
5226
  if (!hasLatexTrigger(str)) return str;
5068
- return processSlice(str, false).out;
5227
+ const mask = selectMask(str);
5228
+ return (mask === null ? processSlice(str, { legacy: true, probe: false }) : processSlice(str, { probe: false, mask })).out;
5069
5229
  }
5070
5230
  function hasLatexTrigger(str) {
5071
5231
  return str.includes("$") || str.includes("\\[") || str.includes("\\(");
@@ -5094,42 +5254,240 @@ function hasUnclosedTextCommand(text) {
5094
5254
  }
5095
5255
  var RESIDUAL_OPEN_BRACKET_RE = /(?<!!)\\\[/;
5096
5256
  var LEADING_DOUBLE_DOLLAR_RE = /^\s*\$\$/;
5097
- function processSlice(slice, probe = true) {
5257
+ var PUA_START = 57344;
5258
+ var PUA_END = 63743;
5259
+ var PUA_SIZE = PUA_END - PUA_START + 1;
5260
+ function selectMask(source) {
5261
+ const first = String.fromCharCode(PUA_START);
5262
+ if (source.indexOf(first) === -1) return first;
5263
+ const seen = new Uint8Array(PUA_SIZE);
5264
+ for (let i = 0; i < source.length; i++) {
5265
+ const c = source.charCodeAt(i);
5266
+ if (c >= PUA_START && c <= PUA_END) seen[c - PUA_START] = 1;
5267
+ }
5268
+ for (let k = 0; k < PUA_SIZE; k++) if (seen[k] === 0) return String.fromCharCode(PUA_START + k);
5269
+ return null;
5270
+ }
5271
+ var PuaPresence = class {
5272
+ bits = new Uint8Array(PUA_SIZE);
5273
+ distinct = 0;
5274
+ reset() {
5275
+ this.bits.fill(0);
5276
+ this.distinct = 0;
5277
+ }
5278
+ add(text, from) {
5279
+ for (let i = from; i < text.length; i++) {
5280
+ const c = text.charCodeAt(i);
5281
+ if (c >= PUA_START && c <= PUA_END) {
5282
+ const k = c - PUA_START;
5283
+ if (this.bits[k] === 0) {
5284
+ this.bits[k] = 1;
5285
+ this.distinct += 1;
5286
+ }
5287
+ }
5288
+ }
5289
+ }
5290
+ select() {
5291
+ if (this.distinct === 0) return String.fromCharCode(PUA_START);
5292
+ if (this.distinct >= PUA_SIZE) return null;
5293
+ for (let k = 0; k < PUA_SIZE; k++) if (this.bits[k] === 0) return String.fromCharCode(PUA_START + k);
5294
+ return null;
5295
+ }
5296
+ };
5297
+ function transformRun(input, probe, runStartsAtLineStart, seamEligible) {
5298
+ let text = input;
5299
+ let tailSensitive = false;
5300
+ let truncatedAtSeamStart = false;
5301
+ text = escapeMhchemCommands(text);
5302
+ text = escapeCurrencyDollarSigns(text);
5303
+ text = convertLatexDelimiters(text);
5304
+ if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) tailSensitive = true;
5305
+ text = escapeLatexPipes(text);
5306
+ if (probe && findUnclosedDelimiterStart(text, "both") !== -1) tailSensitive = true;
5307
+ text = escapeLatexPipesInUnclosed(text);
5308
+ if (probe && hasUnclosedTextCommand(text)) tailSensitive = true;
5309
+ text = escapeTextUnderscores(text);
5310
+ text = convertSingleToDoubleDollar(text);
5311
+ let unclosedDouble;
5312
+ if (probe || seamEligible && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
5313
+ unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
5314
+ if (unclosedDouble !== -1) {
5315
+ tailSensitive = true;
5316
+ if (seamEligible && opensMathFlow(text, unclosedDouble, runStartsAtLineStart) && text.slice(0, unclosedDouble).trim() === "") {
5317
+ truncatedAtSeamStart = true;
5318
+ }
5319
+ }
5320
+ }
5321
+ text = truncateUnclosedLatexBlock(text, runStartsAtLineStart, unclosedDouble);
5322
+ return { out: text, tailSensitive, truncatedAtSeamStart };
5323
+ }
5324
+ function processSliceLegacy(slice, probe) {
5098
5325
  const segments = splitByProtectedRegions(slice);
5099
5326
  const parts = [];
5100
5327
  let quiescent = true;
5101
5328
  let truncatedAtSeamStart = false;
5102
5329
  for (let index = 0; index < segments.length; index++) {
5103
5330
  const segment = segments[index];
5104
- if (segment.isCode) {
5331
+ if (segment.kind !== "text") {
5105
5332
  parts.push(segment.text);
5106
5333
  continue;
5107
5334
  }
5108
- let text = segment.text;
5109
- text = escapeMhchemCommands(text);
5110
- text = escapeCurrencyDollarSigns(text);
5111
- text = convertLatexDelimiters(text);
5112
- if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) quiescent = false;
5113
- text = escapeLatexPipes(text);
5114
- if (probe && findUnclosedDelimiterStart(text, "both") !== -1) quiescent = false;
5115
- text = escapeLatexPipesInUnclosed(text);
5116
- if (probe && hasUnclosedTextCommand(text)) quiescent = false;
5117
- text = escapeTextUnderscores(text);
5118
- text = convertSingleToDoubleDollar(text);
5119
- let unclosedDouble;
5120
- if (probe || index === 0 && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
5121
- unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
5122
- if (unclosedDouble !== -1) {
5123
- quiescent = false;
5124
- if (index === 0 && opensMathFlow(text, unclosedDouble) && text.slice(0, unclosedDouble).trim() === "") {
5125
- truncatedAtSeamStart = true;
5335
+ const r = transformRun(segment.text, probe, true, index === 0);
5336
+ if (r.tailSensitive) quiescent = false;
5337
+ if (r.truncatedAtSeamStart) truncatedAtSeamStart = true;
5338
+ parts.push(r.out);
5339
+ }
5340
+ return { out: parts.join(""), quiescent, truncatedAtSeamStart, degradedReason: null };
5341
+ }
5342
+ var SCOPE_DEPTH_CAP = 8;
5343
+ var VOID_TAGS2 = /* @__PURE__ */ new Set(["br", "hr", "img", "wbr", "input", "source"]);
5344
+ var TAG_NAME_RE = /^<(\/?)([A-Za-z][A-Za-z0-9]*)/;
5345
+ function tagInfo(tag) {
5346
+ const m = TAG_NAME_RE.exec(tag);
5347
+ const closing = m?.[1] === "/";
5348
+ const name = (m?.[2] ?? "").toLowerCase();
5349
+ const opensScope = !closing && !tag.endsWith("/>") && !VOID_TAGS2.has(name);
5350
+ return { name, closing, opensScope };
5351
+ }
5352
+ function buildRunTree(segments) {
5353
+ const root2 = [];
5354
+ const stack = [];
5355
+ const current = () => stack.length > 0 ? stack[stack.length - 1].children : root2;
5356
+ const unwind = () => {
5357
+ while (stack.length > 0) {
5358
+ const frame = stack.pop();
5359
+ current().push({ type: "atom", text: frame.open }, ...frame.children);
5360
+ }
5361
+ };
5362
+ for (const segment of segments) {
5363
+ if (segment.kind === "tag") {
5364
+ const info = tagInfo(segment.text);
5365
+ if (info.closing) {
5366
+ const top = stack[stack.length - 1];
5367
+ if (top !== void 0 && top.name === info.name) {
5368
+ stack.pop();
5369
+ const parent = current();
5370
+ if (top.suppressed) {
5371
+ parent.push({ type: "atom", text: top.open }, ...top.children, { type: "atom", text: segment.text });
5372
+ } else {
5373
+ parent.push({ type: "scope", open: top.open, close: segment.text, children: top.children });
5374
+ }
5375
+ } else {
5376
+ current().push({ type: "atom", text: segment.text });
5126
5377
  }
5378
+ } else if (info.opensScope) {
5379
+ stack.push({ name: info.name, open: segment.text, suppressed: stack.length >= SCOPE_DEPTH_CAP, children: [] });
5380
+ } else {
5381
+ current().push({ type: "atom", text: segment.text });
5127
5382
  }
5383
+ continue;
5384
+ }
5385
+ const t = segment.text;
5386
+ let start = 0;
5387
+ for (let i = 0; i < t.length; i++) {
5388
+ const c = t.charCodeAt(i);
5389
+ if (c !== 10 && c !== 13) continue;
5390
+ if (i > start) current().push({ type: "text", text: t.slice(start, i) });
5391
+ const end = c === 13 && t.charCodeAt(i + 1) === 10 ? i + 2 : i + 1;
5392
+ unwind();
5393
+ root2.push({ type: "text", text: t.slice(i, end) });
5394
+ start = end;
5395
+ i = end - 1;
5396
+ }
5397
+ if (start < t.length) current().push({ type: "text", text: t.slice(start) });
5398
+ }
5399
+ unwind();
5400
+ return root2;
5401
+ }
5402
+ var restoreFailureInjector = null;
5403
+ function restore(out, atoms, mask) {
5404
+ if (restoreFailureInjector !== null && restoreFailureInjector(atoms)) return null;
5405
+ let result = "";
5406
+ let k = 0;
5407
+ let last = 0;
5408
+ for (; ; ) {
5409
+ const idx = out.indexOf(mask, last);
5410
+ if (idx === -1) break;
5411
+ if (k >= atoms.length) return null;
5412
+ result += out.slice(last, idx) + atoms[k];
5413
+ k += 1;
5414
+ last = idx + 1;
5415
+ }
5416
+ return result + out.slice(last);
5417
+ }
5418
+ function emitRun(nodes, mask) {
5419
+ let text = "";
5420
+ const atoms = [];
5421
+ for (const node of nodes) {
5422
+ if (node.type === "text") {
5423
+ text += node.text;
5424
+ } else if (node.type === "atom") {
5425
+ atoms.push(node.text);
5426
+ text += mask;
5427
+ } else {
5428
+ const inner = emitRun(node.children, mask);
5429
+ if (inner === null) return null;
5430
+ const transformed = transformRun(inner.text, false, false, false);
5431
+ const restored = restore(transformed.out, inner.atoms, mask);
5432
+ if (restored === null) return null;
5433
+ atoms.push(node.open + restored + node.close);
5434
+ text += mask;
5128
5435
  }
5129
- text = truncateUnclosedLatexBlock(text, unclosedDouble);
5130
- parts.push(text);
5131
5436
  }
5132
- return { out: parts.join(""), quiescent, truncatedAtSeamStart };
5437
+ return { text, atoms };
5438
+ }
5439
+ function reportRestoreViolation() {
5440
+ if (true) {
5441
+ console.error(
5442
+ "[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."
5443
+ );
5444
+ }
5445
+ }
5446
+ function processSliceDefault(slice, probe, mask) {
5447
+ const segments = splitByProtectedRegions(slice);
5448
+ const parts = [];
5449
+ let quiescent = true;
5450
+ let truncatedAtSeamStart = false;
5451
+ let offset = 0;
5452
+ let i = 0;
5453
+ while (i < segments.length) {
5454
+ const segment = segments[i];
5455
+ if (isHardBoundary(segment.kind)) {
5456
+ parts.push(segment.text);
5457
+ offset += segment.text.length;
5458
+ i += 1;
5459
+ continue;
5460
+ }
5461
+ const runStart = offset;
5462
+ const runSegments = [];
5463
+ while (i < segments.length && !isHardBoundary(segments[i].kind)) {
5464
+ runSegments.push(segments[i]);
5465
+ offset += segments[i].text.length;
5466
+ i += 1;
5467
+ }
5468
+ const before = runStart === 0 ? -1 : slice.charCodeAt(runStart - 1);
5469
+ const runStartsAtLineStart = before === -1 || before === 10 || before === 13;
5470
+ const seamEligible = runStart === 0;
5471
+ const emitted = emitRun(buildRunTree(runSegments), mask);
5472
+ if (emitted === null) {
5473
+ reportRestoreViolation();
5474
+ return { ...processSliceLegacy(slice, probe), degradedReason: "restore-invariant" };
5475
+ }
5476
+ const r = transformRun(emitted.text, probe, runStartsAtLineStart, seamEligible);
5477
+ const restored = restore(r.out, emitted.atoms, mask);
5478
+ if (restored === null) {
5479
+ reportRestoreViolation();
5480
+ return { ...processSliceLegacy(slice, probe), degradedReason: "restore-invariant" };
5481
+ }
5482
+ if (r.tailSensitive) quiescent = false;
5483
+ if (r.truncatedAtSeamStart) truncatedAtSeamStart = true;
5484
+ parts.push(restored);
5485
+ }
5486
+ return { out: parts.join(""), quiescent, truncatedAtSeamStart, degradedReason: null };
5487
+ }
5488
+ function processSlice(slice, options) {
5489
+ if (options.legacy === true) return processSliceLegacy(slice, options.probe);
5490
+ return processSliceDefault(slice, options.probe, options.mask);
5133
5491
  }
5134
5492
  function isBlankRawLine(text, from, to) {
5135
5493
  for (let i = from; i < to; i++) {
@@ -5145,7 +5503,7 @@ function findRawSafeCut(active) {
5145
5503
  let backtickHazard = false;
5146
5504
  let latentLt = false;
5147
5505
  for (const segment of segments) {
5148
- if (segment.isCode) {
5506
+ if (segment.kind !== "text") {
5149
5507
  if (segment.text.includes(">")) latentLt = false;
5150
5508
  offset += segment.text.length;
5151
5509
  continue;
@@ -5180,12 +5538,28 @@ function createIncrementalLatexPreprocessor(options) {
5180
5538
  const freezeThreshold = options?.freezeThreshold ?? DEFAULT_FREEZE_ATTEMPT_THRESHOLD;
5181
5539
  const onAttempt = options?.onAttempt;
5182
5540
  const backoff = options?.backoff ?? true;
5541
+ const onDegrade = options?.onDegrade;
5183
5542
  let prevSource = "";
5184
5543
  let prevOutput = "";
5185
5544
  let frozenSrcEnd = 0;
5186
5545
  let frozenOut = "";
5187
5546
  let triggered = false;
5188
5547
  let nextAttemptLen = 0;
5548
+ let lineageDegraded = false;
5549
+ const presence = new PuaPresence();
5550
+ const commit = (source, out) => {
5551
+ prevSource = source;
5552
+ prevOutput = out;
5553
+ return out;
5554
+ };
5555
+ const legacyWhole = (source) => processSlice(source, { legacy: true, probe: false }).out;
5556
+ const degrade = (source, reason) => {
5557
+ lineageDegraded = true;
5558
+ frozenSrcEnd = 0;
5559
+ frozenOut = "";
5560
+ onDegrade?.(reason);
5561
+ return commit(source, legacyWhole(source));
5562
+ };
5189
5563
  return function incrementalPreprocessLaTeX(source) {
5190
5564
  if (source === prevSource) return prevOutput;
5191
5565
  const isAppend = source.length > prevSource.length && source.startsWith(prevSource);
@@ -5194,16 +5568,20 @@ function createIncrementalLatexPreprocessor(options) {
5194
5568
  frozenOut = "";
5195
5569
  triggered = false;
5196
5570
  nextAttemptLen = 0;
5571
+ lineageDegraded = false;
5572
+ presence.reset();
5573
+ presence.add(source, 0);
5574
+ } else {
5575
+ presence.add(source, prevSource.length);
5197
5576
  }
5198
5577
  if (!triggered) {
5199
5578
  const checkFrom = isAppend ? Math.max(0, prevSource.length - 1) : 0;
5200
- if (!hasLatexTrigger(source.slice(checkFrom))) {
5201
- prevSource = source;
5202
- prevOutput = source;
5203
- return source;
5204
- }
5579
+ if (!hasLatexTrigger(source.slice(checkFrom))) return commit(source, source);
5205
5580
  triggered = true;
5206
5581
  }
5582
+ if (lineageDegraded) return commit(source, legacyWhole(source));
5583
+ const mask = presence.select();
5584
+ if (mask === null) return degrade(source, "mask-exhausted");
5207
5585
  let active = source.slice(frozenSrcEnd);
5208
5586
  if (active.length > freezeThreshold && active.length >= nextAttemptLen) {
5209
5587
  const activeLength = active.length;
@@ -5218,18 +5596,20 @@ function createIncrementalLatexPreprocessor(options) {
5218
5596
  };
5219
5597
  const cut = findRawSafeCut(active);
5220
5598
  if (cut > 0) {
5221
- const candidate = processSlice(active.slice(0, cut));
5599
+ const candidate = processSlice(active.slice(0, cut), { probe: true, mask });
5600
+ if (candidate.degradedReason !== null) {
5601
+ onAttempt?.({ activeLength, frozenBytes: 0 });
5602
+ return degrade(source, candidate.degradedReason);
5603
+ }
5222
5604
  if (candidate.quiescent) freeze(cut, candidate);
5223
5605
  }
5224
5606
  nextAttemptLen = advanced || !backoff ? 0 : active.length * 2;
5225
5607
  onAttempt?.({ activeLength, frozenBytes });
5226
5608
  }
5227
- const tail = processSlice(active, false);
5609
+ const tail = processSlice(active, { probe: false, mask });
5610
+ if (tail.degradedReason !== null) return degrade(source, tail.degradedReason);
5228
5611
  const head = tail.truncatedAtSeamStart ? frozenOut.replace(/\s+$/, "") : frozenOut;
5229
- const out = head + tail.out;
5230
- prevSource = source;
5231
- prevOutput = out;
5232
- return out;
5612
+ return commit(source, head + tail.out);
5233
5613
  };
5234
5614
  }
5235
5615
 
@@ -5255,6 +5635,8 @@ function createRemendPreprocessor(options) {
5255
5635
  // Annotate the CommonJS export names for ESM import in node:
5256
5636
  0 && (module.exports = {
5257
5637
  DEFAULT_PAYLOAD,
5638
+ ENGINE_PLACEHOLDER_TAGS,
5639
+ ENGINE_PROVENANCE_PROPERTY,
5258
5640
  PIPELINE_STAGES,
5259
5641
  SENTINEL_FN_CONTENT,
5260
5642
  SENTINEL_LINK_URL,
@@ -5299,7 +5681,9 @@ function createRemendPreprocessor(options) {
5299
5681
  preprocessLaTeX,
5300
5682
  rehypeFooterAdorn,
5301
5683
  rehypeRebaseHashLinks,
5684
+ rehypeVerifyEngineTags,
5302
5685
  removeComments,
5686
+ resolveCrossChunkReference,
5303
5687
  sanitizeCrossChunkUrl,
5304
5688
  sanitizeSchema,
5305
5689
  shortenDocumentId,