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/shell-app.js CHANGED
@@ -8469,6 +8469,34 @@ var useInput = ink.useInput;
8469
8469
  // src/react-ink/theme-adapter.ts
8470
8470
  import chalk from "chalk";
8471
8471
  import stripAnsi from "strip-ansi";
8472
+ var DEFAULT_ROLE_KEYS = {
8473
+ codeInline: "codeInline",
8474
+ heading: "heading",
8475
+ blockquoteBar: "blockquoteBar",
8476
+ diffAddedBg: "diffAddedBg",
8477
+ diffRemovedBg: "diffRemovedBg",
8478
+ diffAddedText: "diffAddedText",
8479
+ diffRemovedText: "diffRemovedText",
8480
+ synKeyword: "synKeyword",
8481
+ synString: "synString",
8482
+ synNumber: "synNumber",
8483
+ synComment: "synComment",
8484
+ synType: "synType"
8485
+ };
8486
+ var ROLE_FALLBACK_KEYS = {
8487
+ codeInline: "accent",
8488
+ heading: "accent",
8489
+ blockquoteBar: "muted",
8490
+ diffAddedBg: "success",
8491
+ diffRemovedBg: "error",
8492
+ diffAddedText: "success",
8493
+ diffRemovedText: "error",
8494
+ synKeyword: "accent",
8495
+ synString: "success",
8496
+ synNumber: "warning",
8497
+ synComment: "muted",
8498
+ synType: "info"
8499
+ };
8472
8500
  function applyForeground(color, text) {
8473
8501
  if (!color) return text;
8474
8502
  try {
@@ -8495,15 +8523,27 @@ function applyBackground(background, foreground, text) {
8495
8523
  return applyForeground(foreground, text);
8496
8524
  }
8497
8525
  }
8498
- function createThemeAdapter(themeName, colors) {
8526
+ function createThemeAdapter(themeName, colors, roleOverrides) {
8499
8527
  const fallback = colors.text ?? "#e5e5e7";
8528
+ const roles = { ...DEFAULT_ROLE_KEYS, ...roleOverrides };
8529
+ const resolveRoleColor = (role) => resolveColorToken(
8530
+ colors[roles[role]],
8531
+ resolveColorToken(colors[ROLE_FALLBACK_KEYS[role]], fallback)
8532
+ );
8500
8533
  return {
8501
8534
  name: themeName,
8502
8535
  colors,
8536
+ roles,
8503
8537
  color: (key, text) => applyForeground(resolveColorToken(colors[key], fallback), text),
8504
8538
  background: (key, text, textKey = "text") => applyBackground(resolveColorToken(colors[key]), resolveColorToken(colors[textKey], fallback), ` ${stripAnsi(text)} `),
8505
8539
  dim: (text) => applyForeground(resolveColorToken(colors.dim, "#666666"), text),
8506
- muted: (text) => applyForeground(resolveColorToken(colors.muted, "#808080"), text)
8540
+ muted: (text) => applyForeground(resolveColorToken(colors.muted, "#808080"), text),
8541
+ role: (role, text) => applyForeground(resolveRoleColor(role), text),
8542
+ roleBackground: (role, text, foregroundRole) => applyBackground(
8543
+ resolveRoleColor(role),
8544
+ foregroundRole ? resolveRoleColor(foregroundRole) : void 0,
8545
+ text
8546
+ )
8507
8547
  };
8508
8548
  }
8509
8549
 
@@ -8888,8 +8928,1120 @@ function parseSkillInvocation(content) {
8888
8928
  };
8889
8929
  }
8890
8930
 
8931
+ // src/react-ink/markdown/format-token.ts
8932
+ import chalk2 from "chalk";
8933
+ import { marked } from "marked";
8934
+ import stripAnsi3 from "strip-ansi";
8935
+
8936
+ // src/ui/utils.ts
8937
+ import { eastAsianWidth } from "get-east-asian-width";
8938
+ var segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
8939
+ var TextWidthCalculator = class {
8940
+ getWidth(text) {
8941
+ return visibleWidth(text);
8942
+ }
8943
+ clearCache() {
8944
+ widthCache.clear();
8945
+ }
8946
+ };
8947
+ var textWidthCalculator = new TextWidthCalculator();
8948
+ function looksLikeEmojiCandidate(segment) {
8949
+ const cp = segment.codePointAt(0);
8950
+ return cp >= 126976 && cp <= 130047 || // pictographs and emoji proper
8951
+ cp >= 8960 && cp <= 9215 || // miscellaneous technical glyphs
8952
+ cp >= 9728 && cp <= 10175 || // assorted symbols and dingbats
8953
+ cp >= 11088 && cp <= 11093 || // a few star/circle characters
8954
+ segment.includes("\uFE0F") || // carries VS16, the emoji-presentation selector
8955
+ segment.length > 2;
8956
+ }
8957
+ function createUnicodeRegex(vPattern, uFallback) {
8958
+ try {
8959
+ return new RegExp(vPattern, "v");
8960
+ } catch {
8961
+ return new RegExp(uFallback, "u");
8962
+ }
8963
+ }
8964
+ var zeroWidthRegex = createUnicodeRegex(
8965
+ "^(?:\\p{Default_Ignorable_Code_Point}|\\p{Control}|\\p{Mark}|\\p{Surrogate})+$",
8966
+ "^(?:\\p{Default_Ignorable_Code_Point}|\\p{Control}|\\p{Mark}|\\p{Surrogate})+$"
8967
+ );
8968
+ var leadingNonPrintingRegex = createUnicodeRegex(
8969
+ "^[\\p{Default_Ignorable_Code_Point}\\p{Control}\\p{Format}\\p{Mark}\\p{Surrogate}]+",
8970
+ "^[\\p{Default_Ignorable_Code_Point}\\p{Control}\\p{Format}\\p{Mark}\\p{Surrogate}]+"
8971
+ );
8972
+ var rgiEmojiRegex = createUnicodeRegex("^\\p{RGI_Emoji}$", "^\\p{Extended_Pictographic}$");
8973
+ var AnsiStripper = {
8974
+ strip(text) {
8975
+ if (!text.includes("\x1B")) {
8976
+ return text;
8977
+ }
8978
+ let cleaned = text;
8979
+ cleaned = cleaned.replace(/\x1b\[[0-9;]*[mGKHJ]/g, "");
8980
+ cleaned = cleaned.replace(/\x1b\]8;;[^\x07]*\x07/g, "");
8981
+ cleaned = cleaned.replace(/\x1b_[^\x07\x1b]*(?:\x07|\x1b\\)/g, "");
8982
+ return cleaned;
8983
+ }
8984
+ };
8985
+ var WIDTH_CACHE_SIZE = 512;
8986
+ var widthCache = /* @__PURE__ */ new Map();
8987
+ function measureClusterWidth(segment) {
8988
+ if (zeroWidthRegex.test(segment)) {
8989
+ return 0;
8990
+ }
8991
+ if (looksLikeEmojiCandidate(segment) && rgiEmojiRegex.test(segment)) {
8992
+ return 2;
8993
+ }
8994
+ const base = segment.replace(leadingNonPrintingRegex, "");
8995
+ const cp = base.codePointAt(0);
8996
+ if (cp === void 0) {
8997
+ return 0;
8998
+ }
8999
+ let width = eastAsianWidth(cp);
9000
+ if (segment.length > 1) {
9001
+ for (const char of segment.slice(1)) {
9002
+ const c = char.codePointAt(0);
9003
+ if (c >= 65280 && c <= 65519) {
9004
+ width += eastAsianWidth(c);
9005
+ }
9006
+ }
9007
+ }
9008
+ return width;
9009
+ }
9010
+ function visibleWidth(str2) {
9011
+ if (str2.length === 0) {
9012
+ return 0;
9013
+ }
9014
+ let isPureAscii = true;
9015
+ for (let i = 0; i < str2.length; i++) {
9016
+ const code = str2.charCodeAt(i);
9017
+ if (code < 32 || code > 126) {
9018
+ isPureAscii = false;
9019
+ break;
9020
+ }
9021
+ }
9022
+ if (isPureAscii) {
9023
+ return str2.length;
9024
+ }
9025
+ const cached = widthCache.get(str2);
9026
+ if (cached !== void 0) {
9027
+ return cached;
9028
+ }
9029
+ let clean = str2;
9030
+ if (str2.includes(" ")) {
9031
+ clean = clean.replace(/\t/g, " ");
9032
+ }
9033
+ clean = AnsiStripper.strip(clean);
9034
+ let width = 0;
9035
+ for (const { segment } of segmenter.segment(clean)) {
9036
+ width += measureClusterWidth(segment);
9037
+ }
9038
+ if (widthCache.size >= WIDTH_CACHE_SIZE) {
9039
+ const firstKey = widthCache.keys().next().value;
9040
+ if (firstKey !== void 0) {
9041
+ widthCache.delete(firstKey);
9042
+ }
9043
+ }
9044
+ widthCache.set(str2, width);
9045
+ return width;
9046
+ }
9047
+ function extractAnsiCode(str2, pos) {
9048
+ if (pos >= str2.length || str2[pos] !== "\x1B") return null;
9049
+ const next = str2[pos + 1];
9050
+ if (next === "[") {
9051
+ let j = pos + 2;
9052
+ while (j < str2.length && !/[mGKHJ]/.test(str2[j])) j++;
9053
+ if (j < str2.length) return { code: str2.substring(pos, j + 1), length: j + 1 - pos };
9054
+ return null;
9055
+ }
9056
+ if (next === "]") {
9057
+ let j = pos + 2;
9058
+ while (j < str2.length) {
9059
+ if (str2[j] === "\x07") return { code: str2.substring(pos, j + 1), length: j + 1 - pos };
9060
+ if (str2[j] === "\x1B" && str2[j + 1] === "\\") return { code: str2.substring(pos, j + 2), length: j + 2 - pos };
9061
+ j++;
9062
+ }
9063
+ return null;
9064
+ }
9065
+ if (next === "_") {
9066
+ let j = pos + 2;
9067
+ while (j < str2.length) {
9068
+ if (str2[j] === "\x07") return { code: str2.substring(pos, j + 1), length: j + 1 - pos };
9069
+ if (str2[j] === "\x1B" && str2[j + 1] === "\\") return { code: str2.substring(pos, j + 2), length: j + 2 - pos };
9070
+ j++;
9071
+ }
9072
+ return null;
9073
+ }
9074
+ return null;
9075
+ }
9076
+ var AnsiStateTracker = class {
9077
+ // Each attribute is kept on its own flag, which lets us clear them one at a time.
9078
+ _bold = false;
9079
+ _dim = false;
9080
+ italic = false;
9081
+ underline = false;
9082
+ blink = false;
9083
+ inverse = false;
9084
+ hidden = false;
9085
+ strikethrough = false;
9086
+ _colors = { fg: null, bg: null };
9087
+ process(ansiCode) {
9088
+ if (!ansiCode.endsWith("m")) {
9089
+ return;
9090
+ }
9091
+ const match = ansiCode.match(/\x1b\[([\d;]*)m/);
9092
+ if (!match) return;
9093
+ const params = match[1];
9094
+ if (params === "" || params === "0") {
9095
+ this.reset();
9096
+ return;
9097
+ }
9098
+ const parts = params.split(";");
9099
+ let i = 0;
9100
+ while (i < parts.length) {
9101
+ const code = Number.parseInt(parts[i] ?? "", 10);
9102
+ if (Number.isNaN(code)) {
9103
+ i++;
9104
+ continue;
9105
+ }
9106
+ const consumed = this.tryConsumeColorCode(parts, i, code);
9107
+ if (consumed > 0) {
9108
+ i += consumed;
9109
+ continue;
9110
+ }
9111
+ this.applyStandardCode(code);
9112
+ i++;
9113
+ }
9114
+ }
9115
+ tryConsumeColorCode(parts, index, code) {
9116
+ if (code !== 38 && code !== 48) {
9117
+ return 0;
9118
+ }
9119
+ if (parts[index + 1] === "5" && parts[index + 2] !== void 0) {
9120
+ const colorCode = `${parts[index]};${parts[index + 1]};${parts[index + 2]}`;
9121
+ if (code === 38) {
9122
+ this._colors.fg = colorCode;
9123
+ } else {
9124
+ this._colors.bg = colorCode;
9125
+ }
9126
+ return 3;
9127
+ }
9128
+ if (parts[index + 1] === "2" && parts[index + 4] !== void 0) {
9129
+ const colorCode = `${parts[index]};${parts[index + 1]};${parts[index + 2]};${parts[index + 3]};${parts[index + 4]}`;
9130
+ if (code === 38) {
9131
+ this._colors.fg = colorCode;
9132
+ } else {
9133
+ this._colors.bg = colorCode;
9134
+ }
9135
+ return 5;
9136
+ }
9137
+ return 0;
9138
+ }
9139
+ applyStandardCode(code) {
9140
+ switch (code) {
9141
+ case 0:
9142
+ this.reset();
9143
+ return;
9144
+ case 1:
9145
+ this._bold = true;
9146
+ return;
9147
+ case 2:
9148
+ this._dim = true;
9149
+ return;
9150
+ case 3:
9151
+ this.italic = true;
9152
+ return;
9153
+ case 4:
9154
+ this.underline = true;
9155
+ return;
9156
+ case 5:
9157
+ this.blink = true;
9158
+ return;
9159
+ case 7:
9160
+ this.inverse = true;
9161
+ return;
9162
+ case 8:
9163
+ this.hidden = true;
9164
+ return;
9165
+ case 9:
9166
+ this.strikethrough = true;
9167
+ return;
9168
+ case 21:
9169
+ this._bold = false;
9170
+ return;
9171
+ case 22:
9172
+ this._bold = false;
9173
+ this._dim = false;
9174
+ return;
9175
+ case 23:
9176
+ this.italic = false;
9177
+ return;
9178
+ case 24:
9179
+ this.underline = false;
9180
+ return;
9181
+ case 25:
9182
+ this.blink = false;
9183
+ return;
9184
+ case 27:
9185
+ this.inverse = false;
9186
+ return;
9187
+ case 28:
9188
+ this.hidden = false;
9189
+ return;
9190
+ case 29:
9191
+ this.strikethrough = false;
9192
+ return;
9193
+ case 39:
9194
+ this._colors.fg = null;
9195
+ return;
9196
+ case 49:
9197
+ this._colors.bg = null;
9198
+ return;
9199
+ default:
9200
+ if (code >= 30 && code <= 37 || code >= 90 && code <= 97) {
9201
+ this._colors.fg = String(code);
9202
+ return;
9203
+ }
9204
+ if (code >= 40 && code <= 47 || code >= 100 && code <= 107) {
9205
+ this._colors.bg = String(code);
9206
+ }
9207
+ }
9208
+ }
9209
+ reset() {
9210
+ this._bold = false;
9211
+ this._dim = false;
9212
+ this.italic = false;
9213
+ this.underline = false;
9214
+ this.blink = false;
9215
+ this.inverse = false;
9216
+ this.hidden = false;
9217
+ this.strikethrough = false;
9218
+ this._colors.fg = null;
9219
+ this._colors.bg = null;
9220
+ }
9221
+ /** Wipe all tracked state so the instance can be reused. */
9222
+ clear() {
9223
+ this.reset();
9224
+ }
9225
+ getActiveCodes() {
9226
+ const codes = [];
9227
+ if (this._bold) codes.push("1");
9228
+ if (this._dim) codes.push("2");
9229
+ if (this.italic) codes.push("3");
9230
+ if (this.underline) codes.push("4");
9231
+ if (this.blink) codes.push("5");
9232
+ if (this.inverse) codes.push("7");
9233
+ if (this.hidden) codes.push("8");
9234
+ if (this.strikethrough) codes.push("9");
9235
+ if (this._colors.fg) codes.push(this._colors.fg);
9236
+ if (this._colors.bg) codes.push(this._colors.bg);
9237
+ if (codes.length === 0) return "";
9238
+ return `\x1B[${codes.join(";")}m`;
9239
+ }
9240
+ hasActiveCodes() {
9241
+ 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;
9242
+ }
9243
+ /**
9244
+ * Produce the escape code needed to switch off any attribute that would
9245
+ * otherwise smear into the padding at the end of a line. In practice that
9246
+ * is just underline. Yields an empty string when nothing needs disabling.
9247
+ */
9248
+ getLineEndReset() {
9249
+ if (this.underline) {
9250
+ return "\x1B[24m";
9251
+ }
9252
+ return "";
9253
+ }
9254
+ };
9255
+ function mergeTextIntoTracker(text, tracker) {
9256
+ let i = 0;
9257
+ while (i < text.length) {
9258
+ const ansiResult = extractAnsiCode(text, i);
9259
+ if (ansiResult) {
9260
+ tracker.process(ansiResult.code);
9261
+ i += ansiResult.length;
9262
+ } else {
9263
+ i++;
9264
+ }
9265
+ }
9266
+ }
9267
+ function tokenizeTextWithAnsi(text) {
9268
+ const tokens = [];
9269
+ let current = "";
9270
+ let pendingAnsi = "";
9271
+ let inWhitespace = false;
9272
+ let i = 0;
9273
+ while (i < text.length) {
9274
+ const ansiResult = extractAnsiCode(text, i);
9275
+ if (ansiResult) {
9276
+ pendingAnsi += ansiResult.code;
9277
+ i += ansiResult.length;
9278
+ continue;
9279
+ }
9280
+ const char = text[i];
9281
+ const charIsSpace = char === " ";
9282
+ if (charIsSpace !== inWhitespace && current) {
9283
+ tokens.push(current);
9284
+ current = "";
9285
+ }
9286
+ if (pendingAnsi) {
9287
+ current += pendingAnsi;
9288
+ pendingAnsi = "";
9289
+ }
9290
+ inWhitespace = charIsSpace;
9291
+ current += char;
9292
+ i++;
9293
+ }
9294
+ if (pendingAnsi) {
9295
+ current += pendingAnsi;
9296
+ }
9297
+ if (current) {
9298
+ tokens.push(current);
9299
+ }
9300
+ return tokens;
9301
+ }
9302
+ var TextWrapper = class {
9303
+ wrap(text, width) {
9304
+ return wrapTextWithAnsi(text, width);
9305
+ }
9306
+ };
9307
+ var textWrapper = new TextWrapper();
9308
+ var TokenWrapEngine = {
9309
+ wrap(tokens, width, tracker) {
9310
+ const wrapped = [];
9311
+ let currentLine = "";
9312
+ let currentVisibleLength = 0;
9313
+ for (const token of tokens) {
9314
+ const tokenVisibleLength = visibleWidth(token);
9315
+ const isWhitespace = token.trim() === "";
9316
+ if (tokenVisibleLength > width && !isWhitespace) {
9317
+ if (currentLine) {
9318
+ const lineEndReset = tracker.getLineEndReset();
9319
+ if (lineEndReset) {
9320
+ currentLine += lineEndReset;
9321
+ }
9322
+ wrapped.push(currentLine);
9323
+ currentLine = "";
9324
+ currentVisibleLength = 0;
9325
+ }
9326
+ const broken = splitLongToken(token, width, tracker);
9327
+ wrapped.push(...broken.slice(0, -1));
9328
+ currentLine = broken[broken.length - 1];
9329
+ currentVisibleLength = visibleWidth(currentLine);
9330
+ continue;
9331
+ }
9332
+ const totalNeeded = currentVisibleLength + tokenVisibleLength;
9333
+ if (totalNeeded > width && currentVisibleLength > 0) {
9334
+ let lineToWrap = currentLine.trimEnd();
9335
+ const lineEndReset = tracker.getLineEndReset();
9336
+ if (lineEndReset) {
9337
+ lineToWrap += lineEndReset;
9338
+ }
9339
+ wrapped.push(lineToWrap);
9340
+ if (isWhitespace) {
9341
+ currentLine = tracker.getActiveCodes();
9342
+ currentVisibleLength = 0;
9343
+ } else {
9344
+ currentLine = tracker.getActiveCodes() + token;
9345
+ currentVisibleLength = tokenVisibleLength;
9346
+ }
9347
+ } else {
9348
+ currentLine += token;
9349
+ currentVisibleLength += tokenVisibleLength;
9350
+ }
9351
+ mergeTextIntoTracker(token, tracker);
9352
+ }
9353
+ return { wrapped, currentLine, currentVisibleLength };
9354
+ }
9355
+ };
9356
+ function wrapTextWithAnsi(text, width) {
9357
+ if (!text) {
9358
+ return [""];
9359
+ }
9360
+ const inputLines = text.split("\n");
9361
+ const result = [];
9362
+ const tracker = new AnsiStateTracker();
9363
+ for (const inputLine of inputLines) {
9364
+ const prefix = result.length > 0 ? tracker.getActiveCodes() : "";
9365
+ result.push(...wrapLinePreservingAnsi(prefix + inputLine, width));
9366
+ mergeTextIntoTracker(inputLine, tracker);
9367
+ }
9368
+ return result.length > 0 ? result : [""];
9369
+ }
9370
+ function wrapLinePreservingAnsi(line, width) {
9371
+ if (!line) {
9372
+ return [""];
9373
+ }
9374
+ const visibleLength = visibleWidth(line);
9375
+ if (visibleLength <= width) {
9376
+ return [line];
9377
+ }
9378
+ const tracker = new AnsiStateTracker();
9379
+ const tokens = tokenizeTextWithAnsi(line);
9380
+ const { wrapped, currentLine } = TokenWrapEngine.wrap(tokens, width, tracker);
9381
+ if (currentLine) {
9382
+ wrapped.push(currentLine);
9383
+ }
9384
+ return wrapped.length > 0 ? wrapped.map((segmentLine) => segmentLine.trimEnd()) : [""];
9385
+ }
9386
+ function splitLongToken(word, width, tracker) {
9387
+ const lines = [];
9388
+ let currentLine = tracker.getActiveCodes();
9389
+ let currentWidth = 0;
9390
+ let i = 0;
9391
+ const segments = [];
9392
+ while (i < word.length) {
9393
+ const ansiResult = extractAnsiCode(word, i);
9394
+ if (ansiResult) {
9395
+ segments.push({ type: "ansi", value: ansiResult.code });
9396
+ i += ansiResult.length;
9397
+ } else {
9398
+ let end = i;
9399
+ while (end < word.length) {
9400
+ const nextAnsi = extractAnsiCode(word, end);
9401
+ if (nextAnsi) break;
9402
+ end++;
9403
+ }
9404
+ const textPortion = word.slice(i, end);
9405
+ for (const seg of segmenter.segment(textPortion)) {
9406
+ segments.push({ type: "grapheme", value: seg.segment });
9407
+ }
9408
+ i = end;
9409
+ }
9410
+ }
9411
+ for (const seg of segments) {
9412
+ if (seg.type === "ansi") {
9413
+ currentLine += seg.value;
9414
+ tracker.process(seg.value);
9415
+ continue;
9416
+ }
9417
+ const grapheme = seg.value;
9418
+ if (!grapheme) continue;
9419
+ const clusterWidth = visibleWidth(grapheme);
9420
+ if (currentWidth + clusterWidth > width) {
9421
+ const lineEndReset = tracker.getLineEndReset();
9422
+ if (lineEndReset) {
9423
+ currentLine += lineEndReset;
9424
+ }
9425
+ lines.push(currentLine);
9426
+ currentLine = tracker.getActiveCodes();
9427
+ currentWidth = 0;
9428
+ }
9429
+ currentLine += grapheme;
9430
+ currentWidth += clusterWidth;
9431
+ }
9432
+ if (currentLine) {
9433
+ lines.push(currentLine);
9434
+ }
9435
+ return lines.length > 0 ? lines : [""];
9436
+ }
9437
+ var pooledStyleTracker = new AnsiStateTracker();
9438
+
9439
+ // src/react-ink/markdown/format-token.ts
9440
+ var EOL = "\n";
9441
+ var BLOCKQUOTE_BAR = "\u2502";
9442
+ var markedConfigured = false;
9443
+ function configureMarked() {
9444
+ if (markedConfigured) {
9445
+ return;
9446
+ }
9447
+ markedConfigured = true;
9448
+ marked.use({
9449
+ tokenizer: {
9450
+ del() {
9451
+ return void 0;
9452
+ }
9453
+ }
9454
+ });
9455
+ }
9456
+ var TOKEN_CACHE_MAX = 500;
9457
+ var tokenCache = /* @__PURE__ */ new Map();
9458
+ var MD_SYNTAX_RE = /[#*`|[>\-_~]|\n\n|^\d+\. |\n\d+\. /;
9459
+ function hasMarkdownSyntax(text) {
9460
+ return MD_SYNTAX_RE.test(text.length > 500 ? text.slice(0, 500) : text);
9461
+ }
9462
+ function hashContent(content) {
9463
+ let hash = 2166136261;
9464
+ for (let i = 0; i < content.length; i++) {
9465
+ hash ^= content.charCodeAt(i);
9466
+ hash = Math.imul(hash, 16777619);
9467
+ }
9468
+ return (hash >>> 0).toString(36) + ":" + content.length.toString(36);
9469
+ }
9470
+ function cachedLexer(content) {
9471
+ configureMarked();
9472
+ if (!hasMarkdownSyntax(content)) {
9473
+ return [
9474
+ {
9475
+ type: "paragraph",
9476
+ raw: content,
9477
+ text: content,
9478
+ tokens: [{ type: "text", raw: content, text: content }]
9479
+ }
9480
+ ];
9481
+ }
9482
+ const key = hashContent(content);
9483
+ const hit = tokenCache.get(key);
9484
+ if (hit) {
9485
+ tokenCache.delete(key);
9486
+ tokenCache.set(key, hit);
9487
+ return hit;
9488
+ }
9489
+ const tokens = marked.lexer(content);
9490
+ if (tokenCache.size >= TOKEN_CACHE_MAX) {
9491
+ const first = tokenCache.keys().next().value;
9492
+ if (first !== void 0) {
9493
+ tokenCache.delete(first);
9494
+ }
9495
+ }
9496
+ tokenCache.set(key, tokens);
9497
+ return tokens;
9498
+ }
9499
+ function formatToken(token, theme, highlight = null, listDepth = 0, orderedListNumber = null, parent = null) {
9500
+ switch (token.type) {
9501
+ case "blockquote": {
9502
+ const inner = (token.tokens ?? []).map((child) => formatToken(child, theme, highlight)).join("");
9503
+ const bar = theme.dim(BLOCKQUOTE_BAR);
9504
+ return inner.split(EOL).map((line) => stripAnsi3(line).trim() ? `${bar} ${chalk2.italic(line)}` : line).join(EOL);
9505
+ }
9506
+ case "code": {
9507
+ const codeToken = token;
9508
+ if (!highlight) {
9509
+ return codeToken.text + EOL;
9510
+ }
9511
+ let language = "plaintext";
9512
+ if (codeToken.lang && highlight.supportsLanguage(codeToken.lang)) {
9513
+ language = codeToken.lang;
9514
+ }
9515
+ return highlight.highlight(codeToken.text, { language }) + EOL;
9516
+ }
9517
+ case "codespan":
9518
+ return theme.role("codeInline", token.text);
9519
+ case "em":
9520
+ return chalk2.italic(
9521
+ (token.tokens ?? []).map((child) => formatToken(child, theme, highlight, 0, null, parent)).join("")
9522
+ );
9523
+ case "strong":
9524
+ return chalk2.bold(
9525
+ (token.tokens ?? []).map((child) => formatToken(child, theme, highlight, 0, null, parent)).join("")
9526
+ );
9527
+ case "heading": {
9528
+ const headingToken = token;
9529
+ const inner = (headingToken.tokens ?? []).map((child) => formatToken(child, theme, highlight)).join("");
9530
+ const colored = theme.role("heading", inner);
9531
+ if (headingToken.depth === 1) {
9532
+ return chalk2.bold.italic.underline(colored) + EOL + EOL;
9533
+ }
9534
+ return chalk2.bold(colored) + EOL + EOL;
9535
+ }
9536
+ case "hr":
9537
+ return "---";
9538
+ case "image":
9539
+ return token.href;
9540
+ case "link": {
9541
+ const linkToken = token;
9542
+ if (linkToken.href.startsWith("mailto:")) {
9543
+ return linkToken.href.replace(/^mailto:/, "");
9544
+ }
9545
+ const linkText = (linkToken.tokens ?? []).map((child) => formatToken(child, theme, highlight, 0, null, linkToken)).join("");
9546
+ const plainLinkText = stripAnsi3(linkText);
9547
+ const display = plainLinkText && plainLinkText !== linkToken.href ? linkText : linkToken.href;
9548
+ return `\x1B]8;;${linkToken.href}\x07${display}\x1B]8;;\x07`;
9549
+ }
9550
+ case "list": {
9551
+ const listToken = token;
9552
+ const start = typeof listToken.start === "number" ? listToken.start : Number(listToken.start) || 1;
9553
+ return listToken.items.map(
9554
+ (item, index) => formatToken(
9555
+ item,
9556
+ theme,
9557
+ highlight,
9558
+ listDepth,
9559
+ listToken.ordered ? start + index : null,
9560
+ listToken
9561
+ )
9562
+ ).join("");
9563
+ }
9564
+ case "list_item":
9565
+ return (token.tokens ?? []).map(
9566
+ (child) => `${" ".repeat(listDepth)}${formatToken(child, theme, highlight, listDepth + 1, orderedListNumber, token)}`
9567
+ ).join("");
9568
+ case "paragraph":
9569
+ return (token.tokens ?? []).map((child) => formatToken(child, theme, highlight)).join("") + EOL;
9570
+ case "space":
9571
+ case "br":
9572
+ return EOL;
9573
+ case "text": {
9574
+ const textToken = token;
9575
+ if (parent?.type === "link") {
9576
+ return textToken.text;
9577
+ }
9578
+ if (parent?.type === "list_item") {
9579
+ const marker = orderedListNumber === null ? "-" : `${getListNumber(listDepth, orderedListNumber)}.`;
9580
+ const body = textToken.tokens ? textToken.tokens.map((child) => formatToken(child, theme, highlight, listDepth, orderedListNumber, token)).join("") : textToken.text;
9581
+ return `${marker} ${body}${EOL}`;
9582
+ }
9583
+ return textToken.text;
9584
+ }
9585
+ case "escape":
9586
+ return token.text;
9587
+ case "table":
9588
+ case "def":
9589
+ case "del":
9590
+ case "html":
9591
+ return "";
9592
+ default:
9593
+ return "";
9594
+ }
9595
+ }
9596
+ function numberToLetter(n) {
9597
+ let result = "";
9598
+ while (n > 0) {
9599
+ n--;
9600
+ result = String.fromCharCode(97 + n % 26) + result;
9601
+ n = Math.floor(n / 26);
9602
+ }
9603
+ return result;
9604
+ }
9605
+ var ROMAN_VALUES = [
9606
+ [1e3, "m"],
9607
+ [900, "cm"],
9608
+ [500, "d"],
9609
+ [400, "cd"],
9610
+ [100, "c"],
9611
+ [90, "xc"],
9612
+ [50, "l"],
9613
+ [40, "xl"],
9614
+ [10, "x"],
9615
+ [9, "ix"],
9616
+ [5, "v"],
9617
+ [4, "iv"],
9618
+ [1, "i"]
9619
+ ];
9620
+ function numberToRoman(n) {
9621
+ let result = "";
9622
+ for (const [value, numeral] of ROMAN_VALUES) {
9623
+ while (n >= value) {
9624
+ result += numeral;
9625
+ n -= value;
9626
+ }
9627
+ }
9628
+ return result;
9629
+ }
9630
+ function getListNumber(listDepth, orderedListNumber) {
9631
+ switch (listDepth) {
9632
+ case 0:
9633
+ case 1:
9634
+ return orderedListNumber.toString();
9635
+ case 2:
9636
+ return numberToLetter(orderedListNumber);
9637
+ case 3:
9638
+ return numberToRoman(orderedListNumber);
9639
+ default:
9640
+ return orderedListNumber.toString();
9641
+ }
9642
+ }
9643
+ function padAligned(content, displayWidth, targetWidth, align) {
9644
+ const padding = Math.max(0, targetWidth - displayWidth);
9645
+ if (align === "center") {
9646
+ const leftPad = Math.floor(padding / 2);
9647
+ return " ".repeat(leftPad) + content + " ".repeat(padding - leftPad);
9648
+ }
9649
+ if (align === "right") {
9650
+ return " ".repeat(padding) + content;
9651
+ }
9652
+ return content + " ".repeat(padding);
9653
+ }
9654
+ function stringWidth(text) {
9655
+ return visibleWidth(text);
9656
+ }
9657
+
9658
+ // src/react-ink/markdown/highlight.ts
9659
+ import { extname } from "node:path";
9660
+ import hljs from "highlight.js";
9661
+ function scopeToRole(scope) {
9662
+ const head = scope.split(".")[0] ?? scope;
9663
+ switch (head) {
9664
+ case "keyword":
9665
+ case "built_in":
9666
+ case "literal":
9667
+ case "operator":
9668
+ return "synKeyword";
9669
+ case "string":
9670
+ case "regexp":
9671
+ case "symbol":
9672
+ case "char":
9673
+ case "meta":
9674
+ return "synString";
9675
+ case "number":
9676
+ return "synNumber";
9677
+ case "comment":
9678
+ case "quote":
9679
+ return "synComment";
9680
+ case "type":
9681
+ case "class":
9682
+ case "title":
9683
+ case "tag":
9684
+ case "name":
9685
+ case "attr":
9686
+ case "attribute":
9687
+ case "selector":
9688
+ return "synType";
9689
+ default:
9690
+ return null;
9691
+ }
9692
+ }
9693
+ var HTML_ENTITIES = {
9694
+ "&amp;": "&",
9695
+ "&lt;": "<",
9696
+ "&gt;": ">",
9697
+ "&quot;": '"',
9698
+ "&#x27;": "'",
9699
+ "&#39;": "'"
9700
+ };
9701
+ function decodeEntities2(text) {
9702
+ return text.replace(/&(?:amp|lt|gt|quot|#x27|#39);/g, (match) => HTML_ENTITIES[match] ?? match);
9703
+ }
9704
+ function parseHljsHtml(html) {
9705
+ const nodes = [];
9706
+ const scopeStack = [];
9707
+ const tagRe = /<span class="hljs-([^"]+)">|<\/span>/g;
9708
+ let lastIndex = 0;
9709
+ let match;
9710
+ const pushText = (raw) => {
9711
+ if (!raw) return;
9712
+ const currentScope = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : null;
9713
+ const scope = currentScope ? currentScope.split(/\s+/)[0].replace(/_$/, "") : null;
9714
+ nodes.push({ text: decodeEntities2(raw), scope });
9715
+ };
9716
+ while ((match = tagRe.exec(html)) !== null) {
9717
+ pushText(html.slice(lastIndex, match.index));
9718
+ lastIndex = tagRe.lastIndex;
9719
+ if (match[0] === "</span>") {
9720
+ scopeStack.pop();
9721
+ } else if (match[1]) {
9722
+ scopeStack.push(match[1]);
9723
+ }
9724
+ }
9725
+ pushText(html.slice(lastIndex));
9726
+ return nodes;
9727
+ }
9728
+ function createHighlighter(theme) {
9729
+ return {
9730
+ supportsLanguage: (language) => {
9731
+ if (!language || language === "plaintext" || language === "text") {
9732
+ return false;
9733
+ }
9734
+ try {
9735
+ return hljs.getLanguage(language) !== void 0;
9736
+ } catch {
9737
+ return false;
9738
+ }
9739
+ },
9740
+ highlight: (code, options) => {
9741
+ const language = options.language;
9742
+ if (!language || language === "plaintext" || language === "text") {
9743
+ return code;
9744
+ }
9745
+ try {
9746
+ if (hljs.getLanguage(language) === void 0) {
9747
+ return code;
9748
+ }
9749
+ const { value } = hljs.highlight(code, { language, ignoreIllegals: true });
9750
+ return parseHljsHtml(value).map((node) => {
9751
+ const role = node.scope ? scopeToRole(node.scope) : null;
9752
+ return role ? theme.role(role, node.text) : node.text;
9753
+ }).join("");
9754
+ } catch {
9755
+ return code;
9756
+ }
9757
+ }
9758
+ };
9759
+ }
9760
+ function highlightByPath(code, filePath, theme) {
9761
+ const language = languageFromPath(filePath);
9762
+ if (!language) {
9763
+ return code;
9764
+ }
9765
+ const highlighter = createHighlighter(theme);
9766
+ if (!highlighter.supportsLanguage(language)) {
9767
+ return code;
9768
+ }
9769
+ return highlighter.highlight(code, { language });
9770
+ }
9771
+ var EXTENSION_LANGUAGES = {
9772
+ ts: "typescript",
9773
+ tsx: "typescript",
9774
+ mts: "typescript",
9775
+ cts: "typescript",
9776
+ js: "javascript",
9777
+ jsx: "javascript",
9778
+ mjs: "javascript",
9779
+ cjs: "javascript",
9780
+ py: "python",
9781
+ rb: "ruby",
9782
+ rs: "rust",
9783
+ go: "go",
9784
+ java: "java",
9785
+ kt: "kotlin",
9786
+ c: "c",
9787
+ h: "c",
9788
+ cc: "cpp",
9789
+ cpp: "cpp",
9790
+ hpp: "cpp",
9791
+ cs: "csharp",
9792
+ sh: "bash",
9793
+ bash: "bash",
9794
+ zsh: "bash",
9795
+ yml: "yaml",
9796
+ yaml: "yaml",
9797
+ json: "json",
9798
+ md: "markdown",
9799
+ html: "xml",
9800
+ xml: "xml",
9801
+ css: "css",
9802
+ scss: "scss",
9803
+ sql: "sql",
9804
+ toml: "ini",
9805
+ ini: "ini",
9806
+ php: "php",
9807
+ swift: "swift"
9808
+ };
9809
+ function languageFromPath(filePath) {
9810
+ const ext = extname(filePath).slice(1).toLowerCase();
9811
+ if (!ext) {
9812
+ return null;
9813
+ }
9814
+ const mapped = EXTENSION_LANGUAGES[ext];
9815
+ if (mapped) {
9816
+ return mapped;
9817
+ }
9818
+ try {
9819
+ return hljs.getLanguage(ext) !== void 0 ? ext : null;
9820
+ } catch {
9821
+ return null;
9822
+ }
9823
+ }
9824
+
9825
+ // src/react-ink/markdown/MarkdownTable.tsx
9826
+ import chalk3 from "chalk";
9827
+ import stripAnsi4 from "strip-ansi";
9828
+ var MIN_COLUMN_WIDTH = 3;
9829
+ var COLUMN_GAP = 2;
9830
+ function MarkdownTable({ token, theme, highlight = null }) {
9831
+ const formatCell = (tokens) => (tokens ?? []).map((child) => formatToken(child, theme, highlight)).join("");
9832
+ const displayWidth = (tokens) => stringWidth(stripAnsi4(formatCell(tokens)));
9833
+ const columnCount = token.header.length;
9834
+ const columnWidths = token.header.map((header, index) => {
9835
+ let max = displayWidth(header.tokens);
9836
+ for (const row of token.rows) {
9837
+ max = Math.max(max, displayWidth(row[index]?.tokens));
9838
+ }
9839
+ return Math.max(max, MIN_COLUMN_WIDTH);
9840
+ });
9841
+ const renderRow2 = (cells, key, bold) => /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: columnWidths.map((width, index) => {
9842
+ const cell = cells[index];
9843
+ const content = formatCell(cell?.tokens);
9844
+ const visible = stringWidth(stripAnsi4(content));
9845
+ const align = token.align?.[index];
9846
+ const padded = padAligned(content, visible, width, align ?? "left");
9847
+ const styled = bold ? chalk3.bold(padded) : padded;
9848
+ const gap = index < columnCount - 1 ? " ".repeat(COLUMN_GAP) : "";
9849
+ return /* @__PURE__ */ jsxs(Text, { children: [
9850
+ styled,
9851
+ gap
9852
+ ] }, index);
9853
+ }) }, key);
9854
+ const separator = /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: columnWidths.map((width, index) => {
9855
+ const gap = index < columnCount - 1 ? " ".repeat(COLUMN_GAP) : "";
9856
+ return /* @__PURE__ */ jsxs(Text, { children: [
9857
+ theme.dim("-".repeat(width)),
9858
+ gap
9859
+ ] }, index);
9860
+ }) });
9861
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
9862
+ renderRow2(token.header, "header", true),
9863
+ separator,
9864
+ token.rows.map((row, rowIndex) => renderRow2(row, `row-${rowIndex}`, false))
9865
+ ] });
9866
+ }
9867
+
9868
+ // src/react-ink/markdown/Markdown.tsx
9869
+ function stripPromptXMLTags(text) {
9870
+ return text.replace(/<\/?(?:system-reminder|prompt|context)[^>]*>/g, "");
9871
+ }
9872
+ function Markdown({ children, theme, highlightCode = true, dim = false }) {
9873
+ const highlight = useMemo(
9874
+ () => highlightCode ? createHighlighter(theme) : null,
9875
+ [highlightCode, theme]
9876
+ );
9877
+ const elements = useMemo(() => {
9878
+ configureMarked();
9879
+ const tokens = cachedLexer(stripPromptXMLTags(children));
9880
+ const out = [];
9881
+ let buffer = "";
9882
+ const flush = () => {
9883
+ if (buffer) {
9884
+ const text = buffer.replace(/\n+$/, "");
9885
+ if (text) {
9886
+ out.push(
9887
+ /* @__PURE__ */ jsx(Text, { children: dim ? theme.dim(text) : text }, out.length)
9888
+ );
9889
+ }
9890
+ buffer = "";
9891
+ }
9892
+ };
9893
+ for (const token of tokens) {
9894
+ if (token.type === "table") {
9895
+ flush();
9896
+ out.push(
9897
+ /* @__PURE__ */ jsx(
9898
+ MarkdownTable,
9899
+ {
9900
+ token,
9901
+ theme,
9902
+ highlight
9903
+ },
9904
+ out.length
9905
+ )
9906
+ );
9907
+ } else {
9908
+ buffer += formatToken(token, theme, highlight);
9909
+ }
9910
+ }
9911
+ flush();
9912
+ return out;
9913
+ }, [children, dim, highlight, theme]);
9914
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: elements });
9915
+ }
9916
+
8891
9917
  // src/react-ink/utils/tool-display.ts
