@ai-matrx/content-ir 0.22.6 → 0.23.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
@@ -643,6 +643,143 @@ declare function pendingDisplayMathOpener(text: string): number;
643
643
  /** Map of `$$` opener offset → closer offset for every pair the renderer shows as math. */
644
644
  declare function pairDisplayMath(text: string): Map<number, number>;
645
645
 
646
+ /**
647
+ * @ai-matrx/kit/delimiter-guard — stops ONE stray markdown delimiter from
648
+ * swallowing a whole section of a streamed answer.
649
+ *
650
+ * THE FAILURE CLASS
651
+ * -----------------
652
+ * Markdown delimiters pair greedily and blindly. A single stray opener emitted
653
+ * by a model (the common shape is a mangled citation:
654
+ * `…/a-quicker-way-to-heal-prp-and-prf$$ .`) pairs with the next matching
655
+ * delimiter anywhere later in the message, and everything in between —
656
+ * headings, bold, links, whole sections — collapses into one node.
657
+ *
658
+ * Two delimiters cause this in a remark-math + CommonMark pipeline:
659
+ *
660
+ * 1. `$$` (remark-math). The swallowed prose becomes a math node, KaTeX fails
661
+ * to parse it, and `rehype-katex` falls back to its built-in error
662
+ * rendering: the raw source re-emitted inside `<span class="katex-error"
663
+ * style="color:#cc0000">`. The symptom is a huge block of BRIGHT RED
664
+ * unrendered markdown mid-answer. That red is KaTeX reporting a parse error
665
+ * on text that was never math — not a style of yours.
666
+ * 2. `[` (CommonMark link label). The swallowed prose becomes the label of one
667
+ * enormous hyperlink — the same bug wearing blue instead of red.
668
+ *
669
+ * WHAT THIS DOES
670
+ * --------------
671
+ * Before the markdown pipeline runs, each candidate span is checked for
672
+ * plausibility. A `$$…$$` span carrying markdown structure (links, URLs, bold,
673
+ * headings, list markers) or reading as prose is not math; a link label that is
674
+ * hundreds of characters long or contains block structure is not a label. The
675
+ * offending OPENER is neutralized (`&#36;&#36;`, `&#91;`) and scanning resumes at the next
676
+ * delimiter, so genuine math and genuine links later in the same message still
677
+ * render. Real content is never touched.
678
+ *
679
+ * LOUD RECOVERY: every firing is a real upstream defect (a model emitting
680
+ * malformed delimiters, or a producer mangling a citation). Callers report the
681
+ * returned violations — see `reportDelimiterViolations`.
682
+ *
683
+ * Ported verbatim from matrx-frontend `lib/markdown/delimiter-guard.ts`, with
684
+ * ONE coupling inversion: the app's `captureError` store import became the
685
+ * injected `capture` sink on `reportDelimiterViolations`' context (payload
686
+ * shape preserved exactly). No sink means console-only loud recovery.
687
+ */
688
+ type DelimiterViolationReason =
689
+ /** A `$$…$$` pair whose contents are prose/markdown, not math. */
690
+ "prose-span"
691
+ /** A `$$` with no closing partner in the content. */
692
+ | "unpaired"
693
+ /** A `[…](…)` link whose label swallowed prose/structure. */
694
+ | "runaway-link";
695
+ interface DelimiterViolation {
696
+ reason: DelimiterViolationReason;
697
+ /** Character offset of the offending delimiter in the input string. */
698
+ index: number;
699
+ /** Length of the span between the delimiters (0 for `unpaired`). */
700
+ spanLength: number;
701
+ /** Short excerpt of what would have been swallowed. */
702
+ preview: string;
703
+ }
704
+ interface DelimiterGuardResult {
705
+ /** Input with runaway openers escaped. */
706
+ text: string;
707
+ violations: DelimiterViolation[];
708
+ }
709
+ /**
710
+ * Escapes `$$` delimiters that would make remark-math swallow non-math text.
711
+ * Pure — safe to call on every render / stream chunk.
712
+ */
713
+ declare function guardMathDelimiters(text: string): DelimiterGuardResult;
714
+ /**
715
+ * Escapes the `[` of a markdown link whose label ran away — the link twin of
716
+ * the stray-`$$` bug. An unclosed citation bracket pairs with a `]` hundreds of
717
+ * characters later and turns an entire section into one hyperlink.
718
+ *
719
+ * Pure. Runs after the math guard so both share one escaping pass conceptually,
720
+ * but each is independently usable.
721
+ */
722
+ declare function guardRunawayLinks(text: string): DelimiterGuardResult;
723
+ /**
724
+ * The front door: run every delimiter guard in order. Offsets in the returned
725
+ * violations refer to each guard's own input, so they are for diagnostics only.
726
+ */
727
+ declare function guardMarkdownDelimiters(text: string): DelimiterGuardResult;
728
+ /**
729
+ * The payload handed to the injected capture sink — exactly the shape the
730
+ * original passed to the Matrx `captureError` store. A Matrx host passes
731
+ * `captureError` straight through; any host can log/report it its own way.
732
+ */
733
+ interface DelimiterCaptureInput {
734
+ source: "markdown-delimiters";
735
+ message: string;
736
+ relation: string;
737
+ details: string;
738
+ conversationId?: string | undefined;
739
+ callSite: "guardMarkdownDelimiters";
740
+ raw: {
741
+ messageId?: string | undefined;
742
+ violations: DelimiterViolation[];
743
+ };
744
+ }
745
+ interface DelimiterReportContext {
746
+ renderPath: string;
747
+ messageId?: string | undefined;
748
+ conversationId?: string | undefined;
749
+ /**
750
+ * Optional error-capture sink (a Matrx host passes its `captureError`).
751
+ * Absent, the loud recovery is console-only. Must never be relied on to
752
+ * throw — failures inside it are swallowed so capture can never break
753
+ * rendering.
754
+ */
755
+ capture?: ((input: DelimiterCaptureInput) => void) | undefined;
756
+ }
757
+ /**
758
+ * Loud recovery. A firing means malformed math delimiters reached the renderer
759
+ * — the guard kept the message readable, but the producer is still emitting
760
+ * broken content and must be found.
761
+ */
762
+ declare function reportDelimiterViolations(violations: DelimiterViolation[], context: DelimiterReportContext): void;
763
+
764
+ declare function stripThinking(input: string): string;
765
+ declare function hasThinkingTags(input: string): boolean;
766
+ /**
767
+ * Streaming-aware variant: handles partial `<thinking>` / `<reasoning>` blocks
768
+ * whose closing tag hasn't arrived yet. Returns:
769
+ * - `visible`: text with all *closed* thinking/reasoning blocks removed AND
770
+ * any currently-open thinking block truncated (so the partial chain-of-
771
+ * thought never reaches the textarea or the clipboard).
772
+ * - `isThinking`: true when the stream is currently inside an unclosed
773
+ * `<thinking>` or `<reasoning>` tag — callers can render a "thinking..."
774
+ * animation while waiting for the close tag.
775
+ * Tags inside code — including a fence or inline span still arriving — are
776
+ * content, never an open block.
777
+ */
778
+ declare function stripThinkingStreaming(input: string): {
779
+ visible: string;
780
+ isThinking: boolean;
781
+ };
782
+
646
783
  /**
647
784
  * THE GFM table rule — one splitter, one cell-pipe rule, one "where does a
648
785
  * table start and run" — for every surface that reads a markdown table: the
@@ -883,4 +1020,4 @@ declare function findWholeTableStart(lines: readonly string[], from?: number): n
883
1020
  */
