indusagi 0.12.33 → 0.12.34

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/react-ink.js CHANGED
@@ -1,6 +1,34 @@
1
1
  // src/react-ink/theme-adapter.ts
2
2
  import chalk from "chalk";
3
3
  import stripAnsi from "strip-ansi";
4
+ var DEFAULT_ROLE_KEYS = {
5
+ codeInline: "codeInline",
6
+ heading: "heading",
7
+ blockquoteBar: "blockquoteBar",
8
+ diffAddedBg: "diffAddedBg",
9
+ diffRemovedBg: "diffRemovedBg",
10
+ diffAddedText: "diffAddedText",
11
+ diffRemovedText: "diffRemovedText",
12
+ synKeyword: "synKeyword",
13
+ synString: "synString",
14
+ synNumber: "synNumber",
15
+ synComment: "synComment",
16
+ synType: "synType"
17
+ };
18
+ var ROLE_FALLBACK_KEYS = {
19
+ codeInline: "accent",
20
+ heading: "accent",
21
+ blockquoteBar: "muted",
22
+ diffAddedBg: "success",
23
+ diffRemovedBg: "error",
24
+ diffAddedText: "success",
25
+ diffRemovedText: "error",
26
+ synKeyword: "accent",
27
+ synString: "success",
28
+ synNumber: "warning",
29
+ synComment: "muted",
30
+ synType: "info"
31
+ };
4
32
  function applyForeground(color, text) {
5
33
  if (!color) return text;
6
34
  try {
@@ -27,15 +55,27 @@ function applyBackground(background, foreground, text) {
27
55
  return applyForeground(foreground, text);
28
56
  }
29
57
  }
30
- function createThemeAdapter(themeName, colors) {
58
+ function createThemeAdapter(themeName, colors, roleOverrides) {
31
59
  const fallback = colors.text ?? "#e5e5e7";
60
+ const roles = { ...DEFAULT_ROLE_KEYS, ...roleOverrides };
61
+ const resolveRoleColor = (role) => resolveColorToken(
62
+ colors[roles[role]],
63
+ resolveColorToken(colors[ROLE_FALLBACK_KEYS[role]], fallback)
64
+ );
32
65
  return {
33
66
  name: themeName,
34
67
  colors,
68
+ roles,
35
69
  color: (key, text) => applyForeground(resolveColorToken(colors[key], fallback), text),
36
70
  background: (key, text, textKey = "text") => applyBackground(resolveColorToken(colors[key]), resolveColorToken(colors[textKey], fallback), ` ${stripAnsi(text)} `),
37
71
  dim: (text) => applyForeground(resolveColorToken(colors.dim, "#666666"), text),
38
- muted: (text) => applyForeground(resolveColorToken(colors.muted, "#808080"), text)
72
+ muted: (text) => applyForeground(resolveColorToken(colors.muted, "#808080"), text),
73
+ role: (role, text) => applyForeground(resolveRoleColor(role), text),
74
+ roleBackground: (role, text, foregroundRole) => applyBackground(
75
+ resolveRoleColor(role),
76
+ foregroundRole ? resolveRoleColor(foregroundRole) : void 0,
77
+ text
78
+ )
39
79
  };
40
80
  }
41
81
 
@@ -560,8 +600,1128 @@ function parseSkillInvocation(content) {
560
600
  };
561
601
  }
562
602
 
603
+ // src/react-host/index.ts
604
+ var React = await loadHostReact();
605
+ var Fragment2 = React.Fragment;
606
+ var createElement = React.createElement;
607
+ var useEffect = React.useEffect;
608
+ var useMemo = React.useMemo;
609
+ var useState = React.useState;
610
+
611
+ // src/react-ink/markdown/format-token.ts
612
+ import chalk2 from "chalk";
613
+ import { marked } from "marked";
614
+ import stripAnsi3 from "strip-ansi";
615
+
616
+ // src/ui/utils.ts
617
+ import { eastAsianWidth } from "get-east-asian-width";
618
+ var segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
619
+ var TextWidthCalculator = class {
620
+ getWidth(text) {
621
+ return visibleWidth(text);
622
+ }
623
+ clearCache() {
624
+ widthCache.clear();
625
+ }
626
+ };
627
+ var textWidthCalculator = new TextWidthCalculator();
628
+ function looksLikeEmojiCandidate(segment) {
629
+ const cp = segment.codePointAt(0);
630
+ return cp >= 126976 && cp <= 130047 || // pictographs and emoji proper
631
+ cp >= 8960 && cp <= 9215 || // miscellaneous technical glyphs
632
+ cp >= 9728 && cp <= 10175 || // assorted symbols and dingbats
633
+ cp >= 11088 && cp <= 11093 || // a few star/circle characters
634
+ segment.includes("\uFE0F") || // carries VS16, the emoji-presentation selector
635
+ segment.length > 2;
636
+ }
637
+ function createUnicodeRegex(vPattern, uFallback) {
638
+ try {
639
+ return new RegExp(vPattern, "v");
640
+ } catch {
641
+ return new RegExp(uFallback, "u");
642
+ }
643
+ }
644
+ var zeroWidthRegex = createUnicodeRegex(
645
+ "^(?:\\p{Default_Ignorable_Code_Point}|\\p{Control}|\\p{Mark}|\\p{Surrogate})+$",
646
+ "^(?:\\p{Default_Ignorable_Code_Point}|\\p{Control}|\\p{Mark}|\\p{Surrogate})+$"
647
+ );
648
+ var leadingNonPrintingRegex = createUnicodeRegex(
649
+ "^[\\p{Default_Ignorable_Code_Point}\\p{Control}\\p{Format}\\p{Mark}\\p{Surrogate}]+",
650
+ "^[\\p{Default_Ignorable_Code_Point}\\p{Control}\\p{Format}\\p{Mark}\\p{Surrogate}]+"
651
+ );
652
+ var rgiEmojiRegex = createUnicodeRegex("^\\p{RGI_Emoji}$", "^\\p{Extended_Pictographic}$");
653
+ var AnsiStripper = {
654
+ strip(text) {
655
+ if (!text.includes("\x1B")) {
656
+ return text;
657
+ }
658
+ let cleaned = text;
659
+ cleaned = cleaned.replace(/\x1b\[[0-9;]*[mGKHJ]/g, "");
660
+ cleaned = cleaned.replace(/\x1b\]8;;[^\x07]*\x07/g, "");
661
+ cleaned = cleaned.replace(/\x1b_[^\x07\x1b]*(?:\x07|\x1b\\)/g, "");
662
+ return cleaned;
663
+ }
664
+ };
665
+ var WIDTH_CACHE_SIZE = 512;
666
+ var widthCache = /* @__PURE__ */ new Map();
667
+ function measureClusterWidth(segment) {
668
+ if (zeroWidthRegex.test(segment)) {
669
+ return 0;
670
+ }
671
+ if (looksLikeEmojiCandidate(segment) && rgiEmojiRegex.test(segment)) {
672
+ return 2;
673
+ }
674
+ const base = segment.replace(leadingNonPrintingRegex, "");
675
+ const cp = base.codePointAt(0);
676
+ if (cp === void 0) {
677
+ return 0;
678
+ }
679
+ let width = eastAsianWidth(cp);
680
+ if (segment.length > 1) {
681
+ for (const char of segment.slice(1)) {
682
+ const c = char.codePointAt(0);
683
+ if (c >= 65280 && c <= 65519) {
684
+ width += eastAsianWidth(c);
685
+ }
686
+ }
687
+ }
688
+ return width;
689
+ }
690
+ function visibleWidth(str) {
691
+ if (str.length === 0) {
692
+ return 0;
693
+ }
694
+ let isPureAscii = true;
695
+ for (let i = 0; i < str.length; i++) {
696
+ const code = str.charCodeAt(i);
697
+ if (code < 32 || code > 126) {
698
+ isPureAscii = false;
699
+ break;
700
+ }
701
+ }
702
+ if (isPureAscii) {
703
+ return str.length;
704
+ }
705
+ const cached = widthCache.get(str);
706
+ if (cached !== void 0) {
707
+ return cached;
708
+ }
709
+ let clean = str;
710
+ if (str.includes(" ")) {
711
+ clean = clean.replace(/\t/g, " ");
712
+ }
713
+ clean = AnsiStripper.strip(clean);
714
+ let width = 0;
715
+ for (const { segment } of segmenter.segment(clean)) {
716
+ width += measureClusterWidth(segment);
717
+ }
718
+ if (widthCache.size >= WIDTH_CACHE_SIZE) {
719
+ const firstKey = widthCache.keys().next().value;
720
+ if (firstKey !== void 0) {
721
+ widthCache.delete(firstKey);
722
+ }
723
+ }
724
+ widthCache.set(str, width);
725
+ return width;
726
+ }
727
+ function extractAnsiCode(str, pos) {
728
+ if (pos >= str.length || str[pos] !== "\x1B") return null;
729
+ const next = str[pos + 1];
730
+ if (next === "[") {
731
+ let j = pos + 2;
732
+ while (j < str.length && !/[mGKHJ]/.test(str[j])) j++;
733
+ if (j < str.length) return { code: str.substring(pos, j + 1), length: j + 1 - pos };
734
+ return null;
735
+ }
736
+ if (next === "]") {
737
+ let j = pos + 2;
738
+ while (j < str.length) {
739
+ if (str[j] === "\x07") return { code: str.substring(pos, j + 1), length: j + 1 - pos };
740
+ if (str[j] === "\x1B" && str[j + 1] === "\\") return { code: str.substring(pos, j + 2), length: j + 2 - pos };
741
+ j++;
742
+ }
743
+ return null;
744
+ }
745
+ if (next === "_") {
746
+ let j = pos + 2;
747
+ while (j < str.length) {
748
+ if (str[j] === "\x07") return { code: str.substring(pos, j + 1), length: j + 1 - pos };
749
+ if (str[j] === "\x1B" && str[j + 1] === "\\") return { code: str.substring(pos, j + 2), length: j + 2 - pos };
750
+ j++;
751
+ }
752
+ return null;
753
+ }
754
+ return null;
755
+ }
756
+ var AnsiStateTracker = class {
757
+ // Each attribute is kept on its own flag, which lets us clear them one at a time.
758
+ _bold = false;
759
+ _dim = false;
760
+ italic = false;
761
+ underline = false;
762
+ blink = false;
763
+ inverse = false;
764
+ hidden = false;
765
+ strikethrough = false;
766
+ _colors = { fg: null, bg: null };
767
+ process(ansiCode) {
768
+ if (!ansiCode.endsWith("m")) {
769
+ return;
770
+ }
771
+ const match = ansiCode.match(/\x1b\[([\d;]*)m/);
772
+ if (!match) return;
773
+ const params = match[1];
774
+ if (params === "" || params === "0") {
775
+ this.reset();
776
+ return;
777
+ }
778
+ const parts = params.split(";");
779
+ let i = 0;
780
+ while (i < parts.length) {
781
+ const code = Number.parseInt(parts[i] ?? "", 10);
782
+ if (Number.isNaN(code)) {
783
+ i++;
784
+ continue;
785
+ }
786
+ const consumed = this.tryConsumeColorCode(parts, i, code);
787
+ if (consumed > 0) {
788
+ i += consumed;
789
+ continue;
790
+ }
791
+ this.applyStandardCode(code);
792
+ i++;
793
+ }
794
+ }
795
+ tryConsumeColorCode(parts, index, code) {
796
+ if (code !== 38 && code !== 48) {
797
+ return 0;
798
+ }
799
+ if (parts[index + 1] === "5" && parts[index + 2] !== void 0) {
800
+ const colorCode = `${parts[index]};${parts[index + 1]};${parts[index + 2]}`;
801
+ if (code === 38) {
802
+ this._colors.fg = colorCode;
803
+ } else {
804
+ this._colors.bg = colorCode;
805
+ }
806
+ return 3;
807
+ }
808
+ if (parts[index + 1] === "2" && parts[index + 4] !== void 0) {
809
+ const colorCode = `${parts[index]};${parts[index + 1]};${parts[index + 2]};${parts[index + 3]};${parts[index + 4]}`;
810
+ if (code === 38) {
811
+ this._colors.fg = colorCode;
812
+ } else {
813
+ this._colors.bg = colorCode;
814
+ }
815
+ return 5;
816
+ }
817
+ return 0;
818
+ }
819
+ applyStandardCode(code) {
820
+ switch (code) {
821
+ case 0:
822
+ this.reset();
823
+ return;
824
+ case 1:
825
+ this._bold = true;
826
+ return;
827
+ case 2:
828
+ this._dim = true;
829
+ return;
830
+ case 3:
831
+ this.italic = true;
832
+ return;
833
+ case 4:
834
+ this.underline = true;
835
+ return;
836
+ case 5:
837
+ this.blink = true;
838
+ return;
839
+ case 7:
840
+ this.inverse = true;
841
+ return;
842
+ case 8:
843
+ this.hidden = true;
844
+ return;
845
+ case 9:
846
+ this.strikethrough = true;
847
+ return;
848
+ case 21:
849
+ this._bold = false;
850
+ return;
851
+ case 22:
852
+ this._bold = false;
853
+ this._dim = false;
854
+ return;
855
+ case 23:
856
+ this.italic = false;
857
+ return;
858
+ case 24:
859
+ this.underline = false;
860
+ return;
861
+ case 25:
862
+ this.blink = false;
863
+ return;
864
+ case 27:
865
+ this.inverse = false;
866
+ return;
867
+ case 28:
868
+ this.hidden = false;
869
+ return;
870
+ case 29:
871
+ this.strikethrough = false;
872
+ return;
873
+ case 39:
874
+ this._colors.fg = null;
875
+ return;
876
+ case 49:
877
+ this._colors.bg = null;
878
+ return;
879
+ default:
880
+ if (code >= 30 && code <= 37 || code >= 90 && code <= 97) {
881
+ this._colors.fg = String(code);
882
+ return;
883
+ }
884
+ if (code >= 40 && code <= 47 || code >= 100 && code <= 107) {
885
+ this._colors.bg = String(code);
886
+ }
887
+ }
888
+ }
889
+ reset() {
890
+ this._bold = false;
891
+ this._dim = false;
892
+ this.italic = false;
893
+ this.underline = false;
894
+ this.blink = false;
895
+ this.inverse = false;
896
+ this.hidden = false;
897
+ this.strikethrough = false;
898
+ this._colors.fg = null;
899
+ this._colors.bg = null;
900
+ }
901
+ /** Wipe all tracked state so the instance can be reused. */
902
+ clear() {
903
+ this.reset();
904
+ }
905
+ getActiveCodes() {
906
+ const codes = [];
907
+ if (this._bold) codes.push("1");
908
+ if (this._dim) codes.push("2");
909
+ if (this.italic) codes.push("3");
910
+ if (this.underline) codes.push("4");
911
+ if (this.blink) codes.push("5");
912
+ if (this.inverse) codes.push("7");
913
+ if (this.hidden) codes.push("8");
914
+ if (this.strikethrough) codes.push("9");
915
+ if (this._colors.fg) codes.push(this._colors.fg);
916
+ if (this._colors.bg) codes.push(this._colors.bg);
917
+ if (codes.length === 0) return "";
918
+ return `\x1B[${codes.join(";")}m`;
919
+ }
920
+ hasActiveCodes() {
921
+ return this._bold || this._dim || this.italic || this.underline || this.blink || this.inverse || this.hidden || this.strikethrough || this._colors.fg !== null || this._colors.bg !== null;
922
+ }
923
+ /**
924
+ * Produce the escape code needed to switch off any attribute that would
925
+ * otherwise smear into the padding at the end of a line. In practice that
926
+ * is just underline. Yields an empty string when nothing needs disabling.
927
+ */
928
+ getLineEndReset() {
929
+ if (this.underline) {
930
+ return "\x1B[24m";
931
+ }
932
+ return "";
933
+ }
934
+ };
935
+ function mergeTextIntoTracker(text, tracker) {
936
+ let i = 0;
937
+ while (i < text.length) {
938
+ const ansiResult = extractAnsiCode(text, i);
939
+ if (ansiResult) {
940
+ tracker.process(ansiResult.code);
941
+ i += ansiResult.length;
942
+ } else {
943
+ i++;
944
+ }
945
+ }
946
+ }
947
+ function tokenizeTextWithAnsi(text) {
948
+ const tokens = [];
949
+ let current = "";
950
+ let pendingAnsi = "";
951
+ let inWhitespace = false;
952
+ let i = 0;
953
+ while (i < text.length) {
954
+ const ansiResult = extractAnsiCode(text, i);
955
+ if (ansiResult) {
956
+ pendingAnsi += ansiResult.code;
957
+ i += ansiResult.length;
958
+ continue;
959
+ }
960
+ const char = text[i];
961
+ const charIsSpace = char === " ";
962
+ if (charIsSpace !== inWhitespace && current) {
963
+ tokens.push(current);
964
+ current = "";
965
+ }
966
+ if (pendingAnsi) {
967
+ current += pendingAnsi;
968
+ pendingAnsi = "";
969
+ }
970
+ inWhitespace = charIsSpace;
971
+ current += char;
972
+ i++;
973
+ }
974
+ if (pendingAnsi) {
975
+ current += pendingAnsi;
976
+ }
977
+ if (current) {
978
+ tokens.push(current);
979
+ }
980
+ return tokens;
981
+ }
982
+ var TextWrapper = class {
983
+ wrap(text, width) {
984
+ return wrapTextWithAnsi(text, width);
985
+ }
986
+ };
987
+ var textWrapper = new TextWrapper();
988
+ var TokenWrapEngine = {
989
+ wrap(tokens, width, tracker) {
990
+ const wrapped = [];
991
+ let currentLine = "";
992
+ let currentVisibleLength = 0;
993
+ for (const token of tokens) {
994
+ const tokenVisibleLength = visibleWidth(token);
995
+ const isWhitespace = token.trim() === "";
996
+ if (tokenVisibleLength > width && !isWhitespace) {
997
+ if (currentLine) {
998
+ const lineEndReset = tracker.getLineEndReset();
999
+ if (lineEndReset) {
1000
+ currentLine += lineEndReset;
1001
+ }
1002
+ wrapped.push(currentLine);
1003
+ currentLine = "";
1004
+ currentVisibleLength = 0;
1005
+ }
1006
+ const broken = splitLongToken(token, width, tracker);
1007
+ wrapped.push(...broken.slice(0, -1));
1008
+ currentLine = broken[broken.length - 1];
1009
+ currentVisibleLength = visibleWidth(currentLine);
1010
+ continue;
1011
+ }
1012
+ const totalNeeded = currentVisibleLength + tokenVisibleLength;
1013
+ if (totalNeeded > width && currentVisibleLength > 0) {
1014
+ let lineToWrap = currentLine.trimEnd();
1015
+ const lineEndReset = tracker.getLineEndReset();
1016
+ if (lineEndReset) {
1017
+ lineToWrap += lineEndReset;
1018
+ }
1019
+ wrapped.push(lineToWrap);
1020
+ if (isWhitespace) {
1021
+ currentLine = tracker.getActiveCodes();
1022
+ currentVisibleLength = 0;
1023
+ } else {
1024
+ currentLine = tracker.getActiveCodes() + token;
1025
+ currentVisibleLength = tokenVisibleLength;
1026
+ }
1027
+ } else {
1028
+ currentLine += token;
1029
+ currentVisibleLength += tokenVisibleLength;
1030
+ }
1031
+ mergeTextIntoTracker(token, tracker);
1032
+ }
1033
+ return { wrapped, currentLine, currentVisibleLength };
1034
+ }
1035
+ };
1036
+ function wrapTextWithAnsi(text, width) {
1037
+ if (!text) {
1038
+ return [""];
1039
+ }
1040
+ const inputLines = text.split("\n");
1041
+ const result = [];
1042
+ const tracker = new AnsiStateTracker();
1043
+ for (const inputLine of inputLines) {
1044
+ const prefix = result.length > 0 ? tracker.getActiveCodes() : "";
1045
+ result.push(...wrapLinePreservingAnsi(prefix + inputLine, width));
1046
+ mergeTextIntoTracker(inputLine, tracker);
1047
+ }
1048
+ return result.length > 0 ? result : [""];
1049
+ }
1050
+ function wrapLinePreservingAnsi(line, width) {
1051
+ if (!line) {
1052
+ return [""];
1053
+ }
1054
+ const visibleLength = visibleWidth(line);
1055
+ if (visibleLength <= width) {
1056
+ return [line];
1057
+ }
1058
+ const tracker = new AnsiStateTracker();
1059
+ const tokens = tokenizeTextWithAnsi(line);
1060
+ const { wrapped, currentLine } = TokenWrapEngine.wrap(tokens, width, tracker);
1061
+ if (currentLine) {
1062
+ wrapped.push(currentLine);
1063
+ }
1064
+ return wrapped.length > 0 ? wrapped.map((segmentLine) => segmentLine.trimEnd()) : [""];
1065
+ }
1066
+ function splitLongToken(word, width, tracker) {
1067
+ const lines = [];
1068
+ let currentLine = tracker.getActiveCodes();
1069
+ let currentWidth = 0;
1070
+ let i = 0;
1071
+ const segments = [];
1072
+ while (i < word.length) {
1073
+ const ansiResult = extractAnsiCode(word, i);
1074
+ if (ansiResult) {
1075
+ segments.push({ type: "ansi", value: ansiResult.code });
1076
+ i += ansiResult.length;
1077
+ } else {
1078
+ let end = i;
1079
+ while (end < word.length) {
1080
+ const nextAnsi = extractAnsiCode(word, end);
1081
+ if (nextAnsi) break;
1082
+ end++;
1083
+ }
1084
+ const textPortion = word.slice(i, end);
1085
+ for (const seg of segmenter.segment(textPortion)) {
1086
+ segments.push({ type: "grapheme", value: seg.segment });
1087
+ }
1088
+ i = end;
1089
+ }
1090
+ }
1091
+ for (const seg of segments) {
1092
+ if (seg.type === "ansi") {
1093
+ currentLine += seg.value;
1094
+ tracker.process(seg.value);
1095
+ continue;
1096
+ }
1097
+ const grapheme = seg.value;
1098
+ if (!grapheme) continue;
1099
+ const clusterWidth = visibleWidth(grapheme);
1100
+ if (currentWidth + clusterWidth > width) {
1101
+ const lineEndReset = tracker.getLineEndReset();
1102
+ if (lineEndReset) {
1103
+ currentLine += lineEndReset;
1104
+ }
1105
+ lines.push(currentLine);
1106
+ currentLine = tracker.getActiveCodes();
1107
+ currentWidth = 0;
1108
+ }
1109
+ currentLine += grapheme;
1110
+ currentWidth += clusterWidth;
1111
+ }
1112
+ if (currentLine) {
1113
+ lines.push(currentLine);
1114
+ }
1115
+ return lines.length > 0 ? lines : [""];
1116
+ }
1117
+ var pooledStyleTracker = new AnsiStateTracker();
1118
+
1119
+ // src/react-ink/markdown/format-token.ts
1120
+ var EOL = "\n";
1121
+ var BLOCKQUOTE_BAR = "\u2502";
1122
+ var markedConfigured = false;
1123
+ function configureMarked() {
1124
+ if (markedConfigured) {
1125
+ return;
1126
+ }
1127
+ markedConfigured = true;
1128
+ marked.use({
1129
+ tokenizer: {
1130
+ del() {
1131
+ return void 0;
1132
+ }
1133
+ }
1134
+ });
1135
+ }
1136
+ var TOKEN_CACHE_MAX = 500;
1137
+ var tokenCache = /* @__PURE__ */ new Map();
1138
+ var MD_SYNTAX_RE = /[#*`|[>\-_~]|\n\n|^\d+\. |\n\d+\. /;
1139
+ function hasMarkdownSyntax(text) {
1140
+ return MD_SYNTAX_RE.test(text.length > 500 ? text.slice(0, 500) : text);
1141
+ }
1142
+ function hashContent(content) {
1143
+ let hash = 2166136261;
1144
+ for (let i = 0; i < content.length; i++) {
1145
+ hash ^= content.charCodeAt(i);
1146
+ hash = Math.imul(hash, 16777619);
1147
+ }
1148
+ return (hash >>> 0).toString(36) + ":" + content.length.toString(36);
1149
+ }
1150
+ function cachedLexer(content) {
1151
+ configureMarked();
1152
+ if (!hasMarkdownSyntax(content)) {
1153
+ return [
1154
+ {
1155
+ type: "paragraph",
1156
+ raw: content,
1157
+ text: content,
1158
+ tokens: [{ type: "text", raw: content, text: content }]
1159
+ }
1160
+ ];
1161
+ }
1162
+ const key = hashContent(content);
1163
+ const hit = tokenCache.get(key);
1164
+ if (hit) {
1165
+ tokenCache.delete(key);
1166
+ tokenCache.set(key, hit);
1167
+ return hit;
1168
+ }
1169
+ const tokens = marked.lexer(content);
1170
+ if (tokenCache.size >= TOKEN_CACHE_MAX) {
1171
+ const first = tokenCache.keys().next().value;
1172
+ if (first !== void 0) {
1173
+ tokenCache.delete(first);
1174
+ }
1175
+ }
1176
+ tokenCache.set(key, tokens);
1177
+ return tokens;
1178
+ }
1179
+ function formatToken(token, theme, highlight = null, listDepth = 0, orderedListNumber = null, parent = null) {
1180
+ switch (token.type) {
1181
+ case "blockquote": {
1182
+ const inner = (token.tokens ?? []).map((child) => formatToken(child, theme, highlight)).join("");
1183
+ const bar = theme.dim(BLOCKQUOTE_BAR);
1184
+ return inner.split(EOL).map((line) => stripAnsi3(line).trim() ? `${bar} ${chalk2.italic(line)}` : line).join(EOL);
1185
+ }
1186
+ case "code": {
1187
+ const codeToken = token;
1188
+ if (!highlight) {
1189
+ return codeToken.text + EOL;
1190
+ }
1191
+ let language = "plaintext";
1192
+ if (codeToken.lang && highlight.supportsLanguage(codeToken.lang)) {
1193
+ language = codeToken.lang;
1194
+ }
1195
+ return highlight.highlight(codeToken.text, { language }) + EOL;
1196
+ }
1197
+ case "codespan":
1198
+ return theme.role("codeInline", token.text);
1199
+ case "em":
1200
+ return chalk2.italic(
1201
+ (token.tokens ?? []).map((child) => formatToken(child, theme, highlight, 0, null, parent)).join("")
1202
+ );
1203
+ case "strong":
1204
+ return chalk2.bold(
1205
+ (token.tokens ?? []).map((child) => formatToken(child, theme, highlight, 0, null, parent)).join("")
1206
+ );
1207
+ case "heading": {
1208
+ const headingToken = token;
1209
+ const inner = (headingToken.tokens ?? []).map((child) => formatToken(child, theme, highlight)).join("");
1210
+ const colored = theme.role("heading", inner);
1211
+ if (headingToken.depth === 1) {
1212
+ return chalk2.bold.italic.underline(colored) + EOL + EOL;
1213
+ }
1214
+ return chalk2.bold(colored) + EOL + EOL;
1215
+ }
1216
+ case "hr":
1217
+ return "---";
1218
+ case "image":
1219
+ return token.href;
1220
+ case "link": {
1221
+ const linkToken = token;
1222
+ if (linkToken.href.startsWith("mailto:")) {
1223
+ return linkToken.href.replace(/^mailto:/, "");
1224
+ }
1225
+ const linkText = (linkToken.tokens ?? []).map((child) => formatToken(child, theme, highlight, 0, null, linkToken)).join("");
1226
+ const plainLinkText = stripAnsi3(linkText);
1227
+ const display = plainLinkText && plainLinkText !== linkToken.href ? linkText : linkToken.href;
1228
+ return `\x1B]8;;${linkToken.href}\x07${display}\x1B]8;;\x07`;
1229
+ }
1230
+ case "list": {
1231
+ const listToken = token;
1232
+ const start = typeof listToken.start === "number" ? listToken.start : Number(listToken.start) || 1;
1233
+ return listToken.items.map(
1234
+ (item, index) => formatToken(
1235
+ item,
1236
+ theme,
1237
+ highlight,
1238
+ listDepth,
1239
+ listToken.ordered ? start + index : null,
1240
+ listToken
1241
+ )
1242
+ ).join("");
1243
+ }
1244
+ case "list_item":
1245
+ return (token.tokens ?? []).map(
1246
+ (child) => `${" ".repeat(listDepth)}${formatToken(child, theme, highlight, listDepth + 1, orderedListNumber, token)}`
1247
+ ).join("");
1248
+ case "paragraph":
1249
+ return (token.tokens ?? []).map((child) => formatToken(child, theme, highlight)).join("") + EOL;
1250
+ case "space":
1251
+ case "br":
1252
+ return EOL;
1253
+ case "text": {
1254
+ const textToken = token;
1255
+ if (parent?.type === "link") {
1256
+ return textToken.text;
1257
+ }
1258
+ if (parent?.type === "list_item") {
1259
+ const marker = orderedListNumber === null ? "-" : `${getListNumber(listDepth, orderedListNumber)}.`;
1260
+ const body = textToken.tokens ? textToken.tokens.map((child) => formatToken(child, theme, highlight, listDepth, orderedListNumber, token)).join("") : textToken.text;
1261
+ return `${marker} ${body}${EOL}`;
1262
+ }
1263
+ return textToken.text;
1264
+ }
1265
+ case "escape":
1266
+ return token.text;
1267
+ case "table":
1268
+ case "def":
1269
+ case "del":
1270
+ case "html":
1271
+ return "";
1272
+ default:
1273
+ return "";
1274
+ }
1275
+ }
1276
+ function numberToLetter(n) {
1277
+ let result = "";
1278
+ while (n > 0) {
1279
+ n--;
1280
+ result = String.fromCharCode(97 + n % 26) + result;
1281
+ n = Math.floor(n / 26);
1282
+ }
1283
+ return result;
1284
+ }
1285
+ var ROMAN_VALUES = [
1286
+ [1e3, "m"],
1287
+ [900, "cm"],
1288
+ [500, "d"],
1289
+ [400, "cd"],
1290
+ [100, "c"],
1291
+ [90, "xc"],
1292
+ [50, "l"],
1293
+ [40, "xl"],
1294
+ [10, "x"],
1295
+ [9, "ix"],
1296
+ [5, "v"],
1297
+ [4, "iv"],
1298
+ [1, "i"]
1299
+ ];
1300
+ function numberToRoman(n) {
1301
+ let result = "";
1302
+ for (const [value, numeral] of ROMAN_VALUES) {
1303
+ while (n >= value) {
1304
+ result += numeral;
1305
+ n -= value;
1306
+ }
1307
+ }
1308
+ return result;
1309
+ }
1310
+ function getListNumber(listDepth, orderedListNumber) {
1311
+ switch (listDepth) {
1312
+ case 0:
1313
+ case 1:
1314
+ return orderedListNumber.toString();
1315
+ case 2:
1316
+ return numberToLetter(orderedListNumber);
1317
+ case 3:
1318
+ return numberToRoman(orderedListNumber);
1319
+ default:
1320
+ return orderedListNumber.toString();
1321
+ }
1322
+ }
1323
+ function padAligned(content, displayWidth, targetWidth, align) {
1324
+ const padding = Math.max(0, targetWidth - displayWidth);
1325
+ if (align === "center") {
1326
+ const leftPad = Math.floor(padding / 2);
1327
+ return " ".repeat(leftPad) + content + " ".repeat(padding - leftPad);
1328
+ }
1329
+ if (align === "right") {
1330
+ return " ".repeat(padding) + content;
1331
+ }
1332
+ return content + " ".repeat(padding);
1333
+ }
1334
+ function stringWidth(text) {
1335
+ return visibleWidth(text);
1336
+ }
1337
+
1338
+ // src/react-ink/markdown/highlight.ts
1339
+ import { extname } from "node:path";
1340
+ import hljs from "highlight.js";
1341
+ function scopeToRole(scope) {
1342
+ const head = scope.split(".")[0] ?? scope;
1343
+ switch (head) {
1344
+ case "keyword":
1345
+ case "built_in":
1346
+ case "literal":
1347
+ case "operator":
1348
+ return "synKeyword";
1349
+ case "string":
1350
+ case "regexp":
1351
+ case "symbol":
1352
+ case "char":
1353
+ case "meta":
1354
+ return "synString";
1355
+ case "number":
1356
+ return "synNumber";
1357
+ case "comment":
1358
+ case "quote":
1359
+ return "synComment";
1360
+ case "type":
1361
+ case "class":
1362
+ case "title":
1363
+ case "tag":
1364
+ case "name":
1365
+ case "attr":
1366
+ case "attribute":
1367
+ case "selector":
1368
+ return "synType";
1369
+ default:
1370
+ return null;
1371
+ }
1372
+ }
1373
+ var HTML_ENTITIES = {
1374
+ "&amp;": "&",
1375
+ "&lt;": "<",
1376
+ "&gt;": ">",
1377
+ "&quot;": '"',
1378
+ "&#x27;": "'",
1379
+ "&#39;": "'"
1380
+ };
1381
+ function decodeEntities(text) {
1382
+ return text.replace(/&(?:amp|lt|gt|quot|#x27|#39);/g, (match) => HTML_ENTITIES[match] ?? match);
1383
+ }
1384
+ function parseHljsHtml(html) {
1385
+ const nodes = [];
1386
+ const scopeStack = [];
1387
+ const tagRe = /<span class="hljs-([^"]+)">|<\/span>/g;
1388
+ let lastIndex = 0;
1389
+ let match;
1390
+ const pushText = (raw) => {
1391
+ if (!raw) return;
1392
+ const currentScope = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : null;
1393
+ const scope = currentScope ? currentScope.split(/\s+/)[0].replace(/_$/, "") : null;
1394
+ nodes.push({ text: decodeEntities(raw), scope });
1395
+ };
1396
+ while ((match = tagRe.exec(html)) !== null) {
1397
+ pushText(html.slice(lastIndex, match.index));
1398
+ lastIndex = tagRe.lastIndex;
1399
+ if (match[0] === "</span>") {
1400
+ scopeStack.pop();
1401
+ } else if (match[1]) {
1402
+ scopeStack.push(match[1]);
1403
+ }
1404
+ }
1405
+ pushText(html.slice(lastIndex));
1406
+ return nodes;
1407
+ }
1408
+ function createHighlighter(theme) {
1409
+ return {
1410
+ supportsLanguage: (language) => {
1411
+ if (!language || language === "plaintext" || language === "text") {
1412
+ return false;
1413
+ }
1414
+ try {
1415
+ return hljs.getLanguage(language) !== void 0;
1416
+ } catch {
1417
+ return false;
1418
+ }
1419
+ },
1420
+ highlight: (code, options) => {
1421
+ const language = options.language;
1422
+ if (!language || language === "plaintext" || language === "text") {
1423
+ return code;
1424
+ }
1425
+ try {
1426
+ if (hljs.getLanguage(language) === void 0) {
1427
+ return code;
1428
+ }
1429
+ const { value } = hljs.highlight(code, { language, ignoreIllegals: true });
1430
+ return parseHljsHtml(value).map((node) => {
1431
+ const role = node.scope ? scopeToRole(node.scope) : null;
1432
+ return role ? theme.role(role, node.text) : node.text;
1433
+ }).join("");
1434
+ } catch {
1435
+ return code;
1436
+ }
1437
+ }
1438
+ };
1439
+ }
1440
+ function highlightByPath(code, filePath, theme) {
1441
+ const language = languageFromPath(filePath);
1442
+ if (!language) {
1443
+ return code;
1444
+ }
1445
+ const highlighter = createHighlighter(theme);
1446
+ if (!highlighter.supportsLanguage(language)) {
1447
+ return code;
1448
+ }
1449
+ return highlighter.highlight(code, { language });
1450
+ }
1451
+ var EXTENSION_LANGUAGES = {
1452
+ ts: "typescript",
1453
+ tsx: "typescript",
1454
+ mts: "typescript",
1455
+ cts: "typescript",
1456
+ js: "javascript",
1457
+ jsx: "javascript",
1458
+ mjs: "javascript",
1459
+ cjs: "javascript",
1460
+ py: "python",
1461
+ rb: "ruby",
1462
+ rs: "rust",
1463
+ go: "go",
1464
+ java: "java",
1465
+ kt: "kotlin",
1466
+ c: "c",
1467
+ h: "c",
1468
+ cc: "cpp",
1469
+ cpp: "cpp",
1470
+ hpp: "cpp",
1471
+ cs: "csharp",
1472
+ sh: "bash",
1473
+ bash: "bash",
1474
+ zsh: "bash",
1475
+ yml: "yaml",
1476
+ yaml: "yaml",
1477
+ json: "json",
1478
+ md: "markdown",
1479
+ html: "xml",
1480
+ xml: "xml",
1481
+ css: "css",
1482
+ scss: "scss",
1483
+ sql: "sql",
1484
+ toml: "ini",
1485
+ ini: "ini",
1486
+ php: "php",
1487
+ swift: "swift"
1488
+ };
1489
+ function languageFromPath(filePath) {
1490
+ const ext = extname(filePath).slice(1).toLowerCase();
1491
+ if (!ext) {
1492
+ return null;
1493
+ }
1494
+ const mapped = EXTENSION_LANGUAGES[ext];
1495
+ if (mapped) {
1496
+ return mapped;
1497
+ }
1498
+ try {
1499
+ return hljs.getLanguage(ext) !== void 0 ? ext : null;
1500
+ } catch {
1501
+ return null;
1502
+ }
1503
+ }
1504
+
1505
+ // src/react-ink/markdown/MarkdownTable.tsx
1506
+ import chalk3 from "chalk";
1507
+ import stripAnsi4 from "strip-ansi";
1508
+ var MIN_COLUMN_WIDTH = 3;
1509
+ var COLUMN_GAP = 2;
1510
+ function MarkdownTable({ token, theme, highlight = null }) {
1511
+ const formatCell = (tokens) => (tokens ?? []).map((child) => formatToken(child, theme, highlight)).join("");
1512
+ const displayWidth = (tokens) => stringWidth(stripAnsi4(formatCell(tokens)));
1513
+ const columnCount = token.header.length;
1514
+ const columnWidths = token.header.map((header, index) => {
1515
+ let max = displayWidth(header.tokens);
1516
+ for (const row of token.rows) {
1517
+ max = Math.max(max, displayWidth(row[index]?.tokens));
1518
+ }
1519
+ return Math.max(max, MIN_COLUMN_WIDTH);
1520
+ });
1521
+ const renderRow = (cells, key, bold) => /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: columnWidths.map((width, index) => {
1522
+ const cell = cells[index];
1523
+ const content = formatCell(cell?.tokens);
1524
+ const visible = stringWidth(stripAnsi4(content));
1525
+ const align = token.align?.[index];
1526
+ const padded = padAligned(content, visible, width, align ?? "left");
1527
+ const styled = bold ? chalk3.bold(padded) : padded;
1528
+ const gap = index < columnCount - 1 ? " ".repeat(COLUMN_GAP) : "";
1529
+ return /* @__PURE__ */ jsxs(Text, { children: [
1530
+ styled,
1531
+ gap
1532
+ ] }, index);
1533
+ }) }, key);
1534
+ const separator = /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: columnWidths.map((width, index) => {
1535
+ const gap = index < columnCount - 1 ? " ".repeat(COLUMN_GAP) : "";
1536
+ return /* @__PURE__ */ jsxs(Text, { children: [
1537
+ theme.dim("-".repeat(width)),
1538
+ gap
1539
+ ] }, index);
1540
+ }) });
1541
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1542
+ renderRow(token.header, "header", true),
1543
+ separator,
1544
+ token.rows.map((row, rowIndex) => renderRow(row, `row-${rowIndex}`, false))
1545
+ ] });
1546
+ }
1547
+
1548
+ // src/react-ink/markdown/Markdown.tsx
1549
+ function stripPromptXMLTags(text) {
1550
+ return text.replace(/<\/?(?:system-reminder|prompt|context)[^>]*>/g, "");
1551
+ }
1552
+ function Markdown({ children, theme, highlightCode = true, dim = false }) {
1553
+ const highlight = useMemo(
1554
+ () => highlightCode ? createHighlighter(theme) : null,
1555
+ [highlightCode, theme]
1556
+ );
1557
+ const elements = useMemo(() => {
1558
+ configureMarked();
1559
+ const tokens = cachedLexer(stripPromptXMLTags(children));
1560
+ const out = [];
1561
+ let buffer = "";
1562
+ const flush = () => {
1563
+ if (buffer) {
1564
+ const text = buffer.replace(/\n+$/, "");
1565
+ if (text) {
1566
+ out.push(
1567
+ /* @__PURE__ */ jsx(Text, { children: dim ? theme.dim(text) : text }, out.length)
1568
+ );
1569
+ }
1570
+ buffer = "";
1571
+ }
1572
+ };
1573
+ for (const token of tokens) {
1574
+ if (token.type === "table") {
1575
+ flush();
1576
+ out.push(
1577
+ /* @__PURE__ */ jsx(
1578
+ MarkdownTable,
1579
+ {
1580
+ token,
1581
+ theme,
1582
+ highlight
1583
+ },
1584
+ out.length
1585
+ )
1586
+ );
1587
+ } else {
1588
+ buffer += formatToken(token, theme, highlight);
1589
+ }
1590
+ }
1591
+ flush();
1592
+ return out;
1593
+ }, [children, dim, highlight, theme]);
1594
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: elements });
1595
+ }
1596
+
563
1597
  // src/react-ink/utils/tool-display.ts
