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/index.js CHANGED
@@ -12790,6 +12790,34 @@ var useInput = ink.useInput;
12790
12790
  // src/react-ink/theme-adapter.ts
12791
12791
  import chalk from "chalk";
12792
12792
  import stripAnsi from "strip-ansi";
12793
+ var DEFAULT_ROLE_KEYS = {
12794
+ codeInline: "codeInline",
12795
+ heading: "heading",
12796
+ blockquoteBar: "blockquoteBar",
12797
+ diffAddedBg: "diffAddedBg",
12798
+ diffRemovedBg: "diffRemovedBg",
12799
+ diffAddedText: "diffAddedText",
12800
+ diffRemovedText: "diffRemovedText",
12801
+ synKeyword: "synKeyword",
12802
+ synString: "synString",
12803
+ synNumber: "synNumber",
12804
+ synComment: "synComment",
12805
+ synType: "synType"
12806
+ };
12807
+ var ROLE_FALLBACK_KEYS = {
12808
+ codeInline: "accent",
12809
+ heading: "accent",
12810
+ blockquoteBar: "muted",
12811
+ diffAddedBg: "success",
12812
+ diffRemovedBg: "error",
12813
+ diffAddedText: "success",
12814
+ diffRemovedText: "error",
12815
+ synKeyword: "accent",
12816
+ synString: "success",
12817
+ synNumber: "warning",
12818
+ synComment: "muted",
12819
+ synType: "info"
12820
+ };
12793
12821
  function applyForeground(color, text) {
12794
12822
  if (!color) return text;
12795
12823
  try {
@@ -12816,15 +12844,27 @@ function applyBackground(background, foreground, text) {
12816
12844
  return applyForeground(foreground, text);
12817
12845
  }
12818
12846
  }
12819
- function createThemeAdapter(themeName, colors) {
12847
+ function createThemeAdapter(themeName, colors, roleOverrides) {
12820
12848
  const fallback = colors.text ?? "#e5e5e7";
12849
+ const roles = { ...DEFAULT_ROLE_KEYS, ...roleOverrides };
12850
+ const resolveRoleColor = (role) => resolveColorToken(
12851
+ colors[roles[role]],
12852
+ resolveColorToken(colors[ROLE_FALLBACK_KEYS[role]], fallback)
12853
+ );
12821
12854
  return {
12822
12855
  name: themeName,
12823
12856
  colors,
12857
+ roles,
12824
12858
  color: (key, text) => applyForeground(resolveColorToken(colors[key], fallback), text),
12825
12859
  background: (key, text, textKey = "text") => applyBackground(resolveColorToken(colors[key]), resolveColorToken(colors[textKey], fallback), ` ${stripAnsi(text)} `),
12826
12860
  dim: (text) => applyForeground(resolveColorToken(colors.dim, "#666666"), text),
12827
- muted: (text) => applyForeground(resolveColorToken(colors.muted, "#808080"), text)
12861
+ muted: (text) => applyForeground(resolveColorToken(colors.muted, "#808080"), text),
12862
+ role: (role, text) => applyForeground(resolveRoleColor(role), text),
12863
+ roleBackground: (role, text, foregroundRole) => applyBackground(
12864
+ resolveRoleColor(role),
12865
+ foregroundRole ? resolveRoleColor(foregroundRole) : void 0,
12866
+ text
12867
+ )
12828
12868
  };
12829
12869
  }
12830
12870
 
@@ -13209,8 +13249,1120 @@ function parseSkillInvocation(content) {
13209
13249
  };
13210
13250
  }
13211
13251
 
13252
+ // src/react-ink/markdown/format-token.ts
13253
+ import chalk2 from "chalk";
13254
+ import { marked } from "marked";
13255
+ import stripAnsi3 from "strip-ansi";
13256
+
13257
+ // src/ui/utils.ts
13258
+ import { eastAsianWidth } from "get-east-asian-width";
13259
+ var segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
13260
+ var TextWidthCalculator = class {
13261
+ getWidth(text) {
13262
+ return visibleWidth(text);
13263
+ }
13264
+ clearCache() {
13265
+ widthCache.clear();
13266
+ }
13267
+ };
13268
+ var textWidthCalculator = new TextWidthCalculator();
13269
+ function looksLikeEmojiCandidate(segment) {
13270
+ const cp = segment.codePointAt(0);
13271
+ return cp >= 126976 && cp <= 130047 || // pictographs and emoji proper
13272
+ cp >= 8960 && cp <= 9215 || // miscellaneous technical glyphs
13273
+ cp >= 9728 && cp <= 10175 || // assorted symbols and dingbats
13274
+ cp >= 11088 && cp <= 11093 || // a few star/circle characters
13275
+ segment.includes("\uFE0F") || // carries VS16, the emoji-presentation selector
13276
+ segment.length > 2;
13277
+ }
13278
+ function createUnicodeRegex(vPattern, uFallback) {
13279
+ try {
13280
+ return new RegExp(vPattern, "v");
13281
+ } catch {
13282
+ return new RegExp(uFallback, "u");
13283
+ }
13284
+ }
13285
+ var zeroWidthRegex = createUnicodeRegex(
13286
+ "^(?:\\p{Default_Ignorable_Code_Point}|\\p{Control}|\\p{Mark}|\\p{Surrogate})+$",
13287
+ "^(?:\\p{Default_Ignorable_Code_Point}|\\p{Control}|\\p{Mark}|\\p{Surrogate})+$"
13288
+ );
13289
+ var leadingNonPrintingRegex = createUnicodeRegex(
13290
+ "^[\\p{Default_Ignorable_Code_Point}\\p{Control}\\p{Format}\\p{Mark}\\p{Surrogate}]+",
13291
+ "^[\\p{Default_Ignorable_Code_Point}\\p{Control}\\p{Format}\\p{Mark}\\p{Surrogate}]+"
13292
+ );
13293
+ var rgiEmojiRegex = createUnicodeRegex("^\\p{RGI_Emoji}$", "^\\p{Extended_Pictographic}$");
13294
+ var AnsiStripper = {
13295
+ strip(text) {
13296
+ if (!text.includes("\x1B")) {
13297
+ return text;
13298
+ }
13299
+ let cleaned = text;
13300
+ cleaned = cleaned.replace(/\x1b\[[0-9;]*[mGKHJ]/g, "");
13301
+ cleaned = cleaned.replace(/\x1b\]8;;[^\x07]*\x07/g, "");
13302
+ cleaned = cleaned.replace(/\x1b_[^\x07\x1b]*(?:\x07|\x1b\\)/g, "");
13303
+ return cleaned;
13304
+ }
13305
+ };
13306
+ var WIDTH_CACHE_SIZE = 512;
13307
+ var widthCache = /* @__PURE__ */ new Map();
13308
+ function measureClusterWidth(segment) {
13309
+ if (zeroWidthRegex.test(segment)) {
13310
+ return 0;
13311
+ }
13312
+ if (looksLikeEmojiCandidate(segment) && rgiEmojiRegex.test(segment)) {
13313
+ return 2;
13314
+ }
13315
+ const base = segment.replace(leadingNonPrintingRegex, "");
13316
+ const cp = base.codePointAt(0);
13317
+ if (cp === void 0) {
13318
+ return 0;
13319
+ }
13320
+ let width = eastAsianWidth(cp);
13321
+ if (segment.length > 1) {
13322
+ for (const char of segment.slice(1)) {
13323
+ const c = char.codePointAt(0);
13324
+ if (c >= 65280 && c <= 65519) {
13325
+ width += eastAsianWidth(c);
13326
+ }
13327
+ }
13328
+ }
13329
+ return width;
13330
+ }
13331
+ function visibleWidth(str2) {
13332
+ if (str2.length === 0) {
13333
+ return 0;
13334
+ }
13335
+ let isPureAscii = true;
13336
+ for (let i = 0; i < str2.length; i++) {
13337
+ const code = str2.charCodeAt(i);
13338
+ if (code < 32 || code > 126) {
13339
+ isPureAscii = false;
13340
+ break;
13341
+ }
13342
+ }
13343
+ if (isPureAscii) {
13344
+ return str2.length;
13345
+ }
13346
+ const cached = widthCache.get(str2);
13347
+ if (cached !== void 0) {
13348
+ return cached;
13349
+ }
13350
+ let clean = str2;
13351
+ if (str2.includes(" ")) {
13352
+ clean = clean.replace(/\t/g, " ");
13353
+ }
13354
+ clean = AnsiStripper.strip(clean);
13355
+ let width = 0;
13356
+ for (const { segment } of segmenter.segment(clean)) {
13357
+ width += measureClusterWidth(segment);
13358
+ }
13359
+ if (widthCache.size >= WIDTH_CACHE_SIZE) {
13360
+ const firstKey = widthCache.keys().next().value;
13361
+ if (firstKey !== void 0) {
13362
+ widthCache.delete(firstKey);
13363
+ }
13364
+ }
13365
+ widthCache.set(str2, width);
13366
+ return width;
13367
+ }
13368
+ function extractAnsiCode(str2, pos) {
13369
+ if (pos >= str2.length || str2[pos] !== "\x1B") return null;
13370
+ const next = str2[pos + 1];
13371
+ if (next === "[") {
13372
+ let j = pos + 2;
13373
+ while (j < str2.length && !/[mGKHJ]/.test(str2[j])) j++;
13374
+ if (j < str2.length) return { code: str2.substring(pos, j + 1), length: j + 1 - pos };
13375
+ return null;
13376
+ }
13377
+ if (next === "]") {
13378
+ let j = pos + 2;
13379
+ while (j < str2.length) {
13380
+ if (str2[j] === "\x07") return { code: str2.substring(pos, j + 1), length: j + 1 - pos };
13381
+ if (str2[j] === "\x1B" && str2[j + 1] === "\\") return { code: str2.substring(pos, j + 2), length: j + 2 - pos };
13382
+ j++;
13383
+ }
13384
+ return null;
13385
+ }
13386
+ if (next === "_") {
13387
+ let j = pos + 2;
13388
+ while (j < str2.length) {
13389
+ if (str2[j] === "\x07") return { code: str2.substring(pos, j + 1), length: j + 1 - pos };
13390
+ if (str2[j] === "\x1B" && str2[j + 1] === "\\") return { code: str2.substring(pos, j + 2), length: j + 2 - pos };
13391
+ j++;
13392
+ }
13393
+ return null;
13394
+ }
13395
+ return null;
13396
+ }
13397
+ var AnsiStateTracker = class {
13398
+ // Each attribute is kept on its own flag, which lets us clear them one at a time.
13399
+ _bold = false;
13400
+ _dim = false;
13401
+ italic = false;
13402
+ underline = false;
13403
+ blink = false;
13404
+ inverse = false;
13405
+ hidden = false;
13406
+ strikethrough = false;
13407
+ _colors = { fg: null, bg: null };
13408
+ process(ansiCode) {
13409
+ if (!ansiCode.endsWith("m")) {
13410
+ return;
13411
+ }
13412
+ const match = ansiCode.match(/\x1b\[([\d;]*)m/);
13413
+ if (!match) return;
13414
+ const params = match[1];
13415
+ if (params === "" || params === "0") {
13416
+ this.reset();
13417
+ return;
13418
+ }
13419
+ const parts = params.split(";");
13420
+ let i = 0;
13421
+ while (i < parts.length) {
13422
+ const code = Number.parseInt(parts[i] ?? "", 10);
13423
+ if (Number.isNaN(code)) {
13424
+ i++;
13425
+ continue;
13426
+ }
13427
+ const consumed = this.tryConsumeColorCode(parts, i, code);
13428
+ if (consumed > 0) {
13429
+ i += consumed;
13430
+ continue;
13431
+ }
13432
+ this.applyStandardCode(code);
13433
+ i++;
13434
+ }
13435
+ }
13436
+ tryConsumeColorCode(parts, index, code) {
13437
+ if (code !== 38 && code !== 48) {
13438
+ return 0;
13439
+ }
13440
+ if (parts[index + 1] === "5" && parts[index + 2] !== void 0) {
13441
+ const colorCode = `${parts[index]};${parts[index + 1]};${parts[index + 2]}`;
13442
+ if (code === 38) {
13443
+ this._colors.fg = colorCode;
13444
+ } else {
13445
+ this._colors.bg = colorCode;
13446
+ }
13447
+ return 3;
13448
+ }
13449
+ if (parts[index + 1] === "2" && parts[index + 4] !== void 0) {
13450
+ const colorCode = `${parts[index]};${parts[index + 1]};${parts[index + 2]};${parts[index + 3]};${parts[index + 4]}`;
13451
+ if (code === 38) {
13452
+ this._colors.fg = colorCode;
13453
+ } else {
13454
+ this._colors.bg = colorCode;
13455
+ }
13456
+ return 5;
13457
+ }
13458
+ return 0;
13459
+ }
13460
+ applyStandardCode(code) {
13461
+ switch (code) {
13462
+ case 0:
13463
+ this.reset();
13464
+ return;
13465
+ case 1:
13466
+ this._bold = true;
13467
+ return;
13468
+ case 2:
13469
+ this._dim = true;
13470
+ return;
13471
+ case 3:
13472
+ this.italic = true;
13473
+ return;
13474
+ case 4:
13475
+ this.underline = true;
13476
+ return;
13477
+ case 5:
13478
+ this.blink = true;
13479
+ return;
13480
+ case 7:
13481
+ this.inverse = true;
13482
+ return;
13483
+ case 8:
13484
+ this.hidden = true;
13485
+ return;
13486
+ case 9:
13487
+ this.strikethrough = true;
13488
+ return;
13489
+ case 21:
13490
+ this._bold = false;
13491
+ return;
13492
+ case 22:
13493
+ this._bold = false;
13494
+ this._dim = false;
13495
+ return;
13496
+ case 23:
13497
+ this.italic = false;
13498
+ return;
13499
+ case 24:
13500
+ this.underline = false;
13501
+ return;
13502
+ case 25:
13503
+ this.blink = false;
13504
+ return;
13505
+ case 27:
13506
+ this.inverse = false;
13507
+ return;
13508
+ case 28:
13509
+ this.hidden = false;
13510
+ return;
13511
+ case 29:
13512
+ this.strikethrough = false;
13513
+ return;
13514
+ case 39:
13515
+ this._colors.fg = null;
13516
+ return;
13517
+ case 49:
13518
+ this._colors.bg = null;
13519
+ return;
13520
+ default:
13521
+ if (code >= 30 && code <= 37 || code >= 90 && code <= 97) {
13522
+ this._colors.fg = String(code);
13523
+ return;
13524
+ }
13525
+ if (code >= 40 && code <= 47 || code >= 100 && code <= 107) {
13526
+ this._colors.bg = String(code);
13527
+ }
13528
+ }
13529
+ }
13530
+ reset() {
13531
+ this._bold = false;
13532
+ this._dim = false;
13533
+ this.italic = false;
13534
+ this.underline = false;
13535
+ this.blink = false;
13536
+ this.inverse = false;
13537
+ this.hidden = false;
13538
+ this.strikethrough = false;
13539
+ this._colors.fg = null;
13540
+ this._colors.bg = null;
13541
+ }
13542
+ /** Wipe all tracked state so the instance can be reused. */
13543
+ clear() {
13544
+ this.reset();
13545
+ }
13546
+ getActiveCodes() {
13547
+ const codes = [];
13548
+ if (this._bold) codes.push("1");
13549
+ if (this._dim) codes.push("2");
13550
+ if (this.italic) codes.push("3");
13551
+ if (this.underline) codes.push("4");
13552
+ if (this.blink) codes.push("5");
13553
+ if (this.inverse) codes.push("7");
13554
+ if (this.hidden) codes.push("8");
13555
+ if (this.strikethrough) codes.push("9");
13556
+ if (this._colors.fg) codes.push(this._colors.fg);
13557
+ if (this._colors.bg) codes.push(this._colors.bg);
13558
+ if (codes.length === 0) return "";
13559
+ return `\x1B[${codes.join(";")}m`;
13560
+ }
13561
+ hasActiveCodes() {
13562
+ 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;
13563
+ }
13564
+ /**
13565
+ * Produce the escape code needed to switch off any attribute that would
13566
+ * otherwise smear into the padding at the end of a line. In practice that
13567
+ * is just underline. Yields an empty string when nothing needs disabling.
13568
+ */
13569
+ getLineEndReset() {
13570
+ if (this.underline) {
13571
+ return "\x1B[24m";
13572
+ }
13573
+ return "";
13574
+ }
13575
+ };
13576
+ function mergeTextIntoTracker(text, tracker) {
13577
+ let i = 0;
13578
+ while (i < text.length) {
13579
+ const ansiResult = extractAnsiCode(text, i);
13580
+ if (ansiResult) {
13581
+ tracker.process(ansiResult.code);
13582
+ i += ansiResult.length;
13583
+ } else {
13584
+ i++;
13585
+ }
13586
+ }
13587
+ }
13588
+ function tokenizeTextWithAnsi(text) {
13589
+ const tokens = [];
13590
+ let current = "";
13591
+ let pendingAnsi = "";
13592
+ let inWhitespace = false;
13593
+ let i = 0;
13594
+ while (i < text.length) {
13595
+ const ansiResult = extractAnsiCode(text, i);
13596
+ if (ansiResult) {
13597
+ pendingAnsi += ansiResult.code;
13598
+ i += ansiResult.length;
13599
+ continue;
13600
+ }
13601
+ const char = text[i];
13602
+ const charIsSpace = char === " ";
13603
+ if (charIsSpace !== inWhitespace && current) {
13604
+ tokens.push(current);
13605
+ current = "";
13606
+ }
13607
+ if (pendingAnsi) {
13608
+ current += pendingAnsi;
13609
+ pendingAnsi = "";
13610
+ }
13611
+ inWhitespace = charIsSpace;
13612
+ current += char;
13613
+ i++;
13614
+ }
13615
+ if (pendingAnsi) {
13616
+ current += pendingAnsi;
13617
+ }
13618
+ if (current) {
13619
+ tokens.push(current);
13620
+ }
13621
+ return tokens;
13622
+ }
13623
+ var TextWrapper = class {
13624
+ wrap(text, width) {
13625
+ return wrapTextWithAnsi(text, width);
13626
+ }
13627
+ };
13628
+ var textWrapper = new TextWrapper();
13629
+ var TokenWrapEngine = {
13630
+ wrap(tokens, width, tracker) {
13631
+ const wrapped = [];
13632
+ let currentLine = "";
13633
+ let currentVisibleLength = 0;
13634
+ for (const token of tokens) {
13635
+ const tokenVisibleLength = visibleWidth(token);
13636
+ const isWhitespace = token.trim() === "";
13637
+ if (tokenVisibleLength > width && !isWhitespace) {
13638
+ if (currentLine) {
13639
+ const lineEndReset = tracker.getLineEndReset();
13640
+ if (lineEndReset) {
13641
+ currentLine += lineEndReset;
13642
+ }
13643
+ wrapped.push(currentLine);
13644
+ currentLine = "";
13645
+ currentVisibleLength = 0;
13646
+ }
13647
+ const broken = splitLongToken(token, width, tracker);
13648
+ wrapped.push(...broken.slice(0, -1));
13649
+ currentLine = broken[broken.length - 1];
13650
+ currentVisibleLength = visibleWidth(currentLine);
13651
+ continue;
13652
+ }
13653
+ const totalNeeded = currentVisibleLength + tokenVisibleLength;
13654
+ if (totalNeeded > width && currentVisibleLength > 0) {
13655
+ let lineToWrap = currentLine.trimEnd();
13656
+ const lineEndReset = tracker.getLineEndReset();
13657
+ if (lineEndReset) {
13658
+ lineToWrap += lineEndReset;
13659
+ }
13660
+ wrapped.push(lineToWrap);
13661
+ if (isWhitespace) {
13662
+ currentLine = tracker.getActiveCodes();
13663
+ currentVisibleLength = 0;
13664
+ } else {
13665
+ currentLine = tracker.getActiveCodes() + token;
13666
+ currentVisibleLength = tokenVisibleLength;
13667
+ }
13668
+ } else {
13669
+ currentLine += token;
13670
+ currentVisibleLength += tokenVisibleLength;
13671
+ }
13672
+ mergeTextIntoTracker(token, tracker);
13673
+ }
13674
+ return { wrapped, currentLine, currentVisibleLength };
13675
+ }
13676
+ };
13677
+ function wrapTextWithAnsi(text, width) {
13678
+ if (!text) {
13679
+ return [""];
13680
+ }
13681
+ const inputLines = text.split("\n");
13682
+ const result = [];
13683
+ const tracker = new AnsiStateTracker();
13684
+ for (const inputLine of inputLines) {
13685
+ const prefix = result.length > 0 ? tracker.getActiveCodes() : "";
13686
+ result.push(...wrapLinePreservingAnsi(prefix + inputLine, width));
13687
+ mergeTextIntoTracker(inputLine, tracker);
13688
+ }
13689
+ return result.length > 0 ? result : [""];
13690
+ }
13691
+ function wrapLinePreservingAnsi(line4, width) {
13692
+ if (!line4) {
13693
+ return [""];
13694
+ }
13695
+ const visibleLength = visibleWidth(line4);
13696
+ if (visibleLength <= width) {
13697
+ return [line4];
13698
+ }
13699
+ const tracker = new AnsiStateTracker();
13700
+ const tokens = tokenizeTextWithAnsi(line4);
13701
+ const { wrapped, currentLine } = TokenWrapEngine.wrap(tokens, width, tracker);
13702
+ if (currentLine) {
13703
+ wrapped.push(currentLine);
13704
+ }
13705
+ return wrapped.length > 0 ? wrapped.map((segmentLine) => segmentLine.trimEnd()) : [""];
13706
+ }
13707
+ function splitLongToken(word, width, tracker) {
13708
+ const lines = [];
13709
+ let currentLine = tracker.getActiveCodes();
13710
+ let currentWidth = 0;
13711
+ let i = 0;
13712
+ const segments = [];
13713
+ while (i < word.length) {
13714
+ const ansiResult = extractAnsiCode(word, i);
13715
+ if (ansiResult) {
13716
+ segments.push({ type: "ansi", value: ansiResult.code });
13717
+ i += ansiResult.length;
13718
+ } else {
13719
+ let end = i;
13720
+ while (end < word.length) {
13721
+ const nextAnsi = extractAnsiCode(word, end);
13722
+ if (nextAnsi) break;
13723
+ end++;
13724
+ }
13725
+ const textPortion = word.slice(i, end);
13726
+ for (const seg of segmenter.segment(textPortion)) {
13727
+ segments.push({ type: "grapheme", value: seg.segment });
13728
+ }
13729
+ i = end;
13730
+ }
13731
+ }
13732
+ for (const seg of segments) {
13733
+ if (seg.type === "ansi") {
13734
+ currentLine += seg.value;
13735
+ tracker.process(seg.value);
13736
+ continue;
13737
+ }
13738
+ const grapheme = seg.value;
13739
+ if (!grapheme) continue;
13740
+ const clusterWidth = visibleWidth(grapheme);
13741
+ if (currentWidth + clusterWidth > width) {
13742
+ const lineEndReset = tracker.getLineEndReset();
13743
+ if (lineEndReset) {
13744
+ currentLine += lineEndReset;
13745
+ }
13746
+ lines.push(currentLine);
13747
+ currentLine = tracker.getActiveCodes();
13748
+ currentWidth = 0;
13749
+ }
13750
+ currentLine += grapheme;
13751
+ currentWidth += clusterWidth;
13752
+ }
13753
+ if (currentLine) {
13754
+ lines.push(currentLine);
13755
+ }
13756
+ return lines.length > 0 ? lines : [""];
13757
+ }
13758
+ var pooledStyleTracker = new AnsiStateTracker();
13759
+
13760
+ // src/react-ink/markdown/format-token.ts
13761
+ var EOL = "\n";
13762
+ var BLOCKQUOTE_BAR = "\u2502";
13763
+ var markedConfigured = false;
13764
+ function configureMarked() {
13765
+ if (markedConfigured) {
13766
+ return;
13767
+ }
13768
+ markedConfigured = true;
13769
+ marked.use({
13770
+ tokenizer: {
13771
+ del() {
13772
+ return void 0;
13773
+ }
13774
+ }
13775
+ });
13776
+ }
13777
+ var TOKEN_CACHE_MAX = 500;
13778
+ var tokenCache = /* @__PURE__ */ new Map();
13779
+ var MD_SYNTAX_RE = /[#*`|[>\-_~]|\n\n|^\d+\. |\n\d+\. /;
13780
+ function hasMarkdownSyntax(text) {
13781
+ return MD_SYNTAX_RE.test(text.length > 500 ? text.slice(0, 500) : text);
13782
+ }
13783
+ function hashContent(content) {
13784
+ let hash = 2166136261;
13785
+ for (let i = 0; i < content.length; i++) {
13786
+ hash ^= content.charCodeAt(i);
13787
+ hash = Math.imul(hash, 16777619);
13788
+ }
13789
+ return (hash >>> 0).toString(36) + ":" + content.length.toString(36);
13790
+ }
13791
+ function cachedLexer(content) {
13792
+ configureMarked();
13793
+ if (!hasMarkdownSyntax(content)) {
13794
+ return [
13795
+ {
13796
+ type: "paragraph",
13797
+ raw: content,
13798
+ text: content,
13799
+ tokens: [{ type: "text", raw: content, text: content }]
13800
+ }
13801
+ ];
13802
+ }
13803
+ const key = hashContent(content);
13804
+ const hit = tokenCache.get(key);
13805
+ if (hit) {
13806
+ tokenCache.delete(key);
13807
+ tokenCache.set(key, hit);
13808
+ return hit;
13809
+ }
13810
+ const tokens = marked.lexer(content);
13811
+ if (tokenCache.size >= TOKEN_CACHE_MAX) {
13812
+ const first = tokenCache.keys().next().value;
13813
+ if (first !== void 0) {
13814
+ tokenCache.delete(first);
13815
+ }
13816
+ }
13817
+ tokenCache.set(key, tokens);
13818
+ return tokens;
13819
+ }
13820
+ function formatToken(token, theme, highlight = null, listDepth = 0, orderedListNumber = null, parent = null) {
13821
+ switch (token.type) {
13822
+ case "blockquote": {
13823
+ const inner = (token.tokens ?? []).map((child) => formatToken(child, theme, highlight)).join("");
13824
+ const bar = theme.dim(BLOCKQUOTE_BAR);
13825
+ return inner.split(EOL).map((line4) => stripAnsi3(line4).trim() ? `${bar} ${chalk2.italic(line4)}` : line4).join(EOL);
13826
+ }
13827
+ case "code": {
13828
+ const codeToken = token;
13829
+ if (!highlight) {
13830
+ return codeToken.text + EOL;
13831
+ }
13832
+ let language = "plaintext";
13833
+ if (codeToken.lang && highlight.supportsLanguage(codeToken.lang)) {
13834
+ language = codeToken.lang;
13835
+ }
13836
+ return highlight.highlight(codeToken.text, { language }) + EOL;
13837
+ }
13838
+ case "codespan":
13839
+ return theme.role("codeInline", token.text);
13840
+ case "em":
13841
+ return chalk2.italic(
13842
+ (token.tokens ?? []).map((child) => formatToken(child, theme, highlight, 0, null, parent)).join("")
13843
+ );
13844
+ case "strong":
13845
+ return chalk2.bold(
13846
+ (token.tokens ?? []).map((child) => formatToken(child, theme, highlight, 0, null, parent)).join("")
13847
+ );
13848
+ case "heading": {
13849
+ const headingToken = token;
13850
+ const inner = (headingToken.tokens ?? []).map((child) => formatToken(child, theme, highlight)).join("");
13851
+ const colored = theme.role("heading", inner);
13852
+ if (headingToken.depth === 1) {
13853
+ return chalk2.bold.italic.underline(colored) + EOL + EOL;
13854
+ }
13855
+ return chalk2.bold(colored) + EOL + EOL;
13856
+ }
13857
+ case "hr":
13858
+ return "---";
13859
+ case "image":
13860
+ return token.href;
13861
+ case "link": {
13862
+ const linkToken = token;
13863
+ if (linkToken.href.startsWith("mailto:")) {
13864
+ return linkToken.href.replace(/^mailto:/, "");
13865
+ }
13866
+ const linkText = (linkToken.tokens ?? []).map((child) => formatToken(child, theme, highlight, 0, null, linkToken)).join("");
13867
+ const plainLinkText = stripAnsi3(linkText);
13868
+ const display = plainLinkText && plainLinkText !== linkToken.href ? linkText : linkToken.href;
13869
+ return `\x1B]8;;${linkToken.href}\x07${display}\x1B]8;;\x07`;
13870
+ }
13871
+ case "list": {
13872
+ const listToken = token;
13873
+ const start = typeof listToken.start === "number" ? listToken.start : Number(listToken.start) || 1;
13874
+ return listToken.items.map(
13875
+ (item, index) => formatToken(
13876
+ item,
13877
+ theme,
13878
+ highlight,
13879
+ listDepth,
13880
+ listToken.ordered ? start + index : null,
13881
+ listToken
13882
+ )
13883
+ ).join("");
13884
+ }
13885
+ case "list_item":
13886
+ return (token.tokens ?? []).map(
13887
+ (child) => `${" ".repeat(listDepth)}${formatToken(child, theme, highlight, listDepth + 1, orderedListNumber, token)}`
13888
+ ).join("");
13889
+ case "paragraph":
13890
+ return (token.tokens ?? []).map((child) => formatToken(child, theme, highlight)).join("") + EOL;
13891
+ case "space":
13892
+ case "br":
13893
+ return EOL;
13894
+ case "text": {
13895
+ const textToken = token;
13896
+ if (parent?.type === "link") {
13897
+ return textToken.text;
13898
+ }
13899
+ if (parent?.type === "list_item") {
13900
+ const marker = orderedListNumber === null ? "-" : `${getListNumber(listDepth, orderedListNumber)}.`;
13901
+ const body = textToken.tokens ? textToken.tokens.map((child) => formatToken(child, theme, highlight, listDepth, orderedListNumber, token)).join("") : textToken.text;
13902
+ return `${marker} ${body}${EOL}`;
13903
+ }
13904
+ return textToken.text;
13905
+ }
13906
+ case "escape":
13907
+ return token.text;
13908
+ case "table":
13909
+ case "def":
13910
+ case "del":
13911
+ case "html":
13912
+ return "";
13913
+ default:
13914
+ return "";
13915
+ }
13916
+ }
13917
+ function numberToLetter(n) {
13918
+ let result = "";
13919
+ while (n > 0) {
13920
+ n--;
13921
+ result = String.fromCharCode(97 + n % 26) + result;
13922
+ n = Math.floor(n / 26);
13923
+ }
13924
+ return result;
13925
+ }
13926
+ var ROMAN_VALUES = [
13927
+ [1e3, "m"],
13928
+ [900, "cm"],
13929
+ [500, "d"],
13930
+ [400, "cd"],
13931
+ [100, "c"],
13932
+ [90, "xc"],
13933
+ [50, "l"],
13934
+ [40, "xl"],
13935
+ [10, "x"],
13936
+ [9, "ix"],
13937
+ [5, "v"],
13938
+ [4, "iv"],
13939
+ [1, "i"]
13940
+ ];
13941
+ function numberToRoman(n) {
13942
+ let result = "";
13943
+ for (const [value, numeral] of ROMAN_VALUES) {
13944
+ while (n >= value) {
13945
+ result += numeral;
13946
+ n -= value;
13947
+ }
13948
+ }
13949
+ return result;
13950
+ }
13951
+ function getListNumber(listDepth, orderedListNumber) {
13952
+ switch (listDepth) {
13953
+ case 0:
13954
+ case 1:
13955
+ return orderedListNumber.toString();
13956
+ case 2:
13957
+ return numberToLetter(orderedListNumber);
13958
+ case 3:
13959
+ return numberToRoman(orderedListNumber);
13960
+ default:
13961
+ return orderedListNumber.toString();
13962
+ }
13963
+ }
13964
+ function padAligned(content, displayWidth, targetWidth, align) {
13965
+ const padding = Math.max(0, targetWidth - displayWidth);
13966
+ if (align === "center") {
13967
+ const leftPad = Math.floor(padding / 2);
13968
+ return " ".repeat(leftPad) + content + " ".repeat(padding - leftPad);
13969
+ }
13970
+ if (align === "right") {
13971
+ return " ".repeat(padding) + content;
13972
+ }
13973
+ return content + " ".repeat(padding);
13974
+ }
13975
+ function stringWidth(text) {
13976
+ return visibleWidth(text);
13977
+ }
13978
+
13979
+ // src/react-ink/markdown/highlight.ts
13980
+ import { extname } from "node:path";
13981
+ import hljs from "highlight.js";
13982
+ function scopeToRole(scope) {
13983
+ const head = scope.split(".")[0] ?? scope;
13984
+ switch (head) {
13985
+ case "keyword":
13986
+ case "built_in":
13987
+ case "literal":
13988
+ case "operator":
13989
+ return "synKeyword";
13990
+ case "string":
13991
+ case "regexp":
13992
+ case "symbol":
13993
+ case "char":
13994
+ case "meta":
13995
+ return "synString";
13996
+ case "number":
13997
+ return "synNumber";
13998
+ case "comment":
13999
+ case "quote":
14000
+ return "synComment";
14001
+ case "type":
14002
+ case "class":
14003
+ case "title":
14004
+ case "tag":
14005
+ case "name":
14006
+ case "attr":
14007
+ case "attribute":
14008
+ case "selector":
14009
+ return "synType";
14010
+ default:
14011
+ return null;
14012
+ }
14013
+ }
14014
+ var HTML_ENTITIES = {
14015
+ "&amp;": "&",
14016
+ "&lt;": "<",
14017
+ "&gt;": ">",
14018
+ "&quot;": '"',
14019
+ "&#x27;": "'",
14020
+ "&#39;": "'"
14021
+ };
14022
+ function decodeEntities2(text) {
14023
+ return text.replace(/&(?:amp|lt|gt|quot|#x27|#39);/g, (match) => HTML_ENTITIES[match] ?? match);
14024
+ }
14025
+ function parseHljsHtml(html) {
14026
+ const nodes = [];
14027
+ const scopeStack = [];
14028
+ const tagRe = /<span class="hljs-([^"]+)">|<\/span>/g;
14029
+ let lastIndex = 0;
14030
+ let match;
14031
+ const pushText = (raw) => {
14032
+ if (!raw) return;
14033
+ const currentScope = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : null;
14034
+ const scope = currentScope ? currentScope.split(/\s+/)[0].replace(/_$/, "") : null;
14035
+ nodes.push({ text: decodeEntities2(raw), scope });
14036
+ };
14037
+ while ((match = tagRe.exec(html)) !== null) {
14038
+ pushText(html.slice(lastIndex, match.index));
14039
+ lastIndex = tagRe.lastIndex;
14040
+ if (match[0] === "</span>") {
14041
+ scopeStack.pop();
14042
+ } else if (match[1]) {
14043
+ scopeStack.push(match[1]);
14044
+ }
14045
+ }
14046
+ pushText(html.slice(lastIndex));
14047
+ return nodes;
14048
+ }
14049
+ function createHighlighter(theme) {
14050
+ return {
14051
+ supportsLanguage: (language) => {
14052
+ if (!language || language === "plaintext" || language === "text") {
14053
+ return false;
14054
+ }
14055
+ try {
14056
+ return hljs.getLanguage(language) !== void 0;
14057
+ } catch {
14058
+ return false;
14059
+ }
14060
+ },
14061
+ highlight: (code, options) => {
14062
+ const language = options.language;
14063
+ if (!language || language === "plaintext" || language === "text") {
14064
+ return code;
14065
+ }
14066
+ try {
14067
+ if (hljs.getLanguage(language) === void 0) {
14068
+ return code;
14069
+ }
14070
+ const { value } = hljs.highlight(code, { language, ignoreIllegals: true });
14071
+ return parseHljsHtml(value).map((node) => {
14072
+ const role = node.scope ? scopeToRole(node.scope) : null;
14073
+ return role ? theme.role(role, node.text) : node.text;
14074
+ }).join("");
14075
+ } catch {
14076
+ return code;
14077
+ }
14078
+ }
14079
+ };
14080
+ }
14081
+ function highlightByPath(code, filePath, theme) {
14082
+ const language = languageFromPath(filePath);
14083
+ if (!language) {
14084
+ return code;
14085
+ }
14086
+ const highlighter = createHighlighter(theme);
14087
+ if (!highlighter.supportsLanguage(language)) {
14088
+ return code;
14089
+ }
14090
+ return highlighter.highlight(code, { language });
14091
+ }
14092
+ var EXTENSION_LANGUAGES = {
14093
+ ts: "typescript",
14094
+ tsx: "typescript",
14095
+ mts: "typescript",
14096
+ cts: "typescript",
14097
+ js: "javascript",
14098
+ jsx: "javascript",
14099
+ mjs: "javascript",
14100
+ cjs: "javascript",
14101
+ py: "python",
14102
+ rb: "ruby",
14103
+ rs: "rust",
14104
+ go: "go",
14105
+ java: "java",
14106
+ kt: "kotlin",
14107
+ c: "c",
14108
+ h: "c",
14109
+ cc: "cpp",
14110
+ cpp: "cpp",
14111
+ hpp: "cpp",
14112
+ cs: "csharp",
14113
+ sh: "bash",
14114
+ bash: "bash",
14115
+ zsh: "bash",
14116
+ yml: "yaml",
14117
+ yaml: "yaml",
14118
+ json: "json",
14119
+ md: "markdown",
14120
+ html: "xml",
14121
+ xml: "xml",
14122
+ css: "css",
14123
+ scss: "scss",
14124
+ sql: "sql",
14125
+ toml: "ini",
14126
+ ini: "ini",
14127
+ php: "php",
14128
+ swift: "swift"
14129
+ };
14130
+ function languageFromPath(filePath) {
14131
+ const ext = extname(filePath).slice(1).toLowerCase();
14132
+ if (!ext) {
14133
+ return null;
14134
+ }
14135
+ const mapped = EXTENSION_LANGUAGES[ext];
14136
+ if (mapped) {
14137
+ return mapped;
14138
+ }
14139
+ try {
14140
+ return hljs.getLanguage(ext) !== void 0 ? ext : null;
14141
+ } catch {
14142
+ return null;
14143
+ }
14144
+ }
14145
+
14146
+ // src/react-ink/markdown/MarkdownTable.tsx
14147
+ import chalk3 from "chalk";
14148
+ import stripAnsi4 from "strip-ansi";
14149
+ var MIN_COLUMN_WIDTH = 3;
14150
+ var COLUMN_GAP = 2;
14151
+ function MarkdownTable({ token, theme, highlight = null }) {
14152
+ const formatCell = (tokens) => (tokens ?? []).map((child) => formatToken(child, theme, highlight)).join("");
14153
+ const displayWidth = (tokens) => stringWidth(stripAnsi4(formatCell(tokens)));
14154
+ const columnCount = token.header.length;
14155
+ const columnWidths = token.header.map((header, index) => {
14156
+ let max = displayWidth(header.tokens);
14157
+ for (const row of token.rows) {
14158
+ max = Math.max(max, displayWidth(row[index]?.tokens));
14159
+ }
14160
+ return Math.max(max, MIN_COLUMN_WIDTH);
14161
+ });
14162
+ const renderRow2 = (cells, key, bold) => /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: columnWidths.map((width, index) => {
14163
+ const cell = cells[index];
14164
+ const content = formatCell(cell?.tokens);
14165
+ const visible = stringWidth(stripAnsi4(content));
14166
+ const align = token.align?.[index];
14167
+ const padded = padAligned(content, visible, width, align ?? "left");
14168
+ const styled = bold ? chalk3.bold(padded) : padded;
14169
+ const gap = index < columnCount - 1 ? " ".repeat(COLUMN_GAP) : "";
14170
+ return /* @__PURE__ */ jsxs(Text, { children: [
14171
+ styled,
14172
+ gap
14173
+ ] }, index);
14174
+ }) }, key);
14175
+ const separator = /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: columnWidths.map((width, index) => {
14176
+ const gap = index < columnCount - 1 ? " ".repeat(COLUMN_GAP) : "";
14177
+ return /* @__PURE__ */ jsxs(Text, { children: [
14178
+ theme.dim("-".repeat(width)),
14179
+ gap
14180
+ ] }, index);
14181
+ }) });
14182
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
14183
+ renderRow2(token.header, "header", true),
14184
+ separator,
14185
+ token.rows.map((row, rowIndex) => renderRow2(row, `row-${rowIndex}`, false))
14186
+ ] });
14187
+ }
14188
+
14189
+ // src/react-ink/markdown/Markdown.tsx
14190
+ function stripPromptXMLTags(text) {
14191
+ return text.replace(/<\/?(?:system-reminder|prompt|context)[^>]*>/g, "");
14192
+ }
14193
+ function Markdown({ children, theme, highlightCode = true, dim = false }) {
14194
+ const highlight = useMemo(
14195
+ () => highlightCode ? createHighlighter(theme) : null,
14196
+ [highlightCode, theme]
14197
+ );
14198
+ const elements = useMemo(() => {
14199
+ configureMarked();
14200
+ const tokens = cachedLexer(stripPromptXMLTags(children));
14201
+ const out = [];
14202
+ let buffer = "";
14203
+ const flush = () => {
14204
+ if (buffer) {
14205
+ const text = buffer.replace(/\n+$/, "");
14206
+ if (text) {
14207
+ out.push(
14208
+ /* @__PURE__ */ jsx(Text, { children: dim ? theme.dim(text) : text }, out.length)
14209
+ );
14210
+ }
14211
+ buffer = "";
14212
+ }
14213
+ };
14214
+ for (const token of tokens) {
14215
+ if (token.type === "table") {
14216
+ flush();
14217
+ out.push(
14218
+ /* @__PURE__ */ jsx(
14219
+ MarkdownTable,
14220
+ {
14221
+ token,
14222
+ theme,
14223
+ highlight
14224
+ },
14225
+ out.length
14226
+ )
14227
+ );
14228
+ } else {
14229
+ buffer += formatToken(token, theme, highlight);
14230
+ }
14231
+ }
14232
+ flush();
14233
+ return out;
14234
+ }, [children, dim, highlight, theme]);
14235
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: elements });
14236
+ }
14237
+
13212
14238
  // src/react-ink/utils/tool-display.ts