8892
9918
  import { homedir as homedir2 } from "node:os";
9919
+
9920
+ // src/react-ink/diff/structured.ts
9921
+ import { structuredPatch } from "diff";
9922
+
9923
+ // src/react-ink/diff/word-diff.ts
9924
+ import { diffWordsWithSpace } from "diff";
9925
+ var CHANGE_THRESHOLD = 0.4;
9926
+ function wordDiffLine(oldLine, newLine, side) {
9927
+ const lineText = side === "removed" ? oldLine : newLine;
9928
+ if (oldLine.length === 0 || newLine.length === 0) {
9929
+ return [{ text: lineText, changed: true }];
9930
+ }
9931
+ let changes;
9932
+ try {
9933
+ changes = diffWordsWithSpace(oldLine, newLine);
9934
+ } catch {
9935
+ return [{ text: lineText, changed: true }];
9936
+ }
9937
+ let changedChars = 0;
9938
+ let totalChars = 0;
9939
+ for (const change of changes) {
9940
+ totalChars += change.value.length;
9941
+ if (change.added || change.removed) {
9942
+ changedChars += change.value.length;
9943
+ }
9944
+ }
9945
+ const fraction = totalChars === 0 ? 0 : changedChars / totalChars;
9946
+ if (fraction > CHANGE_THRESHOLD) {
9947
+ return [{ text: lineText, changed: true }];
9948
+ }
9949
+ const spans = [];
9950
+ for (const change of changes) {
9951
+ const belongs = side === "removed" ? !change.added : !change.removed;
9952
+ if (!belongs) {
9953
+ continue;
9954
+ }
9955
+ spans.push({ text: change.value, changed: Boolean(change.added || change.removed) });
9956
+ }
9957
+ return spans.length > 0 ? spans : [{ text: lineText, changed: false }];
9958
+ }
9959
+
9960
+ // src/react-ink/diff/structured.ts
9961
+ var CONTEXT_LINES = 3;
9962
+ function buildStructuredDiff(oldStr, newStr, filePath = "") {
9963
+ if (oldStr === newStr) {
9964
+ return null;
9965
+ }
9966
+ let patch;
9967
+ try {
9968
+ patch = structuredPatch(filePath, filePath, oldStr, newStr, "", "", { context: CONTEXT_LINES });
9969
+ } catch {
9970
+ return null;
9971
+ }
9972
+ const hunks = [];
9973
+ let addedCount = 0;
9974
+ let removedCount = 0;
9975
+ for (const hunk of patch.hunks) {
9976
+ const lines = classifyHunkLines(hunk);
9977
+ for (const line of lines) {
9978
+ if (line.kind === "added") addedCount += 1;
9979
+ else if (line.kind === "removed") removedCount += 1;
9980
+ }
9981
+ if (lines.length > 0) {
9982
+ hunks.push({ oldStart: hunk.oldStart, newStart: hunk.newStart, lines });
9983
+ }
9984
+ }
9985
+ if (hunks.length === 0) {
9986
+ return null;
9987
+ }
9988
+ return { hunks, addedCount, removedCount };
9989
+ }
9990
+ function classifyHunkLines(hunk) {
9991
+ const out = [];
9992
+ let oldNum = hunk.oldStart;
9993
+ let newNum = hunk.newStart;
9994
+ const rawLines = hunk.lines.filter((line) => !line.startsWith("\\"));
9995
+ let removedRun = [];
9996
+ let removedRunStart = -1;
9997
+ const flushPairing = (addedRun2) => {
9998
+ const pairs = Math.min(removedRun.length, addedRun2.length);
9999
+ for (let i = 0; i < pairs; i += 1) {
10000
+ const removed = removedRun[i];
10001
+ const added = addedRun2[i];
10002
+ removed.spans = wordDiffLine(removed.text, added.text, "removed");
10003
+ added.spans = wordDiffLine(removed.text, added.text, "added");
10004
+ }
10005
+ removedRun = [];
10006
+ removedRunStart = -1;
10007
+ };
10008
+ let addedRun = [];
10009
+ for (const raw of rawLines) {
10010
+ const marker = raw[0];
10011
+ const text = raw.slice(1);
10012
+ if (marker === "-") {
10013
+ if (addedRun.length > 0) {
10014
+ addedRun = [];
10015
+ }
10016
+ const line = { kind: "removed", oldLine: oldNum, text };
10017
+ out.push(line);
10018
+ if (removedRunStart === -1) removedRunStart = out.length - 1;
10019
+ removedRun.push(line);
10020
+ oldNum += 1;
10021
+ } else if (marker === "+") {
10022
+ const line = { kind: "added", newLine: newNum, text };
10023
+ out.push(line);
10024
+ addedRun.push(line);
10025
+ newNum += 1;
10026
+ } else {
10027
+ if (removedRun.length > 0 && addedRun.length > 0) {
10028
+ flushPairing(addedRun);
10029
+ }
10030
+ removedRun = [];
10031
+ removedRunStart = -1;
10032
+ addedRun = [];
10033
+ out.push({ kind: "context", oldLine: oldNum, newLine: newNum, text });
10034
+ oldNum += 1;
10035
+ newNum += 1;
10036
+ }
10037
+ }
10038
+ if (removedRun.length > 0 && addedRun.length > 0) {
10039
+ flushPairing(addedRun);
10040
+ }
10041
+ return out;
10042
+ }
10043
+
10044
+ // src/react-ink/utils/tool-display.ts
8893
10045
  function asRecord2(value) {
8894
10046
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
8895
10047
  }
