@urbicon-ui/blocks 6.35.0 → 6.36.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.
Files changed (99) hide show
  1. package/dist/components/Chat/Chat/Chat.svelte +53 -0
  2. package/dist/components/Chat/Chat/Chat.svelte.d.ts +4 -0
  3. package/dist/components/Chat/Chat/chat.variants.d.ts +20 -0
  4. package/dist/components/Chat/Chat/chat.variants.js +15 -0
  5. package/dist/components/Chat/Chat/index.d.ts +47 -0
  6. package/dist/components/Chat/Chat/index.js +2 -0
  7. package/dist/components/Chat/ChatMessage/ChatMessage.svelte +342 -0
  8. package/dist/components/Chat/ChatMessage/ChatMessage.svelte.d.ts +4 -0
  9. package/dist/components/Chat/ChatMessage/chat-message.variants.d.ts +138 -0
  10. package/dist/components/Chat/ChatMessage/chat-message.variants.js +118 -0
  11. package/dist/components/Chat/ChatMessage/index.d.ts +102 -0
  12. package/dist/components/Chat/ChatMessage/index.js +2 -0
  13. package/dist/components/Chat/ChatMessageList/ChatMessageList.svelte +301 -0
  14. package/dist/components/Chat/ChatMessageList/ChatMessageList.svelte.d.ts +6 -0
  15. package/dist/components/Chat/ChatMessageList/chat-message-list.variants.d.ts +23 -0
  16. package/dist/components/Chat/ChatMessageList/chat-message-list.variants.js +32 -0
  17. package/dist/components/Chat/ChatMessageList/chat-scroll.d.ts +45 -0
  18. package/dist/components/Chat/ChatMessageList/chat-scroll.js +52 -0
  19. package/dist/components/Chat/ChatMessageList/index.d.ts +93 -0
  20. package/dist/components/Chat/ChatMessageList/index.js +3 -0
  21. package/dist/components/Chat/CitationChip/CitationChip.svelte +107 -0
  22. package/dist/components/Chat/CitationChip/CitationChip.svelte.d.ts +4 -0
  23. package/dist/components/Chat/CitationChip/citation-chip.variants.d.ts +40 -0
  24. package/dist/components/Chat/CitationChip/citation-chip.variants.js +42 -0
  25. package/dist/components/Chat/CitationChip/index.d.ts +69 -0
  26. package/dist/components/Chat/CitationChip/index.js +2 -0
  27. package/dist/components/Chat/CodeBlock/CodeBlock.svelte +120 -0
  28. package/dist/components/Chat/CodeBlock/CodeBlock.svelte.d.ts +4 -0
  29. package/dist/components/Chat/CodeBlock/code-block.variants.d.ts +40 -0
  30. package/dist/components/Chat/CodeBlock/code-block.variants.js +51 -0
  31. package/dist/components/Chat/CodeBlock/index.d.ts +55 -0
  32. package/dist/components/Chat/CodeBlock/index.js +2 -0
  33. package/dist/components/Chat/PromptInput/PromptInput.svelte +432 -0
  34. package/dist/components/Chat/PromptInput/PromptInput.svelte.d.ts +4 -0
  35. package/dist/components/Chat/PromptInput/index.d.ts +165 -0
  36. package/dist/components/Chat/PromptInput/index.js +2 -0
  37. package/dist/components/Chat/PromptInput/prompt-input.variants.d.ts +90 -0
  38. package/dist/components/Chat/PromptInput/prompt-input.variants.js +102 -0
  39. package/dist/components/Chat/ReasoningDisclosure/ReasoningDisclosure.svelte +107 -0
  40. package/dist/components/Chat/ReasoningDisclosure/ReasoningDisclosure.svelte.d.ts +4 -0
  41. package/dist/components/Chat/ReasoningDisclosure/index.d.ts +85 -0
  42. package/dist/components/Chat/ReasoningDisclosure/index.js +2 -0
  43. package/dist/components/Chat/ReasoningDisclosure/reasoning-disclosure.variants.d.ts +20 -0
  44. package/dist/components/Chat/ReasoningDisclosure/reasoning-disclosure.variants.js +31 -0
  45. package/dist/components/Chat/StreamingMarkdown/MdBlock.svelte +99 -0
  46. package/dist/components/Chat/StreamingMarkdown/MdBlock.svelte.d.ts +9 -0
  47. package/dist/components/Chat/StreamingMarkdown/MdInline.svelte +82 -0
  48. package/dist/components/Chat/StreamingMarkdown/MdInline.svelte.d.ts +9 -0
  49. package/dist/components/Chat/StreamingMarkdown/StreamingMarkdown.svelte +118 -0
  50. package/dist/components/Chat/StreamingMarkdown/StreamingMarkdown.svelte.d.ts +4 -0
  51. package/dist/components/Chat/StreamingMarkdown/index.d.ts +86 -0
  52. package/dist/components/Chat/StreamingMarkdown/index.js +2 -0
  53. package/dist/components/Chat/StreamingMarkdown/md-context.d.ts +58 -0
  54. package/dist/components/Chat/StreamingMarkdown/md-context.js +1 -0
  55. package/dist/components/Chat/StreamingMarkdown/streaming-markdown.variants.d.ts +145 -0
  56. package/dist/components/Chat/StreamingMarkdown/streaming-markdown.variants.js +73 -0
  57. package/dist/components/Chat/ToolCallCard/ToolCallCard.svelte +174 -0
  58. package/dist/components/Chat/ToolCallCard/ToolCallCard.svelte.d.ts +4 -0
  59. package/dist/components/Chat/ToolCallCard/index.d.ts +84 -0
  60. package/dist/components/Chat/ToolCallCard/index.js +2 -0
  61. package/dist/components/Chat/ToolCallCard/tool-call-card.variants.d.ts +38 -0
  62. package/dist/components/Chat/ToolCallCard/tool-call-card.variants.js +38 -0
  63. package/dist/components/Chat/chat.types.d.ts +82 -0
  64. package/dist/components/Chat/chat.types.js +7 -0
  65. package/dist/components/Chat/index.d.ts +22 -0
  66. package/dist/components/Chat/index.js +14 -0
  67. package/dist/components/Chat/markdown/blocks.d.ts +54 -0
  68. package/dist/components/Chat/markdown/blocks.js +462 -0
  69. package/dist/components/Chat/markdown/inline.d.ts +31 -0
  70. package/dist/components/Chat/markdown/inline.js +446 -0
  71. package/dist/components/Chat/markdown/repair.d.ts +23 -0
  72. package/dist/components/Chat/markdown/repair.js +337 -0
  73. package/dist/components/Chat/markdown/streaming-harness.d.ts +23 -0
  74. package/dist/components/Chat/markdown/streaming-harness.js +62 -0
  75. package/dist/components/Chat/markdown/types.d.ts +139 -0
  76. package/dist/components/Chat/markdown/types.js +13 -0
  77. package/dist/components/Chat/markdown/url-policy.d.ts +33 -0
  78. package/dist/components/Chat/markdown/url-policy.js +109 -0
  79. package/dist/components/FileUpload/FileUpload.svelte +35 -123
  80. package/dist/components/FileUpload/FileUpload.svelte.d.ts +1 -1
  81. package/dist/components/FileUpload/index.d.ts +2 -28
  82. package/dist/components/index.d.ts +1 -0
  83. package/dist/components/index.js +1 -0
  84. package/dist/primitives/Collapsible/Collapsible.svelte +5 -0
  85. package/dist/primitives/SplitPane/SplitPane.svelte +293 -0
  86. package/dist/primitives/SplitPane/SplitPane.svelte.d.ts +4 -0
  87. package/dist/primitives/SplitPane/index.d.ts +88 -0
  88. package/dist/primitives/SplitPane/index.js +2 -0
  89. package/dist/primitives/SplitPane/split-pane.utils.d.ts +73 -0
  90. package/dist/primitives/SplitPane/split-pane.utils.js +94 -0
  91. package/dist/primitives/SplitPane/split-pane.variants.d.ts +40 -0
  92. package/dist/primitives/SplitPane/split-pane.variants.js +56 -0
  93. package/dist/primitives/index.d.ts +2 -0
  94. package/dist/primitives/index.js +1 -0
  95. package/dist/utils/file-intake.d.ts +78 -0
  96. package/dist/utils/file-intake.js +144 -0
  97. package/dist/utils/index.d.ts +1 -0
  98. package/dist/utils/index.js +1 -0
  99. package/package.json +3 -3