13213
14239
  import { homedir as homedir2 } from "node:os";
14240
+
14241
+ // src/react-ink/diff/structured.ts
14242
+ import { structuredPatch } from "diff";
14243
+
14244
+ // src/react-ink/diff/word-diff.ts
14245
+ import { diffWordsWithSpace } from "diff";
14246
+ var CHANGE_THRESHOLD = 0.4;
14247
+ function wordDiffLine(oldLine, newLine, side) {
14248
+ const lineText = side === "removed" ? oldLine : newLine;
14249
+ if (oldLine.length === 0 || newLine.length === 0) {
14250
+ return [{ text: lineText, changed: true }];
14251
+ }
14252
+ let changes;
14253
+ try {
14254
+ changes = diffWordsWithSpace(oldLine, newLine);
14255
+ } catch {
14256
+ return [{ text: lineText, changed: true }];
14257
+ }
14258
+ let changedChars = 0;
14259
+ let totalChars = 0;
14260
+ for (const change of changes) {
14261
+ totalChars += change.value.length;
14262
+ if (change.added || change.removed) {
14263
+ changedChars += change.value.length;
14264
+ }
14265
+ }
14266
+ const fraction = totalChars === 0 ? 0 : changedChars / totalChars;
14267
+ if (fraction > CHANGE_THRESHOLD) {
14268
+ return [{ text: lineText, changed: true }];
14269
+ }
14270
+ const spans = [];
14271
+ for (const change of changes) {
14272
+ const belongs = side === "removed" ? !change.added : !change.removed;
14273
+ if (!belongs) {
14274
+ continue;
14275
+ }
14276
+ spans.push({ text: change.value, changed: Boolean(change.added || change.removed) });
14277
+ }
14278
+ return spans.length > 0 ? spans : [{ text: lineText, changed: false }];
14279
+ }
14280
+
14281
+ // src/react-ink/diff/structured.ts
14282
+ var CONTEXT_LINES = 3;
14283
+ function buildStructuredDiff(oldStr, newStr, filePath = "") {
14284
+ if (oldStr === newStr) {
14285
+ return null;
14286
+ }
14287
+ let patch;
14288
+ try {
14289
+ patch = structuredPatch(filePath, filePath, oldStr, newStr, "", "", { context: CONTEXT_LINES });
14290
+ } catch {
14291
+ return null;
14292
+ }
14293
+ const hunks = [];
14294
+ let addedCount = 0;
14295
+ let removedCount = 0;
14296
+ for (const hunk of patch.hunks) {
14297
+ const lines = classifyHunkLines(hunk);
14298
+ for (const line4 of lines) {
14299
+ if (line4.kind === "added") addedCount += 1;
14300
+ else if (line4.kind === "removed") removedCount += 1;
14301
+ }
14302
+ if (lines.length > 0) {
14303
+ hunks.push({ oldStart: hunk.oldStart, newStart: hunk.newStart, lines });
14304
+ }
14305
+ }
14306
+ if (hunks.length === 0) {
14307
+ return null;
14308
+ }
14309
+ return { hunks, addedCount, removedCount };
14310
+ }
14311
+ function classifyHunkLines(hunk) {
14312
+ const out = [];
14313
+ let oldNum = hunk.oldStart;
14314
+ let newNum = hunk.newStart;
14315
+ const rawLines = hunk.lines.filter((line4) => !line4.startsWith("\\"));
14316
+ let removedRun = [];
14317
+ let removedRunStart = -1;
14318
+ const flushPairing = (addedRun2) => {
14319
+ const pairs = Math.min(removedRun.length, addedRun2.length);
14320
+ for (let i = 0; i < pairs; i += 1) {
14321
+ const removed = removedRun[i];
14322
+ const added = addedRun2[i];
14323
+ removed.spans = wordDiffLine(removed.text, added.text, "removed");
14324
+ added.spans = wordDiffLine(removed.text, added.text, "added");
14325
+ }
14326
+ removedRun = [];
14327
+ removedRunStart = -1;
14328
+ };
14329
+ let addedRun = [];
14330
+ for (const raw of rawLines) {
14331
+ const marker = raw[0];
14332
+ const text = raw.slice(1);
14333
+ if (marker === "-") {
14334
+ if (addedRun.length > 0) {
14335
+ addedRun = [];
14336
+ }
14337
+ const line4 = { kind: "removed", oldLine: oldNum, text };
14338
+ out.push(line4);
14339
+ if (removedRunStart === -1) removedRunStart = out.length - 1;
14340
+ removedRun.push(line4);
14341
+ oldNum += 1;
14342
+ } else if (marker === "+") {
14343
+ const line4 = { kind: "added", newLine: newNum, text };
14344
+ out.push(line4);
14345
+ addedRun.push(line4);
14346
+ newNum += 1;
14347
+ } else {
14348
+ if (removedRun.length > 0 && addedRun.length > 0) {
14349
+ flushPairing(addedRun);
14350
+ }
14351
+ removedRun = [];
14352
+ removedRunStart = -1;
14353
+ addedRun = [];
14354
+ out.push({ kind: "context", oldLine: oldNum, newLine: newNum, text });
14355
+ oldNum += 1;
14356
+ newNum += 1;
14357
+ }
14358
+ }
14359
+ if (removedRun.length > 0 && addedRun.length > 0) {
14360
+ flushPairing(addedRun);
14361
+ }
14362
+ return out;
14363
+ }
14364
+
14365
+ // src/react-ink/utils/tool-display.ts
13214
14366
  function asRecord2(value) {
13215
14367
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
13216
14368
  }