@@ -8972,6 +10124,12 @@ function clipBody(value, maxLines = 8, maxChars = 1400) {
8972
10124
  }
8973
10125
  return previewMultiline(value, { maxLines, maxChars });
8974
10126
  }
10127
+ function highlightedBody(body, filePath, theme) {
10128
+ if (!body || !theme || !filePath || languageFromPath(filePath) === null) {
10129
+ return { body };
10130
+ }
10131
+ return { body: highlightByPath(body, filePath, theme), preformatted: true };
10132
+ }
8975
10133
  function extractDetails(value) {
8976
10134
  return asRecord2(asRecord2(value)?.details);
8977
10135
  }
@@ -9038,7 +10196,7 @@ function fallbackBody(output, fallbackOutputText, showImages = false) {
9038
10196
  return clipBody(fallbackOutputText);
9039
10197
  }
9040
10198
  function describeToolSource(source) {
9041
- const { args, fallbackArgsText, fallbackOutputText, output, showImages = false, toolName } = source;
10199
+ const { args, fallbackArgsText, fallbackOutputText, output, showImages = false, theme, toolName } = source;
9042
10200
  const argsRecord = asRecord2(args);
9043
10201
  const details = extractDetails(output);
9044
10202
  const outputText = toolText(output, showImages);
@@ -9056,10 +10214,12 @@ function describeToolSource(source) {
9056
10214
  summary += `:${start}${end ? `-${end}` : ""}`;
9057
10215
  }
9058
10216
  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";
10217
+ const readBody = hasResult && !containsImage(output) ? highlightedBody(clipBody(outputText, 10, 1800), rawPath, theme) : { body: hasResult ? clipBody(outputText, 10, 1800) : void 0 };
9059
10218
  return {
9060
10219
  title,
9061
10220
  summary: hasResult ? responseSummary : summary,
9062
- body: hasResult ? clipBody(outputText, 10, 1800) : void 0,
10221
+ body: readBody.body,
10222
+ preformatted: readBody.preformatted,
9063
10223
  emptyText: "Reading file..."
9064
10224
  };
9065
10225
  }