@@ -0,0 +1,462 @@
1
+ import { parseInlines } from './inline.js';
2
+ import { repairMarkdownTail } from './repair.js';
3
+ function splitLines(text) {
4
+ const lines = [];
5
+ let start = 0;
6
+ for (let i = 0; i <= text.length; i += 1) {
7
+ if (i === text.length || text[i] === '\n') {
8
+ lines.push({ text: text.slice(start, i), start });
9
+ start = i + 1;
10
+ }
11
+ }
12
+ return lines;
13
+ }
14
+ const FENCE_OPEN = /^ {0,3}(```+)\s*([^\s`]*)\s*$/;
15
+ const HEADING = /^ {0,3}(#{1,6})\s+(.*?)(?:\s+#+\s*)?$/;
16
+ const HR = /^ {0,3}([-*_])(?:\s*\1){2,}\s*$/;
17
+ const QUOTE = /^ {0,3}>\s?/;
18
+ const LIST_MARKER = /^(\s*)([-*+]|\d{1,9}[.)])(\s+|$)/;
19
+ const TABLE_DELIMITER = /^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/;
20
+ const REF_DEF = /^ {0,3}\[([^\]]+)\]:\s*(\S+)(?:\s+["'(](.*)["')])?\s*$/;
21
+ function isBlank(line) {
22
+ return line.trim().length === 0;
23
+ }
24
+ export function scanBlocks(text) {
25
+ const lines = splitLines(text);
26
+ const blocks = [];
27
+ const refs = new Map();
28
+ let unstableRefLabel;
29
+ let i = 0;
30
+ while (i < lines.length) {
31
+ const line = lines[i];
32
+ if (isBlank(line.text)) {
33
+ i += 1;
34
+ continue;
35
+ }
36
+ const fence = FENCE_OPEN.exec(line.text);
37
+ if (fence) {
38
+ const marker = fence[1];
39
+ const code = [];
40
+ let open = true;
41
+ let j = i + 1;
42
+ for (; j < lines.length; j += 1) {
43
+ const closing = /^ {0,3}(```+)\s*$/.exec(lines[j].text);
44
+ if (closing && closing[1].length >= marker.length) {
45
+ open = false;
46
+ j += 1;
47
+ break;
48
+ }
49
+ code.push(lines[j].text);
50
+ }
51
+ blocks.push({ type: 'fence', start: line.start, lang: fence[2] || undefined, code, open });
52
+ i = j;
53
+ continue;
54
+ }
55
+ const heading = HEADING.exec(line.text);
56
+ if (heading) {
57
+ blocks.push({
58
+ type: 'heading',
59
+ start: line.start,
60
+ level: heading[1].length,
61
+ text: heading[2]
62
+ });
63
+ i += 1;
64
+ continue;
65
+ }
66
+ if (HR.test(line.text) && !LIST_MARKER.test(line.text)) {
67
+ blocks.push({ type: 'hr', start: line.start });
68
+ i += 1;
69
+ continue;
70
+ }
71
+ if (QUOTE.test(line.text)) {
72
+ const quoteLines = [];
73
+ let j = i;
74
+ while (j < lines.length && QUOTE.test(lines[j].text)) {
75
+ quoteLines.push(lines[j].text.replace(QUOTE, ''));
76
+ j += 1;
77
+ }
78
+ blocks.push({ type: 'quote', start: line.start, lines: quoteLines });
79
+ i = j;
80
+ continue;
81
+ }
82
+ const listMatch = LIST_MARKER.exec(line.text);
83
+ if (listMatch && !HR.test(line.text)) {
84
+ const baseIndent = listMatch[1].length;
85
+ const ordered = /\d/.test(listMatch[2]);
86
+ const startNumber = ordered ? Number.parseInt(listMatch[2], 10) : undefined;
87
+ const listLines = [];
88
+ let j = i;
89
+ let pendingBlanks = 0;
90
+ while (j < lines.length) {
91
+ const current = lines[j].text;
92
+ if (isBlank(current)) {
93
+ pendingBlanks += 1;
94
+ j += 1;
95
+ continue;
96
+ }
97
+ const marker = LIST_MARKER.exec(current);
98
+ const indent = /^\s*/.exec(current)?.[0].length ?? 0;
99
+ const isSameKindMarker = marker && marker[1].length <= baseIndent + 1 && /\d/.test(marker[2]) === ordered;
100
+ const isContinuation = indent >= baseIndent + 2;
101
+ const isLazy = pendingBlanks === 0 && listLines.length > 0 && !marker;
102
+ if (isSameKindMarker || isContinuation || (isLazy && indent > 0) || (isLazy && !marker)) {
103
+ for (; pendingBlanks > 0; pendingBlanks -= 1)
104
+ listLines.push('');
105
+ listLines.push(current);
106
+ j += 1;
107
+ continue;
108
+ }
109
+ break;
110
+ }
111
+ blocks.push({ type: 'list', start: line.start, ordered, startNumber, lines: listLines });
112
+ i = j;
113
+ continue;
114
+ }
115
+ const refDef = REF_DEF.exec(line.text);
116
+ if (refDef) {
117
+ const label = refDef[1].trim().toLowerCase();
118
+ // First definition wins (CommonMark) — critical for stream/one-shot parity.
119
+ if (!refs.has(label)) {
120
+ refs.set(label, { href: refDef[2], title: refDef[3] });
121
+ // `splitLines` emits a trailing empty line when the text ends in \n,
122
+ // so index === length-1 means this line is still unterminated.
123
+ if (i === lines.length - 1)
124
+ unstableRefLabel = label;
125
+ }
126
+ i += 1;
127
+ continue;
128
+ }
129
+ if (line.text.includes('|') &&
130
+ i + 1 < lines.length &&
131
+ TABLE_DELIMITER.test(lines[i + 1].text)) {
132
+ const rows = [];
133
+ let j = i + 2;
134
+ while (j < lines.length && !isBlank(lines[j].text) && lines[j].text.includes('|')) {
135
+ rows.push(lines[j].text);
136
+ j += 1;
137
+ }
138
+ blocks.push({
139
+ type: 'table',
140
+ start: line.start,
141
+ header: line.text,
142
+ delimiter: lines[i + 1].text,
143
+ rows
144
+ });
145
+ i = j;
146
+ continue;
147
+ }
148
+ // Paragraph: consecutive non-blank lines that start no other block type.
149
+ const paragraphLines = [line.text];
150
+ let j = i + 1;
151
+ while (j < lines.length) {
152
+ const current = lines[j].text;
153
+ if (isBlank(current) ||
154
+ FENCE_OPEN.test(current) ||
155
+ HEADING.test(current) ||
156
+ HR.test(current) ||
157
+ QUOTE.test(current) ||
158
+ LIST_MARKER.exec(current) ||
159
+ (current.includes('|') && j + 1 < lines.length && TABLE_DELIMITER.test(lines[j + 1].text))) {
160
+ break;
161
+ }
162
+ paragraphLines.push(current);
163
+ j += 1;
164
+ }
165
+ blocks.push({ type: 'paragraph', start: line.start, lines: paragraphLines });
166
+ i = j;
167
+ }
168
+ return { blocks, refs, unstableRefLabel };
169
+ }
170
+ function materialize(raw, ctx) {
171
+ switch (raw.type) {
172
+ case 'paragraph':
173
+ return { kind: 'paragraph', children: parseInlines(raw.lines.join('\n'), ctx) };
174
+ case 'heading':
175
+ return { kind: 'heading', level: raw.level, children: parseInlines(raw.text, ctx) };
176
+ case 'fence':
177
+ return {
178
+ kind: 'code-block',
179
+ lang: raw.lang,
180
+ code: raw.code.join('\n'),
181
+ ...(raw.open ? { open: true } : {})
182
+ };
183
+ case 'hr':
184
+ return { kind: 'hr' };
185
+ case 'quote': {
186
+ const inner = scanBlocks(raw.lines.join('\n'));
187
+ return { kind: 'blockquote', children: inner.blocks.map((b) => materialize(b, ctx)) };
188
+ }
189
+ case 'table':
190
+ return materializeTable(raw, ctx);
191
+ case 'list':
192
+ return materializeList(raw, ctx);
193
+ }
194
+ }
195
+ const TASK_MARKER = /^\[([ xX])\]\s+/;
196
+ function materializeList(raw, ctx) {
197
+ const items = [];
198
+ let currentContent = null;
199
+ let currentChecked;
200
+ let contentIndent = 2;
201
+ const flush = () => {
202
+ if (currentContent === null)
203
+ return;
204
+ const inner = scanBlocks(currentContent.join('\n'));
205
+ items.push({
206
+ children: inner.blocks.map((b) => materialize(b, ctx)),
207
+ ...(currentChecked !== undefined ? { checked: currentChecked } : {})
208
+ });
209
+ };
210
+ for (const line of raw.lines) {
211
+ const marker = LIST_MARKER.exec(line);
212
+ const indent = /^\s*/.exec(line)?.[0].length ?? 0;
213
+ if (marker &&
214
+ indent <= (items.length === 0 && currentContent === null ? indent : contentIndent - 1)) {
215
+ flush();
216
+ contentIndent = marker[1].length + marker[2].length + (marker[3]?.length || 1);
217
+ let rest = line.slice(marker[0].length);
218
+ const task = TASK_MARKER.exec(rest);
219
+ currentChecked = undefined;
220
+ if (task) {
221
+ currentChecked = task[1] !== ' ';
222
+ rest = rest.slice(task[0].length);
223
+ }
224
+ currentContent = [rest];
225
+ }
226
+ else if (currentContent) {
227
+ currentContent.push(line.slice(Math.min(indent, contentIndent)));
228
+ }
229
+ }
230
+ flush();
231
+ return {
232
+ kind: 'list',
233
+ ordered: raw.ordered,
234
+ ...(raw.ordered && raw.startNumber !== undefined && raw.startNumber !== 1
235
+ ? { start: raw.startNumber }
236
+ : {}),
237
+ items
238
+ };
239
+ }
240
+ function splitTableRow(row) {
241
+ const cells = [];
242
+ let current = '';
243
+ for (let i = 0; i < row.length; i += 1) {
244
+ const ch = row[i];
245
+ if (ch === '\\' && row[i + 1] === '|') {
246
+ current += '|';
247
+ i += 1;
248
+ }
249
+ else if (ch === '|') {
250
+ cells.push(current);
251
+ current = '';
252
+ }
253
+ else {
254
+ current += ch;
255
+ }
256
+ }
257
+ cells.push(current);
258
+ // Leading/trailing pipes produce empty edge cells — drop them.
259
+ if (cells.length > 0 && cells[0].trim() === '')
260
+ cells.shift();
261
+ if (cells.length > 0 && cells[cells.length - 1].trim() === '')
262
+ cells.pop();
263
+ return cells.map((c) => c.trim());
264
+ }
265
+ function materializeTable(raw, ctx) {
266
+ const align = splitTableRow(raw.delimiter).map((cell) => {
267
+ const left = cell.startsWith(':');
268
+ const right = cell.endsWith(':');
269
+ if (left && right)
270
+ return 'center';
271
+ if (right)
272
+ return 'right';
273
+ if (left)
274
+ return 'left';
275
+ return null;
276
+ });
277
+ const header = splitTableRow(raw.header).map((cell) => parseInlines(cell, ctx));
278
+ const rows = raw.rows.map((row) => splitTableRow(row).map((cell) => parseInlines(cell, ctx)));
279
+ return { kind: 'table', align, header, rows };
280
+ }
281
+ // ── Normalization (streaming-safe CRLF handling) ─────────────────────────────
282
+ function normalizeChunk(chunk, prevEndedWithCR) {
283
+ let text = chunk;
284
+ if (prevEndedWithCR && text.startsWith('\n'))
285
+ text = text.slice(1);
286
+ const endsWithCR = text.endsWith('\r');
287
+ text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
288
+ return { text, endsWithCR };
289
+ }
290
+ // ── One-shot parse ───────────────────────────────────────────────────────────
291
+ export function parseMarkdown(text, options = {}) {
292
+ const normalized = normalizeChunk(text, false).text;
293
+ const { blocks: rawBlocks, refs } = scanBlocks(normalized);
294
+ const ctx = { linkRefs: refs, options };
295
+ const blocks = rawBlocks.map((raw, index) => Object.assign(materialize(raw, ctx), { key: index }));
296
+ return { blocks, linkRefs: refs };
297
+ }
298
+ // ── Incremental parser ───────────────────────────────────────────────────────
299
+ /** Blocks kept unsettled at the end of the stream (see module docstring). */
300
+ const TAIL_BUFFER = 2;
301
+ export function createIncrementalParser(options = {}) {
302
+ let raw = '';
303
+ let norm = '';
304
+ let endsWithCR = false;
305
+ let settledUpTo = 0;
306
+ let settled = [];
307
+ let settledRefs = new Map();
308
+ let unresolvedLabels = new Set();
309
+ let nextKey = 0;
310
+ let doc = { blocks: [], linkRefs: settledRefs };
311
+ function mergeRefs(tailRefs) {
312
+ const merged = new Map(settledRefs);
313
+ for (const [label, def] of tailRefs) {
314
+ if (!merged.has(label))
315
+ merged.set(label, def);
316
+ }
317
+ return merged;
318
+ }
319
+ function compute() {
320
+ const tail = norm.slice(settledUpTo);
321
+ const scan = scanBlocks(tail);
322
+ const merged = mergeRefs(scan.refs);
323
+ // Late reference definition that a settled block failed to resolve →
324
+ // re-parse everything, reconciling keys so the UI patches in place. An
325
+ // unstable definition (final unterminated line) does not count as
326
+ // arrived yet — invalidating on it would freeze a half-streamed URL.
327
+ let invalidated = false;
328
+ for (const label of unresolvedLabels) {
329
+ if (scan.refs.has(label) && label !== scan.unstableRefLabel && !settledRefs.has(label)) {
330
+ invalidated = true;
331
+ break;
332
+ }
333
+ }
334
+ if (invalidated) {
335
+ const previous = doc.blocks;
336
+ settledUpTo = 0;
337
+ settled = [];
338
+ settledRefs = new Map();
339
+ unresolvedLabels = new Set();
340
+ nextKey = 0;
341
+ computeFresh(previous);
342
+ return;
343
+ }
344
+ computeTail(tail, scan, merged);
345
+ }
346
+ function computeFresh(previousBlocks) {
347
+ const tail = norm;
348
+ const scan = scanBlocks(tail);
349
+ computeTail(tail, scan, mergeRefs(scan.refs));
350
+ // Key reconciliation by position: same-kind blocks keep their old key.
351
+ let maxKey = -1;
352
+ doc.blocks.forEach((block, index) => {
353
+ const old = previousBlocks[index];
354
+ if (old && old.kind === block.kind)
355
+ block.key = old.key;
356
+ maxKey = Math.max(maxKey, block.key);
357
+ });
358
+ nextKey = Math.max(nextKey, maxKey + 1);
359
+ }
360
+ function computeTail(tail, scan, merged) {
361
+ const rawBlocks = scan.blocks;
362
+ // Settle everything before the second-to-last block. Never settle while
363
+ // the raw text ends in a bare CR (its normalization could still change).
364
+ if (rawBlocks.length > TAIL_BUFFER && !endsWithCR) {
365
+ const settleCount = rawBlocks.length - TAIL_BUFFER;
366
+ const boundary = rawBlocks[settleCount].start;
367
+ // Settling must never bake in a half-streamed definition — resolve
368
+ // against stable refs only; the unresolved hook plus the invalidation
369
+ // path above pick the block up once the definition completes.
370
+ const stableRefs = scan.unstableRefLabel === undefined ? merged : new Map(merged);
371
+ if (scan.unstableRefLabel !== undefined && !settledRefs.has(scan.unstableRefLabel)) {
372
+ stableRefs.delete(scan.unstableRefLabel);
373
+ }
374
+ const settleCtx = {
375
+ linkRefs: stableRefs,
376
+ options,
377
+ onUnresolvedRef: (label) => unresolvedLabels.add(label)
378
+ };
379
+ for (let k = 0; k < settleCount; k += 1) {
380
+ // A settling block must keep the key it was *displayed* under, or the
381
+ // keyed {#each} remounts it. In the steady state nextKey coincides
382
+ // with the shown key by construction, but after a computeFresh
383
+ // reconciliation the shown keys lag behind nextKey — so always prefer
384
+ // the previous document's key at this position (same kind).
385
+ const prevShown = doc.blocks[settled.length + k];
386
+ const block = materialize(rawBlocks[k], settleCtx);
387
+ block.key = prevShown && prevShown.kind === block.kind ? prevShown.key : nextKey;
388
+ nextKey = Math.max(nextKey, block.key + 1);
389
+ settled.push(block);
390
+ }
391
+ // Reference definitions inside the settled region become permanent.
392
+ const settledRegion = scanBlocks(tail.slice(0, boundary));
393
+ for (const [label, def] of settledRegion.refs) {
394
+ if (!settledRefs.has(label))
395
+ settledRefs.set(label, def);
396
+ }
397
+ settledUpTo += boundary;
398
+ computeTail(norm.slice(settledUpTo), scanBlocks(norm.slice(settledUpTo)), merged);
399
+ return;
400
+ }
401
+ // Display path: repair only the last block's slice, then parse the buffer.
402
+ let displayText = tail;
403
+ if (rawBlocks.length > 0) {
404
+ const lastStart = rawBlocks[rawBlocks.length - 1].start;
405
+ displayText = tail.slice(0, lastStart) + repairMarkdownTail(tail.slice(lastStart));
406
+ }
407
+ const displayScan = scanBlocks(displayText);
408
+ const displayRefs = mergeRefs(displayScan.refs);
409
+ const ctx = { linkRefs: displayRefs, options };
410
+ // Tail keys mirror the settle path: reuse the key each position was last
411
+ // displayed under (kept stable across computeFresh reconciliation); fresh
412
+ // positions draw collision-free keys above nextKey without persisting the
413
+ // counter — the same position deterministically redraws the same key.
414
+ const usedKeys = new Set(settled.map((block) => block.key));
415
+ let alloc = nextKey;
416
+ const takeFresh = () => {
417
+ while (usedKeys.has(alloc))
418
+ alloc += 1;
419
+ const key = alloc;
420
+ alloc += 1;
421
+ return key;
422
+ };
423
+ const tailBlocks = displayScan.blocks.map((rawBlock, index) => {
424
+ const block = materialize(rawBlock, ctx);
425
+ const prevShown = doc.blocks[settled.length + index];
426
+ block.key =
427
+ prevShown && prevShown.kind === block.kind && !usedKeys.has(prevShown.key)
428
+ ? prevShown.key
429
+ : takeFresh();
430
+ usedKeys.add(block.key);
431
+ return block;
432
+ });
433
+ doc = { blocks: [...settled, ...tailBlocks], linkRefs: displayRefs };
434
+ }
435
+ return {
436
+ append(chunk) {
437
+ raw += chunk;
438
+ const normalized = normalizeChunk(chunk, endsWithCR);
439
+ norm += normalized.text;
440
+ endsWithCR = normalized.endsWithCR;
441
+ compute();
442
+ return doc;
443
+ },
444
+ get document() {
445
+ return doc;
446
+ },
447
+ get source() {
448
+ return raw;
449
+ },
450
+ reset() {
451
+ raw = '';
452
+ norm = '';
453
+ endsWithCR = false;
454
+ settledUpTo = 0;
455
+ settled = [];
456
+ settledRefs = new Map();
457
+ unresolvedLabels = new Set();
458
+ nextKey = 0;
459
+ doc = { blocks: [], linkRefs: settledRefs };
460
+ }
461
+ };
462
+ }
@@ -0,0 +1,31 @@
1
+ import type { InlineNode, MarkdownParseOptions } from './types.js';
2
+ /**
3
+ * Inline parser for the streaming-markdown subset: code spans, links/images
4
+ * (inline + reference + shortcut), citations, emphasis (`*`/`_`), strong,
5
+ * strikethrough (`~~`), hard breaks, optional autolinks. Raw HTML is not a
6
+ * concept here — angle brackets are plain text (decision A2).
7
+ *
8
+ * Two passes: tokenize into a flat list (code/link/image/citation/break/text
9
+ * and emphasis delimiter runs), then match delimiter runs into a tree. The
10
+ * delimiter rules are CommonMark-lite: `*` needs non-space on the inner side,
11
+ * `_` additionally must not sit inside a word, `~~` only pairs as a double.
12
+ */
13
+ export interface InlineContext {
14
+ linkRefs: ReadonlyMap<string, {
15
+ href: string;
16
+ title?: string;
17
+ }>;
18
+ options: MarkdownParseOptions;
19
+ /**
20
+ * Fired for `[label][ref]` / `[label]` candidates whose definition is not
21
+ * (yet) known. The incremental parser uses this to know which settled
22
+ * blocks must be invalidated when a late `[ref]: url` definition streams in.
23
+ */
24
+ onUnresolvedRef?: (label: string) => void;
25
+ }
26
+ export declare function parseInlines(input: string, ctx: InlineContext): InlineNode[];
27
+ /** Parse citation markers of the form 【id】 (some models emit fullwidth brackets). */
28
+ export declare function tryFullwidthCitation(input: string, at: number, ctx: InlineContext): {
29
+ node: InlineNode;
30
+ end: number;
31
+ } | null;