@@ -13293,6 +14445,12 @@ function clipBody(value, maxLines = 8, maxChars = 1400) {
13293
14445
  }
13294
14446
  return previewMultiline(value, { maxLines, maxChars });
13295
14447
  }
14448
+ function highlightedBody(body, filePath, theme) {
14449
+ if (!body || !theme || !filePath || languageFromPath(filePath) === null) {
14450
+ return { body };
14451
+ }
14452
+ return { body: highlightByPath(body, filePath, theme), preformatted: true };
14453
+ }
13296
14454
  function extractDetails(value) {
13297
14455
  return asRecord2(asRecord2(value)?.details);
13298
14456
  }
@@ -13359,7 +14517,7 @@ function fallbackBody(output, fallbackOutputText, showImages = false) {
13359
14517
  return clipBody(fallbackOutputText);
13360
14518
  }
13361
14519
  function describeToolSource(source) {
13362
- const { args, fallbackArgsText, fallbackOutputText, output, showImages = false, toolName } = source;
14520
+ const { args, fallbackArgsText, fallbackOutputText, output, showImages = false, theme, toolName } = source;
13363
14521
  const argsRecord = asRecord2(args);
13364
14522
  const details = extractDetails(output);
13365
14523
  const outputText = toolText(output, showImages);
@@ -13377,10 +14535,12 @@ function describeToolSource(source) {
13377
14535
  summary += `:${start}${end ? `-${end}` : ""}`;
13378
14536
  }
13379
14537
  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";
14538
+ const readBody = hasResult && !containsImage(output) ? highlightedBody(clipBody(outputText, 10, 1800), rawPath, theme) : { body: hasResult ? clipBody(outputText, 10, 1800) : void 0 };
13380
14539
  return {
13381
14540
  title,
13382
14541
  summary: hasResult ? responseSummary : summary,
13383
- body: hasResult ? clipBody(outputText, 10, 1800) : void 0,
14542
+ body: readBody.body,
14543
+ preformatted: readBody.preformatted,
13384
14544
  emptyText: "Reading file..."
13385
14545
  };
13386
14546
  }