@@ -9067,10 +10227,14 @@ function describeToolSource(source) {
9067
10227
  const rawPath = asString(argsRecord?.file_path) ?? asString(argsRecord?.path) ?? "";
9068
10228
  const content = asString(argsRecord?.content);
9069
10229
  const responseSummary = firstMeaningfulLine(outputText) ?? "Wrote file";
10230
+ const writeDiff = !hasResult && content !== void 0 && content.length > 0 ? buildStructuredDiff("", content, rawPath) ?? void 0 : void 0;
10231
+ const writeBody = hasResult ? { body: clipBody(outputText, 8, 1500) } : highlightedBody(clipBody(content, 8, 1500), rawPath, theme);
9070
10232
  return {
9071
10233
  title,
9072
10234
  summary: hasResult ? responseSummary : rawPath ? shortenPath2(rawPath) : void 0,
9073
- body: hasResult ? clipBody(outputText, 8, 1500) : clipBody(content, 8, 1500),
10235
+ body: writeDiff ? void 0 : writeBody.body,
10236
+ preformatted: writeDiff ? void 0 : writeBody.preformatted,
10237
+ diff: writeDiff,
9074
10238
  emptyText: "Preparing file write..."
9075
10239
  };
9076
10240
  }
@@ -9087,10 +10251,15 @@ function describeToolSource(source) {
9087
10251
  400
9088
10252
  ) : void 0;
