@copilotkit/markdown 2.0.0-rc.19 → 2.0.0-rc.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -7,7 +7,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
7
7
  const DEFAULT_OPTIONS = {
8
8
  segmenter: true,
9
9
  enableTables: true,
10
- enableAutolinks: true
10
+ enableAutolinks: true,
11
+ enableMath: false
11
12
  };
12
13
  /**
13
14
  * Merges user-provided parser options with defaults.
@@ -19,7 +20,8 @@ function normalizeOptions(options) {
19
20
  return {
20
21
  segmenter: options?.segmenter ?? DEFAULT_OPTIONS.segmenter,
21
22
  enableTables: options?.enableTables ?? DEFAULT_OPTIONS.enableTables,
22
- enableAutolinks: options?.enableAutolinks ?? DEFAULT_OPTIONS.enableAutolinks
23
+ enableAutolinks: options?.enableAutolinks ?? DEFAULT_OPTIONS.enableAutolinks,
24
+ enableMath: options?.enableMath ?? DEFAULT_OPTIONS.enableMath
23
25
  };
24
26
  }
25
27
  /**
@@ -665,6 +667,13 @@ function parseInline(text, absoluteStart, path, context) {
665
667
  i = code.next;
666
668
  continue;
667
669
  }
670
+ const math = context.options.enableMath ? parseMathInline(text, i, absoluteStart, path, nodes.length) : null;
671
+ if (math) {
672
+ flushTextBuffer();
673
+ nodes.push(math.node);
674
+ i = math.next;
675
+ continue;
676
+ }
668
677
  const strong = parseDelimitedInline(text, i, "**", "strong", absoluteStart, path, nodes.length, context, citations, warnings, hasWarnedSegmenterUnavailable) ?? parseDelimitedInline(text, i, "__", "strong", absoluteStart, path, nodes.length, context, citations, warnings, hasWarnedSegmenterUnavailable);
669
678
  if (strong) {
670
679
  flushTextBuffer();
@@ -1016,6 +1025,32 @@ function parseInlineCode(text, at, start, path, index) {
1016
1025
  next: close + len
1017
1026
  };
1018
1027
  }
1028
+ const DISPLAY_MATH = /^\$\$([\s\S]+?)\$\$/;
1029
+ const INLINE_MATH = /^\$(?!\s)([^$\n]*?\S)\$(?!\d)/;
1030
+ function parseMathInline(text, at, start, path, index) {
1031
+ if (text[at] !== "$") return null;
1032
+ const rest = text.slice(at);
1033
+ const display = DISPLAY_MATH.exec(rest);
1034
+ const match = display ?? INLINE_MATH.exec(rest);
1035
+ if (!match) return null;
1036
+ return {
1037
+ node: {
1038
+ path: `${path}.${index}`,
1039
+ type: "math",
1040
+ range: {
1041
+ start: start + at,
1042
+ end: start + at + match[0].length
1043
+ },
1044
+ closed: true,
1045
+ props: {
1046
+ text: match[1],
1047
+ display: display !== null
1048
+ },
1049
+ children: []
1050
+ },
1051
+ next: at + match[0].length
1052
+ };
1053
+ }
1019
1054
  function parseDelimitedInline(text, at, delimiter, type, start, path, index, context, citations, warnings, hasWarnedSegmenterUnavailable) {
1020
1055
  if (!text.startsWith(delimiter, at)) return null;
1021
1056
  const close = text.indexOf(delimiter, at + delimiter.length);
@@ -1072,6 +1107,11 @@ function parseDelimitedInline(text, at, delimiter, type, start, path, index, con
1072
1107
  //#endregion
1073
1108
  //#region src/parser/block-parser.ts
1074
1109
  /**
1110
+ * GFM task-list marker at the start of a list item: `[ ]`, `[x]` or `[X]`,
1111
+ * followed by whitespace and then content.
1112
+ */
1113
+ const TASK_MARKER = /^\[([ xX])\][ \t]+(?=\S)/;
1114
+ /**
1075
1115
  * Parses a contiguous range of source lines into block-level draft nodes.
1076
1116
  *
1077
1117
  * @param lines - Line-split source with offsets.
@@ -1344,9 +1384,10 @@ function parseList(lines, from, to, path, context, ordered) {
1344
1384
  const marker = matchListItem(lines[i].text);
1345
1385
  if (!marker || marker.ordered !== ordered) break;
1346
1386
  if (ordered && listStartNumber === null) listStartNumber = marker.start;
1387
+ const task = TASK_MARKER.exec(marker.content);
1347
1388
  const contentLines = [{
1348
1389
  ...lines[i],
1349
- text: marker.content
1390
+ text: task ? marker.content.slice(task[0].length) : marker.content
1350
1391
  }];
1351
1392
  const contentIndent = marker.contentIndent;
1352
1393
  i += 1;
@@ -1401,7 +1442,7 @@ function parseList(lines, from, to, path, context, ordered) {
1401
1442
  end: contentLines[contentLines.length - 1].end
1402
1443
  },
1403
1444
  closed: context.isComplete || contentLines[contentLines.length - 1].hasNewline,
1404
- props: {},
1445
+ props: task ? { checked: task[1] !== " " } : {},
1405
1446
  children: [paragraph]
1406
1447
  });
1407
1448
  citations = parsedInline.citations;
@@ -1676,11 +1717,19 @@ function opToAstNode(op, id, parentId, children) {
1676
1717
  tight: op.props["tight"],
1677
1718
  children
1678
1719
  };
1679
- case "list-item": return {
1680
- ...base,
1681
- type: "list-item",
1682
- children
1683
- };
1720
+ case "list-item": {
1721
+ const checked = op.props["checked"];
1722
+ return checked === void 0 ? {
1723
+ ...base,
1724
+ type: "list-item",
1725
+ children
1726
+ } : {
1727
+ ...base,
1728
+ type: "list-item",
1729
+ checked,
1730
+ children
1731
+ };
1732
+ }
1684
1733
  case "code-block": return {
1685
1734
  ...base,
1686
1735
  type: "code-block",
@@ -1764,6 +1813,12 @@ function opToAstNode(op, id, parentId, children) {
1764
1813
  url: op.props["url"],
1765
1814
  text: op.props["text"]
1766
1815
  };
1816
+ case "math": return {
1817
+ ...base,
1818
+ type: "math",
1819
+ text: op.props["text"],
1820
+ display: op.props["display"]
1821
+ };
1767
1822
  case "citation": return {
1768
1823
  ...base,
1769
1824
  type: "citation",
@@ -2002,12 +2057,14 @@ function isProduction() {
2002
2057
  *
2003
2058
  * No-op in production and after the first warning. Only the built-in default
2004
2059
  * renderers call this — a custom `markdownRenderer` never triggers it.
2060
+ * Pass `{ rendersMath: true }` from a default renderer that typesets math, so
2061
+ * only code blocks are flagged.
2005
2062
  *
2006
2063
  * @internal
2007
2064
  */