@@ -13388,10 +14548,14 @@ function describeToolSource(source) {
13388
14548
  const rawPath = asString(argsRecord?.file_path) ?? asString(argsRecord?.path) ?? "";
13389
14549
  const content = asString(argsRecord?.content);
13390
14550
  const responseSummary = firstMeaningfulLine(outputText) ?? "Wrote file";
14551
+ const writeDiff = !hasResult && content !== void 0 && content.length > 0 ? buildStructuredDiff("", content, rawPath) ?? void 0 : void 0;
14552
+ const writeBody = hasResult ? { body: clipBody(outputText, 8, 1500) } : highlightedBody(clipBody(content, 8, 1500), rawPath, theme);
13391
14553
  return {
13392
14554
  title,
13393
14555
  summary: hasResult ? responseSummary : rawPath ? shortenPath2(rawPath) : void 0,
13394
- body: hasResult ? clipBody(outputText, 8, 1500) : clipBody(content, 8, 1500),
14556
+ body: writeDiff ? void 0 : writeBody.body,
14557
+ preformatted: writeDiff ? void 0 : writeBody.preformatted,
14558
+ diff: writeDiff,
13395
14559
  emptyText: "Preparing file write..."
13396
14560
  };
13397
14561
  }
@@ -13408,10 +14572,15 @@ function describeToolSource(source) {
13408
14572
  400
13409
14573
  ) : void 0;
