@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.js CHANGED
@@ -2131,6 +2131,38 @@ function normalizeForMatch(s) {
2131
2131
  return normalizeIdentifier3(s);
2132
2132
  }
2133
2133
 
2134
+ // src/components/blankLineScanner.ts
2135
+ function createBlankLineScanner() {
2136
+ let end = 0;
2137
+ let newline = false;
2138
+ let cr = false;
2139
+ return (source, from = 0) => {
2140
+ if (from === 0) {
2141
+ end = 0;
2142
+ newline = false;
2143
+ cr = false;
2144
+ }
2145
+ for (let i = from; i < source.length; i++) {
2146
+ const c = source[i];
2147
+ if (c === "\n") {
2148
+ if (newline) {
2149
+ end = i + 1;
2150
+ newline = false;
2151
+ } else newline = true;
2152
+ cr = false;
2153
+ } else if (newline) {
2154
+ if ((c === " " || c === " ") && !cr) continue;
2155
+ if (c === "\r" && !cr) cr = true;
2156
+ else {
2157
+ newline = false;
2158
+ cr = false;
2159
+ }
2160
+ }
2161
+ }
2162
+ return end;
2163
+ };
2164
+ }
2165
+
2134
2166
  // src/components/collectDefLabels.ts
2135
2167
  var SCANNER_BOUNDARY_PROFILE = { defListEnabled: false, mathFlow: false, referenceTaint: false };
2136
2168
  function buildProcessor() {
@@ -2162,19 +2194,12 @@ var setsEqual = (a, b) => {
2162
2194
  for (const v of a) if (!b.has(v)) return false;
2163
2195
  return true;
2164
2196
  };
2165
- var BLANK_LINE_RE = /\r?\n[ \t]*\r?\n/g;
2166
- function lastRegionStart(source) {
2167
- BLANK_LINE_RE.lastIndex = 0;
2168
- let start = 0;
2169
- for (let m = BLANK_LINE_RE.exec(source); m !== null; m = BLANK_LINE_RE.exec(source)) {
2170
- start = m.index + m[0].length;
2171
- }
2172
- return start;
2173
- }
2174
2197
  var DEF_LINE_START_RE = /^[ \t>*+\d.)-]*\[(?:[^\]\\]|\\[\s\S])*\]:/m;