9089
10253
  const responseSummary = firstMeaningfulLine(outputText) ?? "Applied edit";
10254
+ const editDiff = oldText !== void 0 && newText !== void 0 ? buildStructuredDiff(oldText, newText, rawPath) ?? void 0 : void 0;
10255
+ const editBody = hasResult ? highlightedBody(clipBody(outputText, 8, 1500), rawPath, theme) : { body: replacementPreview };
10256
+ const editChangeSummary = editDiff ? `+${editDiff.addedCount} -${editDiff.removedCount}` : void 0;
9090
10257
  return {
9091
10258
  title,
9092
- summary: hasResult ? responseSummary : rawPath ? shortenPath2(rawPath) : void 0,
9093
- body: hasResult ? clipBody(outputText, 8, 1500) : replacementPreview,
10259
+ summary: hasResult ? editChangeSummary ?? responseSummary : editChangeSummary ?? (rawPath ? shortenPath2(rawPath) : void 0),
10260
+ body: editDiff ? void 0 : editBody.body,
10261
+ preformatted: editDiff ? void 0 : editBody.preformatted,
10262
+ diff: editDiff,
9094
10263
  emptyText: "Applying edit..."
9095
10264
  };
9096
10265
  }
@@ -9187,7 +10356,7 @@ function describeToolCall(toolCall) {
9187
10356
  args: toolCall.arguments
9188
10357
  });
