@ai-markdown/core 3.0.0-beta.1

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.
@@ -0,0 +1,497 @@
1
+ import * as hast from 'hast';
2
+ import { Element, RootContent, Root as Root$1, Nodes } from 'hast';
3
+ import * as mdast from 'mdast';
4
+ import { Root } from 'mdast';
5
+ import { Registry, ChunkData, PipelineOptions, AdvanceOptions } from '@ai-markdown/engine';
6
+ export { isFootnoteSection } from '@ai-markdown/engine';
7
+
8
+ /** Framework-neutral block planning and dependency fingerprints.
9
+ * Cache ownership and conversion to framework nodes belong to adapters.
10
+ * See docs/architecture.md and packages/core/README.md for the contract.
11
+ */
12
+
13
+ /** Source-level identity of one renderable hast block. */
14
+ interface BlockInfo {
15
+ raw: string;
16
+ startOffset: number;
17
+ endOffset: number;
18
+ /**
19
+ * Line and column at the block's start. Tracked alongside `startOffset`
20
+ * because two different source documents can produce the same byte offset
21
+ * for the "same" content (`A\n\nB\n\nTarget` vs `ABCD\n\nTarget` — both
22
+ * have `Target` starting at offset 6 but on different lines). Custom
23
+ * components that read `node.position.start.line` would otherwise see
24
+ * stale data from a false-positive cache hit.
25
+ */
26
+ startLine: number;
27
+ startColumn: number;
28
+ /** True if the block contains any TAINT_TYPES node — invalidate on globalCtx change. */
29
+ hasReference: boolean;
30
+ /**
31
+ * Structural digest of the block's hast subtree, computed ONLY for blocks
32
+ * that originate from raw HTML (mdast `html` nodes, or blocks resolved via
33
+ * the range-containment fallback). Undefined for markdown-native blocks.
34
+ *
35
+ * Why it exists: rehype-raw applies the HTML parsing algorithm, so an
36
+ * UNCLOSED container tag (`<details>` mid-stream, before its `</details>`
37
+ * arrives) swallows every FOLLOWING top-level sibling into itself —
38
+ * including the synthetic `<section data-footnotes>`. The block's mdast
39
+ * identity (raw, position, ctx) is byte-identical across all those frames,
40
+ * so without this digest the first swallowed snapshot would be a permanent
41
+ * cache hit: content rendered inside the container freezes, and when the
42
+ * close tag finally arrives the real nodes re-render at top level while the
43
+ * stale copies stay trapped in the cached container (the "duplicate
44
+ * footnote section inside <details>" bug).
45
+ *
46
+ * Digest composition — `maxEnd:descendantCount:hasFootnoteSection:swallowedHash`:
47
+ * - max end offset over the subtree catches swallowed POSITIONED siblings
48
+ * (their offsets lie beyond the mdast node's own range);
49
+ * - descendant count catches position-less synthetic content moving in or
50
+ * out (the footnote section's own element carries no position, and its
51
+ * li contents point BACK at the definitions' offsets, which can be
52
+ * smaller than the container's end — maxEnd alone misses that case);
53
+ * - the explicit dataFootnotes bit closes the degenerate corner where a
54
+ * swallowed section and later real children tie on both numbers;
55
+ * - a hash of the swallowed extent's SOURCE (`[ownEnd, maxEnd)`) catches
56
+ * an equal-length, equal-shape single-frame replacement of the
57
+ * swallowed siblings, which leaves every count unchanged.
58
+ *
59
+ * Markdown-native blocks (paragraph/list/math/code/…) never receive
60
+ * reparented content — every hast descendant derives from their own mdast
61
+ * source range — so they skip the subtree walk entirely. This keeps the
62
+ * per-frame cost away from huge deterministic subtrees like KaTeX output.
63
+ */
64
+ hastDigest?: string;
65
+ /** A raw-HTML container that swallowed the synthesized
66
+ * `<section data-footnotes>` (rehype-raw reparents the following siblings
67
+ * into an unclosed `<details>` / `<div>` mid-stream). The footer is then
68
+ * NOT a top-level plan item, so coordinated mode's "skip the local
69
+ * footer, the aggregate renders it" rule never fired and the document
70
+ * showed two `<section data-footnotes>` with colliding li ids (2026-08-19
71
+ * review r2 P2-9). Coordinated renders strip it from this block's subtree;
72
+ * standalone renders keep it exactly where the full parse puts it. */
73
+ containsFootnoteSection?: boolean;
74
+ /** TAINT-block 专属:按节点类型分桶的 label set。Normalized 形态(uppercase)。
75
+ * Undefined when hasReference === false. */
76
+ taintLabels?: {
77
+ footnoteRefLabels: string[];
78
+ linkRefLabels: string[];
79
+ imageRefLabels: string[];
80
+ footnoteDefLabels: string[];
81
+ /** Chunk-local footnote state the engine baked into this block's
82
+ * `footnote-sup` placeholders (`localOccurrence` / `localNumber` come
83
+ * from a per-chunk counter over all PRECEDING refs, customMdastHandlers):
84
+ * per ref label, refs-before-this-block count and first-appearance
85
+ * rank. Same raw + same registry answers but a different prefix (an
86
+ * in-place `[^x]`→`[^w]` edit upstream) must miss the cache, or the
87
+ * stale occurrence resolves to a null global occurrence and the mark
88
+ * vanishes (2026-08-19 review P2-4). Absent when the block has no refs. */
89
+ footnoteRefLocalCtx?: string;
90
+ /** Baked `localUrl` / `localTitle` of swallowed `cross-chunk-link` /
91
+ * `-image` placeholders, as `[[label, url, title|null]…]` (JSON). The
92
+ * render falls back to these when the registry has no canonical def,
93
+ * where the fingerprint's `lr:`/`ir:` part is the constant `null` —
94
+ * see scanSwallowedSubtree. Absent when nothing was swallowed. */
95
+ swallowedDefCtx?: string;
96
+ };
97
+ }
98
+
99
+ /** Dev invariant: every block hast child must retain its mdast `position`. */
100
+ declare function hasMdastSource(node: Element): boolean;
101
+ /**
102
+ * Compute {@link BlockInfo.hastDigest} for a raw-HTML block: iterative walk
103
+ * over the hast subtree collecting (max end offset, descendant count,
104
+ * contains-footnote-section). See the field's JSDoc for why each component
105
+ * is load-bearing. Exported for tests.
106
+ */
107
+ declare function computeHtmlBlockDigest(el: Element, source?: string, ownEnd?: number): string;
108
+ /** {@link computeHtmlBlockDigest} plus the subtree's max end offset — the end
109
+ * of the SWALLOWED extent, which callers need to ask what fell inside.
110
+ *
111
+ * Both position sources here are HAST offsets, and every use of the extent
112
+ * is unsafe in the SHORT direction (too small and a swallowed reference
113
+ * escapes the taint test, so an edit to a definition outside the container
114
+ * leaves a stale href in the cache). They are redundant on purpose: the
115
+ * container's own end already reaches past the swallowed blocks, because
116
+ * `rehype-raw` reparses the raw string and closes the element wherever
117
+ * parse5 does — NOT where the mdast `html` node ends, which is just the
118
+ * opening tag. The descendant walk covers it a second time. Verified in
119
+ * 2026-08-20 A1 across 162 container/tail shapes: destroying either source
120
+ * alone changes nothing, destroying both under-taints 130 of them. */
121
+ declare function computeHtmlBlockDigestWithExtent(el: Element, source?: string, ownEnd?: number): {
122
+ digest: string;
123
+ maxEnd: number;
124
+ };
125
+ /**
126
+ * One step of the document render plan, in source order.
127
+ *
128
+ * The plan covers EVERY top-level hast child so that the adapter’s block cache
129
+ * can preserve react-markdown's byte-equivalent output — including the whitespace
130
+ * `text` nodes that `mdast-util-to-hast` inserts between block elements.
131
+ *
132
+ * - `block`: a hast element with an mdast counterpart, eligible for caching by
133
+ * `(raw, occurrence index, ctx, startOffset)`.
134
+ * - `inline`: a non-element top-level child (whitespace text, comment, …) or
135
+ * an element that has no mdast counterpart but is still safe to render.
136
+ * These are rendered every frame without caching — they are typically
137
+ * single whitespace characters.
138
+ * - `synthetic`: the synthesized footnote `<section data-footnotes>`, cached
139
+ * in a single slot keyed by `globalCtx`.
140
+ */
141
+ type RenderItem = {
142
+ kind: 'block';
143
+ el: Element;
144
+ key: string;
145
+ info: BlockInfo;
146
+ } | {
147
+ kind: 'inline';
148
+ el: RootContent;
149
+ key: string;
150
+ } | {
151
+ kind: 'synthetic';
152
+ el: Element;
153
+ key: string;
154
+ };
155
+ /** Extra render-time facts {@link buildBlocks} cannot read off the trees. */
156
+ interface BuildBlocksOptions {
157
+ /** Full document when planning only a tail. Its ordered reference and
158
+ * definition context seeds numbering and invalidation for tail blocks. */
159
+ contextMdast?: Root;
160
+ /**
161
+ * Labels this chunk phantom-injected because another chunk defines them
162
+ * (already normalized). The coordinated handlers keep these out of
163
+ * `state.footnoteOrder`, so the cache key's rank must keep them out too —
164
+ * see the note in the rank pass. Empty / omitted in standalone mode, where
165
+ * no phantom injection happened.
166
+ */
167
+ phantomFootnoteLabels?: ReadonlySet<string>;
168
+ }
169
+ /** Result of {@link buildBlocks}. */
170
+ interface BuildBlocksResult {
171
+ /** Render plan in document order — drives the adapter’s block cache. */
172
+ plan: RenderItem[];
173
+ /** JSON-stringified ordered list of TAINT-typed nodes in document order. */
174
+ globalCtx: string;
175
+ /** Flat list of `BlockInfo` for cacheable blocks, in document order. */
176
+ blocks: BlockInfo[];
177
+ /** 1:1 with `blocks` — the hast Element each BlockInfo refers to. */
178
+ blockHasts: Element[];
179
+ /** Synthesized footnote section, if present. */
180
+ synthetic?: Element;
181
+ }
182
+ /**
183
+ * Build the document render plan + ctx digest from a parsed mdast and its
184
+ * rendered hast.
185
+ *
186
+ * Plan construction is driven by `hast.children` (NOT `mdast.children`):
187
+ * pipeline transformers like `remarkSqueezeParagraphs` and
188
+ * `remarkRemoveComments` drop blocks, so the rendered hast may have fewer
189
+ * top-level children than the parsed mdast. Driving by hast guarantees the
190
+ * plan covers exactly what will be rendered.
191
+ *
192
+ * Each hast top-level child becomes one plan item:
193
+ * - `<section data-footnotes>` → `synthetic` (cached in a single slot)
194
+ * - element with mdast counterpart → `block` (cacheable by raw + occurrence + ctx + offset)
195
+ * - text / comment / element-without-counterpart → `inline` (rendered fresh every frame)
196
+ *
197
+ * The mdast counterpart of a block element is found by source offset using
198
+ * a two-tier lookup: an exact-offset Map (mdast-util-to-hast's default 1:1
199
+ * propagation), then range-containment `findLast` fallback for cases like
200
+ * `rehype-raw` splitting one mdast `html` node into multiple hast siblings
201
+ * (e.g. ` <div>Hi</div>` — leading spaces shift the `<div>` offset
202
+ * inside the parent html node's source range).
203
+ *
204
+ * In dev, a hast block whose offset matches no mdast counterpart at all
205
+ * throws — that means a rehype plugin synthesized positions outside the
206
+ * source range, which is a bug worth surfacing. In production it falls
207
+ * through to an `inline` plan item so user content is never silently lost.
208
+ *
209
+ * `globalCtx` walks the full mdast for footnote/link/image refs and
210
+ * definitions in document order (no dedupe — order matters for footnote
211
+ * numbering), then JSON-stringifies the collected tuples. That string is the
212
+ * invalidation key for tainted blocks and the synthetic footnote section.
213
+ */
214
+ declare function buildBlocks(mdast: Root, hast: Root$1, source: string, options?: BuildBlocksOptions): BuildBlocksResult;
215
+ /**
216
+ * Compute a per-block cache fingerprint from the registry slice this block
217
+ * actually depends on (footnote/link/image refs and footnote defs). Two blocks
218
+ * with the same fingerprint render byte-equal output; if any encoded value
219
+ * differs, the block must re-render.
220
+ *
221
+ * Encoding format (deterministic, stable across versions):
222
+ * `<clobberPrefix>|fn:<L>=<globalNumber>|fl:<[[L,prior,rank]…]>|lr:<L>=<url>|<title>|ir:<L>=<url>|fd:<L>=<canonical>/<refCount>`
223
+ *
224
+ * @param taintLabels - Per-block label dependency footprint (from BlockInfo.taintLabels).
225
+ * @param registry - Shared cross-chunk registry.
226
+ * @param thisChunkSym- The Symbol of the chunk this rendering belongs to (for canonical-vs-this comparison).
227
+ * @param clobberPrefix - The documentId-derived id prefix; included so href/id changes invalidate.
228
+ */
229
+ declare function computeBlockFingerprint(taintLabels: NonNullable<BlockInfo['taintLabels']>, registry: Registry, thisChunkSym: symbol, clobberPrefix: string): string;
230
+
231
+ /** Reuse the engine's retained prefix plans, including reference blocks.
232
+ * Reference prefixes require full-document context when planning the tail.
233
+ * Raw HTML and definition regions retain complete planning: their ownership
234
+ * need not be one-to-one. Identity checks, not source-text
235
+ * guesses, establish which trees the incremental engine actually retained.
236
+ * Top-level traversal remains O(blocks); reference context still visits the
237
+ * full mdast when needed, while per-block HAST planning is tail-only. */
238
+ declare function createBlockPlanner(): (mdast: Root, hast: Root$1, source: string, options?: BuildBlocksOptions) => BuildBlocksResult;
239
+
240
+ /** Minimal write capability; registration and cleanup remain with the adapter. */
241
+ interface ContributionRegistry {
242
+ contributeChunkData(symbol: symbol, data: ChunkData): void;
243
+ }
244
+ interface ContributionOptions {
245
+ pipeline: {
246
+ mdast: Root;
247
+ hast: Root$1;
248
+ };
249
+ ownLabels: {
250
+ footnoteLabels: Set<string>;
251
+ linkLabels: Set<string>;
252
+ };
253
+ registry: ContributionRegistry | null;
254
+ targetPhantoms: {
255
+ missingFootnotes: Set<string>;
256
+ missingLinks: Set<string>;
257
+ };
258
+ sym: symbol | null;
259
+ clobberPrefix: string;
260
+ /** Stable identity tuple of parse inputs. Source equality alone cannot
261
+ * validate definition bodies after a plugin or schema change. */
262
+ chain: readonly unknown[];
263
+ }
264
+ /** Create one contribution publisher per mounted chunk.
265
+ * Call commit only in the host's commit/effect phase, after registration.
266
+ * Constructing a session does not register or publish anything. A discarded
267
+ * render must never call commit. The host owns registration and cleanup.
268
+ */
269
+ declare function createContributionSession(): {
270
+ commit({ pipeline, ownLabels, registry, targetPhantoms, sym, clobberPrefix, chain }: ContributionOptions): void;
271
+ };
272
+
273
+ /**
274
+ * Turn-taking coordinator for smooth-streaming chunks that share a
275
+ * `documentId` under `<AIMarkdownDocuments>`.
276
+ *
277
+ * Each `useDocumentSmoothStream` chunk registers here in mount order and
278
+ * reports a sticky `done` (source stopped AND reveal drained). A chunk that
279
+ * mounted with empty content stays gated until every REGISTERED chunk
280
+ * earlier in the order is done — so the chunks read as one typewriter:
281
+ * chunk N reveals completely, then chunk N+1 starts.
282
+ *
283
+ * The structure deliberately mirrors `documentRegistry`'s proven patterns
284
+ * (chunkId-keyed refcount with microtask-deferred reclaim for Strict Mode,
285
+ * monotonic version + microtask-coalesced subscriber fanout, `onEmpty`
286
+ * container eviction) without touching the registry itself: the def
287
+ * registry and this coordinator share only the `documentId` scope. Chunks
288
+ * that never register (non-smooth chunks, opted-out chunks) simply do not
289
+ * exist here and can never block a successor; an unmounting chunk leaves
290
+ * the order, so a virtualized-away predecessor releases its successors by
291
+ * construction.
292
+ *
293
+ * `lastProgressAt` is the reveal-progress heartbeat behind the dev-only
294
+ * stuck-flag warning. Writes to it intentionally do NOT bump `version` and
295
+ * do NOT fan out — stamping happens once per reveal frame, and a fanout to
296
+ * every sibling chunk per frame would be a performance bug. Nothing
297
+ * subscribes to it; the pending chunk's warning timer reads it on demand.
298
+ *
299
+ * @module components/smoothStream/coordinator
300
+ */
301
+ interface SmoothCoordinator {
302
+ /** Chunk mount-order chunkId list. **Read-only** — mutation corrupts the
303
+ * release predicate for every successor. */
304
+ readonly order: readonly string[];
305
+ /** Chunk IDs that have reported done. Sticky: entries are only removed
306
+ * when the chunk unregisters. **Read-only.** */
307
+ readonly done: ReadonlySet<string>;
308
+ /** Reveal-progress heartbeat, ms timestamps from the reporting chunk's
309
+ * clock. Written WITHOUT a version bump (see module doc). */
310
+ readonly lastProgressAt: ReadonlyMap<string, number>;
311
+ /** Monotonic counter bumped by every gating-relevant mutation
312
+ * (register / unregister / done transition). */
313
+ readonly version: number;
314
+ subscribe(cb: () => void): () => void;
315
+ /** Allocate (or refcount-reuse, for Strict Mode remounts) this chunk's
316
+ * slot at the current end of the mount order. */
317
+ register(chunkId: string): void;
318
+ /** Refcount decrement with microtask-deferred reclaim; the last release
319
+ * removes the chunk from the order (waking successors) and, when the
320
+ * coordinator empties, fires the container's `onEmpty`. */
321
+ release(chunkId: string): void;
322
+ /** Sticky, idempotent: the first call per chunk bumps the version; the
323
+ * transition is never retracted while the chunk stays mounted (a
324
+ * predecessor that un-finishes must not re-gate successors). */
325
+ markDone(chunkId: string): void;
326
+ /** Heartbeat stamp — no version bump, no fanout. */
327
+ stampProgress(chunkId: string, at: number): void;
328
+ /** True when every registered chunk BEFORE `chunkId` in the order has
329
+ * reported done. Unregistered predecessors don't exist here, so they
330
+ * never block. A `chunkId` that is not (yet) registered itself is
331
+ * treated as sitting AFTER every registered chunk — a pre-registration
332
+ * query must not release a chunk whose predecessors are still
333
+ * revealing. */
334
+ isReleased(chunkId: string): boolean;
335
+ /** The earliest not-done registered chunk before `chunkId`, or null.
336
+ * Read by the dev-only stuck-flag warning timer. */
337
+ earliestBlockerOf(chunkId: string): string | null;
338
+ }
339
+ /** Outcome of one stuck-flag warning timer firing (dev-only diagnostics). */
340
+ type GateWarnVerdict = 'warn' | 'rearm' | 'clear';
341
+ /**
342
+ * The warn / re-arm / clear decision behind the dev-only stuck-flag
343
+ * warning, extracted as a pure function so the judgment is unit-testable
344
+ * in node — the `setTimeout` wiring in `useDocumentSmoothStream` reduces
345
+ * to "call this when the timer fires".
346
+ *
347
+ * - `'clear'`: no blocker left — the release is propagating; stop.
348
+ * - `'rearm'`: the earliest blocker showed reveal progress within the
349
+ * threshold — a slow model still emitting, not a stuck flag; check
350
+ * again later.
351
+ * - `'warn'`: the blocker has made no visible progress for a full
352
+ * threshold while this chunk stayed gated — the predecessor's
353
+ * `streaming` flag is likely stuck.
354
+ */
355
+ declare function evaluateGateWarn(coordinator: SmoothCoordinator, chunkId: string, nowMs: number, thresholdMs: number): GateWarnVerdict;
356
+
357
+ /** Create one turn-taking scope.
358
+ * Why: sibling chunks coordinate reveal without a framework or DOM dependency.
359
+ * Recommended pattern: register after mount, report sticky completion, release on unmount.
360
+ * Footguns: registration order controls reveal; completion cannot be retracted.
361
+ */
362
+ declare function createSmoothCoordinator(onEmpty?: () => void): SmoothCoordinator;
363
+
364
+ /**
365
+ * Source-tail classification for the streaming cursor.
366
+ *
367
+ * The cursor's DOM walk cannot know whether the source tail is in the body
368
+ * or inside a footnote definition — mdast-util-to-hast relocates every
369
+ * definition into the synthetic end-of-document footer, decoupling the
370
+ * footer's DOM position from source position. This module answers the
371
+ * question at the mdast level, where micromark has already made every
372
+ * grammar decision (lazy continuation lines, container nesting, blank-line
373
+ * attachment, label escapes): the renderer derives a signal per frame from
374
+ * the tree it is about to render and stamps it on a DOM marker the cursor
375
+ * shell reads in the same commit.
376
+ *
377
+ * Deliberately NOT a text scan of the trailing lines: a hand-rolled
378
+ * back-walk would be a third parallel grammar in the repo and provably
379
+ * mis-classifies lazy continuations (`[^1]: line\nline2` keeps line2 in
380
+ * the definition with zero indentation), resurfacing the exact bug this
381
+ * exists to fix.
382
+ *
383
+ * @module components/streamingCursor/tailSignal
384
+ */
385
+
386
+ type TailSignal =
387
+ /** The source tail is inside a footnote definition — its text renders in
388
+ * the footnote footer's `<li>` for `identifier` (mdast's case-folded
389
+ * form), not at the body tail. */
390
+ {
391
+ kind: 'footnote-def';
392
+ identifier: string;
393
+ }
394
+ /** The source tail is inside a link-reference definition — it renders
395
+ * nothing at all; the cursor should hide rather than point at stale
396
+ * body text. */
397
+ | {
398
+ kind: 'invisible-def';
399
+ }
400
+ /** The source tail is ordinary body content — the DOM-tail heuristic is
401
+ * correct as-is. */
402
+ | null;
403
+ /**
404
+ * Classify the source tail of `mdast`.
405
+ *
406
+ * @param mdast - The tree the renderer is about to render (post-pipeline
407
+ * parse output; the incremental path's spliced tree carries correct
408
+ * positions on real nodes).
409
+ * @param contentLength - Length of the PREPROCESSED content string the
410
+ * renderer parsed (not the caller's raw prop). Phantom definitions are
411
+ * injected as an appended suffix before parsing, so every node whose
412
+ * start offset sits at or past the real content's end is a phantom — the
413
+ * scan walks backwards past ALL of them (a real trailing def plus
414
+ * several phantoms is a normal shape) and classifies the first real
415
+ * node.
416
+ */
417
+ declare function deriveTailSignal(mdast: Root, contentLength: number): TailSignal;
418
+
419
+ /** Assemble document footnotes as HAST without framework nodes or DOM access. */
420
+
421
+ declare function buildAggregateTree(registry: Registry, clobberPrefix: string, preserveOrphanReferences?: boolean): Element | null;
422
+
423
+ /**
424
+ * Structural clone of a hast subtree for render-time use.
425
+ *
426
+ * Copies exactly what the render-time mutators touch — every node object,
427
+ * its `children` array (the allow/disallow filters splice it), and its
428
+ * `properties` object (`urlTransform` overwrites `href`/`src`) — while
429
+ * SHARING `position`, `data`, string values, and individual property values
430
+ * by reference. This is both cheaper than a JSON round-trip (no string
431
+ * intermediate; `position` objects, typically over half of a serialized
432
+ * hast, are never materialized) and exact for any tree the pipeline
433
+ * produces (no lossy JSON semantics to reason about).
434
+ *
435
+ * @module components/cloneHastForRender
436
+ */
437
+
438
+ /** See module docs. Safe for any hast node union member. */
439
+ declare function cloneHastForRender<T extends Nodes>(node: T): T;
440
+
441
+ type RemarkRehypeOptions = NonNullable<PipelineOptions['remarkRehypeOptions']>;
442
+ interface PipelineFrameOptions {
443
+ content: string;
444
+ targetPhantoms: {
445
+ missingFootnotes: Set<string>;
446
+ missingLinks: Set<string>;
447
+ };
448
+ remarkPlugins: PipelineOptions['remarkPlugins'];
449
+ rehypePlugins: PipelineOptions['rehypePlugins'];
450
+ remarkRehypeOptions: PipelineOptions['remarkRehypeOptions'];
451
+ handlers?: RemarkRehypeOptions['handlers'];
452
+ preserveForBodyHarvest: boolean;
453
+ documentId: string;
454
+ provenance: string;
455
+ /** False for a one-shot server render; no browser-global probe is needed. */
456
+ incrementalParse: boolean;
457
+ defListEnabled: boolean;
458
+ measure?: AdvanceOptions['measure'];
459
+ }
460
+ declare function createPipelineSession(): {
461
+ reset(): void;
462
+ parse({ content, targetPhantoms, remarkPlugins, rehypePlugins, remarkRehypeOptions, handlers, preserveForBodyHarvest, documentId, provenance, incrementalParse, defListEnabled, measure: measureHere, }: PipelineFrameOptions): {
463
+ mdast: mdast.Root;
464
+ hast: hast.Root;
465
+ };
466
+ };
467
+
468
+ interface DefinitionLabels {
469
+ footnoteLabels: ReadonlySet<string>;
470
+ linkLabels: ReadonlySet<string>;
471
+ }
472
+ type PhantomTargets = PipelineFrameOptions['targetPhantoms'];
473
+ declare function derivePhantomTargets({ content, ownLabels, labels }: {
474
+ content: string;
475
+ ownLabels: DefinitionLabels;
476
+ labels: DefinitionLabels | null;
477
+ }, previous?: PhantomTargets): PhantomTargets;
478
+ interface CoordinationPolicy {
479
+ mode: 'coordinated' | 'orphan' | 'standalone';
480
+ handlers: PipelineFrameOptions['handlers'];
481
+ preserveForBodyHarvest: boolean;
482
+ }
483
+ declare function deriveCoordinationPolicy({ coordinated, registered, preserveOrphanReferences, }: {
484
+ coordinated: boolean;
485
+ registered: boolean;
486
+ /** The host has already resolved any wrapper-level override. */
487
+ preserveOrphanReferences: boolean;
488
+ }, previous?: CoordinationPolicy): CoordinationPolicy;
489
+ type ContributionPolicyInputs = Pick<PipelineFrameOptions, 'remarkPlugins' | 'rehypePlugins' | 'remarkRehypeOptions' | 'handlers' | 'preserveForBodyHarvest' | 'documentId' | 'provenance'> & {
490
+ clobberPrefix: string;
491
+ };
492
+ /** Source/phantom/registry/symbol changes are compared by the publisher itself.
493
+ * This tuple covers the remaining inputs that can alter harvested bodies.
494
+ */
495
+ declare function buildContributionChain(inputs: ContributionPolicyInputs): readonly unknown[];
496
+
497
+ export { type BlockInfo, type BuildBlocksOptions, type BuildBlocksResult, type ContributionOptions, type ContributionPolicyInputs, type ContributionRegistry, type CoordinationPolicy, type DefinitionLabels, type GateWarnVerdict, type PhantomTargets, type PipelineFrameOptions, type RenderItem, type SmoothCoordinator, type TailSignal, buildAggregateTree, buildBlocks, buildContributionChain, cloneHastForRender, computeBlockFingerprint, computeHtmlBlockDigest, computeHtmlBlockDigestWithExtent, createBlockPlanner, createContributionSession, createPipelineSession, createSmoothCoordinator, deriveCoordinationPolicy, derivePhantomTargets, deriveTailSignal, evaluateGateWarn, hasMdastSource };