884
1021
  declare function trailingTableStart(lines: readonly string[], end?: number): number;
885
1022
 
886
- export { AFTER_WINDOW, BacktickRuns, type BlockIslandType, CALLOUT_NAMES, type CodePointIndex, type CodeRange, DIRECTIVE_CONTAINER_OPEN, DirectiveContainerTracker, type DisturbedIsland, FENCE_META_KEY, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, type FenceEnd, type FenceInfo, type FenceLineKind, type FenceLines, type FenceMeta, type FenceOpener, FenceReader, type InlineIslandType, type InnerFenceLine, type IslandMeta, type IslandType, type LinkDefinition, 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, TITLED_IMAGE_LINE, type TokenizeOptions, type XmlBalanceOptions, type XmlBalanceState, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, cleanMarkdown, closeFence, codeSpanText, collectLinkDefinitions, continuesTable, fenceLineKinds, fenceNestsInnerFences, fenceOpenerOf, fenceParts, findBalancedXmlClose, findCodeRanges, findInlineCodeSpans, findLinkDefinitionLines, findTableEnd, findTableStart, findWholeTableStart, hasUnclosedBacktickRun, indexOutsideInlineCode, initialXmlBalance, isEscapedBracketMath, isGfmDelimiterRow, isGrowingDelimiterRow, isHtmlBlockTagName, isInsideInlineCode, isMathPlaceholder, isPipeLedRow, isSingleDollarMath, islandEdit, joinSource, lineIndent, listIslands, looksLikeDisplayMath, looksLikeOpenInlineMath, mapCodeRanges, mapInlineCodeSpans, mapPosition, mapRange, markdownToPlainText, mathToReadable, normalizeLinkLabel, opensStrippedHtmlBlock, opensTable, pairDisplayMath, parseFenceMeta, parseFenceOpener, parseLineRanges, pendingCodeSpanStart, pendingDisplayMathOpener, readXmlTag, removeCodeSpans, replaceFences, resolveReferenceLinks, rowCells, singleDollarAfter, singleDollarMathEnd, soleFence, spliceSave, splitFenceInfo, splitRowSegments, splitsSurrogatePair, startsHtmlBlock, startsLikeTableRow, startsPipelessTable, tableContainerIndent, tableStartsAt, toCodePointOffset, toUtf16Offset, tokenizeSource, trailingTableStart, trimFenceLine, unescapeCellPipes, unwrapCodeSpans, unwrapKindEnvelopes };
1023
+ export { AFTER_WINDOW, BacktickRuns, type BlockIslandType, CALLOUT_NAMES, type CodePointIndex, type CodeRange, DIRECTIVE_CONTAINER_OPEN, type DelimiterCaptureInput, type DelimiterGuardResult, type DelimiterReportContext, type DelimiterViolation, type DelimiterViolationReason, DirectiveContainerTracker, type DisturbedIsland, FENCE_META_KEY, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, type FenceEnd, type FenceInfo, type FenceLineKind, type FenceLines, type FenceMeta, type FenceOpener, FenceReader, type InlineIslandType, type InnerFenceLine, type IslandMeta, type IslandType, type LinkDefinition, 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, TITLED_IMAGE_LINE, type TokenizeOptions, type XmlBalanceOptions, type XmlBalanceState, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, cleanMarkdown, closeFence, codeSpanText, collectLinkDefinitions, continuesTable, fenceLineKinds, fenceNestsInnerFences, fenceOpenerOf, fenceParts, findBalancedXmlClose, findCodeRanges, findInlineCodeSpans, findLinkDefinitionLines, findTableEnd, findTableStart, findWholeTableStart, guardMarkdownDelimiters, guardMathDelimiters, guardRunawayLinks, hasThinkingTags, hasUnclosedBacktickRun, indexOutsideInlineCode, initialXmlBalance, isEscapedBracketMath, isGfmDelimiterRow, isGrowingDelimiterRow, isHtmlBlockTagName, isInsideInlineCode, isMathPlaceholder, isPipeLedRow, isSingleDollarMath, islandEdit, joinSource, lineIndent, listIslands, looksLikeDisplayMath, looksLikeOpenInlineMath, mapCodeRanges, mapInlineCodeSpans, mapPosition, mapRange, markdownToPlainText, mathToReadable, normalizeLinkLabel, opensStrippedHtmlBlock, opensTable, pairDisplayMath, parseFenceMeta, parseFenceOpener, parseLineRanges, pendingCodeSpanStart, pendingDisplayMathOpener, readXmlTag, removeCodeSpans, replaceFences, reportDelimiterViolations, resolveReferenceLinks, rowCells, singleDollarAfter, singleDollarMathEnd, soleFence, spliceSave, splitFenceInfo, splitRowSegments, splitsSurrogatePair, startsHtmlBlock, startsLikeTableRow, startsPipelessTable, stripThinking, stripThinkingStreaming, tableContainerIndent, tableStartsAt, toCodePointOffset, toUtf16Offset, tokenizeSource, trailingTableStart, trimFenceLine, unescapeCellPipes, unwrapCodeSpans, unwrapKindEnvelopes };
package/dist/source.d.ts CHANGED
@@ -643,6 +643,143 @@ declare function pendingDisplayMathOpener(text: string): number;
643
643
  /** Map of `$$` opener offset → closer offset for every pair the renderer shows as math. */