564
1598
  import { homedir } from "node:os";
1599
+
1600
+ // src/react-ink/diff/structured.ts
1601
+ import { structuredPatch } from "diff";
1602
+
1603
+ // src/react-ink/diff/word-diff.ts
1604
+ import { diffWordsWithSpace } from "diff";
1605
+ var CHANGE_THRESHOLD = 0.4;
1606
+ function wordDiffLine(oldLine, newLine, side) {
1607
+ const lineText = side === "removed" ? oldLine : newLine;
1608
+ if (oldLine.length === 0 || newLine.length === 0) {
1609
+ return [{ text: lineText, changed: true }];
1610
+ }
1611
+ let changes;
1612
+ try {
1613
+ changes = diffWordsWithSpace(oldLine, newLine);
1614
+ } catch {
1615
+ return [{ text: lineText, changed: true }];
1616
+ }
1617
+ let changedChars = 0;
1618
+ let totalChars = 0;
1619
+ for (const change of changes) {
1620
+ totalChars += change.value.length;
1621
+ if (change.added || change.removed) {
1622
+ changedChars += change.value.length;
1623
+ }
1624
+ }
1625
+ const fraction = totalChars === 0 ? 0 : changedChars / totalChars;
1626
+ if (fraction > CHANGE_THRESHOLD) {
1627
+ return [{ text: lineText, changed: true }];
1628
+ }
1629
+ const spans = [];
1630
+ for (const change of changes) {
1631
+ const belongs = side === "removed" ? !change.added : !change.removed;
1632
+ if (!belongs) {
1633
+ continue;
1634
+ }
1635
+ spans.push({ text: change.value, changed: Boolean(change.added || change.removed) });
1636
+ }
1637
+ return spans.length > 0 ? spans : [{ text: lineText, changed: false }];
1638
+ }
1639
+
1640
+ // src/react-ink/diff/structured.ts
1641
+ var CONTEXT_LINES = 3;
1642
+ function buildStructuredDiff(oldStr, newStr, filePath = "") {
1643
+ if (oldStr === newStr) {
1644
+ return null;
1645
+ }
1646
+ let patch;
1647
+ try {
1648
+ patch = structuredPatch(filePath, filePath, oldStr, newStr, "", "", { context: CONTEXT_LINES });
1649
+ } catch {
1650
+ return null;
1651
+ }
1652
+ const hunks = [];
1653
+ let addedCount = 0;
1654
+ let removedCount = 0;
1655
+ for (const hunk of patch.hunks) {
1656
+ const lines = classifyHunkLines(hunk);
1657
+ for (const line of lines) {
1658
+ if (line.kind === "added") addedCount += 1;
1659
+ else if (line.kind === "removed") removedCount += 1;
1660
+ }
1661
+ if (lines.length > 0) {
1662
+ hunks.push({ oldStart: hunk.oldStart, newStart: hunk.newStart, lines });
1663
+ }
1664
+ }
1665
+ if (hunks.length === 0) {
1666
+ return null;
1667
+ }
1668
+ return { hunks, addedCount, removedCount };
1669
+ }
1670
+ function classifyHunkLines(hunk) {
1671
+ const out = [];
1672
+ let oldNum = hunk.oldStart;
1673
+ let newNum = hunk.newStart;
1674
+ const rawLines = hunk.lines.filter((line) => !line.startsWith("\\"));
1675
+ let removedRun = [];
1676
+ let removedRunStart = -1;
1677
+ const flushPairing = (addedRun2) => {
1678
+ const pairs = Math.min(removedRun.length, addedRun2.length);
1679
+ for (let i = 0; i < pairs; i += 1) {
1680
+ const removed = removedRun[i];
1681
+ const added = addedRun2[i];
1682
+ removed.spans = wordDiffLine(removed.text, added.text, "removed");
1683
+ added.spans = wordDiffLine(removed.text, added.text, "added");
1684
+ }
1685
+ removedRun = [];
1686
+ removedRunStart = -1;
1687
+ };
1688
+ let addedRun = [];
1689
+ for (const raw of rawLines) {
1690
+ const marker = raw[0];
1691
+ const text = raw.slice(1);
1692
+ if (marker === "-") {
1693
+ if (addedRun.length > 0) {
1694
+ addedRun = [];
1695
+ }
1696
+ const line = { kind: "removed", oldLine: oldNum, text };
1697
+ out.push(line);
1698
+ if (removedRunStart === -1) removedRunStart = out.length - 1;
1699
+ removedRun.push(line);
1700
+ oldNum += 1;
1701
+ } else if (marker === "+") {
1702
+ const line = { kind: "added", newLine: newNum, text };
1703
+ out.push(line);
1704
+ addedRun.push(line);
1705
+ newNum += 1;
1706
+ } else {
1707
+ if (removedRun.length > 0 && addedRun.length > 0) {
1708
+ flushPairing(addedRun);
1709
+ }
1710
+ removedRun = [];
1711
+ removedRunStart = -1;
1712
+ addedRun = [];
1713
+ out.push({ kind: "context", oldLine: oldNum, newLine: newNum, text });
1714
+ oldNum += 1;
1715
+ newNum += 1;
1716
+ }
1717
+ }
1718
+ if (removedRun.length > 0 && addedRun.length > 0) {
1719
+ flushPairing(addedRun);
1720
+ }
1721
+ return out;
1722
+ }
1723
+
1724
+ // src/react-ink/utils/tool-display.ts
565
1725
  function asRecord(value) {
566
1726
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
567
1727
  }
