@oh-my-pi/pi-coding-agent 3.20.1 → 3.24.0
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/CHANGELOG.md +107 -8
- package/docs/custom-tools.md +3 -3
- package/docs/extensions.md +226 -220
- package/docs/hooks.md +2 -2
- package/docs/sdk.md +50 -53
- package/examples/custom-tools/README.md +2 -17
- package/examples/extensions/README.md +76 -74
- package/examples/extensions/todo.ts +2 -5
- package/examples/hooks/custom-compaction.ts +2 -4
- package/examples/hooks/handoff.ts +1 -1
- package/examples/hooks/qna.ts +1 -1
- package/examples/sdk/02-custom-model.ts +1 -1
- package/examples/sdk/README.md +7 -11
- package/package.json +6 -6
- package/src/cli/args.ts +9 -6
- package/src/cli/file-processor.ts +1 -1
- package/src/cli/list-models.ts +1 -1
- package/src/core/agent-session.ts +16 -5
- package/src/core/auth-storage.ts +1 -1
- package/src/core/compaction/branch-summarization.ts +2 -2
- package/src/core/compaction/compaction.ts +2 -2
- package/src/core/compaction/utils.ts +1 -1
- package/src/core/custom-tools/types.ts +1 -1
- package/src/core/custom-tools/wrapper.ts +0 -1
- package/src/core/extensions/index.ts +1 -6
- package/src/core/extensions/runner.ts +1 -1
- package/src/core/extensions/types.ts +1 -1
- package/src/core/extensions/wrapper.ts +1 -8
- package/src/core/file-mentions.ts +5 -8
- package/src/core/hooks/runner.ts +2 -2
- package/src/core/hooks/types.ts +1 -1
- package/src/core/messages.ts +1 -1
- package/src/core/model-registry.ts +1 -1
- package/src/core/model-resolver.ts +1 -1
- package/src/core/sdk.ts +64 -105
- package/src/core/session-manager.ts +18 -22
- package/src/core/settings-manager.ts +66 -1
- package/src/core/slash-commands.ts +12 -5
- package/src/core/system-prompt.ts +49 -36
- package/src/core/title-generator.ts +2 -2
- package/src/core/tools/ask.ts +98 -4
- package/src/core/tools/bash-interceptor.ts +11 -4
- package/src/core/tools/bash.ts +121 -5
- package/src/core/tools/context.ts +7 -0
- package/src/core/tools/edit-diff.ts +73 -24
- package/src/core/tools/edit.ts +221 -34
- package/src/core/tools/exa/render.ts +4 -16
- package/src/core/tools/find.ts +149 -5
- package/src/core/tools/gemini-image.ts +279 -56
- package/src/core/tools/git.ts +17 -3
- package/src/core/tools/grep.ts +185 -5
- package/src/core/tools/index.test.ts +180 -0
- package/src/core/tools/index.ts +96 -242
- package/src/core/tools/ls.ts +133 -5
- package/src/core/tools/lsp/index.ts +32 -29
- package/src/core/tools/lsp/render.ts +21 -22
- package/src/core/tools/notebook.ts +112 -4
- package/src/core/tools/output.ts +175 -15
- package/src/core/tools/read.ts +127 -25
- package/src/core/tools/render-utils.ts +241 -0
- package/src/core/tools/renderers.ts +40 -828
- package/src/core/tools/review.ts +26 -25
- package/src/core/tools/rulebook.ts +11 -3
- package/src/core/tools/task/agents.ts +28 -7
- package/src/core/tools/task/discovery.ts +0 -6
- package/src/core/tools/task/executor.ts +264 -254
- package/src/core/tools/task/index.ts +48 -208
- package/src/core/tools/task/render.ts +26 -11
- package/src/core/tools/task/types.ts +7 -12
- package/src/core/tools/task/worker-protocol.ts +17 -0
- package/src/core/tools/task/worker.ts +238 -0
- package/src/core/tools/truncate.ts +27 -1
- package/src/core/tools/web-fetch.ts +25 -49
- package/src/core/tools/web-search/index.ts +132 -46
- package/src/core/tools/web-search/providers/anthropic.ts +7 -2
- package/src/core/tools/web-search/providers/exa.ts +2 -1
- package/src/core/tools/web-search/providers/perplexity.ts +6 -1
- package/src/core/tools/web-search/render.ts +6 -4
- package/src/core/tools/web-search/types.ts +13 -0
- package/src/core/tools/write.ts +96 -14
- package/src/core/voice.ts +1 -1
- package/src/discovery/helpers.test.ts +1 -1
- package/src/index.ts +5 -16
- package/src/main.ts +5 -5
- package/src/modes/interactive/components/assistant-message.ts +1 -1
- package/src/modes/interactive/components/custom-message.ts +1 -1
- package/src/modes/interactive/components/extensions/inspector-panel.ts +25 -22
- package/src/modes/interactive/components/extensions/state-manager.ts +12 -0
- package/src/modes/interactive/components/footer.ts +1 -1
- package/src/modes/interactive/components/hook-message.ts +1 -1
- package/src/modes/interactive/components/model-selector.ts +1 -1
- package/src/modes/interactive/components/oauth-selector.ts +1 -1
- package/src/modes/interactive/components/settings-defs.ts +49 -0
- package/src/modes/interactive/components/status-line.ts +1 -1
- package/src/modes/interactive/components/tool-execution.ts +93 -538
- package/src/modes/interactive/interactive-mode.ts +19 -7
- package/src/modes/interactive/theme/theme.ts +4 -4
- package/src/modes/print-mode.ts +1 -1
- package/src/modes/rpc/rpc-client.ts +1 -1
- package/src/modes/rpc/rpc-types.ts +1 -1
- package/src/prompts/system-prompt.md +4 -0
- package/src/prompts/task.md +0 -7
- package/src/prompts/tools/gemini-image.md +5 -1
- package/src/prompts/tools/output.md +6 -2
- package/src/prompts/tools/task.md +68 -0
- package/src/prompts/tools/web-fetch.md +1 -0
- package/src/prompts/tools/web-search.md +2 -0
- package/src/utils/image-convert.ts +8 -2
- package/src/utils/image-magick.ts +247 -0
- package/src/utils/image-resize.ts +53 -13
- package/examples/custom-tools/question/index.ts +0 -84
- package/examples/custom-tools/subagent/README.md +0 -172
- package/examples/custom-tools/subagent/agents/planner.md +0 -37
- package/examples/custom-tools/subagent/agents/scout.md +0 -50
- package/examples/custom-tools/subagent/agents/worker.md +0 -24
- package/examples/custom-tools/subagent/agents.ts +0 -156
- package/examples/custom-tools/subagent/commands/implement-and-review.md +0 -10
- package/examples/custom-tools/subagent/commands/implement.md +0 -10
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +0 -9
- package/examples/custom-tools/subagent/index.ts +0 -1002
- package/examples/sdk/05-tools.ts +0 -94
- package/examples/sdk/12-full-control.ts +0 -95
- package/src/prompts/browser.md +0 -71
|
@@ -5,834 +5,46 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
8
|
-
import {
|
|
9
|
-
import { getLanguageFromPath, type Theme } from "../../modes/interactive/theme/theme";
|
|
8
|
+
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
10
9
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
import { renderWebSearchCall, renderWebSearchResult, type WebSearchRenderDetails } from "./web-search/render";
|
|
32
|
-
|
|
33
|
-
// Tree drawing characters
|
|
34
|
-
|
|
35
|
-
interface ToolRenderer<TArgs = any, TDetails = any> {
|
|
36
|
-
renderCall(args: TArgs, theme: Theme): Component;
|
|
37
|
-
renderResult(
|
|
38
|
-
result: { content: Array<{ type: string; text?: string }>; details?: TDetails },
|
|
39
|
-
options: RenderResultOptions,
|
|
10
|
+
import { askToolRenderer } from "./ask";
|
|
11
|
+
import { bashToolRenderer } from "./bash";
|
|
12
|
+
import { editToolRenderer } from "./edit";
|
|
13
|
+
import { findToolRenderer } from "./find";
|
|
14
|
+
import { grepToolRenderer } from "./grep";
|
|
15
|
+
import { lsToolRenderer } from "./ls";
|
|
16
|
+
import { lspToolRenderer } from "./lsp/render";
|
|
17
|
+
import { notebookToolRenderer } from "./notebook";
|
|
18
|
+
import { outputToolRenderer } from "./output";
|
|
19
|
+
import { readToolRenderer } from "./read";
|
|
20
|
+
import { taskToolRenderer } from "./task/render";
|
|
21
|
+
import { webFetchToolRenderer } from "./web-fetch";
|
|
22
|
+
import { webSearchToolRenderer } from "./web-search/render";
|
|
23
|
+
import { writeToolRenderer } from "./write";
|
|
24
|
+
|
|
25
|
+
type ToolRenderer = {
|
|
26
|
+
renderCall: (args: unknown, theme: Theme) => Component;
|
|
27
|
+
renderResult: (
|
|
28
|
+
result: { content: Array<{ type: string; text?: string }>; details?: unknown; isError?: boolean },
|
|
29
|
+
options: RenderResultOptions & { renderContext?: Record<string, unknown> },
|
|
40
30
|
theme: Theme,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const clean = message.replace(/^Error:\s*/, "").trim();
|
|
61
|
-
return new Text(
|
|
62
|
-
`${theme.styledSymbol("status.error", "error")} ${theme.fg("error", `Error: ${clean || "Unknown error"}`)}`,
|
|
63
|
-
0,
|
|
64
|
-
0,
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function renderEmptyMessage(_toolLabel: string, message: string, theme: Theme): Text {
|
|
69
|
-
return new Text(`${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", message)}`, 0, 0);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ============================================================================
|
|
73
|
-
// Grep Renderer
|
|
74
|
-
// ============================================================================
|
|
75
|
-
|
|
76
|
-
interface GrepArgs {
|
|
77
|
-
pattern: string;
|
|
78
|
-
path?: string;
|
|
79
|
-
glob?: string;
|
|
80
|
-
type?: string;
|
|
81
|
-
ignoreCase?: boolean;
|
|
82
|
-
caseSensitive?: boolean;
|
|
83
|
-
literal?: boolean;
|
|
84
|
-
multiline?: boolean;
|
|
85
|
-
context?: number;
|
|
86
|
-
limit?: number;
|
|
87
|
-
outputMode?: string;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const grepRenderer: ToolRenderer<GrepArgs, GrepToolDetails> = {
|
|
91
|
-
renderCall(args, theme) {
|
|
92
|
-
const label = theme.fg("toolTitle", theme.bold("Grep"));
|
|
93
|
-
let text = `${label} ${theme.fg("accent", args.pattern || "?")}`;
|
|
94
|
-
|
|
95
|
-
const meta: string[] = [];
|
|
96
|
-
if (args.path) meta.push(`in ${args.path}`);
|
|
97
|
-
if (args.glob) meta.push(`glob:${args.glob}`);
|
|
98
|
-
if (args.type) meta.push(`type:${args.type}`);
|
|
99
|
-
if (args.outputMode && args.outputMode !== "files_with_matches") meta.push(`mode:${args.outputMode}`);
|
|
100
|
-
if (args.caseSensitive) {
|
|
101
|
-
meta.push("case:sensitive");
|
|
102
|
-
} else if (args.ignoreCase) {
|
|
103
|
-
meta.push("case:insensitive");
|
|
104
|
-
}
|
|
105
|
-
if (args.literal) meta.push("literal");
|
|
106
|
-
if (args.multiline) meta.push("multiline");
|
|
107
|
-
if (args.context !== undefined) meta.push(`context:${args.context}`);
|
|
108
|
-
if (args.limit !== undefined) meta.push(`limit:${args.limit}`);
|
|
109
|
-
|
|
110
|
-
text += formatMeta(meta, theme);
|
|
111
|
-
|
|
112
|
-
return new Text(text, 0, 0);
|
|
113
|
-
},
|
|
114
|
-
|
|
115
|
-
renderResult(result, { expanded }, theme) {
|
|
116
|
-
const label = "Grep";
|
|
117
|
-
const details = result.details;
|
|
118
|
-
|
|
119
|
-
if (details?.error) {
|
|
120
|
-
return renderErrorMessage(label, details.error, theme);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const hasDetailedData = details?.matchCount !== undefined || details?.fileCount !== undefined;
|
|
124
|
-
|
|
125
|
-
if (!hasDetailedData) {
|
|
126
|
-
const textContent = result.content?.find((c) => c.type === "text")?.text;
|
|
127
|
-
if (!textContent || textContent === "No matches found") {
|
|
128
|
-
return renderEmptyMessage(label, "No matches found", theme);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const lines = textContent.split("\n").filter((line) => line.trim() !== "");
|
|
132
|
-
const maxLines = expanded ? lines.length : Math.min(lines.length, COLLAPSED_TEXT_LIMIT);
|
|
133
|
-
const displayLines = lines.slice(0, maxLines);
|
|
134
|
-
const remaining = lines.length - maxLines;
|
|
135
|
-
const hasMore = remaining > 0;
|
|
136
|
-
|
|
137
|
-
const icon = theme.styledSymbol("status.success", "success");
|
|
138
|
-
const summary = formatCount("item", lines.length);
|
|
139
|
-
const expandHint = formatExpandHint(expanded, hasMore, theme);
|
|
140
|
-
let text = `${icon} ${theme.fg("dim", summary)}${expandHint}`;
|
|
141
|
-
|
|
142
|
-
for (let i = 0; i < displayLines.length; i++) {
|
|
143
|
-
const isLast = i === displayLines.length - 1 && remaining === 0;
|
|
144
|
-
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
145
|
-
text += `\n ${theme.fg("dim", branch)} ${theme.fg("toolOutput", displayLines[i])}`;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (remaining > 0) {
|
|
149
|
-
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
150
|
-
"muted",
|
|
151
|
-
formatMoreItems(remaining, "item", theme),
|
|
152
|
-
)}`;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return new Text(text, 0, 0);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const matchCount = details?.matchCount ?? 0;
|
|
159
|
-
const fileCount = details?.fileCount ?? 0;
|
|
160
|
-
const mode = details?.mode ?? "files_with_matches";
|
|
161
|
-
const truncated = details?.truncated ?? details?.truncation?.truncated ?? false;
|
|
162
|
-
const files = details?.files ?? [];
|
|
163
|
-
|
|
164
|
-
if (matchCount === 0) {
|
|
165
|
-
return renderEmptyMessage(label, "No matches found", theme);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const icon = theme.styledSymbol("status.success", "success");
|
|
169
|
-
const summaryParts =
|
|
170
|
-
mode === "files_with_matches"
|
|
171
|
-
? [formatCount("file", fileCount)]
|
|
172
|
-
: [formatCount("match", matchCount), formatCount("file", fileCount)];
|
|
173
|
-
const summaryText = summaryParts.join(theme.sep.dot);
|
|
174
|
-
const scopeLabel = formatScope(details?.scopePath, theme);
|
|
175
|
-
|
|
176
|
-
const fileEntries: Array<{ path: string; count?: number }> = details?.fileMatches?.length
|
|
177
|
-
? details.fileMatches.map((entry) => ({ path: entry.path, count: entry.count }))
|
|
178
|
-
: files.map((path) => ({ path }));
|
|
179
|
-
const maxFiles = expanded ? fileEntries.length : Math.min(fileEntries.length, COLLAPSED_LIST_LIMIT);
|
|
180
|
-
const hasMoreFiles = fileEntries.length > maxFiles;
|
|
181
|
-
const expandHint = formatExpandHint(expanded, hasMoreFiles, theme);
|
|
182
|
-
|
|
183
|
-
let text = `${icon} ${theme.fg("dim", summaryText)}${formatTruncationSuffix(
|
|
184
|
-
truncated,
|
|
185
|
-
theme,
|
|
186
|
-
)}${scopeLabel}${expandHint}`;
|
|
187
|
-
|
|
188
|
-
const truncationReasons: string[] = [];
|
|
189
|
-
if (details?.matchLimitReached) {
|
|
190
|
-
truncationReasons.push(`limit ${details.matchLimitReached} matches`);
|
|
191
|
-
}
|
|
192
|
-
if (details?.headLimitReached) {
|
|
193
|
-
truncationReasons.push(`head limit ${details.headLimitReached}`);
|
|
194
|
-
}
|
|
195
|
-
if (details?.truncation?.truncated) {
|
|
196
|
-
truncationReasons.push("size limit");
|
|
197
|
-
}
|
|
198
|
-
if (details?.linesTruncated) {
|
|
199
|
-
truncationReasons.push("line length");
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const hasTruncation = truncationReasons.length > 0;
|
|
203
|
-
|
|
204
|
-
if (fileEntries.length > 0) {
|
|
205
|
-
for (let i = 0; i < maxFiles; i++) {
|
|
206
|
-
const entry = fileEntries[i];
|
|
207
|
-
const isLast = i === maxFiles - 1 && !hasMoreFiles && !hasTruncation;
|
|
208
|
-
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
209
|
-
const isDir = entry.path.endsWith("/");
|
|
210
|
-
const entryPath = isDir ? entry.path.slice(0, -1) : entry.path;
|
|
211
|
-
const lang = isDir ? undefined : getLanguageFromPath(entryPath);
|
|
212
|
-
const entryIcon = isDir
|
|
213
|
-
? theme.fg("accent", theme.icon.folder)
|
|
214
|
-
: theme.fg("muted", theme.getLangIcon(lang));
|
|
215
|
-
const countLabel =
|
|
216
|
-
entry.count !== undefined
|
|
217
|
-
? ` ${theme.fg("dim", `(${entry.count} match${entry.count !== 1 ? "es" : ""})`)}`
|
|
218
|
-
: "";
|
|
219
|
-
text += `\n ${theme.fg("dim", branch)} ${entryIcon} ${theme.fg("accent", entry.path)}${countLabel}`;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (hasMoreFiles) {
|
|
223
|
-
const moreFilesBranch = hasTruncation ? theme.tree.branch : theme.tree.last;
|
|
224
|
-
text += `\n ${theme.fg("dim", moreFilesBranch)} ${theme.fg(
|
|
225
|
-
"muted",
|
|
226
|
-
formatMoreItems(fileEntries.length - maxFiles, "file", theme),
|
|
227
|
-
)}`;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (hasTruncation) {
|
|
232
|
-
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
233
|
-
"warning",
|
|
234
|
-
`truncated: ${truncationReasons.join(", ")}`,
|
|
235
|
-
)}`;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return new Text(text, 0, 0);
|
|
239
|
-
},
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
// ============================================================================
|
|
243
|
-
// Find Renderer
|
|
244
|
-
// ============================================================================
|
|
245
|
-
|
|
246
|
-
interface FindArgs {
|
|
247
|
-
pattern: string;
|
|
248
|
-
path?: string;
|
|
249
|
-
type?: string;
|
|
250
|
-
hidden?: boolean;
|
|
251
|
-
sortByMtime?: boolean;
|
|
252
|
-
limit?: number;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const findRenderer: ToolRenderer<FindArgs, FindToolDetails> = {
|
|
256
|
-
renderCall(args, theme) {
|
|
257
|
-
const label = theme.fg("toolTitle", theme.bold("Find"));
|
|
258
|
-
let text = `${label} ${theme.fg("accent", args.pattern || "*")}`;
|
|
259
|
-
|
|
260
|
-
const meta: string[] = [];
|
|
261
|
-
if (args.path) meta.push(`in ${args.path}`);
|
|
262
|
-
if (args.type && args.type !== "all") meta.push(`type:${args.type}`);
|
|
263
|
-
if (args.hidden) meta.push("hidden");
|
|
264
|
-
if (args.sortByMtime) meta.push("sort:mtime");
|
|
265
|
-
if (args.limit !== undefined) meta.push(`limit:${args.limit}`);
|
|
266
|
-
|
|
267
|
-
text += formatMeta(meta, theme);
|
|
268
|
-
|
|
269
|
-
return new Text(text, 0, 0);
|
|
270
|
-
},
|
|
271
|
-
|
|
272
|
-
renderResult(result, { expanded }, theme) {
|
|
273
|
-
const label = "Find";
|
|
274
|
-
const details = result.details;
|
|
275
|
-
|
|
276
|
-
if (details?.error) {
|
|
277
|
-
return renderErrorMessage(label, details.error, theme);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const hasDetailedData = details?.fileCount !== undefined;
|
|
281
|
-
const textContent = result.content?.find((c) => c.type === "text")?.text;
|
|
282
|
-
|
|
283
|
-
if (!hasDetailedData) {
|
|
284
|
-
if (!textContent || textContent.includes("No files matching") || textContent.trim() === "") {
|
|
285
|
-
return renderEmptyMessage(label, "No files found", theme);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const lines = textContent.split("\n").filter((l) => l.trim());
|
|
289
|
-
const maxLines = expanded ? lines.length : Math.min(lines.length, COLLAPSED_LIST_LIMIT);
|
|
290
|
-
const displayLines = lines.slice(0, maxLines);
|
|
291
|
-
const remaining = lines.length - maxLines;
|
|
292
|
-
const hasMore = remaining > 0;
|
|
293
|
-
|
|
294
|
-
const icon = theme.styledSymbol("status.success", "success");
|
|
295
|
-
const summary = formatCount("file", lines.length);
|
|
296
|
-
const expandHint = formatExpandHint(expanded, hasMore, theme);
|
|
297
|
-
let text = `${icon} ${theme.fg("dim", summary)}${expandHint}`;
|
|
298
|
-
|
|
299
|
-
for (let i = 0; i < displayLines.length; i++) {
|
|
300
|
-
const isLast = i === displayLines.length - 1 && remaining === 0;
|
|
301
|
-
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
302
|
-
text += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", displayLines[i])}`;
|
|
303
|
-
}
|
|
304
|
-
if (remaining > 0) {
|
|
305
|
-
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
306
|
-
"muted",
|
|
307
|
-
formatMoreItems(remaining, "file", theme),
|
|
308
|
-
)}`;
|
|
309
|
-
}
|
|
310
|
-
return new Text(text, 0, 0);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const fileCount = details?.fileCount ?? 0;
|
|
314
|
-
const truncated = details?.truncated ?? details?.truncation?.truncated ?? false;
|
|
315
|
-
const files = details?.files ?? [];
|
|
316
|
-
|
|
317
|
-
if (fileCount === 0) {
|
|
318
|
-
return renderEmptyMessage(label, "No files found", theme);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const icon = theme.styledSymbol("status.success", "success");
|
|
322
|
-
const summaryText = formatCount("file", fileCount);
|
|
323
|
-
const scopeLabel = formatScope(details?.scopePath, theme);
|
|
324
|
-
const maxFiles = expanded ? files.length : Math.min(files.length, COLLAPSED_LIST_LIMIT);
|
|
325
|
-
const hasMoreFiles = files.length > maxFiles;
|
|
326
|
-
const expandHint = formatExpandHint(expanded, hasMoreFiles, theme);
|
|
327
|
-
|
|
328
|
-
let text = `${icon} ${theme.fg("dim", summaryText)}${formatTruncationSuffix(
|
|
329
|
-
truncated,
|
|
330
|
-
theme,
|
|
331
|
-
)}${scopeLabel}${expandHint}`;
|
|
332
|
-
|
|
333
|
-
const truncationReasons: string[] = [];
|
|
334
|
-
if (details?.resultLimitReached) {
|
|
335
|
-
truncationReasons.push(`limit ${details.resultLimitReached} results`);
|
|
336
|
-
}
|
|
337
|
-
if (details?.truncation?.truncated) {
|
|
338
|
-
truncationReasons.push("size limit");
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
const hasTruncation = truncationReasons.length > 0;
|
|
342
|
-
|
|
343
|
-
if (files.length > 0) {
|
|
344
|
-
for (let i = 0; i < maxFiles; i++) {
|
|
345
|
-
const isLast = i === maxFiles - 1 && !hasMoreFiles && !hasTruncation;
|
|
346
|
-
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
347
|
-
const entry = files[i];
|
|
348
|
-
const isDir = entry.endsWith("/");
|
|
349
|
-
const entryPath = isDir ? entry.slice(0, -1) : entry;
|
|
350
|
-
const lang = isDir ? undefined : getLanguageFromPath(entryPath);
|
|
351
|
-
const entryIcon = isDir
|
|
352
|
-
? theme.fg("accent", theme.icon.folder)
|
|
353
|
-
: theme.fg("muted", theme.getLangIcon(lang));
|
|
354
|
-
text += `\n ${theme.fg("dim", branch)} ${entryIcon} ${theme.fg("accent", entry)}`;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (hasMoreFiles) {
|
|
358
|
-
const moreFilesBranch = hasTruncation ? theme.tree.branch : theme.tree.last;
|
|
359
|
-
text += `\n ${theme.fg("dim", moreFilesBranch)} ${theme.fg(
|
|
360
|
-
"muted",
|
|
361
|
-
formatMoreItems(files.length - maxFiles, "file", theme),
|
|
362
|
-
)}`;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if (hasTruncation) {
|
|
367
|
-
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
368
|
-
"warning",
|
|
369
|
-
`truncated: ${truncationReasons.join(", ")}`,
|
|
370
|
-
)}`;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
return new Text(text, 0, 0);
|
|
374
|
-
},
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
// ============================================================================
|
|
378
|
-
// Notebook Renderer
|
|
379
|
-
// ============================================================================
|
|
380
|
-
|
|
381
|
-
interface NotebookArgs {
|
|
382
|
-
action: string;
|
|
383
|
-
notebookPath: string;
|
|
384
|
-
cellNumber?: number;
|
|
385
|
-
cellType?: string;
|
|
386
|
-
content?: string;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
function normalizeCellLines(lines: string[]): string[] {
|
|
390
|
-
return lines.map((line) => (line.endsWith("\n") ? line.slice(0, -1) : line));
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function renderCellPreview(lines: string[], expanded: boolean, theme: Theme): string {
|
|
394
|
-
const normalized = normalizeCellLines(lines);
|
|
395
|
-
if (normalized.length === 0) {
|
|
396
|
-
return `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", "(empty cell)")}`;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const maxLines = expanded ? normalized.length : Math.min(normalized.length, COLLAPSED_TEXT_LIMIT);
|
|
400
|
-
let text = "";
|
|
401
|
-
|
|
402
|
-
for (let i = 0; i < maxLines; i++) {
|
|
403
|
-
const isLast = i === maxLines - 1 && (expanded || normalized.length <= maxLines);
|
|
404
|
-
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
405
|
-
const line = normalized[i];
|
|
406
|
-
text += `\n ${theme.fg("dim", branch)} ${theme.fg("toolOutput", line)}`;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const remaining = normalized.length - maxLines;
|
|
410
|
-
if (remaining > 0) {
|
|
411
|
-
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", formatMoreItems(remaining, "line", theme))}`;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
return text;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
const notebookRenderer: ToolRenderer<NotebookArgs, NotebookToolDetails> = {
|
|
418
|
-
renderCall(args, theme) {
|
|
419
|
-
const label = theme.fg("toolTitle", theme.bold("Notebook"));
|
|
420
|
-
let text = `${label} ${theme.fg("accent", args.action || "?")}`;
|
|
421
|
-
|
|
422
|
-
const meta: string[] = [];
|
|
423
|
-
meta.push(`in ${args.notebookPath || "?"}`);
|
|
424
|
-
if (args.cellNumber !== undefined) meta.push(`cell:${args.cellNumber}`);
|
|
425
|
-
if (args.cellType) meta.push(`type:${args.cellType}`);
|
|
426
|
-
|
|
427
|
-
text += formatMeta(meta, theme);
|
|
428
|
-
|
|
429
|
-
return new Text(text, 0, 0);
|
|
430
|
-
},
|
|
431
|
-
|
|
432
|
-
renderResult(result, { expanded }, theme) {
|
|
433
|
-
const label = "Notebook";
|
|
434
|
-
const details = result.details;
|
|
435
|
-
|
|
436
|
-
const content = result.content?.[0];
|
|
437
|
-
if (content?.type === "text" && content.text?.startsWith("Error:")) {
|
|
438
|
-
return renderErrorMessage(label, content.text, theme);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const action = details?.action ?? "edit";
|
|
442
|
-
const cellIndex = details?.cellIndex;
|
|
443
|
-
const cellType = details?.cellType;
|
|
444
|
-
const totalCells = details?.totalCells;
|
|
445
|
-
const cellSource = details?.cellSource;
|
|
446
|
-
const lineCount = cellSource?.length;
|
|
447
|
-
const canExpand = cellSource !== undefined && cellSource.length > COLLAPSED_TEXT_LIMIT;
|
|
448
|
-
|
|
449
|
-
const icon = theme.styledSymbol("status.success", "success");
|
|
450
|
-
const actionLabel = action === "insert" ? "Inserted" : action === "delete" ? "Deleted" : "Edited";
|
|
451
|
-
const cellLabel = cellType || "cell";
|
|
452
|
-
const summaryParts = [`${actionLabel} ${cellLabel} at index ${cellIndex ?? "?"}`];
|
|
453
|
-
if (lineCount !== undefined) summaryParts.push(formatCount("line", lineCount));
|
|
454
|
-
if (totalCells !== undefined) summaryParts.push(`${totalCells} total`);
|
|
455
|
-
const summaryText = summaryParts.join(theme.sep.dot);
|
|
456
|
-
|
|
457
|
-
const expandHint = formatExpandHint(expanded, canExpand, theme);
|
|
458
|
-
let text = `${icon} ${theme.fg("dim", summaryText)}${expandHint}`;
|
|
459
|
-
|
|
460
|
-
if (cellSource) {
|
|
461
|
-
text += renderCellPreview(cellSource, expanded, theme);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
return new Text(text, 0, 0);
|
|
465
|
-
},
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
// ============================================================================
|
|
469
|
-
// Ask Renderer
|
|
470
|
-
// ============================================================================
|
|
471
|
-
|
|
472
|
-
interface AskArgs {
|
|
473
|
-
question: string;
|
|
474
|
-
options?: Array<{ label: string }>;
|
|
475
|
-
multi?: boolean;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
const askRenderer: ToolRenderer<AskArgs, AskToolDetails> = {
|
|
479
|
-
renderCall(args, theme) {
|
|
480
|
-
if (!args.question) {
|
|
481
|
-
return renderErrorMessage("Ask", "No question provided", theme);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
const label = theme.fg("toolTitle", theme.bold("Ask"));
|
|
485
|
-
let text = `${label} ${theme.fg("accent", args.question)}`;
|
|
486
|
-
|
|
487
|
-
const meta: string[] = [];
|
|
488
|
-
if (args.multi) meta.push("multi");
|
|
489
|
-
if (args.options?.length) meta.push(`options:${args.options.length}`);
|
|
490
|
-
text += formatMeta(meta, theme);
|
|
491
|
-
|
|
492
|
-
if (args.options?.length) {
|
|
493
|
-
for (let i = 0; i < args.options.length; i++) {
|
|
494
|
-
const opt = args.options[i];
|
|
495
|
-
const isLast = i === args.options.length - 1;
|
|
496
|
-
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
497
|
-
text += `\n ${theme.fg("dim", branch)} ${theme.fg(
|
|
498
|
-
"dim",
|
|
499
|
-
theme.checkbox.unchecked,
|
|
500
|
-
)} ${theme.fg("muted", opt.label)}`;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
return new Text(text, 0, 0);
|
|
505
|
-
},
|
|
506
|
-
|
|
507
|
-
renderResult(result, _opts, theme) {
|
|
508
|
-
const { details } = result;
|
|
509
|
-
if (!details) {
|
|
510
|
-
const txt = result.content[0];
|
|
511
|
-
return new Text(txt?.type === "text" && txt.text ? txt.text : "", 0, 0);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
const hasSelection = details.customInput || details.selectedOptions.length > 0;
|
|
515
|
-
const statusIcon = hasSelection
|
|
516
|
-
? theme.styledSymbol("status.success", "success")
|
|
517
|
-
: theme.styledSymbol("status.warning", "warning");
|
|
518
|
-
|
|
519
|
-
let text = `${statusIcon} ${theme.fg("accent", details.question)}`;
|
|
520
|
-
|
|
521
|
-
if (details.customInput) {
|
|
522
|
-
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.styledSymbol(
|
|
523
|
-
"status.success",
|
|
524
|
-
"success",
|
|
525
|
-
)} ${theme.fg("toolOutput", details.customInput)}`;
|
|
526
|
-
} else if (details.selectedOptions.length > 0) {
|
|
527
|
-
const selected = details.selectedOptions;
|
|
528
|
-
for (let i = 0; i < selected.length; i++) {
|
|
529
|
-
const isLast = i === selected.length - 1;
|
|
530
|
-
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
531
|
-
text += `\n ${theme.fg("dim", branch)} ${theme.fg(
|
|
532
|
-
"success",
|
|
533
|
-
theme.checkbox.checked,
|
|
534
|
-
)} ${theme.fg("toolOutput", selected[i])}`;
|
|
535
|
-
}
|
|
536
|
-
} else {
|
|
537
|
-
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.styledSymbol(
|
|
538
|
-
"status.warning",
|
|
539
|
-
"warning",
|
|
540
|
-
)} ${theme.fg("warning", "Cancelled")}`;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
return new Text(text, 0, 0);
|
|
544
|
-
},
|
|
545
|
-
};
|
|
546
|
-
|
|
547
|
-
// ============================================================================
|
|
548
|
-
// Export
|
|
549
|
-
// ============================================================================
|
|
550
|
-
|
|
551
|
-
// ============================================================================
|
|
552
|
-
// LSP Renderer
|
|
553
|
-
// ============================================================================
|
|
554
|
-
|
|
555
|
-
interface LspArgs {
|
|
556
|
-
action: string;
|
|
557
|
-
file?: string;
|
|
558
|
-
files?: string[];
|
|
559
|
-
line?: number;
|
|
560
|
-
column?: number;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
const lspRenderer: ToolRenderer<LspArgs, LspToolDetails> = {
|
|
564
|
-
renderCall: renderLspCall,
|
|
565
|
-
renderResult: renderLspResult,
|
|
566
|
-
};
|
|
567
|
-
|
|
568
|
-
// ============================================================================
|
|
569
|
-
// Output Renderer
|
|
570
|
-
// ============================================================================
|
|
571
|
-
|
|
572
|
-
interface OutputArgs {
|
|
573
|
-
ids: string[];
|
|
574
|
-
format?: "raw" | "json" | "stripped";
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
type OutputEntry = OutputToolDetails["outputs"][number];
|
|
578
|
-
|
|
579
|
-
function formatOutputMeta(entry: OutputEntry, theme: Theme): string {
|
|
580
|
-
const metaParts = [formatCount("line", entry.lineCount), formatBytes(entry.charCount)];
|
|
581
|
-
if (entry.provenance) {
|
|
582
|
-
metaParts.push(`agent ${entry.provenance.agent}(${entry.provenance.index})`);
|
|
583
|
-
}
|
|
584
|
-
return theme.fg("dim", metaParts.join(theme.sep.dot));
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
const outputRenderer: ToolRenderer<OutputArgs, OutputToolDetails> = {
|
|
588
|
-
renderCall(args, theme) {
|
|
589
|
-
const ids = args.ids?.join(", ") ?? "?";
|
|
590
|
-
const label = theme.fg("toolTitle", theme.bold("Output"));
|
|
591
|
-
let text = `${label} ${theme.fg("accent", ids)}`;
|
|
592
|
-
|
|
593
|
-
const meta: string[] = [];
|
|
594
|
-
if (args.format && args.format !== "raw") meta.push(`format:${args.format}`);
|
|
595
|
-
text += formatMeta(meta, theme);
|
|
596
|
-
|
|
597
|
-
return new Text(text, 0, 0);
|
|
598
|
-
},
|
|
599
|
-
|
|
600
|
-
renderResult(result, { expanded }, theme) {
|
|
601
|
-
const label = "Output";
|
|
602
|
-
const details = result.details;
|
|
603
|
-
|
|
604
|
-
if (details?.notFound?.length) {
|
|
605
|
-
const icon = theme.styledSymbol("status.error", "error");
|
|
606
|
-
let text = `${icon} ${theme.fg("error", `Error: Not found: ${details.notFound.join(", ")}`)}`;
|
|
607
|
-
if (details.availableIds?.length) {
|
|
608
|
-
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
609
|
-
"muted",
|
|
610
|
-
`Available: ${details.availableIds.join(", ")}`,
|
|
611
|
-
)}`;
|
|
612
|
-
} else {
|
|
613
|
-
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
614
|
-
"muted",
|
|
615
|
-
"No outputs available in current session",
|
|
616
|
-
)}`;
|
|
617
|
-
}
|
|
618
|
-
return new Text(text, 0, 0);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
const outputs = details?.outputs ?? [];
|
|
622
|
-
|
|
623
|
-
if (outputs.length === 0) {
|
|
624
|
-
const textContent = result.content?.find((c) => c.type === "text")?.text;
|
|
625
|
-
return renderEmptyMessage(label, textContent || "No outputs", theme);
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
const icon = theme.styledSymbol("status.success", "success");
|
|
629
|
-
const summary = `read ${formatCount("output", outputs.length)}`;
|
|
630
|
-
const previewLimit = expanded ? 3 : 1;
|
|
631
|
-
const maxOutputs = expanded ? outputs.length : Math.min(outputs.length, 5);
|
|
632
|
-
const hasMoreOutputs = outputs.length > maxOutputs;
|
|
633
|
-
const hasMorePreview = outputs.some((o) => (o.previewLines?.length ?? 0) > previewLimit);
|
|
634
|
-
const expandHint = formatExpandHint(expanded, hasMoreOutputs || hasMorePreview, theme);
|
|
635
|
-
let text = `${icon} ${theme.fg("dim", summary)}${expandHint}`;
|
|
636
|
-
|
|
637
|
-
for (let i = 0; i < maxOutputs; i++) {
|
|
638
|
-
const o = outputs[i];
|
|
639
|
-
const isLast = i === maxOutputs - 1 && !hasMoreOutputs;
|
|
640
|
-
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
641
|
-
text += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", o.id)} ${formatOutputMeta(o, theme)}`;
|
|
642
|
-
|
|
643
|
-
const previewLines = o.previewLines ?? [];
|
|
644
|
-
const shownPreview = previewLines.slice(0, previewLimit);
|
|
645
|
-
if (shownPreview.length > 0) {
|
|
646
|
-
const childPrefix = isLast ? " " : ` ${theme.fg("dim", theme.tree.vertical)} `;
|
|
647
|
-
for (const line of shownPreview) {
|
|
648
|
-
const previewText = truncate(line, TRUNCATE_LENGTHS.CONTENT, theme.format.ellipsis);
|
|
649
|
-
text += `\n${childPrefix}${theme.fg("dim", theme.tree.hook)} ${theme.fg(
|
|
650
|
-
"muted",
|
|
651
|
-
"preview:",
|
|
652
|
-
)} ${theme.fg("toolOutput", previewText)}`;
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
if (hasMoreOutputs) {
|
|
658
|
-
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
659
|
-
"muted",
|
|
660
|
-
formatMoreItems(outputs.length - maxOutputs, "output", theme),
|
|
661
|
-
)}`;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
return new Text(text, 0, 0);
|
|
665
|
-
},
|
|
666
|
-
};
|
|
667
|
-
|
|
668
|
-
// ============================================================================
|
|
669
|
-
// Task Renderer
|
|
670
|
-
// ============================================================================
|
|
671
|
-
|
|
672
|
-
const taskRenderer: ToolRenderer<any, TaskToolDetails> = {
|
|
673
|
-
renderCall: renderTaskCall,
|
|
674
|
-
renderResult: renderTaskResult,
|
|
675
|
-
};
|
|
676
|
-
|
|
677
|
-
// ============================================================================
|
|
678
|
-
// Ls Renderer
|
|
679
|
-
// ============================================================================
|
|
680
|
-
|
|
681
|
-
interface LsArgs {
|
|
682
|
-
path?: string;
|
|
683
|
-
limit?: number;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
const lsRenderer: ToolRenderer<LsArgs, LsToolDetails> = {
|
|
687
|
-
renderCall(args, theme) {
|
|
688
|
-
const label = theme.fg("toolTitle", theme.bold("Ls"));
|
|
689
|
-
let text = `${label} ${theme.fg("accent", args.path || ".")}`;
|
|
690
|
-
|
|
691
|
-
const meta: string[] = [];
|
|
692
|
-
if (args.limit !== undefined) meta.push(`limit:${args.limit}`);
|
|
693
|
-
text += formatMeta(meta, theme);
|
|
694
|
-
|
|
695
|
-
return new Text(text, 0, 0);
|
|
696
|
-
},
|
|
697
|
-
|
|
698
|
-
renderResult(result, { expanded }, theme) {
|
|
699
|
-
const label = "Ls";
|
|
700
|
-
const details = result.details;
|
|
701
|
-
const textContent = result.content?.find((c) => c.type === "text")?.text ?? "";
|
|
702
|
-
|
|
703
|
-
if (
|
|
704
|
-
(!textContent || textContent.trim() === "" || textContent.trim() === "(empty directory)") &&
|
|
705
|
-
(!details?.entries || details.entries.length === 0)
|
|
706
|
-
) {
|
|
707
|
-
return renderEmptyMessage(label, "Empty directory", theme);
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
let entries: string[] = details?.entries ? [...details.entries] : [];
|
|
711
|
-
if (entries.length === 0) {
|
|
712
|
-
const rawLines = textContent.split("\n").filter((l: string) => l.trim());
|
|
713
|
-
entries = rawLines.filter((line) => !/^\[.*\]$/.test(line.trim()));
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
if (entries.length === 0) {
|
|
717
|
-
return renderEmptyMessage(label, "Empty directory", theme);
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
let dirCount = details?.dirCount;
|
|
721
|
-
let fileCount = details?.fileCount;
|
|
722
|
-
if (dirCount === undefined || fileCount === undefined) {
|
|
723
|
-
dirCount = 0;
|
|
724
|
-
fileCount = 0;
|
|
725
|
-
for (const entry of entries) {
|
|
726
|
-
if (entry.endsWith("/")) {
|
|
727
|
-
dirCount += 1;
|
|
728
|
-
} else {
|
|
729
|
-
fileCount += 1;
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
const truncated = Boolean(details?.truncation?.truncated || details?.entryLimitReached);
|
|
735
|
-
const icon = truncated
|
|
736
|
-
? theme.styledSymbol("status.warning", "warning")
|
|
737
|
-
: theme.styledSymbol("status.success", "success");
|
|
738
|
-
|
|
739
|
-
const summaryText = [formatCount("dir", dirCount ?? 0), formatCount("file", fileCount ?? 0)].join(theme.sep.dot);
|
|
740
|
-
const maxEntries = expanded ? entries.length : Math.min(entries.length, COLLAPSED_LIST_LIMIT);
|
|
741
|
-
const hasMoreEntries = entries.length > maxEntries;
|
|
742
|
-
const expandHint = formatExpandHint(expanded, hasMoreEntries, theme);
|
|
743
|
-
|
|
744
|
-
let text = `${icon} ${theme.fg("dim", summaryText)}${formatTruncationSuffix(truncated, theme)}${expandHint}`;
|
|
745
|
-
|
|
746
|
-
const truncationReasons: string[] = [];
|
|
747
|
-
if (details?.entryLimitReached) {
|
|
748
|
-
truncationReasons.push(`entry limit ${details.entryLimitReached}`);
|
|
749
|
-
}
|
|
750
|
-
if (details?.truncation?.truncated) {
|
|
751
|
-
truncationReasons.push(`output cap ${formatBytes(details.truncation.maxBytes)}`);
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
const hasTruncation = truncationReasons.length > 0;
|
|
755
|
-
|
|
756
|
-
for (let i = 0; i < maxEntries; i++) {
|
|
757
|
-
const entry = entries[i];
|
|
758
|
-
const isLast = i === maxEntries - 1 && !hasMoreEntries && !hasTruncation;
|
|
759
|
-
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
760
|
-
const isDir = entry.endsWith("/");
|
|
761
|
-
const entryPath = isDir ? entry.slice(0, -1) : entry;
|
|
762
|
-
const lang = isDir ? undefined : getLanguageFromPath(entryPath);
|
|
763
|
-
const entryIcon = isDir ? theme.fg("accent", theme.icon.folder) : theme.fg("muted", theme.getLangIcon(lang));
|
|
764
|
-
const entryColor = isDir ? "accent" : "toolOutput";
|
|
765
|
-
text += `\n ${theme.fg("dim", branch)} ${entryIcon} ${theme.fg(entryColor, entry)}`;
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
if (hasMoreEntries) {
|
|
769
|
-
const moreEntriesBranch = hasTruncation ? theme.tree.branch : theme.tree.last;
|
|
770
|
-
text += `\n ${theme.fg("dim", moreEntriesBranch)} ${theme.fg(
|
|
771
|
-
"muted",
|
|
772
|
-
formatMoreItems(entries.length - maxEntries, "entry", theme),
|
|
773
|
-
)}`;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
if (hasTruncation) {
|
|
777
|
-
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
778
|
-
"warning",
|
|
779
|
-
`truncated: ${truncationReasons.join(", ")}`,
|
|
780
|
-
)}`;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
return new Text(text, 0, 0);
|
|
784
|
-
},
|
|
785
|
-
};
|
|
786
|
-
|
|
787
|
-
// ============================================================================
|
|
788
|
-
// Web Fetch Renderer
|
|
789
|
-
// ============================================================================
|
|
790
|
-
|
|
791
|
-
interface WebFetchArgs {
|
|
792
|
-
url: string;
|
|
793
|
-
timeout?: number;
|
|
794
|
-
raw?: boolean;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
const webFetchRenderer: ToolRenderer<WebFetchArgs, WebFetchToolDetails> = {
|
|
798
|
-
renderCall: renderWebFetchCall,
|
|
799
|
-
renderResult: renderWebFetchResult,
|
|
800
|
-
};
|
|
801
|
-
|
|
802
|
-
// ============================================================================
|
|
803
|
-
// Web Search Renderer
|
|
804
|
-
// ============================================================================
|
|
805
|
-
|
|
806
|
-
interface WebSearchArgs {
|
|
807
|
-
query: string;
|
|
808
|
-
provider?: string;
|
|
809
|
-
[key: string]: unknown;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
const webSearchRenderer: ToolRenderer<WebSearchArgs, WebSearchRenderDetails> = {
|
|
813
|
-
renderCall: renderWebSearchCall,
|
|
814
|
-
renderResult: renderWebSearchResult,
|
|
815
|
-
};
|
|
816
|
-
|
|
817
|
-
// ============================================================================
|
|
818
|
-
// Export
|
|
819
|
-
// ============================================================================
|
|
820
|
-
|
|
821
|
-
export const toolRenderers: Record<
|
|
822
|
-
string,
|
|
823
|
-
{
|
|
824
|
-
renderCall: (args: any, theme: Theme) => Component;
|
|
825
|
-
renderResult: (result: any, options: RenderResultOptions, theme: Theme) => Component;
|
|
826
|
-
}
|
|
827
|
-
> = {
|
|
828
|
-
ask: askRenderer,
|
|
829
|
-
grep: grepRenderer,
|
|
830
|
-
find: findRenderer,
|
|
831
|
-
notebook: notebookRenderer,
|
|
832
|
-
ls: lsRenderer,
|
|
833
|
-
lsp: lspRenderer,
|
|
834
|
-
output: outputRenderer,
|
|
835
|
-
task: taskRenderer,
|
|
836
|
-
web_fetch: webFetchRenderer,
|
|
837
|
-
web_search: webSearchRenderer,
|
|
31
|
+
args?: unknown,
|
|
32
|
+
) => Component;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const toolRenderers: Record<string, ToolRenderer> = {
|
|
36
|
+
ask: askToolRenderer as ToolRenderer,
|
|
37
|
+
bash: bashToolRenderer as ToolRenderer,
|
|
38
|
+
edit: editToolRenderer as ToolRenderer,
|
|
39
|
+
find: findToolRenderer as ToolRenderer,
|
|
40
|
+
grep: grepToolRenderer as ToolRenderer,
|
|
41
|
+
ls: lsToolRenderer as ToolRenderer,
|
|
42
|
+
lsp: lspToolRenderer as ToolRenderer,
|
|
43
|
+
notebook: notebookToolRenderer as ToolRenderer,
|
|
44
|
+
output: outputToolRenderer as ToolRenderer,
|
|
45
|
+
read: readToolRenderer as ToolRenderer,
|
|
46
|
+
task: taskToolRenderer as ToolRenderer,
|
|
47
|
+
web_fetch: webFetchToolRenderer as ToolRenderer,
|
|
48
|
+
web_search: webSearchToolRenderer as ToolRenderer,
|
|
49
|
+
write: writeToolRenderer as ToolRenderer,
|
|
838
50
|
};
|