644
644
  declare function pairDisplayMath(text: string): Map<number, number>;
645
645
 
646
+ /**
647
+ * @ai-matrx/kit/delimiter-guard — stops ONE stray markdown delimiter from
648
+ * swallowing a whole section of a streamed answer.
649
+ *
650
+ * THE FAILURE CLASS
651
+ * -----------------
652
+ * Markdown delimiters pair greedily and blindly. A single stray opener emitted
653
+ * by a model (the common shape is a mangled citation:
654
+ * `…/a-quicker-way-to-heal-prp-and-prf$$ .`) pairs with the next matching
655
+ * delimiter anywhere later in the message, and everything in between —
656
+ * headings, bold, links, whole sections — collapses into one node.
657
+ *
658
+ * Two delimiters cause this in a remark-math + CommonMark pipeline:
659
+ *
660
+ * 1. `$$` (remark-math). The swallowed prose becomes a math node, KaTeX fails
661
+ * to parse it, and `rehype-katex` falls back to its built-in error
662
+ * rendering: the raw source re-emitted inside `<span class="katex-error"
663
+ * style="color:#cc0000">`. The symptom is a huge block of BRIGHT RED
664
+ * unrendered markdown mid-answer. That red is KaTeX reporting a parse error
665
+ * on text that was never math — not a style of yours.
666
+ * 2. `[` (CommonMark link label). The swallowed prose becomes the label of one
667
+ * enormous hyperlink — the same bug wearing blue instead of red.
668
+ *
669
+ * WHAT THIS DOES
670
+ * --------------
671
+ * Before the markdown pipeline runs, each candidate span is checked for
672
+ * plausibility. A `$$…$$` span carrying markdown structure (links, URLs, bold,
673
+ * headings, list markers) or reading as prose is not math; a link label that is
674
+ * hundreds of characters long or contains block structure is not a label. The
675
+ * offending OPENER is neutralized (`&#36;&#36;`, `&#91;`) and scanning resumes at the next
676
+ * delimiter, so genuine math and genuine links later in the same message still
677
+ * render. Real content is never touched.
678
+ *
679
+ * LOUD RECOVERY: every firing is a real upstream defect (a model emitting
680
+ * malformed delimiters, or a producer mangling a citation). Callers report the
681
+ * returned violations — see `reportDelimiterViolations`.
682
+ *
683
+ * Ported verbatim from matrx-frontend `lib/markdown/delimiter-guard.ts`, with
684
+ * ONE coupling inversion: the app's `captureError` store import became the
685
+ * injected `capture` sink on `reportDelimiterViolations`' context (payload
686
+ * shape preserved exactly). No sink means console-only loud recovery.
687
+ */
688
+ type DelimiterViolationReason =
689
+ /** A `$$…$$` pair whose contents are prose/markdown, not math. */
690
+ "prose-span"
691
+ /** A `$$` with no closing partner in the content. */
692
+ | "unpaired"
693
+ /** A `[…](…)` link whose label swallowed prose/structure. */
694
+ | "runaway-link";
695
+ interface DelimiterViolation {
696
+ reason: DelimiterViolationReason;
697
+ /** Character offset of the offending delimiter in the input string. */
698
+ index: number;
699
+ /** Length of the span between the delimiters (0 for `unpaired`). */
700
+ spanLength: number;
701
+ /** Short excerpt of what would have been swallowed. */
702
+ preview: string;
703
+ }
704
+ interface DelimiterGuardResult {
705
+ /** Input with runaway openers escaped. */
706
+ text: string;
707
+ violations: DelimiterViolation[];
708
+ }
709
+ /**
710
+ * Escapes `$$` delimiters that would make remark-math swallow non-math text.
711
+ * Pure — safe to call on every render / stream chunk.
712
+ */
713
+ declare function guardMathDelimiters(text: string): DelimiterGuardResult;
714
+ /**
715
+ * Escapes the `[` of a markdown link whose label ran away — the link twin of
716
+ * the stray-`$$` bug. An unclosed citation bracket pairs with a `]` hundreds of
717
+ * characters later and turns an entire section into one hyperlink.
718
+ *
719
+ * Pure. Runs after the math guard so both share one escaping pass conceptually,
720
+ * but each is independently usable.
721
+ */
722
+ declare function guardRunawayLinks(text: string): DelimiterGuardResult;
723
+ /**
724
+ * The front door: run every delimiter guard in order. Offsets in the returned
725
+ * violations refer to each guard's own input, so they are for diagnostics only.
726
+ */
727
+ declare function guardMarkdownDelimiters(text: string): DelimiterGuardResult;
728
+ /**
729
+ * The payload handed to the injected capture sink — exactly the shape the
730
+ * original passed to the Matrx `captureError` store. A Matrx host passes
731
+ * `captureError` straight through; any host can log/report it its own way.
732
+ */
733
+ interface DelimiterCaptureInput {
734
+ source: "markdown-delimiters";
735
+ message: string;
736
+ relation: string;
737
+ details: string;
738
+ conversationId?: string | undefined;
739
+ callSite: "guardMarkdownDelimiters";
740
+ raw: {
741
+ messageId?: string | undefined;
742
+ violations: DelimiterViolation[];
743
+ };
744
+ }
745
+ interface DelimiterReportContext {
746
+ renderPath: string;
747
+ messageId?: string | undefined;
748
+ conversationId?: string | undefined;
749
+ /**
750
+ * Optional error-capture sink (a Matrx host passes its `captureError`).
751
+ * Absent, the loud recovery is console-only. Must never be relied on to
752
+ * throw — failures inside it are swallowed so capture can never break
753
+ * rendering.
754
+ */
755
+ capture?: ((input: DelimiterCaptureInput) => void) | undefined;
756
+ }
757
+ /**
758
+ * Loud recovery. A firing means malformed math delimiters reached the renderer
759
+ * — the guard kept the message readable, but the producer is still emitting
760
+ * broken content and must be found.
761
+ */
762
+ declare function reportDelimiterViolations(violations: DelimiterViolation[], context: DelimiterReportContext): void;
763
+
764
+ declare function stripThinking(input: string): string;
765
+ declare function hasThinkingTags(input: string): boolean;
766
+ /**
767
+ * Streaming-aware variant: handles partial `<thinking>` / `<reasoning>` blocks
768
+ * whose closing tag hasn't arrived yet. Returns:
769
+ * - `visible`: text with all *closed* thinking/reasoning blocks removed AND
770
+ * any currently-open thinking block truncated (so the partial chain-of-
771
+ * thought never reaches the textarea or the clipboard).
772
+ * - `isThinking`: true when the stream is currently inside an unclosed
773
+ * `<thinking>` or `<reasoning>` tag — callers can render a "thinking..."
774
+ * animation while waiting for the close tag.
775
+ * Tags inside code — including a fence or inline span still arriving — are
776
+ * content, never an open block.
777
+ */
778
+ declare function stripThinkingStreaming(input: string): {
779
+ visible: string;
780
+ isThinking: boolean;
781
+ };
782
+
646
783
  /**
647
784
  * THE GFM table rule — one splitter, one cell-pipe rule, one "where does a
648
785
  * table start and run" — for every surface that reads a markdown table: the
@@ -883,4 +1020,4 @@ declare function findWholeTableStart(lines: readonly string[], from?: number): n
883
1020
  */