@@ -644,6 +1804,12 @@ function clipBody(value, maxLines = 8, maxChars = 1400) {
644
1804
  }
645
1805
  return previewMultiline(value, { maxLines, maxChars });
646
1806
  }
1807
+ function highlightedBody(body, filePath, theme) {
1808
+ if (!body || !theme || !filePath || languageFromPath(filePath) === null) {
1809
+ return { body };
1810
+ }
1811
+ return { body: highlightByPath(body, filePath, theme), preformatted: true };
1812
+ }
647
1813
  function extractDetails(value) {
648
1814
  return asRecord(asRecord(value)?.details);
649
1815
  }
@@ -710,7 +1876,7 @@ function fallbackBody(output, fallbackOutputText, showImages = false) {
710
1876
  return clipBody(fallbackOutputText);
711
1877
  }
712
1878
  function describeToolSource(source) {
713
- const { args, fallbackArgsText, fallbackOutputText, output, showImages = false, toolName } = source;
1879
+ const { args, fallbackArgsText, fallbackOutputText, output, showImages = false, theme, toolName } = source;
714
1880
  const argsRecord = asRecord(args);
715
1881
  const details = extractDetails(output);
716
1882
  const outputText = toolText(output, showImages);
@@ -728,10 +1894,12 @@ function describeToolSource(source) {
728
1894
  summary += `:${start}${end ? `-${end}` : ""}`;
729
1895
  }
730
1896
  const responseSummary = containsImage(output) ? firstMeaningfulLine(outputText) ?? "Read image" : /unchanged/i.test(outputText) ? "Unchanged since last read" : countMeaningfulLines(outputText) > 0 ? `Read ${countMeaningfulLines(outputText)} ${countMeaningfulLines(outputText) === 1 ? "line" : "lines"}` : "Read file";
1897
+ const readBody = hasResult && !containsImage(output) ? highlightedBody(clipBody(outputText, 10, 1800), rawPath, theme) : { body: hasResult ? clipBody(outputText, 10, 1800) : void 0 };
731
1898
  return {
732
1899
  title,
733
1900
  summary: hasResult ? responseSummary : summary,
734
- body: hasResult ? clipBody(outputText, 10, 1800) : void 0,
1901
+ body: readBody.body,
1902
+ preformatted: readBody.preformatted,
735
1903
  emptyText: "Reading file..."
736
1904
  };
737
1905
  }
