@ai-matrx/content-ir 0.15.0 → 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.cjs CHANGED
@@ -321,15 +321,20 @@ var STRUCTURAL_MARKDOWN = /\]\(|https?:\/\/|\*\*|(?:^|\n)[ \t]{0,3}#{1,6}[ \t]|(
321
321
  var LATEX_COMMAND = /\\[a-zA-Z]/;
322
322
  var PROSE_WORD = /[A-Za-z]{3,}/g;
323
323
  var PROSE_WORD_LIMIT = 6;
324
- function looksLikeDisplayMath(inner) {
324
+ function opensFlowBlock(text, open) {
325
+ for (let k = open - 1; k >= 0 && text[k] !== "\n"; k--) if (text[k] !== " " && text[k] !== " ") return false;
326
+ for (let k = open + 2; k < text.length && text[k] !== "\n"; k++) if (text[k] !== " " && text[k] !== " " && text[k] !== "\r") return false;
327
+ return true;
328
+ }
329
+ function looksLikeDisplayMath(inner, flow = false) {
325
330
  const s = inner.trim();
326
331
  if (!s) return false;
327
332
  if (STRUCTURAL_MARKDOWN.test(s)) return false;
328
333
  if (s.length > MAX_MATH_SPAN) return false;
334
+ if (!flow && /\n[ \t]*\n/.test(inner)) return false;
329
335
  if (LATEX_COMMAND.test(s)) {
330
336
  return (s.match(PROSE_WORD) ?? []).length < PROSE_WORD_LIMIT * 3;
331
337
  }
332
- if (/\n[ \t]*\n/.test(s)) return false;
333
338
  return (s.match(PROSE_WORD) ?? []).length < PROSE_WORD_LIMIT;
334
339
  }
335
340
  function codeRanges(text) {
@@ -343,6 +348,50 @@ function codeRanges(text) {
343
348
  }
344
349
  return ranges.sort((a, b) => a[0] - b[0]);
345
350
  }
351
+ function pendingDisplayMathOpener(text) {
352
+ if (!text.includes("$$")) return -1;
353
+ const ranges = codeRanges(text);
354
+ const tokens = [];
355
+ let r = 0;
356
+ let maxEnd = -1;
357
+ for (let i = text.indexOf("$$"); i !== -1 && i < text.length - 1; i = text.indexOf("$$", i)) {
358
+ while (r < ranges.length && ranges[r][0] <= i) {
359
+ maxEnd = Math.max(maxEnd, ranges[r][1]);
360
+ r++;
361
+ }
362
+ if (maxEnd <= i) tokens.push(i);
363
+ i += 2;
364
+ }
365
+ const paired = /* @__PURE__ */ new Set();
366
+ let j = 0;
367
+ while (j < tokens.length) {
368
+ const open = tokens[j];
369
+ const close = tokens[j + 1];
370
+ if (close === void 0) break;
371
+ if (looksLikeDisplayMath(text.slice(open + 2, close), opensFlowBlock(text, open))) {
372
+ paired.add(open);
373
+ paired.add(close);
374
+ j += 2;
375
+ continue;
376
+ }
377
+ j += 1;
378
+ }
379
+ let trail = 0;
380
+ while (trail < text.length && /\s/.test(text[text.length - 1 - trail])) trail++;
381
+ for (const token of tokens) {
382
+ if (paired.has(token)) continue;
383
+ let lead = 0;
384
+ while (lead <= MAX_MATH_SPAN && /\s/.test(text[token + 2 + lead] ?? "x")) lead++;
385
+ if (text.length - (token + 2) - lead - trail > MAX_MATH_SPAN) continue;
386
+ const inner = text.slice(token + 2);
387
+ const s = inner.trim();
388
+ if (STRUCTURAL_MARKDOWN.test(s) || s.length > MAX_MATH_SPAN) continue;
389
+ if (!opensFlowBlock(text, token) && /\n[ \t]*\n/.test(inner)) continue;
390
+ if ((s.match(PROSE_WORD) ?? []).length >= PROSE_WORD_LIMIT * 3) continue;
391
+ return token;
392
+ }
393
+ return -1;
394
+ }
346
395
  function pairDisplayMath(text) {
347
396
  const pairs = /* @__PURE__ */ new Map();
348
397
  if (!text.includes("$$")) return pairs;
@@ -363,7 +412,7 @@ function pairDisplayMath(text) {
363
412
  const open = tokens[j];
364
413
  const close = tokens[j + 1];
365
414
  if (close === void 0) break;
366
- if (looksLikeDisplayMath(text.slice(open + 2, close))) {
415
+ if (looksLikeDisplayMath(text.slice(open + 2, close), opensFlowBlock(text, open))) {
367
416
  pairs.set(open, close);
368
417
  j += 2;
369
418
  continue;
@@ -691,11 +740,15 @@ function isSplitterTreeLine(line) {
691
740
  }
692
741
  var MEDIA_REF_PREFIXES = ["[Image URL:", "[Video URL:", "[Audio URL:"];
693
742
  var Tokenizer = class {
694
- constructor(text, streaming = false) {
743
+ constructor(text, streaming = false, meter = void 0) {
695
744
  this.text = text;
696
745
  this.streaming = streaming;
746
+ this.meter = meter;
697
747
  this.n = text.length;
748
+ this.tick(text.length);
698
749
  this.mathPairs = pairDisplayMath(text);
750
+ this.provisionalFrom = streaming ? pendingDisplayMathOpener(text) : -1;
751
+ if (this.provisionalFrom === -1) this.provisionalFrom = Number.POSITIVE_INFINITY;
699
752
  let start = 0;
700
753
  for (let i = 0; i < text.length; i++) {
701
754
  if (text.charCodeAt(i) === 10) {
@@ -706,13 +759,15 @@ var Tokenizer = class {
706
759
  }
707
760
  }
708
761
  if (start < text.length || text.length === 0) {
762
+ this.pendingCR = streaming && text.length > start && text.charCodeAt(text.length - 1) === 13;
709
763
  this.lineStarts.push(start);
710
- this.contentEnds.push(text.length);
764
+ this.contentEnds.push(this.pendingCR ? text.length - 1 : text.length);
711
765
  this.lineEnds.push(text.length);
712
766
  }
713
767
  }
714
768
  text;
715
769
  streaming;
770
+ meter;
716
771
  n;
717
772
  lineStarts = [];
718
773
  contentEnds = [];
@@ -727,6 +782,23 @@ var Tokenizer = class {
727
782
  /** `$$` opener → closer for the pairs the core renders as math. */
728
783
  mathPairs;
729
784
  finder = (needle, from) => this.next(needle, from);
785
+ /** Streaming: where provisional text starts (a pending `$$`), or +∞. */
786
+ provisionalFrom = Number.POSITIVE_INFINITY;
787
+ /** Streaming: the text ends in a lone CR whose LF has not arrived yet. */
788
+ pendingCR = false;
789
+ /**
790
+ * Streaming: an open last line that begins with `{` is undecided — each new
791
+ * byte can balance or unbalance its braces and flip the whole line between
792
+ * prose and a JSON island — so it is provisional until it ends (G5).
793
+ */
794
+ markOpenJsonTail() {
795
+ const last = this.lineStarts.length - 1;
796
+ if (!this.isOpenTail(last)) return;
797
+ const start = this.lineStarts[last];
798
+ if (this.lineText(last).trimStart().startsWith("{")) {
799
+ this.provisionalFrom = Math.min(this.provisionalFrom, start);
800
+ }
801
+ }
730
802
  /**
731
803
  * `text.indexOf(needle, from)` in O(log k): the first query for a needle
732
804
  * indexes all of its occurrences once, so repeated searches for a closer
@@ -750,9 +822,21 @@ var Tokenizer = class {
750
822
  }
751
823
  return lo < list.length ? list[lo] : -1;
752
824
  }
825
+ /** Charge `n` visited characters to the diagnostic meter (complexity tests). */
826
+ tick(n) {
827
+ if (this.meter) this.meter.work += n;
828
+ }
829
+ /** Streaming only: a paragraph ending at `pe` may still be continued by a line not yet arrived. */
830
+ paragraphMayGrow(pe) {
831
+ if (!this.streaming) return false;
832
+ const rest = this.n - pe;
833
+ if (rest === 0) return true;
834
+ const tail = this.text.slice(pe);
835
+ return tail === "\n" || tail === "\r\n" || tail === "\r";
836
+ }
753
837
  /** Streaming only: the final line, still waiting for its terminator. */
754
838
  isOpenTail(k) {
755
- return this.streaming && k === this.lineStarts.length - 1 && this.lineEnds[k] === this.contentEnds[k];
839
+ return this.streaming && k === this.lineStarts.length - 1 && (this.lineEnds[k] === this.contentEnds[k] || this.pendingCR);
756
840
  }
757
841
  get lineCount() {
758
842
  return this.lineStarts.length;
@@ -777,6 +861,7 @@ var Tokenizer = class {
777
861
  return this.text.slice(this.lineStarts[li], this.contentEnds[li]);
778
862
  }
779
863
  run() {
864
+ if (this.streaming) this.markOpenJsonTail();
780
865
  let pos = 0;
781
866
  while (pos < this.n) {
782
867
  const li = this.lineOf(pos);
@@ -785,6 +870,10 @@ var Tokenizer = class {
785
870
  pos = this.gap(pos, li);
786
871
  continue;
787
872
  }
873
+ if (pos >= this.provisionalFrom) {
874
+ this.push({ kind: "prose", start: pos, end: this.n, island: null, inlines: [] });
875
+ break;
876
+ }
788
877
  const island = this.detectBlock(pos, li, true);
789
878
  if (island) {
790
879
  this.push({ kind: "island", start: island.start, end: island.end, island, inlines: [] });
@@ -988,6 +1077,7 @@ var Tokenizer = class {
988
1077
  continue;
989
1078
  }
990
1079
  if (FOOTNOTE_DEF.test(line.trimStart()) || this.detectBlock(this.lineStarts[k], k, false)) break;
1080
+ if (this.isOpenTail(k)) break;
991
1081
  last = k;
992
1082
  }
993
1083
  return this.island("footnote_def", p, this.contentEnds[last], true, { id: open[1] });
@@ -1149,6 +1239,7 @@ var Tokenizer = class {
1149
1239
  const li = this.lineOf(tp);
1150
1240
  const ce = this.contentEnds[li];
1151
1241
  const tag = readXmlTag(this.text, tp, this.finder);
1242
+ this.tick(tag ? tag.raw.length : 1);
1152
1243
  let openEnd;
1153
1244
  if (tag && !tag.isClosing) {
1154
1245
  if (tag.isSelfClosing) {
@@ -1233,7 +1324,7 @@ var Tokenizer = class {
1233
1324
  return this.island("html_block", p, close.index + close[0].length, true, { tag: name });
1234
1325
  }
1235
1326
  const typeSix = HTML_BLOCK_LEVEL_TAGS.has(name);
1236
- const typeSeven = atParaStart && t.trimEnd() === tag.raw;
1327
+ const typeSeven = atParaStart && t.trimEnd() === tag.raw && !this.isOpenTail(li);
1237
1328
  if (!typeSix && !typeSeven) return null;
1238
1329
  let end = this.contentEnds[li];
1239
1330
  for (let k = li + 1; k < this.lineCount && !this.lineIsBlank(k); k++) {
@@ -1325,6 +1416,10 @@ var Tokenizer = class {
1325
1416
  let breakLine = -1;
1326
1417
  for (let k = li + 1; k < this.lineCount; k++) {
1327
1418
  if (this.lineIsBlank(k)) break;
1419
+ if (this.lineStarts[k] >= this.provisionalFrom) {
1420
+ end = this.n;
1421
+ break;
1422
+ }
1328
1423
  if (this.detectBlock(this.lineStarts[k], k, false)) {
1329
1424
  breakLine = k;
1330
1425
  break;
@@ -1390,6 +1485,7 @@ var Tokenizer = class {
1390
1485
  scanInline(ps, pe) {
1391
1486
  const text = this.text;
1392
1487
  const inlines = [];
1488
+ this.tick(pe - ps);
1393
1489
  const hasKind = text.slice(ps, pe).includes("__kind");
1394
1490
  let kindBudget = 4 * (pe - ps) + 65536;
1395
1491
  const stop = (island, orphan = false) => ({ inlines, breakIsland: island, orphan });
@@ -1400,6 +1496,7 @@ var Tokenizer = class {
1400
1496
  let runs = null;
1401
1497
  const codeSpanClose = (n, from) => {
1402
1498
  if (!runs) {
1499
+ this.tick(pe - ps);
1403
1500
  runs = /* @__PURE__ */ new Map();
1404
1501
  for (let k = ps; k < pe; ) {
1405
1502
  if (text.charCodeAt(k) !== 96) {
@@ -1426,7 +1523,8 @@ var Tokenizer = class {
1426
1523
  return at !== void 0 && at + n <= pe ? at : -1;
1427
1524
  };
1428
1525
  let i = ps;
1429
- while (i < pe) {
1526
+ const scanEnd = Math.min(pe, this.provisionalFrom);
1527
+ while (i < scanEnd) {
1430
1528
  const ch = text[i];
1431
1529
  if (ch === "\\") {
1432
1530
  const nextCh = text[i + 1];
@@ -1444,6 +1542,9 @@ var Tokenizer = class {
1444
1542
  if (ch === "`") {
1445
1543
  const n = backtickRunLength(text, i);
1446
1544
  const close = codeSpanClose(n, i + n);
1545
+ if (close === -1 && this.streaming && this.paragraphMayGrow(pe)) {
1546
+ return { inlines, breakIsland: null, orphan: false };
1547
+ }
1447
1548
  i = close === -1 ? i + n : close + n;
1448
1549
  continue;
1449
1550
  }
@@ -1460,6 +1561,7 @@ var Tokenizer = class {
1460
1561
  continue;
1461
1562
  }
1462
1563
  const end = singleDollarMathEnd(text, i, pe);
1564
+ this.tick(end > 0 ? end - i : Math.min(403, pe - i));
1463
1565
  if (end > 0 && !(this.streaming && end === this.n)) {
1464
1566
  inlines.push(this.island("math_inline", i, end, true, { delimiter: "$" }));
1465
1567
  i = end;
@@ -1487,6 +1589,7 @@ var Tokenizer = class {
1487
1589
  const limit = Math.min(pe, i + 65536);
1488
1590
  const end = matchingJsonObjectEnd(text, i, limit);
1489
1591
  kindBudget -= (end ?? limit) - i;
1592
+ this.tick((end ?? limit) - i);
1490
1593
  if (end !== null) {
1491
1594
  const kind = declaredKind(text.slice(i, end));
1492
1595
  if (kind) {
@@ -1548,6 +1651,7 @@ var Tokenizer = class {
1548
1651
  continue;
1549
1652
  }
1550
1653
  const tag = readXmlTag(text, i, this.finder);
1654
+ this.tick(tag ? tag.raw.length : 1);
1551
1655
  if (tag && i + tag.raw.length <= pe) {
1552
1656
  const name = tag.tagName;
1553
1657
  const tagEnd = i + tag.raw.length;
@@ -1596,7 +1700,7 @@ var Tokenizer = class {
1596
1700
  }
1597
1701
  };
1598
1702
  function tokenizeSource(text, options = {}) {
1599
- const raw = new Tokenizer(text, options.streaming ?? false).run();
1703
+ const raw = new Tokenizer(text, options.streaming ?? false, options.meter).run();
1600
1704
  const cp = buildCodePointIndex(text);
1601
1705
  const inline = (island) => ({
1602
1706
  islandType: island.type,
@@ -1970,6 +2074,7 @@ exports.mapPosition = mapPosition;
1970
2074
  exports.mapRange = mapRange;
1971
2075
  exports.pairDisplayMath = pairDisplayMath;
1972
2076
  exports.parseFenceOpener = parseFenceOpener;
2077
+ exports.pendingDisplayMathOpener = pendingDisplayMathOpener;
1973
2078
  exports.readXmlTag = readXmlTag;
1974
2079
  exports.singleDollarMathEnd = singleDollarMathEnd;
1975
2080
  exports.spliceSave = spliceSave;