9189
10358
  }
9190
- function describeToolResult(message, showImages, args) {
10359
+ function describeToolResult(message, showImages, args, theme) {
9191
10360
  return describeToolSource({
9192
10361
  args,
9193
10362
  toolName: message.toolName,
@@ -9195,12 +10364,13 @@ function describeToolResult(message, showImages, args) {
9195
10364
  content: message.content,
9196
10365
  details: message.details
9197
10366
  },
9198
- showImages
10367
+ showImages,
10368
+ theme
9199
10369
  });
9200
10370
  }
9201
10371
 
9202
10372
  // src/react-ink/components/ToolEventBlock.tsx
9203
- import stripAnsi3 from "strip-ansi";
10373
+ import stripAnsi5 from "strip-ansi";
9204
10374
  function statusMarker(status) {
9205
10375
  switch (status) {
9206
10376
  case "error":
@@ -9212,11 +10382,11 @@ function statusMarker(status) {
9212
10382
  }
9213
10383
  }
9214
10384
  function plainToolText(text) {
9215
- return stripAnsi3(text);
10385
+ return stripAnsi5(text);
9216
10386
  }
9217
10387
  function splitVisibleLines(text) {
9218
10388
  return (text ?? "").split(/\r?\n/).map((line) => line.trimEnd()).filter((line, index, lines) => {
9219
- if (line.length > 0) {
10389
+ if (stripAnsi5(line).length > 0) {
9220
10390
  return true;
9221
10391
  }
9222
10392
  return index !== 0 && index !== lines.length - 1;
@@ -9247,6 +10417,7 @@ function ToolEventBlock({
9247
10417
  indent = 0,
9248
10418
  marginBottom = 1,
9249
10419
  maxContentLines = 10,
10420
+ preformatted = false,
9250
10421
  showSummaryInline = false,
9251
10422
  showTitle = true,
9252
10423
  status,
@@ -9275,9 +10446,9 @@ function ToolEventBlock({
9275
10446
  Box,
9276
10447
  {
9277
10448
  marginLeft: line.kind === "response" ? showTitle ? 2 : 0 : showTitle ? 4 : 2,
9278
- children: /* @__PURE__ */ jsx(Text, { color: "white", children: plainToolText(line.text) })
10449
+ children: preformatted ? /* @__PURE__ */ jsx(Text, { children: line.text }) : /* @__PURE__ */ jsx(Text, { color: "white", children: plainToolText(line.text) })
9279
10450
  },
9280
- `${line.kind}:${index}:${line.text}`
10451
+ `${line.kind}:${index}:${stripAnsi5(line.text)}`
9281
10452
  )),
9282
10453
  hiddenLineCount > 0 ? /* @__PURE__ */ jsx(Box, { marginLeft: showTitle ? 4 : 2, children: /* @__PURE__ */ jsx(Text, { color: "white", children: plainToolText(`... ${hiddenLineCount} more line(s)`) }) }) : null
9283
10454
  ] });
@@ -9302,17 +10473,100 @@ function ToolCallMessage({ theme, toolCall }) {
9302
10473
  );
9303
10474
  }
9304
10475
 
10476
+ // src/react-ink/diff/Diff.tsx
10477
+ import chalk4 from "chalk";
10478
+ function lineMarker(kind) {
10479
+ switch (kind) {
10480
+ case "added":
10481
+ return "+";
10482
+ case "removed":
10483
+ return "-";
10484
+ default:
10485
+ return " ";
10486
+ }
10487
+ }
10488
+ function gutterWidth(diff) {
10489
+ let max = 1;
10490
+ for (const hunk of diff.hunks) {
10491
+ for (const line of hunk.lines) {
10492
+ const num2 = line.kind === "removed" ? line.oldLine : line.newLine;
10493
+ if (num2 !== void 0) {
10494
+ max = Math.max(max, stringWidth(String(num2)));
10495
+ }
10496
+ }
10497
+ }
10498
+ return max;
10499
+ }
10500
+ function renderLineText(line, theme, bgRole, fgRole) {
10501
+ const paintSpan = (text, changed) => {
10502
+ const fg = theme.role(fgRole, text);
10503
+ return changed ? chalk4.bold(fg) : fg;
10504
+ };
10505
+ let body;
10506
+ if (line.spans && line.spans.length > 0) {
10507
+ body = line.spans.map((span) => paintSpan(span.text, span.changed)).join("");
10508
+ } else {
10509
+ body = theme.role(fgRole, line.text);
10510
+ }
10511
+ return bgRole ? theme.roleBackground(bgRole, body) : body;
10512
+ }
10513
+ function Diff({ diff, theme, indent = 0, marginBottom = 0 }) {
10514
+ const gutter = gutterWidth(diff);
10515
+ const renderLine = (line, key) => {
10516
+ const num2 = line.kind === "removed" ? line.oldLine : line.newLine;
10517
+ const gutterText = (num2 !== void 0 ? String(num2) : "").padStart(gutter);
10518
+ const marker = lineMarker(line.kind);
10519
+ const bgRole = line.kind === "added" ? "diffAddedBg" : line.kind === "removed" ? "diffRemovedBg" : null;
10520
+ const fgRole = line.kind === "added" ? "diffAddedText" : line.kind === "removed" ? "diffRemovedText" : "blockquoteBar";
10521
+ const gutterStyled = theme.dim(`${gutterText} `);
10522
+ const markerStyled = line.kind === "context" ? theme.dim(`${marker} `) : theme.role(fgRole, `${marker} `);
10523
+ const text = renderLineText(line, theme, bgRole, fgRole);
10524
+ return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { children: [
10525
+ gutterStyled,
10526
+ markerStyled,
10527
+ text
10528
+ ] }) }, key);
10529
+ };
10530
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginLeft: indent, marginBottom, children: diff.hunks.map((hunk, hunkIndex) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
10531
+ hunkIndex > 0 ? /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { children: theme.dim("...") }) }) : null,
10532
+ hunk.lines.map((line, lineIndex) => renderLine(line, `${hunkIndex}-${lineIndex}`))
10533
+ ] }, `hunk-${hunkIndex}`)) });
10534
+ }
10535
+
9305
10536
  // src/react-ink/components/messages/ToolResultBlock.tsx