@@ -739,10 +1907,14 @@ function describeToolSource(source) {
739
1907
  const rawPath = asString(argsRecord?.file_path) ?? asString(argsRecord?.path) ?? "";
740
1908
  const content = asString(argsRecord?.content);
741
1909
  const responseSummary = firstMeaningfulLine(outputText) ?? "Wrote file";
1910
+ const writeDiff = !hasResult && content !== void 0 && content.length > 0 ? buildStructuredDiff("", content, rawPath) ?? void 0 : void 0;
1911
+ const writeBody = hasResult ? { body: clipBody(outputText, 8, 1500) } : highlightedBody(clipBody(content, 8, 1500), rawPath, theme);
742
1912
  return {
743
1913
  title,
744
1914
  summary: hasResult ? responseSummary : rawPath ? shortenPath2(rawPath) : void 0,
745
- body: hasResult ? clipBody(outputText, 8, 1500) : clipBody(content, 8, 1500),
1915
+ body: writeDiff ? void 0 : writeBody.body,
1916
+ preformatted: writeDiff ? void 0 : writeBody.preformatted,
1917
+ diff: writeDiff,
746
1918
  emptyText: "Preparing file write..."
747
1919
  };
748
1920
  }
@@ -759,10 +1931,15 @@ function describeToolSource(source) {
759
1931
  400
760
1932
  ) : void 0;