884
1021
  declare function trailingTableStart(lines: readonly string[], end?: number): number;
885
1022
 
886
- export { AFTER_WINDOW, BacktickRuns, type BlockIslandType, CALLOUT_NAMES, type CodePointIndex, type CodeRange, DIRECTIVE_CONTAINER_OPEN, DirectiveContainerTracker, type DisturbedIsland, FENCE_META_KEY, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, type FenceEnd, type FenceInfo, type FenceLineKind, type FenceLines, type FenceMeta, type FenceOpener, FenceReader, type InlineIslandType, type InnerFenceLine, type IslandMeta, type IslandType, type LinkDefinition, 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, TITLED_IMAGE_LINE, type TokenizeOptions, type XmlBalanceOptions, type XmlBalanceState, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, cleanMarkdown, closeFence, codeSpanText, collectLinkDefinitions, continuesTable, fenceLineKinds, fenceNestsInnerFences, fenceOpenerOf, fenceParts, findBalancedXmlClose, findCodeRanges, findInlineCodeSpans, findLinkDefinitionLines, findTableEnd, findTableStart, findWholeTableStart, hasUnclosedBacktickRun, indexOutsideInlineCode, initialXmlBalance, isEscapedBracketMath, isGfmDelimiterRow, isGrowingDelimiterRow, isHtmlBlockTagName, isInsideInlineCode, isMathPlaceholder, isPipeLedRow, isSingleDollarMath, islandEdit, joinSource, lineIndent, listIslands, looksLikeDisplayMath, looksLikeOpenInlineMath, mapCodeRanges, mapInlineCodeSpans, mapPosition, mapRange, markdownToPlainText, mathToReadable, normalizeLinkLabel, opensStrippedHtmlBlock, opensTable, pairDisplayMath, parseFenceMeta, parseFenceOpener, parseLineRanges, pendingCodeSpanStart, pendingDisplayMathOpener, readXmlTag, removeCodeSpans, replaceFences, resolveReferenceLinks, rowCells, singleDollarAfter, singleDollarMathEnd, soleFence, spliceSave, splitFenceInfo, splitRowSegments, splitsSurrogatePair, startsHtmlBlock, startsLikeTableRow, startsPipelessTable, tableContainerIndent, tableStartsAt, toCodePointOffset, toUtf16Offset, tokenizeSource, trailingTableStart, trimFenceLine, unescapeCellPipes, unwrapCodeSpans, unwrapKindEnvelopes };
1023
+ export { AFTER_WINDOW, BacktickRuns, type BlockIslandType, CALLOUT_NAMES, type CodePointIndex, type CodeRange, DIRECTIVE_CONTAINER_OPEN, type DelimiterCaptureInput, type DelimiterGuardResult, type DelimiterReportContext, type DelimiterViolation, type DelimiterViolationReason, DirectiveContainerTracker, type DisturbedIsland, FENCE_META_KEY, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, type FenceEnd, type FenceInfo, type FenceLineKind, type FenceLines, type FenceMeta, type FenceOpener, FenceReader, type InlineIslandType, type InnerFenceLine, type IslandMeta, type IslandType, type LinkDefinition, 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, TITLED_IMAGE_LINE, type TokenizeOptions, type XmlBalanceOptions, type XmlBalanceState, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, cleanMarkdown, closeFence, codeSpanText, collectLinkDefinitions, continuesTable, fenceLineKinds, fenceNestsInnerFences, fenceOpenerOf, fenceParts, findBalancedXmlClose, findCodeRanges, findInlineCodeSpans, findLinkDefinitionLines, findTableEnd, findTableStart, findWholeTableStart, guardMarkdownDelimiters, guardMathDelimiters, guardRunawayLinks, hasThinkingTags, hasUnclosedBacktickRun, indexOutsideInlineCode, initialXmlBalance, isEscapedBracketMath, isGfmDelimiterRow, isGrowingDelimiterRow, isHtmlBlockTagName, isInsideInlineCode, isMathPlaceholder, isPipeLedRow, isSingleDollarMath, islandEdit, joinSource, lineIndent, listIslands, looksLikeDisplayMath, looksLikeOpenInlineMath, mapCodeRanges, mapInlineCodeSpans, mapPosition, mapRange, markdownToPlainText, mathToReadable, normalizeLinkLabel, opensStrippedHtmlBlock, opensTable, pairDisplayMath, parseFenceMeta, parseFenceOpener, parseLineRanges, pendingCodeSpanStart, pendingDisplayMathOpener, readXmlTag, removeCodeSpans, replaceFences, reportDelimiterViolations, resolveReferenceLinks, rowCells, singleDollarAfter, singleDollarMathEnd, soleFence, spliceSave, splitFenceInfo, splitRowSegments, splitsSurrogatePair, startsHtmlBlock, startsLikeTableRow, startsPipelessTable, stripThinking, stripThinkingStreaming, tableContainerIndent, tableStartsAt, toCodePointOffset, toUtf16Offset, tokenizeSource, trailingTableStart, trimFenceLine, unescapeCellPipes, unwrapCodeSpans, unwrapKindEnvelopes };
package/dist/source.js CHANGED
@@ -2882,6 +2882,198 @@ function resolveReferenceLinks(fragment, defs) {
2882
2882
  return resolved.replace(new RegExp(`${PLACEHOLDER}(\\d+)${PLACEHOLDER}`, "g"), (_m, i) => spans[Number(i)] ?? "");
2883
2883
  }
