@ai-matrx/content-ir 0.15.1 → 0.16.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/source.d.cts CHANGED
@@ -118,6 +118,16 @@ interface TokenizeOptions {
118
118
  * Omit for stored text: a complete document's last line is final.
119
119
  */
120
120
  readonly streaming?: boolean;
121
+ /**
122
+ * DIAGNOSTIC — complexity tests only. When given, every loop that walks the
123
+ * text itself adds the characters it visits to `meter.work`, so a test can
124
+ * assert the tokenizer's work grows linearly with its input (deterministic,
125
+ * unlike wall-clock timing). Loops over a slice are not charged here: the
126
+ * slice's own cost covers them. Omit in production.
127
+ */
128
+ readonly meter?: {
129
+ work: number;
130
+ };
121
131
  }
122
132
  declare function tokenizeSource(text: string, options?: TokenizeOptions): SourceBlock[];
123
133
  /** The exact source the blocks were cut from. */
@@ -433,6 +443,14 @@ declare function classifyInnerFenceLine(trimmed: string, openTicks: number, nest
433
443
  */
434
444
  declare function looksLikeDisplayMath(inner: string, flow?: boolean): boolean;
435
445
  /** Map of `$$` opener offset → closer offset for every pair the renderer shows as math. */
446
+ /**
447
+ * STREAMING: the last `$$` token that is still unpaired AND could still pair
448
+ * with a `$$` not yet arrived — its span so far is short enough, carries no
449
+ * markdown structure, not too many prose words, and (unless it opens a flow
450
+ * block) no blank line. Everything after it may yet become math, so a
451
+ * streaming tokenizer treats it as provisional. -1 when there is none.
452
+ */
453
+ declare function pendingDisplayMathOpener(text: string): number;
436
454
  declare function pairDisplayMath(text: string): Map<number, number>;
437
455
 
438
- export { type BlockIslandType, CALLOUT_NAMES, type CodePointIndex, type DisturbedIsland, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, type InlineIslandType, type InnerFenceLine, type IslandMeta, type IslandType, type MapOptions, type MappedPosition, type MappedRange, NESTING_FENCE_LANGUAGES, type ReadXmlTag, type SourceBlock, type SourceBlockKind, type SourceChange, type SourceEdit, type SourceIsland, type SourceSpan, SourceSpliceError, type SourceSpliceErrorCode, type SpliceIntegrity, type SpliceOptions, type SpliceResult, type TokenizeOptions, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, parseFenceOpener, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource, trimFenceLine };
456
+ export { type BlockIslandType, CALLOUT_NAMES, type CodePointIndex, type DisturbedIsland, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, type InlineIslandType, type InnerFenceLine, type IslandMeta, type IslandType, type MapOptions, type MappedPosition, type MappedRange, NESTING_FENCE_LANGUAGES, type ReadXmlTag, type SourceBlock, type SourceBlockKind, type SourceChange, type SourceEdit, type SourceIsland, type SourceSpan, SourceSpliceError, type SourceSpliceErrorCode, type SpliceIntegrity, type SpliceOptions, type SpliceResult, type TokenizeOptions, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, parseFenceOpener, pendingDisplayMathOpener, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource, trimFenceLine };
package/dist/source.d.ts CHANGED
@@ -118,6 +118,16 @@ interface TokenizeOptions {
118
118
  * Omit for stored text: a complete document's last line is final.
119
119
  */
120
120
  readonly streaming?: boolean;
121
+ /**
122
+ * DIAGNOSTIC — complexity tests only. When given, every loop that walks the
123
+ * text itself adds the characters it visits to `meter.work`, so a test can
124
+ * assert the tokenizer's work grows linearly with its input (deterministic,
125
+ * unlike wall-clock timing). Loops over a slice are not charged here: the
126
+ * slice's own cost covers them. Omit in production.
127
+ */
128
+ readonly meter?: {
129
+ work: number;
130
+ };
121
131
  }
122
132
  declare function tokenizeSource(text: string, options?: TokenizeOptions): SourceBlock[];
123
133
  /** The exact source the blocks were cut from. */
@@ -433,6 +443,14 @@ declare function classifyInnerFenceLine(trimmed: string, openTicks: number, nest
433
443
  */
434
444
  declare function looksLikeDisplayMath(inner: string, flow?: boolean): boolean;
435
445
  /** Map of `$$` opener offset → closer offset for every pair the renderer shows as math. */
446
+ /**
447
+ * STREAMING: the last `$$` token that is still unpaired AND could still pair
448
+ * with a `$$` not yet arrived — its span so far is short enough, carries no
449
+ * markdown structure, not too many prose words, and (unless it opens a flow
450
+ * block) no blank line. Everything after it may yet become math, so a
451
+ * streaming tokenizer treats it as provisional. -1 when there is none.
452
+ */
453
+ declare function pendingDisplayMathOpener(text: string): number;
436
454
  declare function pairDisplayMath(text: string): Map<number, number>;
437
455
 
438
- export { type BlockIslandType, CALLOUT_NAMES, type CodePointIndex, type DisturbedIsland, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, type InlineIslandType, type InnerFenceLine, type IslandMeta, type IslandType, type MapOptions, type MappedPosition, type MappedRange, NESTING_FENCE_LANGUAGES, type ReadXmlTag, type SourceBlock, type SourceBlockKind, type SourceChange, type SourceEdit, type SourceIsland, type SourceSpan, SourceSpliceError, type SourceSpliceErrorCode, type SpliceIntegrity, type SpliceOptions, type SpliceResult, type TokenizeOptions, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, parseFenceOpener, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource, trimFenceLine };
456
+ export { type BlockIslandType, CALLOUT_NAMES, type CodePointIndex, type DisturbedIsland, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, type InlineIslandType, type InnerFenceLine, type IslandMeta, type IslandType, type MapOptions, type MappedPosition, type MappedRange, NESTING_FENCE_LANGUAGES, type ReadXmlTag, type SourceBlock, type SourceBlockKind, type SourceChange, type SourceEdit, type SourceIsland, type SourceSpan, SourceSpliceError, type SourceSpliceErrorCode, type SpliceIntegrity, type SpliceOptions, type SpliceResult, type TokenizeOptions, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, parseFenceOpener, pendingDisplayMathOpener, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource, trimFenceLine };
package/dist/source.js CHANGED
@@ -346,6 +346,50 @@ function codeRanges(text) {
346
346
  }
347
347
  return ranges.sort((a, b) => a[0] - b[0]);
348
348
  }
349
+ function pendingDisplayMathOpener(text) {
350
+ if (!text.includes("$$")) return -1;
351
+ const ranges = codeRanges(text);
352
+ const tokens = [];
353
+ let r = 0;
354
+ let maxEnd = -1;
355
+ for (let i = text.indexOf("$$"); i !== -1 && i < text.length - 1; i = text.indexOf("$$", i)) {
356
+ while (r < ranges.length && ranges[r][0] <= i) {
357
+ maxEnd = Math.max(maxEnd, ranges[r][1]);
358
+ r++;
359
+ }
360
+ if (maxEnd <= i) tokens.push(i);
361
+ i += 2;
362
+ }
363
+ const paired = /* @__PURE__ */ new Set();
364
+ let j = 0;
365
+ while (j < tokens.length) {
366
+ const open = tokens[j];
367
+ const close = tokens[j + 1];
368
+ if (close === void 0) break;
369
+ if (looksLikeDisplayMath(text.slice(open + 2, close), opensFlowBlock(text, open))) {
370
+ paired.add(open);
371
+ paired.add(close);
372
+ j += 2;
373
+ continue;
374
+ }
375
+ j += 1;
376
+ }
377
+ let trail = 0;
378
+ while (trail < text.length && /\s/.test(text[text.length - 1 - trail])) trail++;
379
+ for (const token of tokens) {
380
+ if (paired.has(token)) continue;
381
+ let lead = 0;
382
+ while (lead <= MAX_MATH_SPAN && /\s/.test(text[token + 2 + lead] ?? "x")) lead++;
383
+ if (text.length - (token + 2) - lead - trail > MAX_MATH_SPAN) continue;
384
+ const inner = text.slice(token + 2);
385
+ const s = inner.trim();
386
+ if (STRUCTURAL_MARKDOWN.test(s) || s.length > MAX_MATH_SPAN) continue;
387
+ if (!opensFlowBlock(text, token) && /\n[ \t]*\n/.test(inner)) continue;
388
+ if ((s.match(PROSE_WORD) ?? []).length >= PROSE_WORD_LIMIT * 3) continue;
389
+ return token;
390
+ }
391
+ return -1;
392
+ }
349
393
  function pairDisplayMath(text) {
350
394
  const pairs = /* @__PURE__ */ new Map();
351
395
  if (!text.includes("$$")) return pairs;
@@ -694,11 +738,15 @@ function isSplitterTreeLine(line) {
694
738
  }
695
739
  var MEDIA_REF_PREFIXES = ["[Image URL:", "[Video URL:", "[Audio URL:"];
696
740
  var Tokenizer = class {
697
- constructor(text, streaming = false) {
741
+ constructor(text, streaming = false, meter = void 0) {
698
742
  this.text = text;
699
743
  this.streaming = streaming;
744
+ this.meter = meter;
700
745
  this.n = text.length;
746
+ this.tick(text.length);
701
747
  this.mathPairs = pairDisplayMath(text);
748
+ this.provisionalFrom = streaming ? pendingDisplayMathOpener(text) : -1;
749
+ if (this.provisionalFrom === -1) this.provisionalFrom = Number.POSITIVE_INFINITY;
702
750
  let start = 0;
703
751
  for (let i = 0; i < text.length; i++) {
704
752
  if (text.charCodeAt(i) === 10) {
@@ -709,13 +757,15 @@ var Tokenizer = class {
709
757
  }
710
758
  }
711
759
  if (start < text.length || text.length === 0) {
760
+ this.pendingCR = streaming && text.length > start && text.charCodeAt(text.length - 1) === 13;
712
761
  this.lineStarts.push(start);
713
- this.contentEnds.push(text.length);
762
+ this.contentEnds.push(this.pendingCR ? text.length - 1 : text.length);
714
763
  this.lineEnds.push(text.length);
715
764
  }
716
765
  }
717
766
  text;
718
767
  streaming;
768
+ meter;
719
769
  n;
720
770
  lineStarts = [];
721
771
  contentEnds = [];
@@ -730,6 +780,23 @@ var Tokenizer = class {
730
780
  /** `$$` opener → closer for the pairs the core renders as math. */
731
781
  mathPairs;
732
782
  finder = (needle, from) => this.next(needle, from);
783
+ /** Streaming: where provisional text starts (a pending `$$`), or +∞. */
784
+ provisionalFrom = Number.POSITIVE_INFINITY;
785
+ /** Streaming: the text ends in a lone CR whose LF has not arrived yet. */
786
+ pendingCR = false;
787
+ /**
788
+ * Streaming: an open last line that begins with `{` is undecided — each new
789
+ * byte can balance or unbalance its braces and flip the whole line between
790
+ * prose and a JSON island — so it is provisional until it ends (G5).
791
+ */
792
+ markOpenJsonTail() {
793
+ const last = this.lineStarts.length - 1;
794
+ if (!this.isOpenTail(last)) return;
795
+ const start = this.lineStarts[last];
796
+ if (this.lineText(last).trimStart().startsWith("{")) {
797
+ this.provisionalFrom = Math.min(this.provisionalFrom, start);
798
+ }
799
+ }
733
800
  /**
734
801
  * `text.indexOf(needle, from)` in O(log k): the first query for a needle
735
802
  * indexes all of its occurrences once, so repeated searches for a closer
@@ -753,9 +820,21 @@ var Tokenizer = class {
753
820
  }
754
821
  return lo < list.length ? list[lo] : -1;
755
822
  }
823
+ /** Charge `n` visited characters to the diagnostic meter (complexity tests). */
824
+ tick(n) {
825
+ if (this.meter) this.meter.work += n;
826
+ }
827
+ /** Streaming only: a paragraph ending at `pe` may still be continued by a line not yet arrived. */
828
+ paragraphMayGrow(pe) {
829
+ if (!this.streaming) return false;
830
+ const rest = this.n - pe;
831
+ if (rest === 0) return true;
832
+ const tail = this.text.slice(pe);
833
+ return tail === "\n" || tail === "\r\n" || tail === "\r";
834
+ }
756
835
  /** Streaming only: the final line, still waiting for its terminator. */
757
836
  isOpenTail(k) {
758
- return this.streaming && k === this.lineStarts.length - 1 && this.lineEnds[k] === this.contentEnds[k];
837
+ return this.streaming && k === this.lineStarts.length - 1 && (this.lineEnds[k] === this.contentEnds[k] || this.pendingCR);
759
838
  }
760
839
  get lineCount() {
761
840
  return this.lineStarts.length;
@@ -780,6 +859,7 @@ var Tokenizer = class {
780
859
  return this.text.slice(this.lineStarts[li], this.contentEnds[li]);
781
860
  }
782
861
  run() {
862
+ if (this.streaming) this.markOpenJsonTail();
783
863
  let pos = 0;
784
864
  while (pos < this.n) {
785
865
  const li = this.lineOf(pos);
@@ -788,6 +868,10 @@ var Tokenizer = class {
788
868
  pos = this.gap(pos, li);
789
869
  continue;
790
870
  }
871
+ if (pos >= this.provisionalFrom) {
872
+ this.push({ kind: "prose", start: pos, end: this.n, island: null, inlines: [] });
873
+ break;
874
+ }
791
875
  const island = this.detectBlock(pos, li, true);
792
876
  if (island) {
793
877
  this.push({ kind: "island", start: island.start, end: island.end, island, inlines: [] });
@@ -991,6 +1075,7 @@ var Tokenizer = class {
991
1075
  continue;
992
1076
  }
993
1077
  if (FOOTNOTE_DEF.test(line.trimStart()) || this.detectBlock(this.lineStarts[k], k, false)) break;
1078
+ if (this.isOpenTail(k)) break;
994
1079
  last = k;
995
1080
  }
996
1081
  return this.island("footnote_def", p, this.contentEnds[last], true, { id: open[1] });
@@ -1152,6 +1237,7 @@ var Tokenizer = class {
1152
1237
  const li = this.lineOf(tp);
1153
1238
  const ce = this.contentEnds[li];
1154
1239
  const tag = readXmlTag(this.text, tp, this.finder);
1240
+ this.tick(tag ? tag.raw.length : 1);
1155
1241
  let openEnd;
1156
1242
  if (tag && !tag.isClosing) {
1157
1243
  if (tag.isSelfClosing) {
@@ -1236,7 +1322,7 @@ var Tokenizer = class {
1236
1322
  return this.island("html_block", p, close.index + close[0].length, true, { tag: name });
1237
1323
  }
1238
1324
  const typeSix = HTML_BLOCK_LEVEL_TAGS.has(name);
1239
- const typeSeven = atParaStart && t.trimEnd() === tag.raw;
1325
+ const typeSeven = atParaStart && t.trimEnd() === tag.raw && !this.isOpenTail(li);
1240
1326
  if (!typeSix && !typeSeven) return null;
1241
1327
  let end = this.contentEnds[li];
1242
1328
  for (let k = li + 1; k < this.lineCount && !this.lineIsBlank(k); k++) {
@@ -1328,6 +1414,10 @@ var Tokenizer = class {
1328
1414
  let breakLine = -1;
1329
1415
  for (let k = li + 1; k < this.lineCount; k++) {
1330
1416
  if (this.lineIsBlank(k)) break;
1417
+ if (this.lineStarts[k] >= this.provisionalFrom) {
1418
+ end = this.n;
1419
+ break;
1420
+ }
1331
1421
  if (this.detectBlock(this.lineStarts[k], k, false)) {
1332
1422
  breakLine = k;
1333
1423
  break;
@@ -1393,6 +1483,7 @@ var Tokenizer = class {
1393
1483
  scanInline(ps, pe) {
1394
1484
  const text = this.text;
1395
1485
  const inlines = [];
1486
+ this.tick(pe - ps);
1396
1487
  const hasKind = text.slice(ps, pe).includes("__kind");
1397
1488
  let kindBudget = 4 * (pe - ps) + 65536;
1398
1489
  const stop = (island, orphan = false) => ({ inlines, breakIsland: island, orphan });
@@ -1403,6 +1494,7 @@ var Tokenizer = class {
1403
1494
  let runs = null;
1404
1495
  const codeSpanClose = (n, from) => {
1405
1496
  if (!runs) {
1497
+ this.tick(pe - ps);
1406
1498
  runs = /* @__PURE__ */ new Map();
1407
1499
  for (let k = ps; k < pe; ) {
1408
1500
  if (text.charCodeAt(k) !== 96) {
@@ -1429,7 +1521,8 @@ var Tokenizer = class {
1429
1521
  return at !== void 0 && at + n <= pe ? at : -1;
1430
1522
  };
1431
1523
  let i = ps;
1432
- while (i < pe) {
1524
+ const scanEnd = Math.min(pe, this.provisionalFrom);
1525
+ while (i < scanEnd) {
1433
1526
  const ch = text[i];
1434
1527
  if (ch === "\\") {
1435
1528
  const nextCh = text[i + 1];
@@ -1447,6 +1540,9 @@ var Tokenizer = class {
1447
1540
  if (ch === "`") {
1448
1541
  const n = backtickRunLength(text, i);
1449
1542
  const close = codeSpanClose(n, i + n);
1543
+ if (close === -1 && this.streaming && this.paragraphMayGrow(pe)) {
1544
+ return { inlines, breakIsland: null, orphan: false };
1545
+ }
1450
1546
  i = close === -1 ? i + n : close + n;
1451
1547
  continue;
1452
1548
  }
@@ -1463,6 +1559,7 @@ var Tokenizer = class {
1463
1559
  continue;
1464
1560
  }
1465
1561
  const end = singleDollarMathEnd(text, i, pe);
1562
+ this.tick(end > 0 ? end - i : Math.min(403, pe - i));
1466
1563
  if (end > 0 && !(this.streaming && end === this.n)) {
1467
1564
  inlines.push(this.island("math_inline", i, end, true, { delimiter: "$" }));
1468
1565
  i = end;
@@ -1490,6 +1587,7 @@ var Tokenizer = class {
1490
1587
  const limit = Math.min(pe, i + 65536);
1491
1588
  const end = matchingJsonObjectEnd(text, i, limit);
1492
1589
  kindBudget -= (end ?? limit) - i;
1590
+ this.tick((end ?? limit) - i);
1493
1591
  if (end !== null) {
1494
1592
  const kind = declaredKind(text.slice(i, end));
1495
1593
  if (kind) {
@@ -1551,6 +1649,7 @@ var Tokenizer = class {
1551
1649
  continue;
1552
1650
  }
1553
1651
  const tag = readXmlTag(text, i, this.finder);
1652
+ this.tick(tag ? tag.raw.length : 1);
1554
1653
  if (tag && i + tag.raw.length <= pe) {
1555
1654
  const name = tag.tagName;
1556
1655
  const tagEnd = i + tag.raw.length;
@@ -1599,7 +1698,7 @@ var Tokenizer = class {
1599
1698
  }
1600
1699
  };
1601
1700
  function tokenizeSource(text, options = {}) {
1602
- const raw = new Tokenizer(text, options.streaming ?? false).run();
1701
+ const raw = new Tokenizer(text, options.streaming ?? false, options.meter).run();
1603
1702
  const cp = buildCodePointIndex(text);
1604
1703
  const inline = (island) => ({
1605
1704
  islandType: island.type,
@@ -1952,6 +2051,6 @@ function mapRange(changes, start, end, unit = "utf16") {
1952
2051
  return { start: newStart, end: newEnd, touched, collapsed: newEnd === newStart && end > start };
1953
2052
  }
1954
2053
 
1955
- export { CALLOUT_NAMES, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, NESTING_FENCE_LANGUAGES, SourceSpliceError, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, parseFenceOpener, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource, trimFenceLine };
2054
+ export { CALLOUT_NAMES, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, NESTING_FENCE_LANGUAGES, SourceSpliceError, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, parseFenceOpener, pendingDisplayMathOpener, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource, trimFenceLine };
1956
2055
  //# sourceMappingURL=source.js.map
1957
2056
  //# sourceMappingURL=source.js.map