761
1933
  const responseSummary = firstMeaningfulLine(outputText) ?? "Applied edit";
1934
+ const editDiff = oldText !== void 0 && newText !== void 0 ? buildStructuredDiff(oldText, newText, rawPath) ?? void 0 : void 0;
1935
+ const editBody = hasResult ? highlightedBody(clipBody(outputText, 8, 1500), rawPath, theme) : { body: replacementPreview };
1936
+ const editChangeSummary = editDiff ? `+${editDiff.addedCount} -${editDiff.removedCount}` : void 0;
762
1937
  return {
763
1938
  title,
764
- summary: hasResult ? responseSummary : rawPath ? shortenPath2(rawPath) : void 0,
765
- body: hasResult ? clipBody(outputText, 8, 1500) : replacementPreview,
1939
+ summary: hasResult ? editChangeSummary ?? responseSummary : editChangeSummary ?? (rawPath ? shortenPath2(rawPath) : void 0),
1940
+ body: editDiff ? void 0 : editBody.body,
1941
+ preformatted: editDiff ? void 0 : editBody.preformatted,
1942
+ diff: editDiff,
766
1943
  emptyText: "Applying edit..."
767
1944
  };
768
1945
  }
@@ -859,7 +2036,7 @@ function describeToolCall(toolCall) {
859
2036
  args: toolCall.arguments
860
2037
  });