2008
- function warnUnsupportedRichSyntaxOnce(content) {
2065
+ function warnUnsupportedRichSyntaxOnce(content, { rendersMath = false } = {}) {
2009
2066
  if (warned || !content || isProduction()) return;
2010
- const hasMath = BLOCK_MATH.test(content) || BRACKET_MATH.test(content);
2067
+ const hasMath = !rendersMath && (BLOCK_MATH.test(content) || BRACKET_MATH.test(content));
2011
2068
  const hasHighlightableCode = HIGHLIGHTABLE_CODE.test(content);
2012
2069
  if (!hasMath && !hasHighlightableCode) return;
2013
2070
  warned = true;
@@ -2016,8 +2073,462 @@ function warnUnsupportedRichSyntaxOnce(content) {
2016
2073
  }
2017
2074
 
2018
2075
  //#endregion
2076
+ //#region src/render/parser-session.ts
2077
+ function getSegmenterKey(segmenter) {
2078
+ if (segmenter === true || segmenter === false) return String(segmenter);
2079
+ return `object:${segmenter.locale ?? ""}:${segmenter.granularity ?? "word"}`;
2080
+ }
2081
+ function getOptionsKey(options) {
2082
+ return `${getSegmenterKey(options.segmenter)}|tables:${String(options.enableTables)}|autolinks:${String(options.enableAutolinks)}|math:${String(options.enableMath)}`;
2083
+ }
2084
+ function parseFullText(text, options, isCompleteInput) {
2085
+ const initialState = createStreamingMarkdownParserState(options);
2086
+ const parsedState = text.length > 0 ? parseStreamingMarkdownChunk(initialState, text) : initialState;
2087
+ return isCompleteInput ? finalizeStreamingMarkdown(parsedState) : parsedState;
2088
+ }
2089
+ /**
2090
+ * Returns the parser session for `text`, reusing `previous` when it can.
2091
+ *
2092
+ * - Same inputs: returns `previous` unchanged.
2093
+ * - Text grew by a suffix: parses only the suffix, so unchanged AST nodes keep
2094
+ * their identity (renderers rely on this to keep DOM and animations stable).
2095
+ * - Text was replaced or options changed: parses from scratch.
2096
+ * - `isComplete` toggled: finalizes (or re-opens) the current state.
2097
+ *
2098
+ * @param previous - The session from the last update, or `null` on first use.
2099
+ * @param text - Full markdown source, which usually grows over time.
2100
+ * @param options - Parser option overrides.
2101
+ * @param isCompleteInput - `true` once the source is final.
2102
+ * @public
2103
+ */
2104
+ function updateStreamingMarkdownParserSession(previous, text, options, isCompleteInput) {
2105
+ const normalized = normalizeOptions(options);
2106
+ const optionsKey = getOptionsKey(normalized);
2107
+ if (previous === null || previous.optionsKey !== optionsKey) return {
2108
+ parserState: parseFullText(text, normalized, isCompleteInput),
2109
+ text,
2110
+ optionsKey,
2111
+ isCompleteInput
2112
+ };
2113
+ const textChanged = text !== previous.text;
2114
+ const completionChanged = previous.isCompleteInput !== isCompleteInput;
2115
+ if (!textChanged && !completionChanged) return previous;
2116
+ if (!textChanged) {
2117
+ const parserState = isCompleteInput ? finalizeStreamingMarkdown(previous.parserState) : parseStreamingMarkdownChunk(previous.parserState, "");
2118
+ return {
2119
+ ...previous,
2120
+ parserState,
2121
+ isCompleteInput
2122
+ };
2123
+ }
2124
+ const suffix = text.startsWith(previous.text) ? text.slice(previous.text.length) : null;
2125
+ const parsed = suffix === null ? parseFullText(text, normalized, false) : parseStreamingMarkdownChunk(previous.parserState, suffix);
2126
+ return {
2127
+ parserState: isCompleteInput ? finalizeStreamingMarkdown(parsed) : parsed,
2128
+ text,
2129
+ optionsKey,
2130
+ isCompleteInput
2131
+ };
2132
+ }
2133
+
2134
+ //#endregion
2135
+ //#region src/render/render-tree.ts
2136
+ /**
2137
+ * Class on the root element every renderer emits.
2138
+ * @public
2139
+ */
2140
+ const STREAMING_MARKDOWN_ROOT_CLASS = "cpk-streaming-markdown-root";
2141
+ /**
2142
+ * Node classes CopilotKit's built-in chat markdown renderers (React, Vue,
2143
+ * Angular) apply, so all three emit the same themed markup.
2144
+ * @public
2145
+ */
2146
+ const CHAT_MARKDOWN_NODE_CLASS_NAMES = { codeBlock: "cpk-markdown-code-block" };
2147
+ const SEGMENT_CLASS = "cpk-streaming-markdown-segment";
2148
+ const CITATION_CLASS = "cpk-streaming-markdown-citation";
2149
+ const CITATION_LABEL_CLASS = "cpk-streaming-markdown-citation-label";
2150
+ const WORD_JOINER = "⁠";
2151
+ /**
2152
+ * Attributes of the default caret element.
2153
+ * @public
2154
+ */
2155
+ const STREAMING_MARKDOWN_CARET_ATTRS = {
2156
+ "aria-hidden": "true",
2157
+ class: "cpk-streaming-markdown-caret",
2158
+ "data-streaming-markdown-caret": "true"
2159
+ };
2160
+ /**
2161
+ * Inline style of the default caret element (camelCase CSS properties).
2162
+ * @public
2163
+ */
2164
+ const STREAMING_MARKDOWN_CARET_STYLE = {
2165
+ display: "inline-block",
2166
+ width: "0.48em",
2167
+ height: "0.48em",
2168
+ marginInlineStart: "0.08em",
2169
+ verticalAlign: "-0.08em",
2170
+ borderRadius: "999px",
2171
+ backgroundColor: "currentColor",
2172
+ opacity: "0.55"
2173
+ };
2174
+ const CITATION_STYLES = `
2175
+ .${STREAMING_MARKDOWN_ROOT_CLASS} .${CITATION_CLASS} {
2176
+ vertical-align: baseline;
2177
+ }
2178
+
2179
+ .${STREAMING_MARKDOWN_ROOT_CLASS} .${CITATION_LABEL_CLASS} {
2180
+ display: inline-flex;
2181
+ align-items: center;
2182
+ justify-content: center;
2183
+ inline-size: 1.4em;
2184
+ block-size: 1.4em;
2185
+ border-radius: 999px;
2186
+ border: 1px solid hsl(0 0% 50% / 0.35);
2187
+ background-color: hsl(0 0% 50% / 0.16);
2188
+ color: inherit;
2189
+ font-size: 0.7em;
2190
+ line-height: 1;
2191
+ font-variant-numeric: tabular-nums;
2192
+ text-decoration: none;
2193
+ transform: translateY(-0.15em);
2194
+ }
2195
+ `;
2196
+ const SEGMENT_FADE_IN_STYLES = `
2197
+ .${STREAMING_MARKDOWN_ROOT_CLASS} .${SEGMENT_CLASS} {
2198
+ opacity: 1;
2199
+ transition: opacity 400ms ease-out;
2200
+ @starting-style {
2201
+ opacity: 0;
2202
+ }
2203
+ }
2204
+ `;
2205
+ const SEGMENT_FALLBACK_STYLES = `
2206
+ .${STREAMING_MARKDOWN_ROOT_CLASS} .${SEGMENT_CLASS} {
2207
+ opacity: 1;
2208
+ transition: opacity 400ms ease-out;
2209
+ }
2210
+ `;
2211
+ /**
2212
+ * Stylesheet every renderer injects next to its root: the per-token fade-in
2213
+ * for text segments and the citation pill. Read at render time, so it is safe
2214
+ * to call during server rendering.
2215
+ *
2216
+ * @public
2217
+ */
2218
+ function getStreamingMarkdownStyles() {
2219
+ return (typeof navigator !== "undefined" && /jsdom/i.test(navigator.userAgent) ? SEGMENT_FALLBACK_STYLES : SEGMENT_FADE_IN_STYLES) + CITATION_STYLES;
2220
+ }
2221
+ const SAFE_HREF = /^(https?:|mailto:|tel:|#|\/|\.\/|\.\.\/)/i;
2222
+ const SAFE_IMG_SRC = /^(https?:|data:image\/(?!svg)|\/|\.\/|\.\.\/)/i;
2223
+ /**
2224
+ * Returns `href` when its scheme is allowed for links, else `undefined`.
2225
+ * @public
2226
+ */
2227
+ function sanitizeStreamingMarkdownHref(href) {
2228
+ if (!href) return void 0;
2229
+ return SAFE_HREF.test(href.trim()) ? href : void 0;
2230
+ }
2231
+ /**
2232
+ * Returns `src` when its scheme is allowed for images, else `undefined`.
2233
+ * @public
2234
+ */
2235
+ function sanitizeStreamingMarkdownImageSrc(src) {
2236
+ if (!src) return void 0;
2237
+ return SAFE_IMG_SRC.test(src.trim()) ? src : void 0;
2238
+ }
2239
+ const RENDERER_KEY_BY_NODE_TYPE = {
2240
+ document: "document",
2241
+ paragraph: "paragraph",
2242
+ heading: "heading",
2243
+ blockquote: "blockquote",
2244
+ list: "list",
2245
+ "list-item": "listItem",
2246
+ "code-block": "codeBlock",
2247
+ table: "table",
2248
+ "table-row": "tableRow",
2249
+ "table-cell": "tableCell",
2250
+ "thematic-break": "thematicBreak",
2251
+ text: "text",
2252
+ em: "em",
2253
+ strong: "strong",
2254
+ strikethrough: "strikethrough",
2255
+ "inline-code": "inlineCode",
2256
+ "soft-break": "softBreak",
2257
+ "hard-break": "hardBreak",
2258
+ image: "image",
2259
+ link: "link",
2260
+ autolink: "autolink",
2261
+ citation: "citation",
2262
+ math: "math"
2263
+ };
2264
+ const SLOT = {
2265
+ kind: "slot",
2266
+ key: "slot"
2267
+ };
2268
+ function indexNodes(state) {
2269
+ return new Map(state.nodes.map((node) => [node.id, node]));
2270
+ }
2271
+ function findOpenNode(stack, nodeById) {
2272
+ for (let index = stack.length - 1; index >= 0; index -= 1) {
2273
+ const candidate = nodeById.get(stack[index]);
2274
+ if (candidate && candidate.type !== "document") return candidate;
2275
+ }
2276
+ return null;
2277
+ }
2278
+ /**
2279
+ * The deepest open (still streaming) node other than the document, or `null`.
2280
+ * This is where the caret goes.
2281
+ *
2282
+ * @public
2283
+ */
2284
+ function findStreamingMarkdownOpenNode(state) {
2285
+ return findOpenNode(state.stack, indexNodes(state));
2286
+ }
2287
+ /**
2288
+ * Citation number and definition for a citation node.
2289
+ *
2290
+ * @public
2291
+ */
2292
+ function getStreamingMarkdownCitationData(node, citations) {
2293
+ return {
2294
+ id: node.idRef,
2295
+ number: node.number ?? citations.numbers[node.idRef] ?? node.idRef,
2296
+ definition: citations.definitions[node.idRef]
2297
+ };
2298
+ }
2299
+ function nodeAttrs(node, context, extra = {}) {
2300
+ const attrs = {};
2301
+ const className = context.nodeClassNames?.[RENDERER_KEY_BY_NODE_TYPE[node.type]];
2302
+ if (className) attrs.class = className;
2303
+ const entries = Object.entries(extra);
2304
+ for (const [name, value] of entries) if (value !== void 0) attrs[name] = value;
2305
+ attrs["data-streaming-markdown-node"] = node.type;
2306
+ attrs["data-node-open"] = String(!node.closed);
2307
+ return attrs;
2308
+ }
2309
+ function element(key, tag, attrs, children, action) {
2310
+ return action ? {
2311
+ kind: "element",
2312
+ key,
2313
+ tag,
2314
+ attrs,
2315
+ children,
2316
+ action
2317
+ } : {
2318
+ kind: "element",
2319
+ key,
2320
+ tag,
2321
+ attrs,
2322
+ children
2323
+ };
2324
+ }
2325
+ function text(key, value) {
2326
+ return {
2327
+ kind: "text",
2328
+ key,
2329
+ text: value
2330
+ };
2331
+ }
2332
+ function caretFor(nodeId, context) {
2333
+ return context.caretTargetNodeId === nodeId ? [{
2334
+ kind: "caret",
2335
+ key: `caret-${nodeId}`
2336
+ }] : [];
2337
+ }
2338
+ function renderNode(node, context, children, output, citation) {
2339
+ const base = {
2340
+ kind: "node",
2341
+ key: String(node.id),
2342
+ node,
2343
+ rendererKey: RENDERER_KEY_BY_NODE_TYPE[node.type],
2344
+ children,
2345
+ output,
2346
+ containsCaret: context.caretPath.has(node.id)
2347
+ };
2348
+ return citation ? {
2349
+ ...base,
2350
+ citation
2351
+ } : base;
2352
+ }
2353
+ function containerChildren(node, context) {
2354
+ return [...node.children.flatMap((childId) => {
2355
+ const child = context.nodeById.get(childId);
2356
+ return child ? [buildNode(child, context)] : [];
2357
+ }), ...caretFor(node.id, context)];
2358
+ }
2359
+ /** A container whose default output is one element wrapping its children. */
2360
+ function wrapped(node, context, tag, extra = {}, action) {
2361
+ const attrs = nodeAttrs(node, context, extra);
2362
+ return renderNode(node, context, containerChildren(node, context), [element(String(node.id), tag, attrs, [SLOT], action)]);
2363
+ }
2364
+ function textSegments(node) {
2365
+ if (node.text.length === 0) return [];
2366
+ if (node.segments.length === 0) return [element(`segment-${node.id}-full`, "span", {
2367
+ class: SEGMENT_CLASS,
2368
+ "data-streaming-markdown-segment-kind": "full",
2369
+ "data-streaming-markdown-whitespace": "false"
2370
+ }, [text("text", node.text)])];
2371
+ return node.segments.map((segment) => element(`segment-${node.id}-${segment.start}-${segment.kind}`, "span", {
2372
+ class: SEGMENT_CLASS,
2373
+ "data-streaming-markdown-segment-kind": segment.kind,
2374
+ "data-streaming-markdown-whitespace": String(segment.isWhitespace)
2375
+ }, [text("text", segment.noBreakBefore ? `${WORD_JOINER}${segment.text}` : segment.text)]));
2376
+ }
2377
+ function taskCheckbox(node, context) {
2378
+ const parent = node.parentId == null ? void 0 : context.nodeById.get(node.parentId);
2379
+ if (!(parent?.type === "list-item" && parent.checked !== void 0 && parent.children[0] === node.id)) return [];
2380
+ const attrs = {
2381
+ type: "checkbox",
2382
+ disabled: true,
2383
+ "data-streaming-markdown-task": "true"
2384
+ };
2385
+ if (parent.checked) attrs.checked = true;
2386
+ return [element(`task-${parent.id}`, "input", attrs, []), text("task-space", " ")];
2387
+ }
2388
+ function buildCitation(node, context) {
2389
+ const citation = getStreamingMarkdownCitationData(node, context.citations);
2390
+ const label = [text("label", String(citation.number))];
2391
+ const href = sanitizeStreamingMarkdownHref(citation.definition?.url);
2392
+ const inner = href ? element("label", "a", {
2393
+ href,
2394
+ target: "_blank",
2395
+ rel: "noopener noreferrer",
2396
+ role: "doc-noteref",
2397
+ class: CITATION_LABEL_CLASS
2398
+ }, label, {
2399
+ type: "citation",
2400
+ citation,
2401
+ node
2402
+ }) : element("label", "span", {
2403
+ role: "doc-noteref",
2404
+ class: CITATION_LABEL_CLASS
2405
+ }, label);
2406
+ const attrs = nodeAttrs(node, context);
2407
+ const supClass = attrs.class ? `${CITATION_CLASS} ${attrs.class}` : CITATION_CLASS;
2408
+ return renderNode(node, context, null, [element(String(node.id), "sup", {
2409
+ ...attrs,
2410
+ class: supClass
2411
+ }, [inner])], citation);
2412
+ }
2413
+ function buildNode(node, context) {
2414
+ const key = String(node.id);
2415
+ switch (node.type) {
2416
+ case "document": return renderNode(node, context, containerChildren(node, context), [SLOT]);
2417
+ case "paragraph": {
2418
+ const attrs = nodeAttrs(node, context);
2419
+ return renderNode(node, context, containerChildren(node, context), [element(key, "p", attrs, [...taskCheckbox(node, context), SLOT])]);
2420
+ }
2421
+ case "heading": return wrapped(node, context, `h${node.level}`);
2422
+ case "blockquote": return wrapped(node, context, "blockquote");
2423
+ case "list": return wrapped(node, context, node.ordered ? "ol" : "ul", {
2424
+ start: node.ordered && node.start != null ? String(node.start) : void 0,
2425
+ "data-list-tight": String(node.tight)
2426
+ });
2427
+ case "list-item": return wrapped(node, context, "li");
2428
+ case "table": {
2429
+ const attrs = nodeAttrs(node, context);
2430
+ return renderNode(node, context, containerChildren(node, context), [element(key, "table", attrs, [element("tbody", "tbody", {}, [SLOT])])]);
2431
+ }
2432
+ case "table-row": return wrapped(node, context, "tr");
2433
+ case "table-cell": {
2434
+ const parent = node.parentId == null ? void 0 : context.nodeById.get(node.parentId);
2435
+ return wrapped(node, context, parent?.type === "table-row" && parent.isHeader ? "th" : "td");
2436
+ }
2437
+ case "em": return wrapped(node, context, "em");
2438
+ case "strong": return wrapped(node, context, "strong");
2439
+ case "strikethrough": return wrapped(node, context, "s");
2440
+ case "link": {
2441
+ const href = sanitizeStreamingMarkdownHref(node.url);
2442
+ return wrapped(node, context, "a", {
2443
+ href,
2444
+ target: "_blank",
2445
+ rel: "noopener noreferrer",
2446
+ title: node.title
2447
+ }, href ? {
2448
+ type: "link",
2449
+ url: href,
2450
+ node
2451
+ } : void 0);
2452
+ }
2453
+ case "code-block": return renderNode(node, context, null, [element(key, "pre", nodeAttrs(node, context), [element("code", "code", node.info ? { "data-code-info": node.info } : {}, [text("text", node.text), ...caretFor(node.id, context)])])]);
2454
+ case "thematic-break": return renderNode(node, context, null, [element(key, "hr", nodeAttrs(node, context), [])]);
2455
+ case "hard-break": return renderNode(node, context, null, [element(key, "br", nodeAttrs(node, context), [])]);
2456
+ case "text": return renderNode(node, context, textSegments(node), [SLOT]);
2457
+ case "soft-break": return renderNode(node, context, [text("text", "\n")], [SLOT]);
2458
+ case "inline-code": return renderNode(node, context, [text("text", node.text)], [element(key, "code", nodeAttrs(node, context), [SLOT])]);
2459
+ case "image": return renderNode(node, context, null, [element(key, "img", nodeAttrs(node, context, {
2460
+ src: sanitizeStreamingMarkdownImageSrc(node.url),
2461
+ alt: node.alt,
2462
+ title: node.title
2463
+ }), [])]);
2464
+ case "autolink": {
2465
+ const href = sanitizeStreamingMarkdownHref(node.url);
2466
+ return renderNode(node, context, [text("text", node.text)], [element(key, "a", nodeAttrs(node, context, {
2467
+ href,
2468
+ target: "_blank",
2469
+ rel: "noopener noreferrer"
2470
+ }), [SLOT], href ? {
2471
+ type: "link",
2472
+ url: href,
2473
+ node
2474
+ } : void 0)]);
2475
+ }
2476
+ case "math": return renderNode(node, context, [text("text", node.text)], [element(key, "span", nodeAttrs(node, context, { "data-math-display": String(node.display) }), [SLOT])]);
2477
+ case "citation": return buildCitation(node, context);
2478
+ default: return node;
2479
+ }
2480
+ }
2481
+ /**
2482
+ * Turns parser state into a framework-neutral render tree: which elements to
2483
+ * create, their sanitized attributes, click actions, text segments for the
2484
+ * per-token animation, and where the caret goes. The React, Vue, and DOM
2485
+ * renderers all render this tree, so markup and sanitization live here only.
2486
+ *
2487
+ * @param state - Parser state from the streaming markdown parser.
2488
+ * @param options - Whether to place a caret, and optional per-node classes.
2489
+ * @returns The root render node (`null` when the state has no root) and the
2490
+ * open node the caret targets.
2491
+ * @public
2492
+ */
2493
+ function buildStreamingMarkdownRenderTree(state, options) {
2494
+ const nodeById = indexNodes(state);
2495
+ const openNode = findOpenNode(state.stack, nodeById);
2496
+ const caretTargetNodeId = options.caret && !state.isComplete && openNode ? openNode.id : null;
2497
+ const caretPath = /* @__PURE__ */ new Set();
2498
+ let cursor = caretTargetNodeId == null ? void 0 : nodeById.get(caretTargetNodeId);
2499
+ while (cursor) {
2500
+ caretPath.add(cursor.id);
2501
+ cursor = cursor.parentId == null ? void 0 : nodeById.get(cursor.parentId);
2502
+ }
2503
+ const context = {
2504
+ nodeById,
2505
+ citations: state.citations,
2506
+ caretTargetNodeId,
2507
+ caretPath,
2508
+ nodeClassNames: options.nodeClassNames
2509
+ };
2510
+ const rootNode = state.rootId == null ? void 0 : nodeById.get(state.rootId);
2511
+ return {
2512
+ root: rootNode ? buildNode(rootNode, context) : null,
2513
+ openNode,
2514
+ isComplete: state.isComplete
2515
+ };
2516
+ }
2517
+
2518
+ //#endregion
2519
+ exports.CHAT_MARKDOWN_NODE_CLASS_NAMES = CHAT_MARKDOWN_NODE_CLASS_NAMES;
2520
+ exports.STREAMING_MARKDOWN_CARET_ATTRS = STREAMING_MARKDOWN_CARET_ATTRS;
2521
+ exports.STREAMING_MARKDOWN_CARET_STYLE = STREAMING_MARKDOWN_CARET_STYLE;
2522
+ exports.STREAMING_MARKDOWN_ROOT_CLASS = STREAMING_MARKDOWN_ROOT_CLASS;
2523
+ exports.buildStreamingMarkdownRenderTree = buildStreamingMarkdownRenderTree;
2019
2524
  exports.createStreamingMarkdownParserState = createStreamingMarkdownParserState;
2020
2525
  exports.finalizeStreamingMarkdown = finalizeStreamingMarkdown;
2526
+ exports.findStreamingMarkdownOpenNode = findStreamingMarkdownOpenNode;
2527
+ exports.getStreamingMarkdownCitationData = getStreamingMarkdownCitationData;
2528
+ exports.getStreamingMarkdownStyles = getStreamingMarkdownStyles;
2021
2529
  exports.parseStreamingMarkdownChunk = parseStreamingMarkdownChunk;
2530
+ exports.sanitizeStreamingMarkdownHref = sanitizeStreamingMarkdownHref;
2531
+ exports.sanitizeStreamingMarkdownImageSrc = sanitizeStreamingMarkdownImageSrc;
2532
+ exports.updateStreamingMarkdownParserSession = updateStreamingMarkdownParserSession;
2022
2533
  exports.warnUnsupportedRichSyntaxOnce = warnUnsupportedRichSyntaxOnce;
2023
2534
  //# sourceMappingURL=index.cjs.map