13410
14574
  const responseSummary = firstMeaningfulLine(outputText) ?? "Applied edit";
14575
+ const editDiff = oldText !== void 0 && newText !== void 0 ? buildStructuredDiff(oldText, newText, rawPath) ?? void 0 : void 0;
14576
+ const editBody = hasResult ? highlightedBody(clipBody(outputText, 8, 1500), rawPath, theme) : { body: replacementPreview };
14577
+ const editChangeSummary = editDiff ? `+${editDiff.addedCount} -${editDiff.removedCount}` : void 0;
13411
14578
  return {
13412
14579
  title,
13413
- summary: hasResult ? responseSummary : rawPath ? shortenPath2(rawPath) : void 0,
13414
- body: hasResult ? clipBody(outputText, 8, 1500) : replacementPreview,
14580
+ summary: hasResult ? editChangeSummary ?? responseSummary : editChangeSummary ?? (rawPath ? shortenPath2(rawPath) : void 0),
14581
+ body: editDiff ? void 0 : editBody.body,
14582
+ preformatted: editDiff ? void 0 : editBody.preformatted,
14583
+ diff: editDiff,
13415
14584
  emptyText: "Applying edit..."
13416
14585
  };
13417
14586
  }
@@ -13508,7 +14677,7 @@ function describeToolCall(toolCall) {
13508
14677
  args: toolCall.arguments
13509
14678
  });