2175
2198
  function createDefLabelScanner(parse = collectDefLabels) {
2176
2199
  let prevSource = null;
2177
2200
  let prevLabels = null;
2201
+ const scanBlankLines = createBlankLineScanner();
2202
+ let regionStart = 0;
2178
2203
  let frozenEnd = 0;
2179
2204
  let frozenFootnotes = /* @__PURE__ */ new Set();
2180
2205
  let frozenLinks = /* @__PURE__ */ new Set();
@@ -2187,13 +2212,16 @@ function createDefLabelScanner(parse = collectDefLabels) {
2187
2212
  };
2188
2213
  return {
2189
2214
  scan(source) {
2215
+ if (source === prevSource && prevLabels !== null) return prevLabels;
2216
+ const previousRegionStart = regionStart;
2217
+ const appended = prevSource !== null && source.startsWith(prevSource);
2218
+ regionStart = scanBlankLines(source, appended ? prevSource.length : 0);
2190
2219
  let isAppend = false;
2191
2220
  if (prevSource !== null && prevLabels !== null) {
2192
2221
  if (source === prevSource) return prevLabels;
2193
2222
  if (source.startsWith(prevSource)) {
2194
2223
  isAppend = true;
2195
- const regionStart = lastRegionStart(prevSource);
2196
- const region = prevSource.slice(regionStart) + source.slice(prevSource.length);
2224
+ const region = source.slice(previousRegionStart);
2197
2225
  if (!DEF_LINE_START_RE.test(region)) {
2198
2226
  prevSource = source;
2199
2227
  return prevLabels;
@@ -2426,8 +2454,47 @@ function* extractContributions(mdast, options = {}) {
2426
2454
  for (const c of out) yield c;
2427
2455
  }
2428
2456
 
2457
+ // src/components/registryIndex.ts
2458
+ function buildRegistryIndex(registry) {
2459
+ const index = {
2460
+ footnotes: /* @__PURE__ */ new Map(),
2461
+ links: /* @__PURE__ */ new Map(),
2462
+ numbers: /* @__PURE__ */ new Map(),
2463
+ counts: /* @__PURE__ */ new Map(),
2464
+ occurrences: /* @__PURE__ */ new Map()
2465
+ };
2466
+ for (const sym of registry.chunkOrder) {
2467
+ const data = registry.chunkData.get(sym);
2468
+ if (!data) continue;
2469
+ for (const label of data.defs.keys()) if (!index.footnotes.has(label)) index.footnotes.set(label, sym);
2470
+ for (const label of data.linkDefs.keys()) if (!index.links.has(label)) index.links.set(label, sym);
2471
+ const local = /* @__PURE__ */ new Map();
2472
+ index.occurrences.set(sym, local);
2473
+ for (const ref of data.refs) {
2474
+ if (ref.kind !== "footnote") continue;
2475
+ const label = ref.label;
2476
+ if (!index.numbers.has(label)) index.numbers.set(label, index.numbers.size + 1);
2477
+ const total = (index.counts.get(label) ?? 0) + 1;
2478
+ index.counts.set(label, total);
2479
+ const prior = local.get(label);
2480
+ if (prior) prior.count++;
2481
+ else local.set(label, { start: total, count: 1 });
2482
+ }
2483
+ }
2484
+ return index;
2485
+ }
2486
+
2429
2487
  // src/components/documentRegistry.ts
2430
2488
  function createRegistry(onEmpty) {
2489
+ let index;
2490
+ let indexedVersion = -1;
2491
+ const getIndex = () => {
2492
+ if (!index || indexedVersion !== reg.version) {
2493
+ index = buildRegistryIndex(reg);
2494
+ indexedVersion = reg.version;
2495
+ }
2496
+ return index;
2497
+ };
2431
2498
  const reg = {
2432
2499
  chunkOrder: [],
2433
2500
  chunkData: /* @__PURE__ */ new Map(),
@@ -2579,72 +2646,25 @@ function createRegistry(onEmpty) {
2579
2646
  };
2580
2647
  },
2581
2648
  canonicalFootnoteFor(label) {
2582
- const id = normalizeId(label);
2583
- for (const sym of this.chunkOrder) {
2584
- const data = this.chunkData.get(sym);
2585
- if (data?.defs.has(id)) return sym;
2586
- }
2587
- return null;
2649
+ return getIndex().footnotes.get(normalizeId(label)) ?? null;
2588
2650
  },
2589
2651
  canonicalLinkFor(label) {
2590
- const id = normalizeId(label);
2591
- for (const sym of this.chunkOrder) {
2592
- const data = this.chunkData.get(sym);
2593
- if (data?.linkDefs.has(id)) return sym;
2594
- }
2595
- return null;
2652
+ return getIndex().links.get(normalizeId(label)) ?? null;
2596
2653
  },
2597
2654
  globalNumber(label) {
2598
- const id = normalizeId(label);
2599
- let n = 0;
2600
- const seen = /* @__PURE__ */ new Set();
2601
- for (const sym of this.chunkOrder) {
2602
- const data = this.chunkData.get(sym);
2603
- if (!data) continue;
2604
- for (const ref of data.refs) {
2605
- if (ref.kind !== "footnote") continue;
2606
- if (!seen.has(ref.label)) {
2607
- seen.add(ref.label);
2608
- n++;
2609
- if (ref.label === id) return n;
2610
- }
2611
- }
2612
- }
2613
- return null;
2655
+ return getIndex().numbers.get(normalizeId(label)) ?? null;
2614
2656
  },
2615
2657
  resolveLinkDef(label) {
2616
- const sym = this.canonicalLinkFor(label);
2617
- if (!sym) return null;
2618
- return this.chunkData.get(sym)?.linkDefs.get(normalizeId(label)) ?? null;
2658
+ const id = normalizeId(label);
2659
+ const sym = getIndex().links.get(id);
2660
+ return sym ? this.chunkData.get(sym)?.linkDefs.get(id) ?? null : null;
2619
2661
  },
2620
2662
  getRefsForLabel(label) {
2621
- const id = normalizeId(label);
2622
- let n = 0;
2623
- for (const sym of this.chunkOrder) {
2624
- const data = this.chunkData.get(sym);
2625
- if (!data) continue;
2626
- for (const ref of data.refs) {
2627
- if (ref.kind === "footnote" && ref.label === id) n++;
2628
- }
2629
- }
2630
- return n;
2663
+ return getIndex().counts.get(normalizeId(label)) ?? 0;
2631
2664
  },
2632
2665
  globalOccurrenceForRef(chunkSym, label, localOccurrence) {
2633
- const id = normalizeId(label);
2634
- let global2 = 0;
2635
- for (const sym of this.chunkOrder) {
2636
- const data = this.chunkData.get(sym);
2637
- if (!data) continue;
2638
- let localCount = 0;
2639
- for (const ref of data.refs) {
2640
- if (ref.kind !== "footnote") continue;
2641
- if (ref.label !== id) continue;
2642
- localCount++;
2643
- global2++;
2644
- if (sym === chunkSym && localCount === localOccurrence) return global2;
2645
- }
2646
- }
2647
- return null;
2666
+ const range = getIndex().occurrences.get(chunkSym)?.get(normalizeId(label));
2667
+ return range && Number.isInteger(localOccurrence) && localOccurrence > 0 && localOccurrence <= range.count ? range.start + localOccurrence - 1 : null;
2648
2668
  },
2649
2669
  _notify() {
2650
2670
  this.version++;
@@ -2701,6 +2721,55 @@ function rehypeUnwrapCrossChunkImages() {
2701
2721
 
2702
2722
  // src/components/pluginChain.ts
2703
2723
  import rehypeSanitize from "rehype-sanitize";
2724
+
2725
+ // src/components/rehypeVerifyEngineTags.ts
2726
+ var ENGINE_PLACEHOLDER_TAGS = /* @__PURE__ */ new Set([
2727
+ "footnote-sup",
2728
+ "cross-chunk-link",
2729
+ "cross-chunk-image"
2730
+ ]);
2731
+ var ENGINE_PROVENANCE_PROPERTY = "engineProvenance";
2732
+ function walk(parent, provenance, ancestors) {
2733
+ const children = parent.children;
2734
+ let i = 0;
2735
+ while (i < children.length) {
2736
+ const node = children[i];
2737
+ if (node.type === "element" && ENGINE_PLACEHOLDER_TAGS.has(node.tagName)) {
2738
+ const props = node.properties ?? {};
2739
+ const stamped = props[ENGINE_PROVENANCE_PROPERTY];
2740
+ const genuine = provenance !== "" && typeof stamped === "string" && stamped === provenance;
2741
+ if (genuine) {
2742
+ delete props[ENGINE_PROVENANCE_PROPERTY];
2743
+ if (ancestors && (node.tagName === "cross-chunk-link" || node.tagName === "cross-chunk-image")) {
2744
+ (node.data ??= {}).referenceAncestors = ancestors.slice();
2745
+ }
2746
+ ancestors?.push(
2747
+ node.tagName === "cross-chunk-link" ? "a" : node.tagName === "cross-chunk-image" ? "img" : node.tagName
2748
+ );
2749
+ walk(node, provenance, ancestors);
2750
+ ancestors?.pop();
2751
+ i += 1;
2752
+ } else {
2753
+ children.splice(i, 1, ...node.children);
2754
+ }
2755
+ continue;
2756
+ }
2757
+ if (node.type === "element") {
2758
+ ancestors?.push(node.tagName);
2759
+ walk(node, provenance, ancestors);
2760
+ ancestors?.pop();
2761
+ }
2762
+ i += 1;
2763
+ }
2764
+ }
2765
+ function rehypeVerifyEngineTags(options) {
2766
+ const provenance = options?.provenance ?? "";
2767
+ return function transformer(tree) {
2768
+ walk(tree, provenance, options?.referenceAncestors ? [] : void 0);
2769
+ };
2770
+ }
2771
+
2772
+ // src/components/pluginChain.ts
2704
2773
  import remarkBreaks from "remark-breaks";
2705
2774
  import remarkCjkFriendly from "remark-cjk-friendly";
2706
2775
  import remarkCjkFriendlyGfmStrikethrough from "remark-cjk-friendly-gfm-strikethrough";
@@ -2717,16 +2786,18 @@ import remarkRemoveComments from "remark-remove-comments";
2717
2786
  // src/components/rehypeRebaseHashLinks.ts
2718
2787
  import { visit as visit5 } from "unist-util-visit";
2719
2788
  var DEFAULT_PREFIX = "user-content-";
2789
+ function rebaseHashHref(href, prefix) {
2790
+ const hashPrefix = "#" + prefix;
2791
+ return href.startsWith("#") && !href.startsWith(hashPrefix) ? hashPrefix + href.slice(1) : href;
2792
+ }
2720
2793
  var rehypeRebaseHashLinks = (options) => {
2721
2794
  const prefix = options?.prefix ?? DEFAULT_PREFIX;
2722
- const hashPrefix = "#" + prefix;
2723
2795
  return (tree) => {
2724
2796
  visit5(tree, "element", (node) => {
2725
2797
  if (node.tagName !== "a") return;
2726
2798
  const href = node.properties?.href;
2727
2799
  if (typeof href !== "string" || !href.startsWith("#")) return;
2728
- if (href.startsWith(hashPrefix)) return;
2729
- node.properties.href = hashPrefix + href.slice(1);
2800
+ node.properties.href = rebaseHashHref(href, prefix);
2730
2801
  });
2731
2802
  };
2732
2803
  };
@@ -2806,10 +2877,21 @@ function buildCoreRemarkPlugins(enginePlugins) {
2806
2877
  ...DISPLAY_OPTIMIZE_CHAIN.filter(([name]) => selected.has(name)).map(([, plugin]) => plugin)
2807
2878
  ];
2808
2879
  }
2809
- function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix) {
2880
+ function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix, options) {
2810
2881
  return [
2811
2882
  // Allow raw HTML through so rehype-sanitize can handle it.
2812
2883
  [rehypeRaw, { passThrough: [] }],
2884
+ // Unwrap forged engine placeholders BEFORE sanitize admits their tag
2885
+ // names. Only when the caller holds a credential (see the option's doc).
2886
+ ...options ? [
2887
+ [
2888
+ rehypeVerifyEngineTags,
2889
+ {
2890
+ provenance: options.provenance,
2891
+ ...sanitizeSchema2.ancestors?.a || sanitizeSchema2.ancestors?.img ? { referenceAncestors: true } : {}
2892
+ }
2893
+ ]
2894
+ ] : [],
2813
2895
  // Sanitize HTML while allowing <mark> (highlight), KaTeX class names,
2814
2896
  // and any extra protocols the caller permitted via the `sanitizeSchema`
2815
2897
  // prop. Override `clobberPrefix` with the instance-scoped value — the
@@ -2849,6 +2931,10 @@ function buildCoreRemarkRehypeOptions(enableDefinitionList) {
2849
2931
  }
2850
2932
 
2851
2933
  // src/components/customMdastHandlers.ts
2934
+ function provenanceProps(s) {
2935
+ const provenance = s.options.provenance;
2936
+ return typeof provenance === "string" ? { engineProvenance: provenance } : {};
2937
+ }
2852
2938
  function localDefProps(s, id) {
2853
2939
  const def = s.definitionById.get(id);
2854
2940
  if (!def || typeof def.url !== "string" || def.url === SENTINEL_LINK_URL) return {};
@@ -2879,15 +2965,14 @@ function buildCrossChunkHandlers() {
2879
2965
  type: "element",
2880
2966
  tagName: "cross-chunk-link",
2881
2967
  properties: {
2882
- // `label` is the ORIGINAL source text (mdast's `label` field), NOT
2883
- // the normalized `identifier`. The placeholder uses it to construct
2884
- // hrefs that line up with mdast-util-to-hast's default `<li id>`
2885
- // which also preserves source case. Registry lookups normalize
2886
- // internally, so cross-chunk case-insensitive matching still works.
2968
+ // Display labels decode escapes; registry identifiers must retain
2969
+ // those bytes. Never use the display label as the lookup key.
2970
+ identifier: node.identifier,
2887
2971
  label: node.label ?? node.identifier,
2888
2972
  referenceType: node.referenceType,
2889
2973
  documentId: s.options.documentId,
2890
- ...localDefProps(s, id)
2974
+ ...localDefProps(s, id),
2975
+ ...provenanceProps(s)
2891
2976
  },
2892
2977
  children: s.all(node)
2893
2978
  };
@@ -2901,11 +2986,13 @@ function buildCrossChunkHandlers() {
2901
2986
  type: "element",
2902
2987
  tagName: "cross-chunk-image",
2903
2988
  properties: {
2989
+ identifier: node.identifier,
2904
2990
  label: node.label ?? node.identifier,
2905
2991
  referenceType: node.referenceType,
2906
2992
  alt: node.alt ?? "",
2907
2993
  documentId: s.options.documentId,
2908
- ...localDefProps(s, id)
2994
+ ...localDefProps(s, id),
2995
+ ...provenanceProps(s)
2909
2996
  },
2910
2997
  children: []
2911
2998
  };
@@ -2922,7 +3009,8 @@ function buildCrossChunkHandlers() {
2922
3009
  properties: {
2923
3010
  label: node.identifier,
2924
3011
  localOccurrence,
2925
- documentId: s.options.documentId
3012
+ documentId: s.options.documentId,
3013
+ ...provenanceProps(s)
2926
3014
  },
2927
3015
  children: []
2928
3016
  };
@@ -2940,7 +3028,8 @@ function buildCrossChunkHandlers() {
2940
3028
  // first client frame), where the local synthetic footer is what
2941
3029
  // renders, so marks and footer agree (core-render-02).
2942
3030
  localNumber: s.footnoteOrder.indexOf(id) + 1,
2943
- documentId: s.options.documentId
3031
+ documentId: s.options.documentId,
3032
+ ...provenanceProps(s)
2944
3033
  },
2945
3034
  children: []
2946
3035
  };
@@ -4160,8 +4249,8 @@ var sanitizeSchema = deepFreeze(
4160
4249
  attributes: {
4161
4250
  ...defaultSchema.attributes,
4162
4251
  code: mergeClassNameAllowlist(defaultSchema.attributes?.code, ["math-inline", "math-display"]),
4163
- "cross-chunk-link": ["label", "referenceType", "documentId", "localUrl", "localTitle"],
4164
- "cross-chunk-image": ["label", "referenceType", "documentId", "alt", "localUrl", "localTitle"],
4252
+ "cross-chunk-link": ["identifier", "label", "referenceType", "documentId", "localUrl", "localTitle"],
4253
+ "cross-chunk-image": ["identifier", "label", "referenceType", "documentId", "alt", "localUrl", "localTitle"],
4165
4254
  "footnote-sup": ["label", "localOccurrence", "localNumber", "documentId"]
4166
4255
  },
4167
4256
  strip: [.../* @__PURE__ */ new Set([...defaultSchema.strip || [], ...STRIPPED_TAGS])]
@@ -4197,6 +4286,49 @@ function sanitizeCrossChunkUrl(rawUrl, key, tagName, urlTransform, schema) {
4197
4286
  return String(transformed);
4198
4287
  }
4199
4288
 
4289
+ // src/components/resolveCrossChunkReference.ts
4290
+ import rehypeSanitize2 from "rehype-sanitize";
4291
+ import { normalizeUri as normalizeUri3 } from "micromark-util-sanitize-uri";
4292
+ function resolveCrossChunkReference(input, schema, urlTransform, clobberPrefix) {
4293
+ const key = input.tagName === "a" ? "href" : "src";
4294
+ const element = {
4295
+ type: "element",
4296
+ tagName: input.tagName,
4297
+ properties: {
4298
+ [key]: normalizeUri3(input.url),
4299
+ ...input.tagName === "img" ? { alt: input.alt ?? "" } : {},
4300
+ ...input.title !== void 0 ? { title: input.title } : {}
4301
+ },
4302
+ children: input.tagName === "a" ? [{ type: "text", value: "__reference_children__" }] : []
4303
+ };
4304
+ const requiredAncestors = schema.ancestors?.[input.tagName];
4305
+ const recordedAncestors = input.node?.data?.referenceAncestors;
4306
+ let finalSchema = schema;
4307
+ if (requiredAncestors && Array.isArray(recordedAncestors) && requiredAncestors.some((tag) => recordedAncestors.includes(tag))) {
4308
+ const ancestors = { ...schema.ancestors };
4309
+ delete ancestors[input.tagName];
4310
+ finalSchema = { ...schema, ancestors };
4311
+ }
4312
+ const root2 = rehypeSanitize2({ ...finalSchema, clobberPrefix })({ type: "root", children: [element] });
4313
+ const node = root2.children[0];
4314
+ if (node?.type !== "element") return { element: null, keepChildren: node?.type === "text" };
4315
+ if (node.tagName === "a" && typeof node.properties.href === "string") {
4316
+ node.properties.href = rebaseHashHref(node.properties.href, clobberPrefix);
4317
+ }
4318
+ node.children = input.node?.children ?? [];
4319
+ if (input.node?.position) node.position = input.node.position;
4320
+ buildTransform({
4321
+ allowedElements: void 0,
4322
+ disallowedElements: void 0,
4323
+ allowElement: void 0,
4324
+ skipHtml: void 0,
4325
+ unwrapDisallowed: void 0,
4326
+ urlTransform
4327
+ })(node, 0, root2);
4328
+ node.children = [];
4329
+ return { element: node, keepChildren: false };
4330
+ }
4331
+
4200
4332
  // src/plugins/defs.ts
4201
4333
  function getEnginePluginInternals(plugin) {
4202
4334
  const candidate = plugin;
@@ -4401,6 +4533,8 @@ var createSmoothStreamController = (options = {}) => {
4401
4533
  let source = "";
4402
4534
  let visibleEnd = 0;
4403
4535
  let pending = [];
4536
+ let pendingHead = 0;
4537
+ const pendingCount = () => pending.length - pendingHead;
4404
4538
  let tentativeEnd = 0;
4405
4539
  let finished = false;
4406
4540
  let seam;
@@ -4445,7 +4579,7 @@ var createSmoothStreamController = (options = {}) => {
4445
4579
  cancelFrame = void 0;
4446
4580
  };
4447
4581
  const ensureScheduled = () => {
4448
- if (disposed || cancelFrame || pending.length === 0) return;
4582
+ if (disposed || cancelFrame || pendingCount() === 0) return;
4449
4583
  lastTickAt = now();
4450
4584
  credit = 0;
4451
4585
  cancelFrame = schedule(tick);
@@ -4459,31 +4593,38 @@ var createSmoothStreamController = (options = {}) => {
4459
4593
  const params = resolveParams();
4460
4594
  let rate;
4461
4595
  if (finished && drainDeadlineAt !== void 0) {
4462
- rate = Math.max(params.minCharsPerSecond, pending.length * 1e3 / Math.max(1, drainDeadlineAt - t));
4596
+ rate = Math.max(params.minCharsPerSecond, pendingCount() * 1e3 / Math.max(1, drainDeadlineAt - t));
4463
4597
  } else if (gapWindow.length > 0 && lastArrivalAt !== void 0) {
4464
4598
  const gaps = gapWindow.map((s) => s.gap).sort((a, b) => a - b);
4465
4599
  const intervalQ = gaps[Math.min(gaps.length - 1, Math.floor(gaps.length * INTERVAL_QUANTILE))];
4466
4600
  const horizon = Math.max(16, Math.min(params.bufferFactor * intervalQ + HORIZON_PAD_MS, params.maxLagMs));
4467
4601
  const deadline = Math.max(lastArrivalAt + horizon, t + DEADLINE_FLOOR_MS);
4468
- rate = Math.max(params.minCharsPerSecond, pending.length * 1e3 / Math.max(1, deadline - t));
4602
+ rate = Math.max(params.minCharsPerSecond, pendingCount() * 1e3 / Math.max(1, deadline - t));
4469
4603
  } else {
4470
- rate = Math.max(params.minCharsPerSecond, pending.length * 1e3 / Math.max(1, params.correctionTauMs));
4604
+ rate = Math.max(params.minCharsPerSecond, pendingCount() * 1e3 / Math.max(1, params.correctionTauMs));
4471
4605
  }
4472
4606
  credit += rate * dt / 1e3;
4473
- const reveal = Math.min(Math.floor(credit), pending.length);
4607
+ const reveal = Math.min(Math.floor(credit), pendingCount());
4474
4608
  if (reveal > 0) {
4475
- visibleEnd = pending[reveal - 1];
4476
- pending = pending.slice(reveal);
4477
- credit = pending.length > 0 ? credit - reveal : 0;
4609
+ visibleEnd = pending[pendingHead + reveal - 1];
4610
+ pendingHead += reveal;
4611
+ if (pendingHead === pending.length) {
4612
+ pending = [];
4613
+ pendingHead = 0;
4614
+ } else if (pendingHead >= 1024 && pendingHead * 2 >= pending.length) {
4615
+ pending = pending.slice(pendingHead);
4616
+ pendingHead = 0;
4617
+ }
4618
+ credit = pendingCount() > 0 ? credit - reveal : 0;
4478
4619
  notify();
4479
4620
  }
4480
- if (!disposed && !cancelFrame && pending.length > 0) cancelFrame = schedule(tick);
4621
+ if (!disposed && !cancelFrame && pendingCount() > 0) cancelFrame = schedule(tick);
4481
4622
  };
4482
4623
  const resegmentTail = () => {
4483
- if (seam !== void 0 && seam < source.length && pending[pending.length - 1] === seam) {
4624
+ if (pendingCount() > 0 && seam !== void 0 && seam < source.length && pending[pending.length - 1] === seam) {
4484
4625
  pending.pop();
4485
4626
  }
4486
- const from = pending.length > 0 ? pending[pending.length - 1] : visibleEnd;
4627
+ const from = pendingCount() > 0 ? pending[pending.length - 1] : visibleEnd;
4487
4628
  let anchor = from;
4488
4629
  if (seam !== void 0 && from <= seam) {
4489
4630
  anchor = Math.max(0, from - RESUME_LOOKBACK);
@@ -4517,6 +4658,7 @@ var createSmoothStreamController = (options = {}) => {
4517
4658
  visibleEnd = next.length;
4518
4659
  tentativeEnd = next.length;
4519
4660
  pending = [];
4661
+ pendingHead = 0;
4520
4662
  seam = next.length;
4521
4663
  credit = 0;
4522
4664
  cancelScheduled();
@@ -4548,7 +4690,7 @@ var createSmoothStreamController = (options = {}) => {
4548
4690
  if (finished) return;
4549
4691
  finished = true;
4550
4692
  lastArrivalAt = void 0;
4551
- if (tentativeEnd > (pending.length > 0 ? pending[pending.length - 1] : visibleEnd)) {
4693
+ if (tentativeEnd > (pendingCount() > 0 ? pending[pending.length - 1] : visibleEnd)) {
4552
4694
  pending.push(tentativeEnd);
4553
4695
  }
4554
4696
  const params = resolveParams();
@@ -4574,10 +4716,11 @@ var createSmoothStreamController = (options = {}) => {
4574
4716
  snap,
4575
4717
  flush() {
4576
4718
  disposed = false;
4577
- const target = finished ? source.length : pending.length > 0 ? pending[pending.length - 1] : visibleEnd;
4719
+ const target = finished ? source.length : pendingCount() > 0 ? pending[pending.length - 1] : visibleEnd;
4578
4720
  if (target <= visibleEnd) return;
4579
4721
  visibleEnd = target;
4580
4722
  pending = [];
4723
+ pendingHead = 0;
4581
4724
  credit = 0;
4582
4725
  cancelScheduled();
4583
4726
  notify();
@@ -4600,6 +4743,12 @@ var createSmoothStreamController = (options = {}) => {
4600
4743
  };
4601
4744
 
4602
4745
  // src/preprocessors/latex.ts
4746
+ function isHardBoundary(kind) {
4747
+ return kind === "code" || kind === "literal" || kind === "multilineTag";
4748
+ }
4749
+ function hasLineEnding(text) {
4750
+ return text.includes("\n") || text.includes("\r");
4751
+ }
4603
4752
  function getRepeatedMarkerLength(content, start, marker) {
4604
4753
  let end = start;
4605
4754
  while (end < content.length && content[end] === marker) {
@@ -4667,11 +4816,11 @@ function splitByProtectedRegions(content) {
4667
4816
  let multilineFenceMarker = null;
4668
4817
  let multilineFenceLength = 0;
4669
4818
  let multilineFenceIndent = 0;
4670
- function pushProtected(start, end) {
4819
+ function pushProtected(start, end, kind) {
4671
4820
  if (start > lastIndex) {
4672
- segments.push({ text: content.substring(lastIndex, start), isCode: false });
4821
+ segments.push({ kind: "text", text: content.substring(lastIndex, start) });
4673
4822
  }
4674
- segments.push({ text: content.substring(start, end), isCode: true });
4823
+ segments.push({ kind, text: content.substring(start, end) });
4675
4824
  lastIndex = end;
4676
4825
  }
4677
4826
  let i = 0;
@@ -4682,7 +4831,7 @@ function splitByProtectedRegions(content) {
4682
4831
  const runLen = getRepeatedMarkerLength(content, i, multilineFenceMarker);
4683
4832
  const closerIndent = lineIndentBefore(content, i);
4684
4833
  if (runLen >= multilineFenceLength && closerIndent !== -1 && closerIndent <= multilineFenceIndent + 3 && restOfLineIsBlank(content, i + runLen)) {
4685
- pushProtected(multilineStart, i + runLen);
4834
+ pushProtected(multilineStart, i + runLen, "code");
4686
4835
  multilineStart = -1;
4687
4836
  multilineFenceMarker = null;
4688
4837
  multilineFenceLength = 0;
@@ -4709,7 +4858,7 @@ function splitByProtectedRegions(content) {
4709
4858
  if (char === "`") {
4710
4859
  const closeIdx = findClosingBacktickRun(content, i + runLen, runLen);
4711
4860
  if (closeIdx !== -1) {
4712
- pushProtected(i, closeIdx + runLen);
4861
+ pushProtected(i, closeIdx + runLen, "code");
4713
4862
  i = closeIdx + runLen;
4714
4863
  continue;
4715
4864
  }
@@ -4734,7 +4883,11 @@ function splitByProtectedRegions(content) {
4734
4883
  endIndex = content.length;
4735
4884
  }
4736
4885
  }
4737
- pushProtected(i, endIndex);
4886
+ pushProtected(
4887
+ i,
4888
+ endIndex,
4889
+ isOpeningPairedTag ? "literal" : hasLineEnding(content.substring(i, endIndex)) ? "multilineTag" : "tag"
4890
+ );
4738
4891
  i = endIndex;
4739
4892
  continue;
4740
4893
  }
@@ -4742,10 +4895,10 @@ function splitByProtectedRegions(content) {
4742
4895
  i += 1;
4743
4896
  }
4744
4897
  if (multilineStart !== -1) {
4745
- pushProtected(multilineStart, content.length);
4898
+ pushProtected(multilineStart, content.length, "code");
4746
4899
  }
4747
4900
  if (lastIndex < content.length) {
4748
- segments.push({ text: content.substring(lastIndex), isCode: false });
4901
+ segments.push({ kind: "text", text: content.substring(lastIndex) });
4749
4902
  }
4750
4903
  return segments;
4751
4904
  }
@@ -4899,20 +5052,22 @@ function escapeLatexPipesInUnclosed(text) {
4899
5052
  const tail = text.substring(unclosedStart + delimLen);
4900
5053
  return before + delim + replaceUnescapedPipes(tail);
4901
5054
  }
4902
- function opensMathFlow(text, pos) {
5055
+ function opensMathFlow(text, pos, runStartsAtLineStart) {
4903
5056
  let i = pos;
4904
5057
  let spaces = 0;
4905
- while (i > 0 && text[i - 1] !== "\n") {
5058
+ while (i > 0) {
5059
+ const prev = text[i - 1];
5060
+ if (prev === "\n" || prev === "\r") return true;
4906
5061
  i -= 1;
4907
5062
  if (text[i] !== " ") return false;
4908
5063
  spaces += 1;
4909
5064
  if (spaces > 3) return false;
4910
5065
  }
4911
- return true;
5066
+ return runStartsAtLineStart;
4912
5067
  }
4913
- function truncateUnclosedLatexBlock(text, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
5068
+ function truncateUnclosedLatexBlock(text, runStartsAtLineStart, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
4914
5069
  if (unclosedStart === -1) return text;
4915
- if (!opensMathFlow(text, unclosedStart)) return text;
5070
+ if (!opensMathFlow(text, unclosedStart, runStartsAtLineStart)) return text;
4916
5071
  return text.substring(0, unclosedStart).trimEnd();
4917
5072
  }
4918
5073
  function escapeTextUnderscores(text) {
@@ -4957,7 +5112,8 @@ function convertSingleToDoubleDollar(text) {
4957
5112
  }
4958
5113
  function preprocessLaTeX(str) {
4959
5114
  if (!hasLatexTrigger(str)) return str;
4960
- return processSlice(str, false).out;
5115
+ const mask = selectMask(str);
5116
+ return (mask === null ? processSlice(str, { legacy: true, probe: false }) : processSlice(str, { probe: false, mask })).out;
4961
5117
  }
4962
5118
  function hasLatexTrigger(str) {
4963
5119
  return str.includes("$") || str.includes("\\[") || str.includes("\\(");
@@ -4986,42 +5142,240 @@ function hasUnclosedTextCommand(text) {
4986
5142
  }
4987
5143
  var RESIDUAL_OPEN_BRACKET_RE = /(?<!!)\\\[/;
4988
5144
  var LEADING_DOUBLE_DOLLAR_RE = /^\s*\$\$/;
4989
- function processSlice(slice, probe = true) {
5145
+ var PUA_START = 57344;
5146
+ var PUA_END = 63743;
5147
+ var PUA_SIZE = PUA_END - PUA_START + 1;
5148
+ function selectMask(source) {
5149
+ const first = String.fromCharCode(PUA_START);
5150
+ if (source.indexOf(first) === -1) return first;
5151
+ const seen = new Uint8Array(PUA_SIZE);
5152
+ for (let i = 0; i < source.length; i++) {
5153
+ const c = source.charCodeAt(i);
5154
+ if (c >= PUA_START && c <= PUA_END) seen[c - PUA_START] = 1;
5155
+ }
5156
+ for (let k = 0; k < PUA_SIZE; k++) if (seen[k] === 0) return String.fromCharCode(PUA_START + k);
5157
+ return null;
5158
+ }
5159
+ var PuaPresence = class {
5160
+ bits = new Uint8Array(PUA_SIZE);
5161
+ distinct = 0;
5162
+ reset() {
5163
+ this.bits.fill(0);
5164
+ this.distinct = 0;
5165
+ }
5166
+ add(text, from) {
5167
+ for (let i = from; i < text.length; i++) {
5168
+ const c = text.charCodeAt(i);
5169
+ if (c >= PUA_START && c <= PUA_END) {
5170
+ const k = c - PUA_START;
5171
+ if (this.bits[k] === 0) {
5172
+ this.bits[k] = 1;
5173
+ this.distinct += 1;
5174
+ }
5175
+ }
5176
+ }
5177
+ }
5178
+ select() {
5179
+ if (this.distinct === 0) return String.fromCharCode(PUA_START);
5180
+ if (this.distinct >= PUA_SIZE) return null;
5181
+ for (let k = 0; k < PUA_SIZE; k++) if (this.bits[k] === 0) return String.fromCharCode(PUA_START + k);
5182
+ return null;
5183
+ }
5184
+ };
5185
+ function transformRun(input, probe, runStartsAtLineStart, seamEligible) {
5186
+ let text = input;
5187
+ let tailSensitive = false;
5188
+ let truncatedAtSeamStart = false;
5189
+ text = escapeMhchemCommands(text);
5190
+ text = escapeCurrencyDollarSigns(text);
5191
+ text = convertLatexDelimiters(text);
5192
+ if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) tailSensitive = true;
5193
+ text = escapeLatexPipes(text);
5194
+ if (probe && findUnclosedDelimiterStart(text, "both") !== -1) tailSensitive = true;
5195
+ text = escapeLatexPipesInUnclosed(text);
5196
+ if (probe && hasUnclosedTextCommand(text)) tailSensitive = true;
5197
+ text = escapeTextUnderscores(text);
5198
+ text = convertSingleToDoubleDollar(text);
5199
+ let unclosedDouble;
5200
+ if (probe || seamEligible && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
5201
+ unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
5202
+ if (unclosedDouble !== -1) {
5203
+ tailSensitive = true;
5204
+ if (seamEligible && opensMathFlow(text, unclosedDouble, runStartsAtLineStart) && text.slice(0, unclosedDouble).trim() === "") {
5205
+ truncatedAtSeamStart = true;
5206
+ }
5207
+ }
5208
+ }
5209
+ text = truncateUnclosedLatexBlock(text, runStartsAtLineStart, unclosedDouble);
5210
+ return { out: text, tailSensitive, truncatedAtSeamStart };
5211
+ }
5212
+ function processSliceLegacy(slice, probe) {
4990
5213
  const segments = splitByProtectedRegions(slice);
4991
5214
  const parts = [];
4992
5215
  let quiescent = true;
4993
5216
  let truncatedAtSeamStart = false;
4994
5217
  for (let index = 0; index < segments.length; index++) {
4995
5218
  const segment = segments[index];
4996
- if (segment.isCode) {
5219
+ if (segment.kind !== "text") {
4997
5220
  parts.push(segment.text);
4998
5221
  continue;
4999
5222
  }
5000
- let text = segment.text;
5001
- text = escapeMhchemCommands(text);
5002
- text = escapeCurrencyDollarSigns(text);
5003
- text = convertLatexDelimiters(text);
5004
- if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) quiescent = false;
5005
- text = escapeLatexPipes(text);
5006
- if (probe && findUnclosedDelimiterStart(text, "both") !== -1) quiescent = false;
5007
- text = escapeLatexPipesInUnclosed(text);
5008
- if (probe && hasUnclosedTextCommand(text)) quiescent = false;
5009
- text = escapeTextUnderscores(text);
5010
- text = convertSingleToDoubleDollar(text);
5011
- let unclosedDouble;
5012
- if (probe || index === 0 && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
5013
- unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
5014
- if (unclosedDouble !== -1) {
5015
- quiescent = false;
5016
- if (index === 0 && opensMathFlow(text, unclosedDouble) && text.slice(0, unclosedDouble).trim() === "") {
5017
- truncatedAtSeamStart = true;
5223
+ const r = transformRun(segment.text, probe, true, index === 0);
5224
+ if (r.tailSensitive) quiescent = false;
5225
+ if (r.truncatedAtSeamStart) truncatedAtSeamStart = true;
5226
+ parts.push(r.out);
5227
+ }
5228
+ return { out: parts.join(""), quiescent, truncatedAtSeamStart, degradedReason: null };
5229
+ }
5230
+ var SCOPE_DEPTH_CAP = 8;
5231
+ var VOID_TAGS2 = /* @__PURE__ */ new Set(["br", "hr", "img", "wbr", "input", "source"]);
5232
+ var TAG_NAME_RE = /^<(\/?)([A-Za-z][A-Za-z0-9]*)/;
5233
+ function tagInfo(tag) {
5234
+ const m = TAG_NAME_RE.exec(tag);
5235
+ const closing = m?.[1] === "/";
5236
+ const name = (m?.[2] ?? "").toLowerCase();
5237
+ const opensScope = !closing && !tag.endsWith("/>") && !VOID_TAGS2.has(name);
5238
+ return { name, closing, opensScope };
5239
+ }
5240
+ function buildRunTree(segments) {
5241
+ const root2 = [];
5242
+ const stack = [];
5243
+ const current = () => stack.length > 0 ? stack[stack.length - 1].children : root2;
5244
+ const unwind = () => {
5245
+ while (stack.length > 0) {
5246
+ const frame = stack.pop();
5247
+ current().push({ type: "atom", text: frame.open }, ...frame.children);
5248
+ }
5249
+ };
5250
+ for (const segment of segments) {
5251
+ if (segment.kind === "tag") {
5252
+ const info = tagInfo(segment.text);
5253
+ if (info.closing) {
5254
+ const top = stack[stack.length - 1];
5255
+ if (top !== void 0 && top.name === info.name) {
5256
+ stack.pop();
5257
+ const parent = current();
5258
+ if (top.suppressed) {
5259
+ parent.push({ type: "atom", text: top.open }, ...top.children, { type: "atom", text: segment.text });
5260
+ } else {
5261
+ parent.push({ type: "scope", open: top.open, close: segment.text, children: top.children });
5262
+ }
5263
+ } else {
5264
+ current().push({ type: "atom", text: segment.text });
5018
5265
  }
5266
+ } else if (info.opensScope) {
5267
+ stack.push({ name: info.name, open: segment.text, suppressed: stack.length >= SCOPE_DEPTH_CAP, children: [] });
5268
+ } else {
5269
+ current().push({ type: "atom", text: segment.text });
5019
5270
  }
5271
+ continue;
5272
+ }
5273
+ const t = segment.text;
5274
+ let start = 0;
5275
+ for (let i = 0; i < t.length; i++) {
5276
+ const c = t.charCodeAt(i);
5277
+ if (c !== 10 && c !== 13) continue;
5278
+ if (i > start) current().push({ type: "text", text: t.slice(start, i) });
5279
+ const end = c === 13 && t.charCodeAt(i + 1) === 10 ? i + 2 : i + 1;
5280
+ unwind();
5281
+ root2.push({ type: "text", text: t.slice(i, end) });
5282
+ start = end;
5283
+ i = end - 1;
5284
+ }
5285
+ if (start < t.length) current().push({ type: "text", text: t.slice(start) });
5286
+ }
5287
+ unwind();
5288
+ return root2;
5289
+ }
5290
+ var restoreFailureInjector = null;
5291
+ function restore(out, atoms, mask) {
5292
+ if (restoreFailureInjector !== null && restoreFailureInjector(atoms)) return null;
5293
+ let result = "";
5294
+ let k = 0;
5295
+ let last = 0;
5296
+ for (; ; ) {
5297
+ const idx = out.indexOf(mask, last);
5298
+ if (idx === -1) break;
5299
+ if (k >= atoms.length) return null;
5300
+ result += out.slice(last, idx) + atoms[k];
5301
+ k += 1;
5302
+ last = idx + 1;
5303
+ }
5304
+ return result + out.slice(last);
5305
+ }
5306
+ function emitRun(nodes, mask) {
5307
+ let text = "";
5308
+ const atoms = [];
5309
+ for (const node of nodes) {
5310
+ if (node.type === "text") {
5311
+ text += node.text;
5312
+ } else if (node.type === "atom") {
5313
+ atoms.push(node.text);
5314
+ text += mask;
5315
+ } else {
5316
+ const inner = emitRun(node.children, mask);
5317
+ if (inner === null) return null;
5318
+ const transformed = transformRun(inner.text, false, false, false);
5319
+ const restored = restore(transformed.out, inner.atoms, mask);
5320
+ if (restored === null) return null;
5321
+ atoms.push(node.open + restored + node.close);
5322
+ text += mask;
5020
5323
  }
5021
- text = truncateUnclosedLatexBlock(text, unclosedDouble);
5022
- parts.push(text);
5023
5324
  }
5024
- return { out: parts.join(""), quiescent, truncatedAtSeamStart };
5325
+ return { text, atoms };
5326
+ }
5327
+ function reportRestoreViolation() {
5328
+ if (false) {
5329
+ console.error(
5330
+ "[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."
5331
+ );
5332
+ }
5333
+ }
5334
+ function processSliceDefault(slice, probe, mask) {
5335
+ const segments = splitByProtectedRegions(slice);
5336
+ const parts = [];
5337
+ let quiescent = true;
5338
+ let truncatedAtSeamStart = false;
5339
+ let offset = 0;
5340
+ let i = 0;
5341
+ while (i < segments.length) {
5342
+ const segment = segments[i];
5343
+ if (isHardBoundary(segment.kind)) {
5344
+ parts.push(segment.text);
5345
+ offset += segment.text.length;
5346
+ i += 1;
5347
+ continue;
5348
+ }
5349
+ const runStart = offset;
5350
+ const runSegments = [];
5351
+ while (i < segments.length && !isHardBoundary(segments[i].kind)) {
5352
+ runSegments.push(segments[i]);
5353
+ offset += segments[i].text.length;
5354
+ i += 1;
5355
+ }
5356
+ const before = runStart === 0 ? -1 : slice.charCodeAt(runStart - 1);
5357
+ const runStartsAtLineStart = before === -1 || before === 10 || before === 13;
5358
+ const seamEligible = runStart === 0;
5359
+ const emitted = emitRun(buildRunTree(runSegments), mask);
5360
+ if (emitted === null) {
5361
+ reportRestoreViolation();
5362
+ return { ...processSliceLegacy(slice, probe), degradedReason: "restore-invariant" };
5363
+ }
5364
+ const r = transformRun(emitted.text, probe, runStartsAtLineStart, seamEligible);
5365
+ const restored = restore(r.out, emitted.atoms, mask);
5366
+ if (restored === null) {
5367
+ reportRestoreViolation();
5368
+ return { ...processSliceLegacy(slice, probe), degradedReason: "restore-invariant" };
5369
+ }
5370
+ if (r.tailSensitive) quiescent = false;
5371
+ if (r.truncatedAtSeamStart) truncatedAtSeamStart = true;
5372
+ parts.push(restored);
5373
+ }
5374
+ return { out: parts.join(""), quiescent, truncatedAtSeamStart, degradedReason: null };
5375
+ }
5376
+ function processSlice(slice, options) {
5377
+ if (options.legacy === true) return processSliceLegacy(slice, options.probe);
5378
+ return processSliceDefault(slice, options.probe, options.mask);
5025
5379
  }
5026
5380
  function isBlankRawLine(text, from, to) {
5027
5381
  for (let i = from; i < to; i++) {
@@ -5037,7 +5391,7 @@ function findRawSafeCut(active) {
5037
5391
  let backtickHazard = false;
5038
5392
  let latentLt = false;
5039
5393
  for (const segment of segments) {
5040
- if (segment.isCode) {
5394
+ if (segment.kind !== "text") {
5041
5395
  if (segment.text.includes(">")) latentLt = false;
5042
5396
  offset += segment.text.length;
5043
5397
  continue;
@@ -5072,12 +5426,28 @@ function createIncrementalLatexPreprocessor(options) {
5072
5426
  const freezeThreshold = options?.freezeThreshold ?? DEFAULT_FREEZE_ATTEMPT_THRESHOLD;
5073
5427
  const onAttempt = options?.onAttempt;
5074
5428
  const backoff = options?.backoff ?? true;
5429
+ const onDegrade = options?.onDegrade;
5075
5430
  let prevSource = "";
5076
5431
  let prevOutput = "";
5077
5432
  let frozenSrcEnd = 0;
5078
5433
  let frozenOut = "";
5079
5434
  let triggered = false;
5080
5435
  let nextAttemptLen = 0;
5436
+ let lineageDegraded = false;
5437
+ const presence = new PuaPresence();
5438
+ const commit = (source, out) => {
5439
+ prevSource = source;
5440
+ prevOutput = out;
5441
+ return out;
5442
+ };
5443
+ const legacyWhole = (source) => processSlice(source, { legacy: true, probe: false }).out;
5444
+ const degrade = (source, reason) => {
5445
+ lineageDegraded = true;
5446
+ frozenSrcEnd = 0;
5447
+ frozenOut = "";
5448
+ onDegrade?.(reason);
5449
+ return commit(source, legacyWhole(source));
5450
+ };
5081
5451
  return function incrementalPreprocessLaTeX(source) {
5082
5452
  if (source === prevSource) return prevOutput;
5083
5453
  const isAppend = source.length > prevSource.length && source.startsWith(prevSource);
@@ -5086,16 +5456,20 @@ function createIncrementalLatexPreprocessor(options) {
5086
5456
  frozenOut = "";
5087
5457
  triggered = false;
5088
5458
  nextAttemptLen = 0;
5459
+ lineageDegraded = false;
5460
+ presence.reset();
5461
+ presence.add(source, 0);
5462
+ } else {
5463
+ presence.add(source, prevSource.length);
5089
5464
  }
5090
5465
  if (!triggered) {
5091
5466
  const checkFrom = isAppend ? Math.max(0, prevSource.length - 1) : 0;
5092
- if (!hasLatexTrigger(source.slice(checkFrom))) {
5093
- prevSource = source;
5094
- prevOutput = source;
5095
- return source;
5096
- }
5467
+ if (!hasLatexTrigger(source.slice(checkFrom))) return commit(source, source);
5097
5468
  triggered = true;
5098
5469
  }
5470
+ if (lineageDegraded) return commit(source, legacyWhole(source));
5471
+ const mask = presence.select();
5472
+ if (mask === null) return degrade(source, "mask-exhausted");
5099
5473
  let active = source.slice(frozenSrcEnd);
5100
5474
  if (active.length > freezeThreshold && active.length >= nextAttemptLen) {
5101
5475
  const activeLength = active.length;
@@ -5110,18 +5484,20 @@ function createIncrementalLatexPreprocessor(options) {
5110
5484
  };
5111
5485
  const cut = findRawSafeCut(active);
5112
5486
  if (cut > 0) {
5113
- const candidate = processSlice(active.slice(0, cut));
5487
+ const candidate = processSlice(active.slice(0, cut), { probe: true, mask });
5488
+ if (candidate.degradedReason !== null) {
5489
+ onAttempt?.({ activeLength, frozenBytes: 0 });
5490
+ return degrade(source, candidate.degradedReason);
5491
+ }
5114
5492
  if (candidate.quiescent) freeze(cut, candidate);
5115
5493
  }
5116
5494
  nextAttemptLen = advanced || !backoff ? 0 : active.length * 2;
5117
5495
  onAttempt?.({ activeLength, frozenBytes });
5118
5496
  }
5119
- const tail = processSlice(active, false);
5497
+ const tail = processSlice(active, { probe: false, mask });
5498
+ if (tail.degradedReason !== null) return degrade(source, tail.degradedReason);
5120
5499
  const head = tail.truncatedAtSeamStart ? frozenOut.replace(/\s+$/, "") : frozenOut;
5121
- const out = head + tail.out;
5122
- prevSource = source;
5123
- prevOutput = out;
5124
- return out;
5500
+ return commit(source, head + tail.out);
5125
5501
  };
5126
5502
  }
5127
5503
 
@@ -5146,6 +5522,8 @@ function createRemendPreprocessor(options) {
5146
5522
  }
5147
5523
  export {
5148
5524
  DEFAULT_PAYLOAD,
5525
+ ENGINE_PLACEHOLDER_TAGS,
5526
+ ENGINE_PROVENANCE_PROPERTY,
5149
5527
  PIPELINE_STAGES,
5150
5528
  SENTINEL_FN_CONTENT,
5151
5529
  SENTINEL_LINK_URL,
@@ -5190,7 +5568,9 @@ export {
5190
5568
  preprocessLaTeX,
5191
5569
  rehypeFooterAdorn,
5192
5570
  rehypeRebaseHashLinks_default as rehypeRebaseHashLinks,
5571
+ rehypeVerifyEngineTags,
5193
5572
  removeComments,
5573
+ resolveCrossChunkReference,
5194
5574
  sanitizeCrossChunkUrl,
5195
5575
  sanitizeSchema,
5196
5576
  shortenDocumentId,