861
2038
  }
862
- function describeToolResult(message, showImages, args) {
2039
+ function describeToolResult(message, showImages, args, theme) {
863
2040
  return describeToolSource({
864
2041
  args,
865
2042
  toolName: message.toolName,
@@ -867,12 +2044,13 @@ function describeToolResult(message, showImages, args) {
867
2044
  content: message.content,
868
2045
  details: message.details
869
2046
  },
870
- showImages
2047
+ showImages,
2048
+ theme
871
2049
  });
872
2050
  }
873
2051
 
874
2052
  // src/react-ink/components/ToolEventBlock.tsx
875
- import stripAnsi3 from "strip-ansi";
2053
+ import stripAnsi5 from "strip-ansi";
876
2054
  function statusMarker(status) {
877
2055
  switch (status) {
878
2056
  case "error":
@@ -884,11 +2062,11 @@ function statusMarker(status) {
884
2062
  }
885
2063
  }
886
2064
  function plainToolText(text) {
887
- return stripAnsi3(text);
2065
+ return stripAnsi5(text);
888
2066
  }
889
2067
  function splitVisibleLines(text) {
890
2068
  return (text ?? "").split(/\r?\n/).map((line) => line.trimEnd()).filter((line, index, lines) => {
891
- if (line.length > 0) {
2069
+ if (stripAnsi5(line).length > 0) {
892
2070
  return true;
893
2071
  }
894
2072
  return index !== 0 && index !== lines.length - 1;
@@ -919,6 +2097,7 @@ function ToolEventBlock({
919
2097
  indent = 0,
920
2098
  marginBottom = 1,
921
2099
  maxContentLines = 10,
2100
+ preformatted = false,
922
2101
  showSummaryInline = false,
923
2102
  showTitle = true,
924
2103
  status,
@@ -947,9 +2126,9 @@ function ToolEventBlock({
947
2126
  Box,
948
2127
  {
949
2128
  marginLeft: line.kind === "response" ? showTitle ? 2 : 0 : showTitle ? 4 : 2,
950
- children: /* @__PURE__ */ jsx(Text, { color: "white", children: plainToolText(line.text) })
2129
+ children: preformatted ? /* @__PURE__ */ jsx(Text, { children: line.text }) : /* @__PURE__ */ jsx(Text, { color: "white", children: plainToolText(line.text) })
951
2130
  },
952
- `${line.kind}:${index}:${line.text}`
2131
+ `${line.kind}:${index}:${stripAnsi5(line.text)}`
953
2132
  )),
954
2133
  hiddenLineCount > 0 ? /* @__PURE__ */ jsx(Box, { marginLeft: showTitle ? 4 : 2, children: /* @__PURE__ */ jsx(Text, { color: "white", children: plainToolText(`... ${hiddenLineCount} more line(s)`) }) }) : null
955
2134
  ] });
@@ -974,17 +2153,100 @@ function ToolCallMessage({ theme, toolCall }) {
974
2153
  );
975
2154
  }
976
2155
 
2156
+ // src/react-ink/diff/Diff.tsx
2157
+ import chalk4 from "chalk";
2158
+ function lineMarker(kind) {
2159
+ switch (kind) {
2160
+ case "added":
2161
+ return "+";
2162
+ case "removed":
2163
+ return "-";
2164
+ default:
2165
+ return " ";
2166
+ }
2167
+ }
2168
+ function gutterWidth(diff) {
2169
+ let max = 1;
2170
+ for (const hunk of diff.hunks) {
2171
+ for (const line of hunk.lines) {
2172
+ const num = line.kind === "removed" ? line.oldLine : line.newLine;
2173
+ if (num !== void 0) {
2174
+ max = Math.max(max, stringWidth(String(num)));
2175
+ }
2176
+ }
2177
+ }
2178
+ return max;
2179
+ }
2180
+ function renderLineText(line, theme, bgRole, fgRole) {
2181
+ const paintSpan = (text, changed) => {
2182
+ const fg = theme.role(fgRole, text);
2183
+ return changed ? chalk4.bold(fg) : fg;
2184
+ };
2185
+ let body;
2186
+ if (line.spans && line.spans.length > 0) {
2187
+ body = line.spans.map((span) => paintSpan(span.text, span.changed)).join("");
2188
+ } else {
2189
+ body = theme.role(fgRole, line.text);
2190
+ }
2191
+ return bgRole ? theme.roleBackground(bgRole, body) : body;
2192
+ }
2193
+ function Diff({ diff, theme, indent = 0, marginBottom = 0 }) {
2194
+ const gutter = gutterWidth(diff);
2195
+ const renderLine = (line, key) => {
2196
+ const num = line.kind === "removed" ? line.oldLine : line.newLine;
2197
+ const gutterText = (num !== void 0 ? String(num) : "").padStart(gutter);
2198
+ const marker = lineMarker(line.kind);
2199
+ const bgRole = line.kind === "added" ? "diffAddedBg" : line.kind === "removed" ? "diffRemovedBg" : null;
2200
+ const fgRole = line.kind === "added" ? "diffAddedText" : line.kind === "removed" ? "diffRemovedText" : "blockquoteBar";
2201
+ const gutterStyled = theme.dim(`${gutterText} `);
2202
+ const markerStyled = line.kind === "context" ? theme.dim(`${marker} `) : theme.role(fgRole, `${marker} `);
2203
+ const text = renderLineText(line, theme, bgRole, fgRole);
2204
+ return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { children: [
2205
+ gutterStyled,
2206
+ markerStyled,
2207
+ text
2208
+ ] }) }, key);
2209
+ };
2210
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginLeft: indent, marginBottom, children: diff.hunks.map((hunk, hunkIndex) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2211
+ hunkIndex > 0 ? /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { children: theme.dim("...") }) }) : null,
2212
+ hunk.lines.map((line, lineIndex) => renderLine(line, `${hunkIndex}-${lineIndex}`))
2213
+ ] }, `hunk-${hunkIndex}`)) });
2214
+ }
2215
+
977
2216
  // src/react-ink/components/messages/ToolResultBlock.tsx