13510
14679
  }
13511
- function describeToolResult(message, showImages, args) {
14680
+ function describeToolResult(message, showImages, args, theme) {
13512
14681
  return describeToolSource({
13513
14682
  args,
13514
14683
  toolName: message.toolName,
@@ -13516,12 +14685,13 @@ function describeToolResult(message, showImages, args) {
13516
14685
  content: message.content,
13517
14686
  details: message.details
13518
14687
  },
13519
- showImages
14688
+ showImages,
14689
+ theme
13520
14690
  });
13521
14691
  }
13522
14692
 
13523
14693
  // src/react-ink/components/ToolEventBlock.tsx
13524
- import stripAnsi3 from "strip-ansi";
14694
+ import stripAnsi5 from "strip-ansi";
13525
14695
  function statusMarker(status) {
13526
14696
  switch (status) {
13527
14697
  case "error":
@@ -13533,11 +14703,11 @@ function statusMarker(status) {
13533
14703
  }
13534
14704
  }
13535
14705
  function plainToolText(text) {
13536
- return stripAnsi3(text);
14706
+ return stripAnsi5(text);
13537
14707
  }
13538
14708
  function splitVisibleLines(text) {
13539
14709
  return (text ?? "").split(/\r?\n/).map((line4) => line4.trimEnd()).filter((line4, index, lines) => {
13540
- if (line4.length > 0) {
14710
+ if (stripAnsi5(line4).length > 0) {
13541
14711
  return true;
13542
14712
  }
13543
14713
  return index !== 0 && index !== lines.length - 1;
@@ -13568,6 +14738,7 @@ function ToolEventBlock({
13568
14738
  indent = 0,
13569
14739
  marginBottom = 1,
13570
14740
  maxContentLines = 10,
14741
+ preformatted = false,
13571
14742
  showSummaryInline = false,
13572
14743
  showTitle = true,
13573
14744
  status,
@@ -13596,9 +14767,9 @@ function ToolEventBlock({
13596
14767
  Box,
13597
14768
  {
13598
14769
  marginLeft: line4.kind === "response" ? showTitle ? 2 : 0 : showTitle ? 4 : 2,
13599
- children: /* @__PURE__ */ jsx(Text, { color: "white", children: plainToolText(line4.text) })
14770
+ children: preformatted ? /* @__PURE__ */ jsx(Text, { children: line4.text }) : /* @__PURE__ */ jsx(Text, { color: "white", children: plainToolText(line4.text) })
13600
14771
  },
13601
- `${line4.kind}:${index}:${line4.text}`
14772
+ `${line4.kind}:${index}:${stripAnsi5(line4.text)}`
13602
14773
  )),
13603
14774
  hiddenLineCount > 0 ? /* @__PURE__ */ jsx(Box, { marginLeft: showTitle ? 4 : 2, children: /* @__PURE__ */ jsx(Text, { color: "white", children: plainToolText(`... ${hiddenLineCount} more line(s)`) }) }) : null
13604
14775
  ] });
@@ -13623,17 +14794,100 @@ function ToolCallMessage({ theme, toolCall }) {
13623
14794
  );
13624
14795
  }
13625
14796
 
14797
+ // src/react-ink/diff/Diff.tsx
14798
+ import chalk4 from "chalk";
14799
+ function lineMarker(kind) {
14800
+ switch (kind) {
14801
+ case "added":
14802
+ return "+";
14803
+ case "removed":
14804
+ return "-";
14805
+ default:
14806
+ return " ";
14807
+ }
14808
+ }
14809
+ function gutterWidth(diff) {
14810
+ let max = 1;
14811
+ for (const hunk of diff.hunks) {
14812
+ for (const line4 of hunk.lines) {
14813
+ const num2 = line4.kind === "removed" ? line4.oldLine : line4.newLine;
14814
+ if (num2 !== void 0) {
14815
+ max = Math.max(max, stringWidth(String(num2)));
14816
+ }
14817
+ }
14818
+ }
14819
+ return max;
14820
+ }
14821
+ function renderLineText(line4, theme, bgRole, fgRole) {
14822
+ const paintSpan = (text, changed) => {
14823
+ const fg = theme.role(fgRole, text);
14824
+ return changed ? chalk4.bold(fg) : fg;
14825
+ };
14826
+ let body;
14827
+ if (line4.spans && line4.spans.length > 0) {
14828
+ body = line4.spans.map((span) => paintSpan(span.text, span.changed)).join("");
14829
+ } else {
14830
+ body = theme.role(fgRole, line4.text);
14831
+ }
14832
+ return bgRole ? theme.roleBackground(bgRole, body) : body;
14833
+ }
14834
+ function Diff({ diff, theme, indent = 0, marginBottom = 0 }) {
14835
+ const gutter = gutterWidth(diff);
14836
+ const renderLine = (line4, key) => {
14837
+ const num2 = line4.kind === "removed" ? line4.oldLine : line4.newLine;
14838
+ const gutterText = (num2 !== void 0 ? String(num2) : "").padStart(gutter);
14839
+ const marker = lineMarker(line4.kind);
14840
+ const bgRole = line4.kind === "added" ? "diffAddedBg" : line4.kind === "removed" ? "diffRemovedBg" : null;
14841
+ const fgRole = line4.kind === "added" ? "diffAddedText" : line4.kind === "removed" ? "diffRemovedText" : "blockquoteBar";
14842
+ const gutterStyled = theme.dim(`${gutterText} `);
14843
+ const markerStyled = line4.kind === "context" ? theme.dim(`${marker} `) : theme.role(fgRole, `${marker} `);
14844
+ const text = renderLineText(line4, theme, bgRole, fgRole);
14845
+ return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { children: [
14846
+ gutterStyled,
14847
+ markerStyled,
14848
+ text
14849
+ ] }) }, key);
14850
+ };
14851
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginLeft: indent, marginBottom, children: diff.hunks.map((hunk, hunkIndex) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
14852
+ hunkIndex > 0 ? /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { children: theme.dim("...") }) }) : null,
14853
+ hunk.lines.map((line4, lineIndex) => renderLine(line4, `${hunkIndex}-${lineIndex}`))
14854
+ ] }, `hunk-${hunkIndex}`)) });
14855
+ }
14856
+
13626
14857
  // src/react-ink/components/messages/ToolResultBlock.tsx
