mini-coder 0.5.12 → 0.5.14
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/BENCHMARK.md +15 -316
- package/PROGRESS.md +3 -3
- package/README.md +54 -21
- package/benchmark-baseline.sh +15 -0
- package/bun.lock +265 -90
- package/package.json +8 -7
- package/skills-lock.json +15 -0
- package/src/agent.ts +526 -13
- package/src/assistant-output.ts +73 -0
- package/src/cli.ts +2 -1
- package/src/delegation.ts +238 -0
- package/src/headless.ts +90 -44
- package/src/index.ts +267 -102
- package/src/input.ts +13 -1
- package/src/mcp.ts +609 -0
- package/src/prompt.ts +11 -13
- package/src/session-message.ts +57 -65
- package/src/session.ts +389 -42
- package/src/settings.ts +199 -7
- package/src/skills.ts +12 -3
- package/src/submit.ts +24 -2
- package/src/theme.ts +186 -3
- package/src/tool-common.ts +2 -0
- package/src/tool-delegate.ts +125 -0
- package/src/tool-shell.ts +190 -8
- package/src/tools.ts +335 -6
- package/src/ui/agent.ts +10 -0
- package/src/ui/commands.test.ts +525 -10
- package/src/ui/commands.ts +224 -8
- package/src/ui/conversation.test.ts +252 -27
- package/src/ui/conversation.ts +468 -390
- package/src/ui/help.ts +27 -11
- package/src/ui.ts +230 -75
- package/src/plugins.ts +0 -183
package/src/ui/conversation.ts
CHANGED
|
@@ -17,12 +17,9 @@ import {
|
|
|
17
17
|
normalize,
|
|
18
18
|
relative,
|
|
19
19
|
} from "node:path";
|
|
20
|
-
import {
|
|
21
|
-
SyntaxHighlight,
|
|
22
|
-
type SyntaxHighlightTheme,
|
|
23
|
-
} from "@cel-tui/components";
|
|
20
|
+
import { SyntaxHighlight } from "@cel-tui/components";
|
|
24
21
|
import { HStack, measureContentHeight, Text, VStack } from "@cel-tui/core";
|
|
25
|
-
import type {
|
|
22
|
+
import type { Node } from "@cel-tui/types";
|
|
26
23
|
import type {
|
|
27
24
|
AssistantMessage,
|
|
28
25
|
TextContent,
|
|
@@ -32,11 +29,13 @@ import type {
|
|
|
32
29
|
import type { AppState } from "../index.ts";
|
|
33
30
|
import type { UiMessage } from "../session.ts";
|
|
34
31
|
import { readBoolean, readFiniteNumber, readString } from "../shared.ts";
|
|
35
|
-
import type
|
|
32
|
+
import { getSyntaxHighlightTheme, type Theme } from "../theme.ts";
|
|
36
33
|
import {
|
|
37
34
|
parseGrepResult,
|
|
35
|
+
parseLegacyShellResult,
|
|
38
36
|
parseReadContinuationHint,
|
|
39
37
|
parseReadResult,
|
|
38
|
+
parseShellResultDetails,
|
|
40
39
|
parseTodoSnapshot,
|
|
41
40
|
type TodoItem,
|
|
42
41
|
} from "../tools.ts";
|
|
@@ -54,26 +53,6 @@ const DEFAULT_TOOL_PREVIEW_WIDTH = 80;
|
|
|
54
53
|
/** Horizontal columns consumed by tool-block padding and the left border. */
|
|
55
54
|
const TOOL_BLOCK_CHROME_WIDTH = 4;
|
|
56
55
|
|
|
57
|
-
/** ANSI16 fallback hex values for syntax-highlighter theme overrides. */
|
|
58
|
-
const ANSI_COLOR_HEX: Readonly<Record<Color, string>> = {
|
|
59
|
-
color00: "#000000",
|
|
60
|
-
color01: "#cd3131",
|
|
61
|
-
color02: "#0dbc79",
|
|
62
|
-
color03: "#e5e510",
|
|
63
|
-
color04: "#2472c8",
|
|
64
|
-
color05: "#bc3fbc",
|
|
65
|
-
color06: "#11a8cd",
|
|
66
|
-
color07: "#e5e5e5",
|
|
67
|
-
color08: "#666666",
|
|
68
|
-
color09: "#f14c4c",
|
|
69
|
-
color10: "#23d18b",
|
|
70
|
-
color11: "#f5f543",
|
|
71
|
-
color12: "#3b8eea",
|
|
72
|
-
color13: "#d670d6",
|
|
73
|
-
color14: "#29b8db",
|
|
74
|
-
color15: "#ffffff",
|
|
75
|
-
};
|
|
76
|
-
|
|
77
56
|
/** A pending tool result shown in the streaming tail. */
|
|
78
57
|
export interface PendingToolResult {
|
|
79
58
|
/** Tool call id from the assistant message. */
|
|
@@ -82,6 +61,8 @@ export interface PendingToolResult {
|
|
|
82
61
|
toolName: string;
|
|
83
62
|
/** Progressive or final tool-result content captured so far. */
|
|
84
63
|
content: ToolResultMessage["content"];
|
|
64
|
+
/** Optional structured details preserved on the tool result. */
|
|
65
|
+
details?: ToolResultMessage["details"];
|
|
85
66
|
/** Whether the tool result was an error. */
|
|
86
67
|
isError: boolean;
|
|
87
68
|
}
|
|
@@ -138,16 +119,14 @@ interface ToolCallArgsCache {
|
|
|
138
119
|
entries: Map<string, ToolCallRenderInfo>;
|
|
139
120
|
}
|
|
140
121
|
|
|
141
|
-
interface
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
count: number;
|
|
122
|
+
interface ConversationMessageRenderCacheEntry {
|
|
123
|
+
node: Node | null;
|
|
124
|
+
height: number;
|
|
145
125
|
showReasoning: boolean;
|
|
146
126
|
verbose: boolean;
|
|
147
127
|
previewWidth: number;
|
|
148
128
|
cwd: string | null;
|
|
149
129
|
theme: Theme | null;
|
|
150
|
-
nodes: Node[];
|
|
151
130
|
}
|
|
152
131
|
|
|
153
132
|
type ToolRenderDirection = "->" | "<-";
|
|
@@ -200,9 +179,36 @@ export interface ToolRenderLine {
|
|
|
200
179
|
text: string;
|
|
201
180
|
}
|
|
202
181
|
|
|
182
|
+
/** A rendered conversation item plus its measured height. */
|
|
183
|
+
export interface ConversationLogItem {
|
|
184
|
+
/** Rendered conversation node. */
|
|
185
|
+
node: Node;
|
|
186
|
+
/** Intrinsic item height in rows, excluding conversation gaps. */
|
|
187
|
+
height: number;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Rendered items plus cached cumulative intrinsic heights. */
|
|
191
|
+
export interface ConversationLayoutSnapshot {
|
|
192
|
+
/** Rendered items in on-screen order. */
|
|
193
|
+
items: readonly ConversationLogItem[];
|
|
194
|
+
/** Cumulative intrinsic heights, excluding inter-item gaps. */
|
|
195
|
+
prefixHeights: readonly number[];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
interface ConversationLayoutCache {
|
|
199
|
+
messages: readonly ConversationMessage[] | null;
|
|
200
|
+
count: number;
|
|
201
|
+
showReasoning: boolean;
|
|
202
|
+
verbose: boolean;
|
|
203
|
+
previewWidth: number;
|
|
204
|
+
cwd: string | null;
|
|
205
|
+
theme: Theme | null;
|
|
206
|
+
items: ConversationLogItem[];
|
|
207
|
+
prefixHeights: number[];
|
|
208
|
+
}
|
|
209
|
+
|
|
203
210
|
const EMPTY_STREAMING_CONTENT: AssistantMessage["content"] = [];
|
|
204
211
|
const EMPTY_PENDING_TOOL_RESULTS: readonly PendingToolResult[] = [];
|
|
205
|
-
const EMPTY_RENDER_NODES: Node[] = [];
|
|
206
212
|
const EMPTY_TOOL_RESULT_ARGS: Record<string, unknown> = Object.freeze({});
|
|
207
213
|
|
|
208
214
|
const toolCallArgsCache: ToolCallArgsCache = {
|
|
@@ -211,16 +217,21 @@ const toolCallArgsCache: ToolCallArgsCache = {
|
|
|
211
217
|
entries: new Map(),
|
|
212
218
|
};
|
|
213
219
|
|
|
214
|
-
|
|
220
|
+
let conversationMessageRenderCache = new WeakMap<
|
|
221
|
+
ConversationMessage,
|
|
222
|
+
ConversationMessageRenderCacheEntry
|
|
223
|
+
>();
|
|
224
|
+
|
|
225
|
+
const conversationLayoutCache: ConversationLayoutCache = {
|
|
215
226
|
messages: null,
|
|
216
|
-
startIndex: 0,
|
|
217
227
|
count: 0,
|
|
218
228
|
showReasoning: false,
|
|
219
229
|
verbose: false,
|
|
220
230
|
previewWidth: DEFAULT_TOOL_PREVIEW_WIDTH,
|
|
221
231
|
cwd: null,
|
|
222
232
|
theme: null,
|
|
223
|
-
|
|
233
|
+
items: [],
|
|
234
|
+
prefixHeights: [0],
|
|
224
235
|
};
|
|
225
236
|
|
|
226
237
|
/** Reset cached committed conversation renders. */
|
|
@@ -228,15 +239,16 @@ export function resetConversationRenderCache(): void {
|
|
|
228
239
|
toolCallArgsCache.messages = null;
|
|
229
240
|
toolCallArgsCache.count = 0;
|
|
230
241
|
toolCallArgsCache.entries = new Map();
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
242
|
+
conversationMessageRenderCache = new WeakMap();
|
|
243
|
+
conversationLayoutCache.messages = null;
|
|
244
|
+
conversationLayoutCache.count = 0;
|
|
245
|
+
conversationLayoutCache.showReasoning = false;
|
|
246
|
+
conversationLayoutCache.verbose = false;
|
|
247
|
+
conversationLayoutCache.previewWidth = DEFAULT_TOOL_PREVIEW_WIDTH;
|
|
248
|
+
conversationLayoutCache.cwd = null;
|
|
249
|
+
conversationLayoutCache.theme = null;
|
|
250
|
+
conversationLayoutCache.items = [];
|
|
251
|
+
conversationLayoutCache.prefixHeights = [0];
|
|
240
252
|
}
|
|
241
253
|
|
|
242
254
|
/** Render a user message with a subtle background. */
|
|
@@ -551,12 +563,56 @@ function formatTodoWriteCallSummary(args: Record<string, unknown>): string {
|
|
|
551
563
|
return `Updating todos... ${todoCount} ${todoLabel} updated`;
|
|
552
564
|
}
|
|
553
565
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
566
|
+
function parseShellToolContent(
|
|
567
|
+
content: ToolResultMessage["content"],
|
|
568
|
+
details?: ToolResultMessage["details"],
|
|
569
|
+
) {
|
|
570
|
+
return (
|
|
571
|
+
parseShellResultDetails(details) ??
|
|
572
|
+
parseLegacyShellResult(getToolContentText(content))
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function buildShellResultLines(
|
|
577
|
+
content: ToolResultMessage["content"],
|
|
578
|
+
details?: ToolResultMessage["details"],
|
|
579
|
+
): { bodyLines: ToolRenderLine[]; footerLines?: ToolRenderLine[] } {
|
|
580
|
+
const result = parseShellToolContent(content, details);
|
|
581
|
+
if (!result) {
|
|
582
|
+
return {
|
|
583
|
+
bodyLines: splitToolTextLines(
|
|
584
|
+
normalizeDisplayLineEndings(getToolContentText(content)),
|
|
585
|
+
"text",
|
|
586
|
+
),
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const bodyLines: ToolRenderLine[] = [];
|
|
591
|
+
const stdout = normalizeDisplayLineEndings(result.stdout);
|
|
592
|
+
const stderr = normalizeDisplayLineEndings(result.stderr);
|
|
593
|
+
|
|
594
|
+
if (stdout !== "") {
|
|
595
|
+
bodyLines.push(...splitToolTextLines(stdout, "text"));
|
|
596
|
+
}
|
|
597
|
+
if (stderr !== "") {
|
|
598
|
+
if (bodyLines.length > 0) {
|
|
599
|
+
bodyLines.push({ kind: "text", text: "" });
|
|
600
|
+
}
|
|
601
|
+
bodyLines.push(...splitToolTextLines(stderr, "text"));
|
|
602
|
+
}
|
|
603
|
+
if (bodyLines.length === 0) {
|
|
604
|
+
bodyLines.push({ kind: "summary", text: "(no output)" });
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return {
|
|
608
|
+
bodyLines,
|
|
609
|
+
footerLines: [
|
|
610
|
+
{
|
|
611
|
+
kind: "text",
|
|
612
|
+
text: `exit ${result.exitCode}`,
|
|
613
|
+
},
|
|
614
|
+
],
|
|
615
|
+
};
|
|
560
616
|
}
|
|
561
617
|
|
|
562
618
|
function getToolHeaderName(toolName: string): string {
|
|
@@ -579,137 +635,6 @@ function getToolHeaderColor(toolName: string, theme: Theme) {
|
|
|
579
635
|
}
|
|
580
636
|
}
|
|
581
637
|
|
|
582
|
-
type SyntaxThemeRegistration = Exclude<SyntaxHighlightTheme, string>;
|
|
583
|
-
type SyntaxThemeTokenColor = NonNullable<
|
|
584
|
-
SyntaxThemeRegistration["tokenColors"]
|
|
585
|
-
>[number];
|
|
586
|
-
|
|
587
|
-
const syntaxThemeCache: Record<
|
|
588
|
-
SyntaxThemeVariant,
|
|
589
|
-
WeakMap<Theme, SyntaxThemeRegistration>
|
|
590
|
-
> = {
|
|
591
|
-
markdown: new WeakMap(),
|
|
592
|
-
code: new WeakMap(),
|
|
593
|
-
shell: new WeakMap(),
|
|
594
|
-
};
|
|
595
|
-
|
|
596
|
-
function colorToHex(color: Color | undefined): string | undefined {
|
|
597
|
-
return color ? ANSI_COLOR_HEX[color] : undefined;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
function pushSyntaxTokenColor(
|
|
601
|
-
tokenColors: SyntaxThemeTokenColor[],
|
|
602
|
-
scope: string | readonly string[],
|
|
603
|
-
foreground: Color | undefined,
|
|
604
|
-
fontStyle?: string,
|
|
605
|
-
): void {
|
|
606
|
-
const foregroundHex = colorToHex(foreground);
|
|
607
|
-
if (!foregroundHex && !fontStyle) {
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
tokenColors.push({
|
|
612
|
-
scope,
|
|
613
|
-
settings: {
|
|
614
|
-
...(foregroundHex ? { foreground: foregroundHex } : {}),
|
|
615
|
-
...(fontStyle ? { fontStyle } : {}),
|
|
616
|
-
},
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
function pushShellSyntaxTokenColors(
|
|
621
|
-
tokenColors: SyntaxThemeTokenColor[],
|
|
622
|
-
theme: Theme,
|
|
623
|
-
): void {
|
|
624
|
-
pushSyntaxTokenColor(
|
|
625
|
-
tokenColors,
|
|
626
|
-
["comment", "quote", "doctag"],
|
|
627
|
-
theme.mutedText,
|
|
628
|
-
"italic",
|
|
629
|
-
);
|
|
630
|
-
pushSyntaxTokenColor(
|
|
631
|
-
tokenColors,
|
|
632
|
-
["keyword", "operator"],
|
|
633
|
-
theme.secondaryAccentText,
|
|
634
|
-
);
|
|
635
|
-
pushSyntaxTokenColor(
|
|
636
|
-
tokenColors,
|
|
637
|
-
["function_", "function", "title"],
|
|
638
|
-
theme.accentText,
|
|
639
|
-
);
|
|
640
|
-
pushSyntaxTokenColor(
|
|
641
|
-
tokenColors,
|
|
642
|
-
["built_in", "class_", "class", "inherited__", "type"],
|
|
643
|
-
theme.accentText,
|
|
644
|
-
);
|
|
645
|
-
pushSyntaxTokenColor(
|
|
646
|
-
tokenColors,
|
|
647
|
-
["escape", "literal", "number", "symbol"],
|
|
648
|
-
theme.secondaryAccentText ?? theme.accentText,
|
|
649
|
-
);
|
|
650
|
-
pushSyntaxTokenColor(tokenColors, ["code", "string"], theme.diffAdded);
|
|
651
|
-
pushSyntaxTokenColor(tokenColors, "regexp", theme.diffRemoved);
|
|
652
|
-
pushSyntaxTokenColor(
|
|
653
|
-
tokenColors,
|
|
654
|
-
["attr", "attribute", "params", "property", "selector-attr"],
|
|
655
|
-
theme.accentText,
|
|
656
|
-
);
|
|
657
|
-
pushSyntaxTokenColor(
|
|
658
|
-
tokenColors,
|
|
659
|
-
[
|
|
660
|
-
"name",
|
|
661
|
-
"tag",
|
|
662
|
-
"selector-class",
|
|
663
|
-
"selector-id",
|
|
664
|
-
"selector-pseudo",
|
|
665
|
-
"selector-tag",
|
|
666
|
-
],
|
|
667
|
-
theme.accentText,
|
|
668
|
-
);
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
function pushMarkdownSyntaxTokenColors(
|
|
672
|
-
tokenColors: SyntaxThemeTokenColor[],
|
|
673
|
-
theme: Theme,
|
|
674
|
-
): void {
|
|
675
|
-
pushSyntaxTokenColor(tokenColors, "quote", theme.mutedText, "italic");
|
|
676
|
-
pushSyntaxTokenColor(tokenColors, "section", theme.accentText, "bold");
|
|
677
|
-
pushSyntaxTokenColor(
|
|
678
|
-
tokenColors,
|
|
679
|
-
"bullet",
|
|
680
|
-
theme.secondaryAccentText,
|
|
681
|
-
"bold",
|
|
682
|
-
);
|
|
683
|
-
pushSyntaxTokenColor(tokenColors, ["code", "string"], theme.diffAdded);
|
|
684
|
-
pushSyntaxTokenColor(tokenColors, "link", theme.accentText, "underline");
|
|
685
|
-
pushSyntaxTokenColor(tokenColors, "strong", undefined, "bold");
|
|
686
|
-
pushSyntaxTokenColor(tokenColors, "emphasis", undefined, "italic");
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
function getSyntaxTheme(
|
|
690
|
-
theme: Theme,
|
|
691
|
-
variant: SyntaxThemeVariant,
|
|
692
|
-
): SyntaxThemeRegistration {
|
|
693
|
-
const cache = syntaxThemeCache[variant];
|
|
694
|
-
const cached = cache.get(theme);
|
|
695
|
-
if (cached) {
|
|
696
|
-
return cached;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
const tokenColors: SyntaxThemeTokenColor[] = [];
|
|
700
|
-
if (variant === "markdown") {
|
|
701
|
-
pushMarkdownSyntaxTokenColors(tokenColors, theme);
|
|
702
|
-
} else {
|
|
703
|
-
pushShellSyntaxTokenColors(tokenColors, theme);
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const syntaxTheme: SyntaxThemeRegistration = {
|
|
707
|
-
tokenColors,
|
|
708
|
-
};
|
|
709
|
-
cache.set(theme, syntaxTheme);
|
|
710
|
-
return syntaxTheme;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
638
|
function getHighlightedBodyNode(
|
|
714
639
|
spec: HighlightedBodySpec,
|
|
715
640
|
theme: Theme,
|
|
@@ -720,7 +645,7 @@ function getHighlightedBodyNode(
|
|
|
720
645
|
|
|
721
646
|
try {
|
|
722
647
|
return SyntaxHighlight(spec.text, spec.language, {
|
|
723
|
-
theme:
|
|
648
|
+
theme: getSyntaxHighlightTheme(theme, spec.themeVariant),
|
|
724
649
|
});
|
|
725
650
|
} catch {
|
|
726
651
|
return null;
|
|
@@ -812,36 +737,58 @@ function renderTodoChecklist(todos: readonly TodoItem[], theme: Theme): Node {
|
|
|
812
737
|
);
|
|
813
738
|
}
|
|
814
739
|
|
|
815
|
-
function
|
|
816
|
-
|
|
817
|
-
|
|
740
|
+
function measureToolNodeHeight(node: Node, width: number): number {
|
|
741
|
+
return measureContentHeight(VStack({}, [node]), { width });
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function renderFullToolBody(spec: ToolBlockSpec, theme: Theme): Node | null {
|
|
745
|
+
if (spec.highlightedBody) {
|
|
746
|
+
const highlighted = getHighlightedBodyNode(spec.highlightedBody, theme);
|
|
747
|
+
if (highlighted) {
|
|
748
|
+
return highlighted;
|
|
749
|
+
}
|
|
818
750
|
}
|
|
819
751
|
|
|
820
|
-
|
|
821
|
-
|
|
752
|
+
if (spec.bodyLines.length === 0) {
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
822
755
|
|
|
823
|
-
|
|
824
|
-
return measureContentHeight(VStack({}, [line]), { width });
|
|
756
|
+
return VStack({}, renderToolLines(spec.bodyLines, theme));
|
|
825
757
|
}
|
|
826
758
|
|
|
827
|
-
function
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
):
|
|
832
|
-
|
|
833
|
-
|
|
759
|
+
function renderToolBodyLines(
|
|
760
|
+
spec: ToolBlockSpec,
|
|
761
|
+
lines: readonly ToolRenderLine[],
|
|
762
|
+
theme: Theme,
|
|
763
|
+
): Node | null {
|
|
764
|
+
if (lines.length === 0) {
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
834
767
|
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
768
|
+
if (spec.highlightedBody) {
|
|
769
|
+
const highlighted = getHighlightedBodyNode(
|
|
770
|
+
{
|
|
771
|
+
...spec.highlightedBody,
|
|
772
|
+
text: lines.map((line) => line.text).join("\n"),
|
|
773
|
+
},
|
|
774
|
+
theme,
|
|
775
|
+
);
|
|
776
|
+
if (highlighted) {
|
|
777
|
+
return highlighted;
|
|
839
778
|
}
|
|
840
|
-
remainingRows -= lineHeight;
|
|
841
|
-
visibleLineCount += 1;
|
|
842
779
|
}
|
|
843
780
|
|
|
844
|
-
return
|
|
781
|
+
return VStack({}, renderToolLines(lines, theme));
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function wrapToolPreviewBody(body: Node): Node {
|
|
785
|
+
return VStack(
|
|
786
|
+
{
|
|
787
|
+
height: UI_TOOL_PREVIEW_ROWS,
|
|
788
|
+
justifyContent: "end",
|
|
789
|
+
},
|
|
790
|
+
body.type === "vstack" ? [...body.children] : [body],
|
|
791
|
+
);
|
|
845
792
|
}
|
|
846
793
|
|
|
847
794
|
function renderToolHeaderPill(
|
|
@@ -874,93 +821,47 @@ function renderToolHeaderRow(spec: ToolBlockSpec, theme: Theme): Node {
|
|
|
874
821
|
return HStack({}, children);
|
|
875
822
|
}
|
|
876
823
|
|
|
877
|
-
function
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
opts: Pick<ConversationRenderOpts, "previewWidth" | "theme" | "verbose">,
|
|
824
|
+
function renderCompactToolBody(
|
|
825
|
+
spec: ToolBlockSpec,
|
|
826
|
+
opts: Pick<ConversationRenderOpts, "previewWidth" | "theme">,
|
|
881
827
|
): { body: Node | null; summary?: ToolRenderLine } {
|
|
882
|
-
if (
|
|
828
|
+
if (spec.bodyLines.length === 0) {
|
|
883
829
|
return { body: null };
|
|
884
830
|
}
|
|
885
831
|
|
|
886
|
-
if (opts.verbose || !previewBody) {
|
|
887
|
-
return { body: VStack({}, [...lines]) };
|
|
888
|
-
}
|
|
889
|
-
|
|
890
832
|
const bodyWidth = getToolBodyWidth(opts.previewWidth);
|
|
891
|
-
|
|
892
|
-
if (totalHeight <= UI_TOOL_PREVIEW_ROWS) {
|
|
893
|
-
return { body: VStack({}, [...lines]) };
|
|
894
|
-
}
|
|
833
|
+
let previewStart = Math.max(0, spec.bodyLines.length - UI_TOOL_PREVIEW_ROWS);
|
|
895
834
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
const hiddenLineCount = Math.max(0, lines.length - visibleLineCount);
|
|
903
|
-
|
|
904
|
-
return {
|
|
905
|
-
body: VStack(
|
|
906
|
-
{
|
|
907
|
-
height: UI_TOOL_PREVIEW_ROWS,
|
|
908
|
-
justifyContent: "end",
|
|
909
|
-
},
|
|
910
|
-
[...previewLines],
|
|
911
|
-
),
|
|
912
|
-
summary:
|
|
913
|
-
hiddenLineCount > 0
|
|
914
|
-
? {
|
|
915
|
-
kind: "summary",
|
|
916
|
-
text: `And ${hiddenLineCount} lines more`,
|
|
917
|
-
}
|
|
918
|
-
: undefined,
|
|
919
|
-
};
|
|
920
|
-
}
|
|
835
|
+
for (;;) {
|
|
836
|
+
const previewLines = spec.bodyLines.slice(previewStart);
|
|
837
|
+
const previewBody = renderToolBodyLines(spec, previewLines, opts.theme);
|
|
838
|
+
if (!previewBody) {
|
|
839
|
+
return { body: null };
|
|
840
|
+
}
|
|
921
841
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
)
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
842
|
+
const previewHeight = measureToolNodeHeight(previewBody, bodyWidth);
|
|
843
|
+
if (
|
|
844
|
+
previewHeight <= UI_TOOL_PREVIEW_ROWS ||
|
|
845
|
+
previewStart >= spec.bodyLines.length - 1
|
|
846
|
+
) {
|
|
847
|
+
const hiddenLineCount = previewStart;
|
|
848
|
+
return {
|
|
849
|
+
body:
|
|
850
|
+
hiddenLineCount > 0 || previewHeight > UI_TOOL_PREVIEW_ROWS
|
|
851
|
+
? wrapToolPreviewBody(previewBody)
|
|
852
|
+
: previewBody,
|
|
853
|
+
summary:
|
|
854
|
+
hiddenLineCount > 0
|
|
855
|
+
? {
|
|
856
|
+
kind: "summary",
|
|
857
|
+
text: `And ${hiddenLineCount} lines more`,
|
|
858
|
+
}
|
|
859
|
+
: undefined,
|
|
860
|
+
};
|
|
861
|
+
}
|
|
930
862
|
|
|
931
|
-
|
|
932
|
-
const totalHeight = measureToolNodeHeight(highlighted, bodyWidth);
|
|
933
|
-
if (totalHeight <= UI_TOOL_PREVIEW_ROWS || highlighted.type !== "vstack") {
|
|
934
|
-
return { body: highlighted };
|
|
863
|
+
previewStart += 1;
|
|
935
864
|
}
|
|
936
|
-
|
|
937
|
-
const visibleLineCount = countVisibleTailNodes(
|
|
938
|
-
highlighted.children,
|
|
939
|
-
bodyWidth,
|
|
940
|
-
UI_TOOL_PREVIEW_ROWS,
|
|
941
|
-
);
|
|
942
|
-
const previewLines = highlighted.children.slice(-visibleLineCount);
|
|
943
|
-
const hiddenLineCount = Math.max(
|
|
944
|
-
0,
|
|
945
|
-
highlighted.children.length - visibleLineCount,
|
|
946
|
-
);
|
|
947
|
-
|
|
948
|
-
return {
|
|
949
|
-
body: VStack(
|
|
950
|
-
{
|
|
951
|
-
height: UI_TOOL_PREVIEW_ROWS,
|
|
952
|
-
justifyContent: "end",
|
|
953
|
-
},
|
|
954
|
-
[...previewLines],
|
|
955
|
-
),
|
|
956
|
-
summary:
|
|
957
|
-
hiddenLineCount > 0
|
|
958
|
-
? {
|
|
959
|
-
kind: "summary",
|
|
960
|
-
text: `And ${hiddenLineCount} lines more`,
|
|
961
|
-
}
|
|
962
|
-
: undefined,
|
|
963
|
-
};
|
|
964
865
|
}
|
|
965
866
|
|
|
966
867
|
function renderToolBody(
|
|
@@ -971,25 +872,13 @@ function renderToolBody(
|
|
|
971
872
|
return { body: spec.bodyNode };
|
|
972
873
|
}
|
|
973
874
|
|
|
974
|
-
if (spec.
|
|
975
|
-
|
|
976
|
-
spec.
|
|
977
|
-
|
|
978
|
-
);
|
|
979
|
-
if (highlighted) {
|
|
980
|
-
return renderToolBodyFromHighlightedNode(
|
|
981
|
-
highlighted,
|
|
982
|
-
spec.previewBody,
|
|
983
|
-
opts,
|
|
984
|
-
);
|
|
985
|
-
}
|
|
875
|
+
if (opts.verbose || !spec.previewBody) {
|
|
876
|
+
return {
|
|
877
|
+
body: renderFullToolBody(spec, opts.theme),
|
|
878
|
+
};
|
|
986
879
|
}
|
|
987
880
|
|
|
988
|
-
return
|
|
989
|
-
renderToolLines(spec.bodyLines, opts.theme),
|
|
990
|
-
spec.previewBody,
|
|
991
|
-
opts,
|
|
992
|
-
);
|
|
881
|
+
return renderCompactToolBody(spec, opts);
|
|
993
882
|
}
|
|
994
883
|
|
|
995
884
|
/** Render a tool block with a left border and compact header pill. */
|
|
@@ -1169,6 +1058,10 @@ function buildReadImageToolCallSpec(
|
|
|
1169
1058
|
};
|
|
1170
1059
|
}
|
|
1171
1060
|
|
|
1061
|
+
function isMcpToolName(toolName: string): boolean {
|
|
1062
|
+
return toolName.includes("__");
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1172
1065
|
function buildGenericToolCallSpec(
|
|
1173
1066
|
toolName: string,
|
|
1174
1067
|
args: Record<string, unknown>,
|
|
@@ -1178,7 +1071,7 @@ function buildGenericToolCallSpec(
|
|
|
1178
1071
|
toolName,
|
|
1179
1072
|
direction: "->",
|
|
1180
1073
|
bodyLines: text && text !== "{}" ? splitToolTextLines(text, "text") : [],
|
|
1181
|
-
previewBody:
|
|
1074
|
+
previewBody: isMcpToolName(toolName),
|
|
1182
1075
|
};
|
|
1183
1076
|
}
|
|
1184
1077
|
|
|
@@ -1219,14 +1112,16 @@ function renderToolCall(
|
|
|
1219
1112
|
|
|
1220
1113
|
function buildShellToolResultSpec(
|
|
1221
1114
|
content: ToolResultMessage["content"],
|
|
1115
|
+
details?: ToolResultMessage["details"],
|
|
1222
1116
|
): ToolBlockSpec {
|
|
1117
|
+
const shellResult = buildShellResultLines(content, details);
|
|
1223
1118
|
return {
|
|
1224
1119
|
toolName: "shell",
|
|
1225
1120
|
direction: "<-",
|
|
1226
|
-
bodyLines:
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1121
|
+
bodyLines: shellResult.bodyLines,
|
|
1122
|
+
...(shellResult.footerLines
|
|
1123
|
+
? { footerLines: shellResult.footerLines }
|
|
1124
|
+
: {}),
|
|
1230
1125
|
previewBody: true,
|
|
1231
1126
|
};
|
|
1232
1127
|
}
|
|
@@ -1414,7 +1309,7 @@ function buildGenericToolResultSpec(
|
|
|
1414
1309
|
toolName,
|
|
1415
1310
|
direction: "<-",
|
|
1416
1311
|
bodyLines: splitToolTextLines(output, isError ? "error" : "text"),
|
|
1417
|
-
previewBody:
|
|
1312
|
+
previewBody: isMcpToolName(toolName),
|
|
1418
1313
|
};
|
|
1419
1314
|
}
|
|
1420
1315
|
|
|
@@ -1427,9 +1322,10 @@ function renderToolResultContent(
|
|
|
1427
1322
|
ConversationRenderOpts,
|
|
1428
1323
|
"previewWidth" | "verbose" | "theme" | "cwd"
|
|
1429
1324
|
>,
|
|
1325
|
+
details?: ToolResultMessage["details"],
|
|
1430
1326
|
): Node {
|
|
1431
1327
|
if (toolName === "shell") {
|
|
1432
|
-
return renderToolBlock(buildShellToolResultSpec(content), opts);
|
|
1328
|
+
return renderToolBlock(buildShellToolResultSpec(content, details), opts);
|
|
1433
1329
|
}
|
|
1434
1330
|
if (toolName === "read") {
|
|
1435
1331
|
return renderToolBlock(
|
|
@@ -1476,6 +1372,7 @@ function renderToolResultContent(
|
|
|
1476
1372
|
* @param resultText - Text content from the tool result message.
|
|
1477
1373
|
* @param isError - Whether the tool execution failed.
|
|
1478
1374
|
* @param opts - Shared conversation render options.
|
|
1375
|
+
* @param details - Optional structured tool-result details.
|
|
1479
1376
|
* @returns The rendered tool block node.
|
|
1480
1377
|
*/
|
|
1481
1378
|
export function renderToolResult(
|
|
@@ -1484,12 +1381,20 @@ export function renderToolResult(
|
|
|
1484
1381
|
resultText: string,
|
|
1485
1382
|
isError: boolean,
|
|
1486
1383
|
opts: ConversationRenderOpts,
|
|
1384
|
+
details?: ToolResultMessage["details"],
|
|
1487
1385
|
): Node {
|
|
1488
1386
|
const content: ToolResultMessage["content"] = resultText
|
|
1489
1387
|
? [{ type: "text", text: resultText }]
|
|
1490
1388
|
: [];
|
|
1491
1389
|
|
|
1492
|
-
return renderToolResultContent(
|
|
1390
|
+
return renderToolResultContent(
|
|
1391
|
+
toolName,
|
|
1392
|
+
args,
|
|
1393
|
+
content,
|
|
1394
|
+
isError,
|
|
1395
|
+
opts,
|
|
1396
|
+
details,
|
|
1397
|
+
);
|
|
1493
1398
|
}
|
|
1494
1399
|
|
|
1495
1400
|
function renderUiTodoMessage(
|
|
@@ -1548,13 +1453,6 @@ function renderEmptyConversationBanner(
|
|
|
1548
1453
|
]);
|
|
1549
1454
|
}
|
|
1550
1455
|
|
|
1551
|
-
function pushConversationNode(nodes: Node[], node: Node | null): void {
|
|
1552
|
-
if (!node) {
|
|
1553
|
-
return;
|
|
1554
|
-
}
|
|
1555
|
-
nodes.push(node);
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
1456
|
function rememberToolCallArgs(
|
|
1559
1457
|
message: AssistantMessage,
|
|
1560
1458
|
toolCallArgs: Map<string, ToolCallRenderInfo>,
|
|
@@ -1576,8 +1474,16 @@ function renderToolResultMessage(
|
|
|
1576
1474
|
content: ToolResultMessage["content"],
|
|
1577
1475
|
isError: boolean,
|
|
1578
1476
|
renderOpts: ConversationRenderOpts,
|
|
1477
|
+
details?: ToolResultMessage["details"],
|
|
1579
1478
|
): Node {
|
|
1580
|
-
return renderToolResultContent(
|
|
1479
|
+
return renderToolResultContent(
|
|
1480
|
+
toolName,
|
|
1481
|
+
args,
|
|
1482
|
+
content,
|
|
1483
|
+
isError,
|
|
1484
|
+
renderOpts,
|
|
1485
|
+
details,
|
|
1486
|
+
);
|
|
1581
1487
|
}
|
|
1582
1488
|
|
|
1583
1489
|
function renderConversationMessage(
|
|
@@ -1604,6 +1510,7 @@ function renderConversationMessage(
|
|
|
1604
1510
|
message.content,
|
|
1605
1511
|
message.isError,
|
|
1606
1512
|
renderOpts,
|
|
1513
|
+
message.details,
|
|
1607
1514
|
);
|
|
1608
1515
|
}
|
|
1609
1516
|
|
|
@@ -1627,60 +1534,150 @@ function cacheToolCallArgs(messages: readonly ConversationMessage[]): void {
|
|
|
1627
1534
|
toolCallArgsCache.count = messages.length;
|
|
1628
1535
|
}
|
|
1629
1536
|
|
|
1630
|
-
function
|
|
1631
|
-
|
|
1632
|
-
|
|
1537
|
+
function canReuseConversationMessageRender(
|
|
1538
|
+
cached: ConversationMessageRenderCacheEntry | undefined,
|
|
1539
|
+
renderOpts: ConversationRenderOpts,
|
|
1633
1540
|
previewWidth: number,
|
|
1634
|
-
):
|
|
1541
|
+
): cached is ConversationMessageRenderCacheEntry {
|
|
1635
1542
|
return (
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
conversationRenderCache.theme === state.theme &&
|
|
1643
|
-
conversationRenderCache.count <= state.messages.length
|
|
1543
|
+
cached !== undefined &&
|
|
1544
|
+
cached.showReasoning === renderOpts.showReasoning &&
|
|
1545
|
+
cached.verbose === renderOpts.verbose &&
|
|
1546
|
+
cached.previewWidth === previewWidth &&
|
|
1547
|
+
cached.cwd === renderOpts.cwd &&
|
|
1548
|
+
cached.theme === renderOpts.theme
|
|
1644
1549
|
);
|
|
1645
1550
|
}
|
|
1646
1551
|
|
|
1647
|
-
function
|
|
1552
|
+
function canReuseCommittedConversationLayout(
|
|
1648
1553
|
state: ConversationLogState,
|
|
1649
1554
|
renderOpts: ConversationRenderOpts,
|
|
1650
|
-
|
|
1555
|
+
previewWidth: number,
|
|
1556
|
+
): boolean {
|
|
1557
|
+
return (
|
|
1558
|
+
conversationLayoutCache.messages === state.messages &&
|
|
1559
|
+
conversationLayoutCache.count <= state.messages.length &&
|
|
1560
|
+
conversationLayoutCache.showReasoning === renderOpts.showReasoning &&
|
|
1561
|
+
conversationLayoutCache.verbose === renderOpts.verbose &&
|
|
1562
|
+
conversationLayoutCache.previewWidth === previewWidth &&
|
|
1563
|
+
conversationLayoutCache.cwd === renderOpts.cwd &&
|
|
1564
|
+
conversationLayoutCache.theme === renderOpts.theme
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
function appendConversationLogItem(
|
|
1569
|
+
items: ConversationLogItem[],
|
|
1570
|
+
prefixHeights: number[],
|
|
1571
|
+
item: ConversationLogItem | null,
|
|
1651
1572
|
): void {
|
|
1573
|
+
if (!item) {
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
items.push(item);
|
|
1578
|
+
prefixHeights.push(prefixHeights[prefixHeights.length - 1]! + item.height);
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
function measureConversationItemHeight(
|
|
1582
|
+
node: Node,
|
|
1583
|
+
previewWidth: number,
|
|
1584
|
+
): number {
|
|
1585
|
+
return measureContentHeight(node, {
|
|
1586
|
+
width: getPreviewWidth(previewWidth),
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
function createConversationLogItem(
|
|
1591
|
+
node: Node | null,
|
|
1592
|
+
previewWidth: number,
|
|
1593
|
+
): ConversationLogItem | null {
|
|
1594
|
+
if (!node) {
|
|
1595
|
+
return null;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
return {
|
|
1599
|
+
node,
|
|
1600
|
+
height: measureConversationItemHeight(node, previewWidth),
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
function getCommittedConversationLogItem(
|
|
1605
|
+
message: ConversationMessage,
|
|
1606
|
+
renderOpts: ConversationRenderOpts,
|
|
1607
|
+
): ConversationLogItem | null {
|
|
1652
1608
|
const previewWidth = getPreviewWidth(renderOpts.previewWidth);
|
|
1653
|
-
|
|
1609
|
+
const cached = conversationMessageRenderCache.get(message);
|
|
1610
|
+
if (canReuseConversationMessageRender(cached, renderOpts, previewWidth)) {
|
|
1611
|
+
if (!cached.node) {
|
|
1612
|
+
return null;
|
|
1613
|
+
}
|
|
1614
|
+
return {
|
|
1615
|
+
node: cached.node,
|
|
1616
|
+
height: cached.height,
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1654
1619
|
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1620
|
+
const node = renderConversationMessage(
|
|
1621
|
+
message,
|
|
1622
|
+
renderOpts,
|
|
1623
|
+
toolCallArgsCache.entries,
|
|
1624
|
+
renderOpts.theme,
|
|
1625
|
+
);
|
|
1626
|
+
const nextEntry: ConversationMessageRenderCacheEntry = {
|
|
1627
|
+
node,
|
|
1628
|
+
height: node ? measureConversationItemHeight(node, previewWidth) : 0,
|
|
1629
|
+
showReasoning: renderOpts.showReasoning,
|
|
1630
|
+
verbose: renderOpts.verbose,
|
|
1631
|
+
previewWidth,
|
|
1632
|
+
cwd: renderOpts.cwd ?? null,
|
|
1633
|
+
theme: renderOpts.theme,
|
|
1634
|
+
};
|
|
1635
|
+
conversationMessageRenderCache.set(message, nextEntry);
|
|
1636
|
+
|
|
1637
|
+
if (!node) {
|
|
1638
|
+
return null;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
return {
|
|
1642
|
+
node,
|
|
1643
|
+
height: nextEntry.height,
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
function getCommittedConversationLayoutSnapshot(
|
|
1648
|
+
state: ConversationLogState,
|
|
1649
|
+
renderOpts: ConversationRenderOpts,
|
|
1650
|
+
): ConversationLayoutSnapshot {
|
|
1651
|
+
const previewWidth = getPreviewWidth(renderOpts.previewWidth);
|
|
1652
|
+
if (!canReuseCommittedConversationLayout(state, renderOpts, previewWidth)) {
|
|
1653
|
+
conversationLayoutCache.messages = state.messages;
|
|
1654
|
+
conversationLayoutCache.count = 0;
|
|
1655
|
+
conversationLayoutCache.showReasoning = renderOpts.showReasoning;
|
|
1656
|
+
conversationLayoutCache.verbose = renderOpts.verbose;
|
|
1657
|
+
conversationLayoutCache.previewWidth = previewWidth;
|
|
1658
|
+
conversationLayoutCache.cwd = renderOpts.cwd ?? null;
|
|
1659
|
+
conversationLayoutCache.theme = renderOpts.theme;
|
|
1660
|
+
conversationLayoutCache.items = [];
|
|
1661
|
+
conversationLayoutCache.prefixHeights = [0];
|
|
1665
1662
|
}
|
|
1666
1663
|
|
|
1667
1664
|
for (
|
|
1668
|
-
let index =
|
|
1665
|
+
let index = conversationLayoutCache.count;
|
|
1669
1666
|
index < state.messages.length;
|
|
1670
1667
|
index++
|
|
1671
1668
|
) {
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
renderOpts,
|
|
1677
|
-
toolCallArgsCache.entries,
|
|
1678
|
-
state.theme,
|
|
1679
|
-
),
|
|
1669
|
+
appendConversationLogItem(
|
|
1670
|
+
conversationLayoutCache.items,
|
|
1671
|
+
conversationLayoutCache.prefixHeights,
|
|
1672
|
+
getCommittedConversationLogItem(state.messages[index]!, renderOpts),
|
|
1680
1673
|
);
|
|
1681
1674
|
}
|
|
1682
1675
|
|
|
1683
|
-
|
|
1676
|
+
conversationLayoutCache.count = state.messages.length;
|
|
1677
|
+
return {
|
|
1678
|
+
items: conversationLayoutCache.items,
|
|
1679
|
+
prefixHeights: conversationLayoutCache.prefixHeights,
|
|
1680
|
+
};
|
|
1684
1681
|
}
|
|
1685
1682
|
|
|
1686
1683
|
function hasStreamingTail(streaming: StreamingConversationState): boolean {
|
|
@@ -1691,21 +1688,78 @@ function hasStreamingTail(streaming: StreamingConversationState): boolean {
|
|
|
1691
1688
|
);
|
|
1692
1689
|
}
|
|
1693
1690
|
|
|
1691
|
+
function appendStreamingConversationLogItems(
|
|
1692
|
+
items: ConversationLogItem[],
|
|
1693
|
+
prefixHeights: number[],
|
|
1694
|
+
streaming: StreamingConversationState,
|
|
1695
|
+
renderOpts: ConversationRenderOpts,
|
|
1696
|
+
previewWidth: number,
|
|
1697
|
+
): void {
|
|
1698
|
+
appendConversationLogItem(
|
|
1699
|
+
items,
|
|
1700
|
+
prefixHeights,
|
|
1701
|
+
createConversationLogItem(
|
|
1702
|
+
renderAssistantMessage(
|
|
1703
|
+
{
|
|
1704
|
+
content: streaming.content,
|
|
1705
|
+
},
|
|
1706
|
+
renderOpts,
|
|
1707
|
+
),
|
|
1708
|
+
previewWidth,
|
|
1709
|
+
),
|
|
1710
|
+
);
|
|
1711
|
+
|
|
1712
|
+
for (const pendingToolResult of streaming.pendingToolResults) {
|
|
1713
|
+
const info = toolCallArgsCache.entries.get(pendingToolResult.toolCallId);
|
|
1714
|
+
appendConversationLogItem(
|
|
1715
|
+
items,
|
|
1716
|
+
prefixHeights,
|
|
1717
|
+
createConversationLogItem(
|
|
1718
|
+
renderToolResultMessage(
|
|
1719
|
+
info?.name ?? pendingToolResult.toolName,
|
|
1720
|
+
info?.args ?? EMPTY_TOOL_RESULT_ARGS,
|
|
1721
|
+
pendingToolResult.content,
|
|
1722
|
+
pendingToolResult.isError,
|
|
1723
|
+
renderOpts,
|
|
1724
|
+
pendingToolResult.details,
|
|
1725
|
+
),
|
|
1726
|
+
previewWidth,
|
|
1727
|
+
),
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
function buildEmptyConversationBannerSnapshot(
|
|
1733
|
+
state: ConversationLogState,
|
|
1734
|
+
previewWidth: number,
|
|
1735
|
+
): ConversationLayoutSnapshot {
|
|
1736
|
+
const banner = renderEmptyConversationBanner(
|
|
1737
|
+
state.theme,
|
|
1738
|
+
state.versionLabel ?? DEV_VERSION_LABEL,
|
|
1739
|
+
);
|
|
1740
|
+
const item = {
|
|
1741
|
+
node: banner,
|
|
1742
|
+
height: measureConversationItemHeight(banner, previewWidth),
|
|
1743
|
+
};
|
|
1744
|
+
return {
|
|
1745
|
+
items: [item],
|
|
1746
|
+
prefixHeights: [0, item.height],
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1694
1750
|
/**
|
|
1695
|
-
* Build
|
|
1751
|
+
* Build rendered conversation items plus cached cumulative intrinsic heights.
|
|
1696
1752
|
*
|
|
1697
1753
|
* @param state - Conversation rendering state.
|
|
1698
1754
|
* @param streaming - Current in-progress assistant tail, if any.
|
|
1699
|
-
* @param startIndex - Index of the first committed message to render.
|
|
1700
1755
|
* @param previewWidth - Available terminal width for width-aware tool previews.
|
|
1701
|
-
* @returns
|
|
1756
|
+
* @returns Rendered items plus cumulative heights in on-screen order.
|
|
1702
1757
|
*/
|
|
1703
|
-
export function
|
|
1758
|
+
export function buildConversationLayoutSnapshot(
|
|
1704
1759
|
state: ConversationLogState,
|
|
1705
1760
|
streaming: StreamingConversationState,
|
|
1706
|
-
startIndex = 0,
|
|
1707
1761
|
previewWidth = DEFAULT_TOOL_PREVIEW_WIDTH,
|
|
1708
|
-
):
|
|
1762
|
+
): ConversationLayoutSnapshot {
|
|
1709
1763
|
const renderOpts: ConversationRenderOpts = {
|
|
1710
1764
|
showReasoning: state.showReasoning,
|
|
1711
1765
|
verbose: state.verbose,
|
|
@@ -1714,45 +1768,69 @@ export function buildConversationLogNodes(
|
|
|
1714
1768
|
previewWidth,
|
|
1715
1769
|
};
|
|
1716
1770
|
|
|
1717
|
-
|
|
1718
|
-
return [
|
|
1719
|
-
renderEmptyConversationBanner(
|
|
1720
|
-
state.theme,
|
|
1721
|
-
state.versionLabel ?? DEV_VERSION_LABEL,
|
|
1722
|
-
),
|
|
1723
|
-
];
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
cacheCommittedConversation(state, renderOpts, startIndex);
|
|
1771
|
+
cacheToolCallArgs(state.messages);
|
|
1727
1772
|
|
|
1728
|
-
|
|
1729
|
-
|
|
1773
|
+
const committedSnapshot = getCommittedConversationLayoutSnapshot(
|
|
1774
|
+
state,
|
|
1775
|
+
renderOpts,
|
|
1776
|
+
);
|
|
1777
|
+
const streamingTailVisible = hasStreamingTail(streaming);
|
|
1778
|
+
if (!streamingTailVisible) {
|
|
1779
|
+
if (committedSnapshot.items.length > 0 || state.messages.length > 0) {
|
|
1780
|
+
return committedSnapshot;
|
|
1781
|
+
}
|
|
1782
|
+
return buildEmptyConversationBannerSnapshot(state, previewWidth);
|
|
1730
1783
|
}
|
|
1731
1784
|
|
|
1732
|
-
const
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
),
|
|
1785
|
+
const items = [...committedSnapshot.items];
|
|
1786
|
+
const prefixHeights = [...committedSnapshot.prefixHeights];
|
|
1787
|
+
appendStreamingConversationLogItems(
|
|
1788
|
+
items,
|
|
1789
|
+
prefixHeights,
|
|
1790
|
+
streaming,
|
|
1791
|
+
renderOpts,
|
|
1792
|
+
previewWidth,
|
|
1741
1793
|
);
|
|
1794
|
+
return {
|
|
1795
|
+
items,
|
|
1796
|
+
prefixHeights,
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1742
1799
|
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1800
|
+
/**
|
|
1801
|
+
* Build the rendered conversation items with cached per-message nodes/heights.
|
|
1802
|
+
*
|
|
1803
|
+
* @param state - Conversation rendering state.
|
|
1804
|
+
* @param streaming - Current in-progress assistant tail, if any.
|
|
1805
|
+
* @param previewWidth - Available terminal width for width-aware tool previews.
|
|
1806
|
+
* @returns Rendered items in on-screen order.
|
|
1807
|
+
*/
|
|
1808
|
+
export function buildConversationLogItems(
|
|
1809
|
+
state: ConversationLogState,
|
|
1810
|
+
streaming: StreamingConversationState,
|
|
1811
|
+
previewWidth = DEFAULT_TOOL_PREVIEW_WIDTH,
|
|
1812
|
+
): readonly ConversationLogItem[] {
|
|
1813
|
+
return buildConversationLayoutSnapshot(state, streaming, previewWidth).items;
|
|
1814
|
+
}
|
|
1756
1815
|
|
|
1757
|
-
|
|
1816
|
+
/**
|
|
1817
|
+
* Build the full conversation log as an array of nodes.
|
|
1818
|
+
*
|
|
1819
|
+
* @param state - Conversation rendering state.
|
|
1820
|
+
* @param streaming - Current in-progress assistant tail, if any.
|
|
1821
|
+
* @param startIndex - Index of the first rendered item to include.
|
|
1822
|
+
* @param previewWidth - Available terminal width for width-aware tool previews.
|
|
1823
|
+
* @param endIndex - Exclusive rendered-item end index.
|
|
1824
|
+
* @returns The rendered conversation log nodes.
|
|
1825
|
+
*/
|
|
1826
|
+
export function buildConversationLogNodes(
|
|
1827
|
+
state: ConversationLogState,
|
|
1828
|
+
streaming: StreamingConversationState,
|
|
1829
|
+
startIndex = 0,
|
|
1830
|
+
previewWidth = DEFAULT_TOOL_PREVIEW_WIDTH,
|
|
1831
|
+
endIndex = Number.POSITIVE_INFINITY,
|
|
1832
|
+
): Node[] {
|
|
1833
|
+
return buildConversationLogItems(state, streaming, previewWidth)
|
|
1834
|
+
.slice(startIndex, endIndex)
|
|
1835
|
+
.map((item) => item.node);
|
|
1758
1836
|
}
|