978
2217
  function ToolResultBlock({ expanded = false, message, nested = false, showImages, theme, toolCall }) {
979
- const descriptor = describeToolResult(message, showImages, toolCall?.arguments);
2218
+ const descriptor = describeToolResult(message, showImages, toolCall?.arguments, theme);
2219
+ const indent = nested ? 4 : 2;
2220
+ if (descriptor.diff) {
2221
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [
2222
+ /* @__PURE__ */ jsx(
2223
+ ToolEventBlock,
2224
+ {
2225
+ detail: void 0,
2226
+ emptyText: descriptor.emptyText,
2227
+ indent,
2228
+ marginBottom: 0,
2229
+ maxContentLines: 0,
2230
+ showSummaryInline: false,
2231
+ showTitle: !nested,
2232
+ status: message.isError ? "error" : "success",
2233
+ summary: descriptor.summary,
2234
+ theme,
2235
+ title: descriptor.title
2236
+ }
2237
+ ),
2238
+ /* @__PURE__ */ jsx(Diff, { diff: descriptor.diff, theme, indent: indent + 2 })
2239
+ ] });
2240
+ }
980
2241
  return /* @__PURE__ */ jsx(
981
2242
  ToolEventBlock,
982
2243
  {
983
2244
  detail: descriptor.body,
984
2245
  emptyText: descriptor.emptyText,
985
- indent: nested ? 4 : 2,
2246
+ indent,
986
2247
  marginBottom: 0,
987
2248
  maxContentLines: expanded ? Number.MAX_SAFE_INTEGER : 10,
2249
+ preformatted: descriptor.preformatted,
988
2250
  showSummaryInline: false,
989
2251
  showTitle: !nested,
990
2252
  status: message.isError ? "error" : "success",
@@ -1009,7 +2271,7 @@ function AssistantMessageView({
1009
2271
  const matchedToolResultIds = /* @__PURE__ */ new Set();
1010
2272
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
1011
2273
  /* @__PURE__ */ jsx(Text, { children: theme.color("accent", `Assistant ${message.provider}/${message.model} ${formatMessageTimestamp(message.timestamp)}`) }),
1012
- parts.text ? /* @__PURE__ */ jsx(Text, { children: parts.text }) : null,
2274
+ parts.text ? /* @__PURE__ */ jsx(Markdown, { theme, children: parts.text }) : null,
1013
2275
  parts.thinking.map((thinking, index) => /* @__PURE__ */ jsx(Text, { children: theme.muted(`[thinking] ${thinking}`) }, index)),
1014
2276
  parts.toolCalls.map((toolCall) => {
1015
2277
  const toolResult = toolResultsByCallId.get(toolCall.id);
@@ -1289,14 +2551,6 @@ function DialogFrame({ children, footer, subtitle, title }) {
1289
2551
  ] });
1290
2552
  }
1291
2553
 
1292
- // src/react-host/index.ts
1293
- var React = await loadHostReact();
1294
- var Fragment2 = React.Fragment;
1295
- var createElement = React.createElement;
1296
- var useEffect = React.useEffect;
1297
- var useMemo = React.useMemo;
1298
- var useState = React.useState;
1299
-
1300
2554
  // src/react-ink/utils/selection-dialog.ts
1301
2555
  function matchesSearchQuery(value, query) {
1302
2556
  if (!query.trim()) {
@@ -1323,6 +2577,7 @@ function SelectableDialog({
1323
2577
  noSearchResultsText = "No options matched the current search.",
1324
2578
  onClose,
1325
2579
  onSelect,
2580
+ onHighlight,
1326
2581
  getSearchText,
1327
2582
  renderItem
1328
2583
  }) {
@@ -1342,6 +2597,12 @@ function SelectableDialog({
1342
2597
  setSelectedIndex(0);
1343
2598
  }
1344
2599
  }, [filteredItems.length, selectedIndex]);
2600
+ const highlighted = filteredItems[selectedIndex];
2601
+ useEffect(() => {
2602
+ if (isActive && onHighlight && highlighted !== void 0) {
2603
+ onHighlight(highlighted, selectedIndex);
2604
+ }
2605
+ }, [highlighted, isActive]);
1345
2606
  const maxVisible = useMemo(() => Math.max(8, (process.stdout.rows ?? 24) - 16), []);
1346
2607
  const windowStart = Math.max(
1347
2608
  0,
@@ -2368,18 +3629,30 @@ function StartupSessionPicker({ onClose, onSelect, sessions, totalCount }) {
2368
3629
  }
2369
3630
 
2370
3631
  // src/react-ink/components/dialogs/ThemeDialog.tsx
2371
- function ThemeDialog({ themes, onClose, onSelect }) {
3632
+ function toItem(entry) {
3633
+ return typeof entry === "string" ? { id: entry, label: entry } : entry;
3634
+ }
3635
+ function ThemeDialog({ themes, onClose, onSelect, onHighlight }) {
3636
+ const items = themes.map(toItem);
2372
3637
  return /* @__PURE__ */ jsx(
2373
3638
  SelectableDialog,
2374
3639
  {
2375
3640
  title: "Themes",
2376
- items: themes,
3641
+ items,
2377
3642
  emptyText: "No themes are available.",
2378
3643
  onClose,
2379
- onSelect,
2380
- renderItem: (themeName, selected) => /* @__PURE__ */ jsxs(Text, { children: [
2381
- selected ? "> " : " ",
2382
- themeName
3644
+ onSelect: (item) => onSelect(item.id),
3645
+ onHighlight: onHighlight ? (item) => onHighlight(item.id) : void 0,
3646
+ getSearchText: (item) => `${item.label} ${item.description ?? ""}`,
3647
+ renderItem: (item, selected) => /* @__PURE__ */ jsxs(Box, { children: [
3648
+ /* @__PURE__ */ jsxs(Text, { children: [
3649
+ selected ? "> " : " ",
3650
+ item.label
3651
+ ] }),
3652
+ item.description ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3653
+ " ",
3654
+ item.description
3655
+ ] }) : null
2383
3656
  ] })
2384
3657
  }
2385
3658
  );
@@ -2423,17 +3696,77 @@ function UserMessageDialog({ items, onClose, onSelect }) {
2423
3696
  }
2424
3697
  );
2425
3698
  }
3699
+
3700
+ // src/react-ink/markdown/StreamingMarkdown.tsx
3701
+ function lastStableBoundary(content) {
3702
+ let boundary = 0;
3703
+ let inFence = false;
3704
+ let lineStart = 0;
3705
+ let sawBlankRun = false;
3706
+ for (let i = 0; i <= content.length; i++) {
3707
+ const atEnd = i === content.length;
3708
+ const ch = atEnd ? "\n" : content[i];
3709
+ if (ch !== "\n" && !atEnd) {
3710
+ continue;
3711
+ }
3712
+ const line = content.slice(lineStart, i);
3713
+ if (line.trimStart().startsWith("```")) {
3714
+ inFence = !inFence;
3715
+ sawBlankRun = false;
3716
+ } else if (line.trim() === "") {
3717
+ if (!inFence) {
3718
+ sawBlankRun = true;
3719
+ }
3720
+ } else {
3721
+ if (sawBlankRun && !inFence) {
3722
+ boundary = lineStart;
3723
+ }
3724
+ sawBlankRun = false;
3725
+ }
3726
+ lineStart = i + 1;
3727
+ if (atEnd) {
3728
+ break;
3729
+ }
3730
+ }
3731
+ return boundary;
3732
+ }
3733
+ function StreamingMarkdown({
3734
+ children,
3735
+ theme,
3736
+ highlightCode = true,
3737
+ dim = false
3738
+ }) {
3739
+ const boundary = useMemo(() => lastStableBoundary(children), [children]);
3740
+ const stablePrefix = useMemo(
3741
+ () => children.slice(0, boundary).replace(/\n+$/, ""),
3742
+ [children, boundary]
3743
+ );
3744
+ const unstableSuffix = useMemo(() => children.slice(boundary), [children, boundary]);
3745
+ const stableNode = useMemo(
3746
+ () => stablePrefix ? /* @__PURE__ */ jsx(Markdown, { theme, highlightCode, dim, children: stablePrefix }) : null,
3747
+ [stablePrefix, theme, highlightCode, dim]
3748
+ );
3749
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3750
+ stableNode,
3751
+ unstableSuffix ? /* @__PURE__ */ jsx(Markdown, { theme, highlightCode, dim, children: unstableSuffix }) : null
3752
+ ] });
3753
+ }
2426
3754
  export {
2427
3755
  AssistantMessageView,
2428
3756
  BashMessageView,
2429
3757
  BranchSummaryMessageView,
3758
+ CHANGE_THRESHOLD,
3759
+ CONTEXT_LINES,
2430
3760
  ChangelogBlock,
2431
3761
  CompactionMessageView,
2432
3762
  CustomMessageView,
2433
3763
  DialogFrame,
3764
+ Diff,
2434
3765
  DisplayBlockView,
2435
3766
  Footer,
2436
3767
  LoginDialog,
3768
+ Markdown,
3769
+ MarkdownTable,
2437
3770
  MessageList,
2438
3771
  MessageRow,
2439
3772
  ModelDialog,
@@ -2445,6 +3778,7 @@ export {
2445
3778
  SkillInvocationMessage,
2446
3779
  StartupSessionPicker,
2447
3780
  StatusLine,
3781
+ StreamingMarkdown,
2448
3782
  TaskPanel,
2449
3783
  ThemeDialog,
2450
3784
  ToolCallMessage,
@@ -2453,6 +3787,10 @@ export {
2453
3787
  TreeDialog,
2454
3788
  UserMessageDialog,
2455
3789
  UserMessageView,
3790
+ buildStructuredDiff,
3791
+ cachedLexer,
3792
+ configureMarked,
3793
+ createHighlighter,
2456
3794
  createThemeAdapter,
2457
3795
  describeToolCall,
2458
3796
  describeToolExecution,
@@ -2462,14 +3800,22 @@ export {
2462
3800
  extractToolText,
2463
3801
  formatCustomContent,
2464
3802
  formatMessageTimestamp,
3803
+ formatToken,
2465
3804
  formatToolCall,
2466
3805
  formatToolResult,
2467
3806
  formatUserContent,
3807
+ getListNumber,
3808
+ hasMarkdownSyntax,
3809
+ highlightByPath,
3810
+ languageFromPath,
2468
3811
  matchesSearchQuery,
3812
+ padAligned,
2469
3813
  parseSkillInvocation,
2470
3814
  previewMultiline,
2471
3815
  previewText,
2472
3816
  safeStringify,
2473
3817
  splitAssistantMessage,
3818
+ stringWidth,
3819
+ wordDiffLine,
2474
3820
  wrapSelectionIndex
2475
3821
  };