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