2884
2884
 
2885
+ // source/delimiter-guard.ts
2886
+ var ZWSP = "\u200B";
2887
+ var ESCAPED_DOLLARS = `${ZWSP}$${ZWSP}$`;
2888
+ var ESCAPED_BRACKET = `${ZWSP}&#91;`;
2889
+ function protectedRanges(text) {
2890
+ return findCodeRanges(text).map((r) => [r.start, r.end]);
2891
+ }
2892
+ function isProtected(index, ranges) {
2893
+ return ranges.some(([start, end]) => index >= start && index < end);
2894
+ }
2895
+ function preview(text, max = 160) {
2896
+ const flat = text.replace(/\s+/g, " ").trim();
2897
+ return flat.length > max ? `${flat.slice(0, max)}\u2026` : flat;
2898
+ }
2899
+ function guardMathDelimiters(text) {
2900
+ if (!text.includes("$$")) return { text, violations: [] };
2901
+ const ranges = protectedRanges(text);
2902
+ const tokens = [];
2903
+ for (let i = 0; i < text.length - 1; i++) {
2904
+ if (text[i] !== "$" || text[i + 1] !== "$") continue;
2905
+ if (!isProtected(i, ranges)) tokens.push(i);
2906
+ i++;
2907
+ }
2908
+ if (tokens.length === 0) return { text, violations: [] };
2909
+ const violations = [];
2910
+ const escapeAt = [];
2911
+ let j = 0;
2912
+ while (j < tokens.length) {
2913
+ const open = tokens[j];
2914
+ const close = tokens[j + 1];
2915
+ if (close === void 0) {
2916
+ violations.push({
2917
+ reason: "unpaired",
2918
+ index: open,
2919
+ spanLength: 0,
2920
+ // An unpaired `$$` is inert to remark-math (nothing closes it), so it
2921
+ // is reported but NOT escaped — it already renders as literal text.
2922
+ preview: preview(text.slice(open, open + 120))
2923
+ });
2924
+ break;
2925
+ }
2926
+ const inner = text.slice(open + 2, close);
2927
+ if (isMathPlaceholder(inner)) {
2928
+ escapeAt.push(open, close);
2929
+ violations.push({ reason: "prose-span", index: open, spanLength: inner.length, preview: preview(inner) });
2930
+ j += 2;
2931
+ continue;
2932
+ }
2933
+ if (looksLikeDisplayMath(inner, opensFlowBlock(text, open))) {
2934
+ j += 2;
2935
+ continue;
2936
+ }
2937
+ escapeAt.push(open);
2938
+ violations.push({
2939
+ reason: "prose-span",
2940
+ index: open,
2941
+ spanLength: inner.length,
2942
+ preview: preview(inner)
2943
+ });
2944
+ j += 1;
2945
+ }
2946
+ let guarded = text;
2947
+ for (const index of [...escapeAt].sort((a, b) => b - a)) {
2948
+ guarded = `${guarded.slice(0, index)}${ESCAPED_DOLLARS}${guarded.slice(index + 2)}`;
2949
+ }
2950
+ return { text: guarded, violations };
2951
+ }
2952
+ var MAX_LINK_LABEL = 200;
2953
+ var LABEL_BLOCK_STRUCTURE = /\n[ \t]*\n|(?:^|\n)[ \t]*[-*+][ \t]+|#{2,6}[ \t]/;
2954
+ function guardRunawayLinks(text) {
2955
+ if (!text.includes("[")) return { text, violations: [] };
2956
+ const ranges = protectedRanges(text);
2957
+ const violations = [];
2958
+ const escapeAt = [];
2959
+ const linkRe = /\[((?:[^[\]\\]|\\.)*)\]\(([^\s)]*)/g;
2960
+ let m;
2961
+ while ((m = linkRe.exec(text)) !== null) {
2962
+ const open = m.index;
2963
+ if (isProtected(open, ranges)) continue;
2964
+ const label = m[1] ?? "";
2965
+ const runaway = label.length > MAX_LINK_LABEL || LABEL_BLOCK_STRUCTURE.test(label);
2966
+ if (!runaway) continue;
2967
+ escapeAt.push(open);
2968
+ violations.push({
2969
+ reason: "runaway-link",
2970
+ index: open,
2971
+ spanLength: label.length,
2972
+ preview: preview(label)
2973
+ });
2974
+ }
2975
+ let guarded = text;
2976
+ for (const index of [...escapeAt].sort((a, b) => b - a)) {
2977
+ guarded = `${guarded.slice(0, index)}${ESCAPED_BRACKET}${guarded.slice(index + 1)}`;
2978
+ }
2979
+ return { text: guarded, violations };
2980
+ }
2981
+ function guardMarkdownDelimiters(text) {
2982
+ const math = guardMathDelimiters(text);
2983
+ const links = guardRunawayLinks(math.text);
2984
+ return {
2985
+ text: links.text,
2986
+ violations: [...math.violations, ...links.violations]
2987
+ };
2988
+ }
2989
+ function reportDelimiterViolations(violations, context) {
2990
+ if (violations.length === 0) return;
2991
+ try {
2992
+ const worst = violations.find((v) => v.reason !== "unpaired") ?? violations[0];
2993
+ if (!worst) return;
2994
+ const message = worst.reason === "prose-span" ? `Malformed math delimiters: a stray "$$" would have turned ${worst.spanLength} chars of prose into a math span (KaTeX would render it as red error text). Escaped it.` : worst.reason === "runaway-link" ? `Runaway markdown link: an unclosed "[" would have turned ${worst.spanLength} chars into one link label. Escaped it.` : `Malformed math delimiters: an unpaired "$$" reached the renderer.`;
2995
+ console.warn(`[markdown-delimiter-guard] ${message}`, {
2996
+ renderPath: context.renderPath,
2997
+ violations
2998
+ });
2999
+ context.capture?.({
3000
+ source: "markdown-delimiters",
3001
+ message,
3002
+ relation: `markdown:${context.renderPath}`,
3003
+ details: worst.preview,
3004
+ conversationId: context.conversationId,
3005
+ callSite: "guardMarkdownDelimiters",
3006
+ raw: { messageId: context.messageId, violations }
3007
+ });
3008
+ } catch {
3009
+ }
3010
+ }
3011
+
3012
+ // source/thinking.ts
3013
+ function codeRanges2(text, live) {
3014
+ const ranges = findCodeRanges(text).map((r) => [r.start, r.end]);
3015
+ if (live) {
3016
+ const pending = pendingCodeSpanStart(text);
3017
+ if (pending >= 0 && !inCode(ranges, pending)) ranges.push([pending, text.length]);
3018
+ }
3019
+ return ranges.sort((x, y) => x[0] - y[0]);
3020
+ }
3021
+ function inCode(ranges, index) {
3022
+ return ranges.some(([a, b]) => index >= a && index < b);
3023
+ }
3024
+ function collapseGaps(text) {
3025
+ const ranges = codeRanges2(text, false);
3026
+ let out = "";
3027
+ let at = 0;
3028
+ for (const [a, b] of ranges) {
3029
+ out += text.slice(at, a).replace(/\n{3,}/g, "\n\n") + text.slice(a, b);
3030
+ at = b;
3031
+ }
3032
+ return out + text.slice(at).replace(/\n{3,}/g, "\n\n");
3033
+ }
3034
+ var OPEN_TAG = /<(thinking|reasoning)\b[^>]*>/gi;
3035
+ function removeClosedBlocks(input, live) {
3036
+ let text = input;
3037
+ for (; ; ) {
3038
+ const ranges = codeRanges2(text, live);
3039
+ OPEN_TAG.lastIndex = 0;
3040
+ let opener = null;
3041
+ for (let m = OPEN_TAG.exec(text); m; m = OPEN_TAG.exec(text)) {
3042
+ if (!inCode(ranges, m.index)) {
3043
+ opener = m;
3044
+ break;
3045
+ }
3046
+ }
3047
+ if (!opener) return { text, openAt: -1 };
3048
+ const closeTag = new RegExp(`</${opener[1]}\\s*>`, "gi");
3049
+ closeTag.lastIndex = opener.index + opener[0].length;
3050
+ let closer = null;
3051
+ for (let m = closeTag.exec(text); m; m = closeTag.exec(text)) {
3052
+ if (!inCode(ranges, m.index)) {
3053
+ closer = m;
3054
+ break;
3055
+ }
3056
+ }
3057
+ if (!closer) return { text, openAt: opener.index };
3058
+ text = text.slice(0, opener.index) + text.slice(closer.index + closer[0].length);
3059
+ }
3060
+ }
3061
+ function stripThinking(input) {
3062
+ if (!input) return "";
3063
+ return collapseGaps(removeClosedBlocks(input, false).text).trim();
3064
+ }
3065
+ function hasThinkingTags(input) {
3066
+ if (!input) return false;
3067
+ return removeClosedBlocks(input, false).text !== input;
3068
+ }
3069
+ function stripThinkingStreaming(input) {
3070
+ if (!input) return { visible: "", isThinking: false };
3071
+ const { text, openAt } = removeClosedBlocks(input, true);
3072
+ const isThinking = openAt !== -1;
3073
+ const kept = isThinking ? text.slice(0, openAt) : text;
3074
+ return { visible: collapseGaps(kept).replace(/^\s+/, ""), isThinking };
3075
+ }
3076
+
2885
3077
  // source/code-range-edits.ts