13627
14858
  function ToolResultBlock({ expanded = false, message, nested = false, showImages, theme, toolCall }) {
13628
- const descriptor = describeToolResult(message, showImages, toolCall?.arguments);
14859
+ const descriptor = describeToolResult(message, showImages, toolCall?.arguments, theme);
14860
+ const indent = nested ? 4 : 2;
14861
+ if (descriptor.diff) {
14862
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [
14863
+ /* @__PURE__ */ jsx(
14864
+ ToolEventBlock,
14865
+ {
14866
+ detail: void 0,
14867
+ emptyText: descriptor.emptyText,
14868
+ indent,
14869
+ marginBottom: 0,
14870
+ maxContentLines: 0,
14871
+ showSummaryInline: false,
14872
+ showTitle: !nested,
14873
+ status: message.isError ? "error" : "success",
14874
+ summary: descriptor.summary,
14875
+ theme,
14876
+ title: descriptor.title
14877
+ }
14878
+ ),
14879
+ /* @__PURE__ */ jsx(Diff, { diff: descriptor.diff, theme, indent: indent + 2 })
14880
+ ] });
14881
+ }
13629
14882
  return /* @__PURE__ */ jsx(
13630
14883
  ToolEventBlock,
13631
14884
  {
13632
14885
  detail: descriptor.body,
13633
14886
  emptyText: descriptor.emptyText,
13634
- indent: nested ? 4 : 2,
14887
+ indent,
13635
14888
  marginBottom: 0,
13636
14889
  maxContentLines: expanded ? Number.MAX_SAFE_INTEGER : 10,
14890
+ preformatted: descriptor.preformatted,
13637
14891
  showSummaryInline: false,
13638
14892
  showTitle: !nested,
13639
14893
  status: message.isError ? "error" : "success",
@@ -13658,7 +14912,7 @@ function AssistantMessageView({
13658
14912
  const matchedToolResultIds = /* @__PURE__ */ new Set();
13659
14913
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
13660
14914
  /* @__PURE__ */ jsx(Text, { children: theme.color("accent", `Assistant ${message.provider}/${message.model} ${formatMessageTimestamp(message.timestamp)}`) }),
13661
- parts.text ? /* @__PURE__ */ jsx(Text, { children: parts.text }) : null,
14915
+ parts.text ? /* @__PURE__ */ jsx(Markdown, { theme, children: parts.text }) : null,
13662
14916
  parts.thinking.map((thinking, index) => /* @__PURE__ */ jsx(Text, { children: theme.muted(`[thinking] ${thinking}`) }, index)),
13663
14917
  parts.toolCalls.map((toolCall) => {
13664
14918
  const toolResult = toolResultsByCallId.get(toolCall.id);