9306
10537
  function ToolResultBlock({ expanded = false, message, nested = false, showImages, theme, toolCall }) {
9307
- const descriptor = describeToolResult(message, showImages, toolCall?.arguments);
10538
+ const descriptor = describeToolResult(message, showImages, toolCall?.arguments, theme);
10539
+ const indent = nested ? 4 : 2;
10540
+ if (descriptor.diff) {
10541
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [
10542
+ /* @__PURE__ */ jsx(
10543
+ ToolEventBlock,
10544
+ {
10545
+ detail: void 0,
10546
+ emptyText: descriptor.emptyText,
10547
+ indent,
10548
+ marginBottom: 0,
10549
+ maxContentLines: 0,
10550
+ showSummaryInline: false,
10551
+ showTitle: !nested,
10552
+ status: message.isError ? "error" : "success",
10553
+ summary: descriptor.summary,
10554
+ theme,
10555
+ title: descriptor.title
10556
+ }
10557
+ ),
10558
+ /* @__PURE__ */ jsx(Diff, { diff: descriptor.diff, theme, indent: indent + 2 })
10559
+ ] });
10560
+ }
9308
10561
  return /* @__PURE__ */ jsx(
9309
10562
  ToolEventBlock,
9310
10563
  {
9311
10564
  detail: descriptor.body,
9312
10565
  emptyText: descriptor.emptyText,
9313
- indent: nested ? 4 : 2,
10566
+ indent,
9314
10567
  marginBottom: 0,
9315
10568
  maxContentLines: expanded ? Number.MAX_SAFE_INTEGER : 10,
10569
+ preformatted: descriptor.preformatted,
9316
10570
  showSummaryInline: false,
9317
10571
  showTitle: !nested,
9318
10572
  status: message.isError ? "error" : "success",
@@ -9337,7 +10591,7 @@ function AssistantMessageView({
9337
10591
  const matchedToolResultIds = /* @__PURE__ */ new Set();
9338
10592
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
9339
10593
  /* @__PURE__ */ jsx(Text, { children: theme.color("accent", `Assistant ${message.provider}/${message.model} ${formatMessageTimestamp(message.timestamp)}`) }),
9340
- parts.text ? /* @__PURE__ */ jsx(Text, { children: parts.text }) : null,
10594
+ parts.text ? /* @__PURE__ */ jsx(Markdown, { theme, children: parts.text }) : null,
9341
10595
  parts.thinking.map((thinking, index) => /* @__PURE__ */ jsx(Text, { children: theme.muted(`[thinking] ${thinking}`) }, index)),
9342
10596
  parts.toolCalls.map((toolCall) => {
9343
10597
  const toolResult = toolResultsByCallId.get(toolCall.id);