2886
3078
  function unwrapCodeSpans(text) {
2887
3079
  if (!text.includes("`")) return text;
@@ -3294,6 +3486,6 @@ function trailingTableStart(lines, end = lines.length) {
3294
3486
  return end;
3295
3487
  }
3296
3488
 
3297
- export { AFTER_WINDOW, BacktickRuns, CALLOUT_NAMES, DIRECTIVE_CONTAINER_OPEN, DirectiveContainerTracker, FENCE_META_KEY, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, FenceReader, NESTING_FENCE_LANGUAGES, SourceSpliceError, TITLED_IMAGE_LINE, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, cleanMarkdown, closeFence, codeSpanText, collectLinkDefinitions, continuesTable, fenceLineKinds, fenceNestsInnerFences, fenceOpenerOf, fenceParts, findBalancedXmlClose, findCodeRanges, findInlineCodeSpans, findLinkDefinitionLines, findTableEnd, findTableStart, findWholeTableStart, hasUnclosedBacktickRun, indexOutsideInlineCode, initialXmlBalance, isEscapedBracketMath, isGfmDelimiterRow, isGrowingDelimiterRow, isHtmlBlockTagName, isInsideInlineCode, isMathPlaceholder, isPipeLedRow, isSingleDollarMath, islandEdit, joinSource, lineIndent, listIslands, looksLikeDisplayMath, looksLikeOpenInlineMath, mapCodeRanges, mapInlineCodeSpans, mapPosition, mapRange, markdownToPlainText, mathToReadable, normalizeLinkLabel, opensStrippedHtmlBlock, opensTable, pairDisplayMath, parseFenceMeta, parseFenceOpener, parseLineRanges, pendingCodeSpanStart, pendingDisplayMathOpener, readXmlTag, removeCodeSpans, replaceFences, resolveReferenceLinks, rowCells, singleDollarAfter, singleDollarMathEnd, soleFence, spliceSave, splitFenceInfo, splitRowSegments, splitsSurrogatePair, startsHtmlBlock, startsLikeTableRow, startsPipelessTable, tableContainerIndent, tableStartsAt, toCodePointOffset, toUtf16Offset, tokenizeSource, trailingTableStart, trimFenceLine, unescapeCellPipes, unwrapCodeSpans, unwrapKindEnvelopes };
3489
+ export { AFTER_WINDOW, BacktickRuns, CALLOUT_NAMES, DIRECTIVE_CONTAINER_OPEN, DirectiveContainerTracker, FENCE_META_KEY, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, FenceReader, NESTING_FENCE_LANGUAGES, SourceSpliceError, TITLED_IMAGE_LINE, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, cleanMarkdown, closeFence, codeSpanText, collectLinkDefinitions, continuesTable, fenceLineKinds, fenceNestsInnerFences, fenceOpenerOf, fenceParts, findBalancedXmlClose, findCodeRanges, findInlineCodeSpans, findLinkDefinitionLines, findTableEnd, findTableStart, findWholeTableStart, guardMarkdownDelimiters, guardMathDelimiters, guardRunawayLinks, hasThinkingTags, hasUnclosedBacktickRun, indexOutsideInlineCode, initialXmlBalance, isEscapedBracketMath, isGfmDelimiterRow, isGrowingDelimiterRow, isHtmlBlockTagName, isInsideInlineCode, isMathPlaceholder, isPipeLedRow, isSingleDollarMath, islandEdit, joinSource, lineIndent, listIslands, looksLikeDisplayMath, looksLikeOpenInlineMath, mapCodeRanges, mapInlineCodeSpans, mapPosition, mapRange, markdownToPlainText, mathToReadable, normalizeLinkLabel, opensStrippedHtmlBlock, opensTable, pairDisplayMath, parseFenceMeta, parseFenceOpener, parseLineRanges, pendingCodeSpanStart, pendingDisplayMathOpener, readXmlTag, removeCodeSpans, replaceFences, reportDelimiterViolations, resolveReferenceLinks, rowCells, singleDollarAfter, singleDollarMathEnd, soleFence, spliceSave, splitFenceInfo, splitRowSegments, splitsSurrogatePair, startsHtmlBlock, startsLikeTableRow, startsPipelessTable, stripThinking, stripThinkingStreaming, tableContainerIndent, tableStartsAt, toCodePointOffset, toUtf16Offset, tokenizeSource, trailingTableStart, trimFenceLine, unescapeCellPipes, unwrapCodeSpans, unwrapKindEnvelopes };
3298
3490
  //# sourceMappingURL=source.js.map
3299
3491
  //# sourceMappingURL=source.js.map