@thoth-agents/pi-thoth-theme 0.1.0 → 0.3.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/README.md +51 -10
- package/dist/index.ts +3967 -0
- package/package.json +7 -9
- package/src/index.ts +0 -53
- package/src/input-box/decorate.ts +0 -304
- package/src/input-box/frame.ts +0 -182
- package/src/input-box/gradient.ts +0 -92
- package/src/input-box/state.ts +0 -90
- package/src/render-kit/index.ts +0 -305
- package/src/render-kit/working.ts +0 -25
- package/src/shared/cache.ts +0 -21
- package/src/shared/config.ts +0 -86
- package/src/shared/duration.ts +0 -1
- package/src/shared/icons.ts +0 -89
- package/src/status-line/cost.ts +0 -56
- package/src/status-line/index.ts +0 -242
- package/src/status-line/layout.ts +0 -272
- package/src/status-line/snapshot.ts +0 -11
- package/src/status-line/throughput.ts +0 -61
- package/src/status-line/tokens.ts +0 -51
- package/src/tools/bash.ts +0 -237
- package/src/tools/border.ts +0 -21
- package/src/tools/box.ts +0 -258
- package/src/tools/edit.ts +0 -228
- package/src/tools/file-icons.ts +0 -139
- package/src/tools/find.ts +0 -227
- package/src/tools/frame.ts +0 -166
- package/src/tools/generic.ts +0 -195
- package/src/tools/grep.ts +0 -371
- package/src/tools/index.ts +0 -135
- package/src/tools/ls.ts +0 -231
- package/src/tools/ownership.ts +0 -70
- package/src/tools/powershell.ts +0 -1
- package/src/tools/read.ts +0 -194
- package/src/tools/ticker.ts +0 -101
- package/src/tools/write.ts +0 -188
- package/src/welcome/age.ts +0 -11
- package/src/welcome/index.ts +0 -132
- package/src/welcome/logo.ts +0 -35
- package/src/welcome/render.ts +0 -323
- package/src/welcome/resources.ts +0 -251
package/src/tools/grep.ts
DELETED
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AgentToolResult,
|
|
3
|
-
GrepToolDetails,
|
|
4
|
-
Theme,
|
|
5
|
-
ToolRenderResultOptions,
|
|
6
|
-
} from '@earendil-works/pi-coding-agent';
|
|
7
|
-
import { truncateToWidth } from '@earendil-works/pi-tui';
|
|
8
|
-
import { toolFooter } from '../render-kit/index.ts';
|
|
9
|
-
import type { ThemeConfig } from '../shared/config.ts';
|
|
10
|
-
import { icon as semanticIcon, statusIcon } from '../shared/icons.ts';
|
|
11
|
-
import { getToolBorderTone } from './border.ts';
|
|
12
|
-
import {
|
|
13
|
-
createComponent,
|
|
14
|
-
escapeControlCharacters,
|
|
15
|
-
getRawResultText,
|
|
16
|
-
renderBox,
|
|
17
|
-
splitResultNotice,
|
|
18
|
-
} from './box.ts';
|
|
19
|
-
import { getFileIcon, getToolIcon } from './file-icons.ts';
|
|
20
|
-
import {
|
|
21
|
-
formatDisplayPath,
|
|
22
|
-
hasToolResult,
|
|
23
|
-
isFramedContext,
|
|
24
|
-
renderFrameBottom,
|
|
25
|
-
renderFrameRow,
|
|
26
|
-
renderFrameTop,
|
|
27
|
-
} from './frame.ts';
|
|
28
|
-
import { type ElapsedRenderContext, syncElapsedTicker } from './ticker.ts';
|
|
29
|
-
|
|
30
|
-
interface GrepArgs {
|
|
31
|
-
pattern?: string;
|
|
32
|
-
path?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface GrepContext extends ElapsedRenderContext {
|
|
36
|
-
cwd?: string;
|
|
37
|
-
lastComponent?: { invalidate?: () => void };
|
|
38
|
-
state?: {
|
|
39
|
-
hasResult?: boolean;
|
|
40
|
-
callComponent?: { invalidate?: () => void };
|
|
41
|
-
[key: string]: unknown;
|
|
42
|
-
};
|
|
43
|
-
toolCallId?: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface GrepMatchItem {
|
|
47
|
-
file: string;
|
|
48
|
-
line: string;
|
|
49
|
-
content: string;
|
|
50
|
-
isContext?: boolean;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
type GrepParseResult =
|
|
54
|
-
| { raw: true; rawLines: string[]; notices: string[] }
|
|
55
|
-
| { raw: false; matches: GrepMatchItem[] };
|
|
56
|
-
|
|
57
|
-
function getGrepCandidates(
|
|
58
|
-
rawLine: string,
|
|
59
|
-
isContext: boolean,
|
|
60
|
-
): GrepMatchItem[] {
|
|
61
|
-
const candidates: GrepMatchItem[] = [];
|
|
62
|
-
// Lookahead includes overlapping delimiters such as :1:2: or -1-2-.
|
|
63
|
-
const delimiterPattern = isContext ? /(?=-(\d+)-)/g : /(?=:(\d+):)/g;
|
|
64
|
-
for (const delimiter of rawLine.matchAll(delimiterPattern)) {
|
|
65
|
-
const file = rawLine.slice(0, delimiter.index);
|
|
66
|
-
if (!file) continue;
|
|
67
|
-
candidates.push({
|
|
68
|
-
file,
|
|
69
|
-
line: delimiter[1],
|
|
70
|
-
content: rawLine.slice(delimiter.index + delimiter[1].length + 2),
|
|
71
|
-
isContext,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
return candidates;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function parseGrepOutput(text: string, details: unknown): GrepParseResult {
|
|
78
|
-
const output = splitResultNotice(text, details, 'grep');
|
|
79
|
-
const lines =
|
|
80
|
-
output.text || output.notices.length > 0 ? output.text.split('\n') : [];
|
|
81
|
-
const metadata =
|
|
82
|
-
details && typeof details === 'object'
|
|
83
|
-
? (details as GrepToolDetails)
|
|
84
|
-
: undefined;
|
|
85
|
-
const rawResult: GrepParseResult = {
|
|
86
|
-
raw: true,
|
|
87
|
-
rawLines: lines,
|
|
88
|
-
notices: output.notices,
|
|
89
|
-
};
|
|
90
|
-
// Limits can remove the match while retaining preceding context. Do not
|
|
91
|
-
// interpret any rows from an incomplete SDK result, even if they look unique.
|
|
92
|
-
if (
|
|
93
|
-
typeof metadata?.matchLimitReached === 'number' ||
|
|
94
|
-
metadata?.truncation?.truncated === true ||
|
|
95
|
-
metadata?.linesTruncated === true
|
|
96
|
-
) {
|
|
97
|
-
return rawResult;
|
|
98
|
-
}
|
|
99
|
-
const matches: GrepMatchItem[] = [];
|
|
100
|
-
for (const rawLine of lines) {
|
|
101
|
-
// An orphan context reading is still a possible interpretation: absence of
|
|
102
|
-
// a visible match for that filename is not evidence that it is invalid.
|
|
103
|
-
const candidates = [
|
|
104
|
-
...getGrepCandidates(rawLine, false),
|
|
105
|
-
...getGrepCandidates(rawLine, true),
|
|
106
|
-
];
|
|
107
|
-
if (candidates.length !== 1) return rawResult;
|
|
108
|
-
matches.push(candidates[0]);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return { raw: false, matches };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function groupMatches(matches: GrepMatchItem[]): [string, GrepMatchItem[]][] {
|
|
115
|
-
const groups: [string, GrepMatchItem[]][] = [];
|
|
116
|
-
// Group only contiguous runs. A later occurrence of a file must not move its
|
|
117
|
-
// rows ahead of intervening SDK output.
|
|
118
|
-
for (const m of matches) {
|
|
119
|
-
const previous = groups.at(-1);
|
|
120
|
-
if (previous?.[0] === m.file) {
|
|
121
|
-
previous[1].push(m);
|
|
122
|
-
} else {
|
|
123
|
-
groups.push([m.file, [m]]);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return groups;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function createCustomGrepTool(cwd: string, config: ThemeConfig) {
|
|
130
|
-
return {
|
|
131
|
-
renderShell: 'self' as const,
|
|
132
|
-
renderCall(rawArgs: unknown, theme: Theme, context: GrepContext) {
|
|
133
|
-
syncElapsedTicker(context);
|
|
134
|
-
const args = (rawArgs ?? {}) as GrepArgs;
|
|
135
|
-
const pattern = escapeControlCharacters(String(args.pattern ?? ''));
|
|
136
|
-
const rawPath = args.path ? String(args.path) : '';
|
|
137
|
-
const searchPath = rawPath
|
|
138
|
-
? ` in ${escapeControlCharacters(formatDisplayPath(rawPath, context?.cwd ?? cwd))}`
|
|
139
|
-
: '';
|
|
140
|
-
const icon = getToolIcon('search', config.icons);
|
|
141
|
-
const borderTone = getToolBorderTone(context);
|
|
142
|
-
|
|
143
|
-
const comp = createComponent((width: number) => {
|
|
144
|
-
const safeWidth = Math.max(0, width);
|
|
145
|
-
const footer =
|
|
146
|
-
context?.executionStarted && context.isPartial
|
|
147
|
-
? toolFooter(theme, { status: 'running', context }, config.icons)
|
|
148
|
-
: undefined;
|
|
149
|
-
const title = `${theme.fg('accent', icon)} ${theme.bold ? theme.bold(theme.fg('toolTitle', 'Grep')) : theme.fg('toolTitle', 'Grep')} ${theme.fg('syntaxString', `"${pattern}"`)}${theme.fg('dim', searchPath)}`;
|
|
150
|
-
|
|
151
|
-
if (!isFramedContext(context)) {
|
|
152
|
-
return renderBox(theme, [], safeWidth, {
|
|
153
|
-
title,
|
|
154
|
-
footer,
|
|
155
|
-
isError: borderTone === 'error',
|
|
156
|
-
isSuccess: borderTone === 'success',
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (hasToolResult(context)) {
|
|
161
|
-
return renderFrameTop(theme, title, safeWidth, borderTone);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return [
|
|
165
|
-
...renderFrameTop(theme, title, safeWidth, borderTone),
|
|
166
|
-
...renderFrameBottom(theme, footer, safeWidth, borderTone),
|
|
167
|
-
];
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
if (context?.state) {
|
|
171
|
-
context.state.callComponent = comp;
|
|
172
|
-
}
|
|
173
|
-
return comp;
|
|
174
|
-
},
|
|
175
|
-
|
|
176
|
-
renderResult(
|
|
177
|
-
result: AgentToolResult<unknown>,
|
|
178
|
-
options: ToolRenderResultOptions,
|
|
179
|
-
theme: Theme,
|
|
180
|
-
context: GrepContext,
|
|
181
|
-
) {
|
|
182
|
-
if (context?.state) {
|
|
183
|
-
context.state.hasResult = true;
|
|
184
|
-
if (
|
|
185
|
-
context.state.callComponent &&
|
|
186
|
-
typeof context.state.callComponent.invalidate === 'function'
|
|
187
|
-
) {
|
|
188
|
-
context.state.callComponent.invalidate();
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
if (
|
|
192
|
-
context?.lastComponent &&
|
|
193
|
-
typeof context.lastComponent.invalidate === 'function'
|
|
194
|
-
) {
|
|
195
|
-
context.lastComponent.invalidate();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const isErr = Boolean(context?.isError);
|
|
199
|
-
const status = options?.isPartial
|
|
200
|
-
? 'running'
|
|
201
|
-
: isErr
|
|
202
|
-
? 'failed'
|
|
203
|
-
: 'completed';
|
|
204
|
-
const footerOptions = {
|
|
205
|
-
status,
|
|
206
|
-
context: { ...context, isPartial: options?.isPartial },
|
|
207
|
-
} as const;
|
|
208
|
-
syncElapsedTicker(footerOptions.context);
|
|
209
|
-
const borderTone = getToolBorderTone({
|
|
210
|
-
isError: isErr,
|
|
211
|
-
isPartial: options?.isPartial,
|
|
212
|
-
});
|
|
213
|
-
const textOutput = getRawResultText(result);
|
|
214
|
-
|
|
215
|
-
if (isErr) {
|
|
216
|
-
return createComponent((width: number) => {
|
|
217
|
-
const safeWidth = Math.max(0, width);
|
|
218
|
-
const footer = toolFooter(theme, footerOptions, config.icons);
|
|
219
|
-
const errText = `${theme.fg('error', `${statusIcon('warning', config.icons)} `)}${theme.fg('error', escapeControlCharacters(textOutput || 'Grep failed'))}`;
|
|
220
|
-
if (!isFramedContext(context)) {
|
|
221
|
-
return [
|
|
222
|
-
truncateToWidth(errText, safeWidth),
|
|
223
|
-
...renderFrameBottom(theme, footer, safeWidth, borderTone),
|
|
224
|
-
];
|
|
225
|
-
}
|
|
226
|
-
return [
|
|
227
|
-
...renderFrameRow(theme, errText, safeWidth, borderTone),
|
|
228
|
-
...renderFrameBottom(theme, footer, safeWidth, borderTone),
|
|
229
|
-
];
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const output = parseGrepOutput(textOutput, result.details);
|
|
234
|
-
|
|
235
|
-
return createComponent((width: number) => {
|
|
236
|
-
const safeWidth = Math.max(0, width);
|
|
237
|
-
const footer = toolFooter(theme, footerOptions, config.icons);
|
|
238
|
-
if (safeWidth === 0) return [];
|
|
239
|
-
if (output.raw) {
|
|
240
|
-
const { rawLines, notices } = output;
|
|
241
|
-
const visibleRawLines = options?.expanded
|
|
242
|
-
? rawLines
|
|
243
|
-
: rawLines.slice(0, 8);
|
|
244
|
-
const lines = visibleRawLines.map((rawLine) =>
|
|
245
|
-
theme.fg('toolOutput', escapeControlCharacters(rawLine)),
|
|
246
|
-
);
|
|
247
|
-
if (visibleRawLines.length < rawLines.length) {
|
|
248
|
-
lines.push(
|
|
249
|
-
theme.fg(
|
|
250
|
-
'dim',
|
|
251
|
-
`${semanticIcon('ellipsis', config.icons)} ${rawLines.length - visibleRawLines.length} more lines ${semanticIcon('separator', config.icons)} ctrl+o to expand`,
|
|
252
|
-
),
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
if (notices.length > 0) lines.push('');
|
|
256
|
-
for (const notice of notices) {
|
|
257
|
-
lines.push(theme.fg('warning', notice));
|
|
258
|
-
}
|
|
259
|
-
if (!isFramedContext(context)) {
|
|
260
|
-
const rawWithPrefix = lines.map((l) => (l ? ` ${l}` : ' '));
|
|
261
|
-
return renderBox(theme, rawWithPrefix, safeWidth, {
|
|
262
|
-
footer,
|
|
263
|
-
isSuccess: borderTone === 'success',
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
return [
|
|
267
|
-
...lines.flatMap((l) =>
|
|
268
|
-
renderFrameRow(theme, l ? ` ${l}` : ' ', safeWidth, borderTone),
|
|
269
|
-
),
|
|
270
|
-
...renderFrameBottom(theme, footer, safeWidth, borderTone),
|
|
271
|
-
];
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const { matches } = output;
|
|
275
|
-
const totalItems = matches.length;
|
|
276
|
-
if (totalItems === 0) {
|
|
277
|
-
if (!isFramedContext(context)) {
|
|
278
|
-
return [
|
|
279
|
-
truncateToWidth(theme.fg('dim', 'no matches found'), safeWidth),
|
|
280
|
-
...renderFrameBottom(theme, footer, safeWidth, borderTone),
|
|
281
|
-
];
|
|
282
|
-
}
|
|
283
|
-
return [
|
|
284
|
-
...renderFrameRow(
|
|
285
|
-
theme,
|
|
286
|
-
theme.fg('dim', 'no matches found'),
|
|
287
|
-
safeWidth,
|
|
288
|
-
borderTone,
|
|
289
|
-
),
|
|
290
|
-
...renderFrameBottom(theme, footer, safeWidth, borderTone),
|
|
291
|
-
];
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const groupEntries = groupMatches(matches);
|
|
295
|
-
const rawLines: string[] = [];
|
|
296
|
-
|
|
297
|
-
const maxGroups = options?.expanded ? groupEntries.length : 3;
|
|
298
|
-
const visibleGroups = groupEntries.slice(0, maxGroups);
|
|
299
|
-
let renderedItemCount = 0;
|
|
300
|
-
|
|
301
|
-
for (let gi = 0; gi < visibleGroups.length; gi++) {
|
|
302
|
-
const [file, fileMatches] = visibleGroups[gi];
|
|
303
|
-
const isLastGroup =
|
|
304
|
-
gi === visibleGroups.length - 1 &&
|
|
305
|
-
visibleGroups.length === groupEntries.length;
|
|
306
|
-
const groupBranch = isLastGroup ? '└─' : '├─';
|
|
307
|
-
const fileIcon = getFileIcon(file, config.icons);
|
|
308
|
-
const actualMatches = fileMatches.filter((m) => !m.isContext).length;
|
|
309
|
-
const matchCountLabel = `(${actualMatches} ${actualMatches === 1 ? 'match' : 'matches'})`;
|
|
310
|
-
|
|
311
|
-
const fileHeader = `${theme.fg('dim', groupBranch)} ${theme.fg('accent', fileIcon)} ${theme.fg('toolTitle', escapeControlCharacters(file))} ${theme.fg('dim', matchCountLabel)}`;
|
|
312
|
-
rawLines.push(fileHeader);
|
|
313
|
-
|
|
314
|
-
const maxMatches = options?.expanded ? fileMatches.length : 2;
|
|
315
|
-
const visibleMatches = fileMatches.slice(0, maxMatches);
|
|
316
|
-
renderedItemCount += visibleMatches.length;
|
|
317
|
-
|
|
318
|
-
const matchBranchPrefix = isLastGroup
|
|
319
|
-
? ' '
|
|
320
|
-
: ` ${theme.fg('dim', '│')} `;
|
|
321
|
-
for (const m of visibleMatches) {
|
|
322
|
-
const separator = m.isContext ? '- ' : ': ';
|
|
323
|
-
const lineNum = theme.fg(
|
|
324
|
-
'dim',
|
|
325
|
-
`${m.line.padStart(4, ' ')}${separator}`,
|
|
326
|
-
);
|
|
327
|
-
const content = theme.fg(
|
|
328
|
-
m.isContext ? 'dim' : 'toolOutput',
|
|
329
|
-
escapeControlCharacters(m.content),
|
|
330
|
-
);
|
|
331
|
-
rawLines.push(`${matchBranchPrefix}${lineNum}${content}`);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (
|
|
335
|
-
fileMatches.length > visibleMatches.length &&
|
|
336
|
-
!options?.expanded
|
|
337
|
-
) {
|
|
338
|
-
const moreFileMatches = fileMatches.length - visibleMatches.length;
|
|
339
|
-
rawLines.push(
|
|
340
|
-
`${matchBranchPrefix}${theme.fg('dim', `${semanticIcon('ellipsis', config.icons)} ${moreFileMatches} more`)}`,
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const remainingItems = totalItems - renderedItemCount;
|
|
346
|
-
if (
|
|
347
|
-
!options?.expanded &&
|
|
348
|
-
(groupEntries.length > maxGroups || remainingItems > 0)
|
|
349
|
-
) {
|
|
350
|
-
const hiddenFiles = groupEntries.length - visibleGroups.length;
|
|
351
|
-
const summary = `${theme.fg('dim', '└─')} ${theme.fg('dim', `${semanticIcon('ellipsis', config.icons)} ${remainingItems} more matches across ${hiddenFiles} files ${semanticIcon('separator', config.icons)} ctrl+o to expand`)}`;
|
|
352
|
-
rawLines.push(summary);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (!isFramedContext(context)) {
|
|
356
|
-
return [
|
|
357
|
-
...rawLines.map((line) => truncateToWidth(` ${line}`, safeWidth)),
|
|
358
|
-
...renderFrameBottom(theme, footer, safeWidth, borderTone),
|
|
359
|
-
];
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return [
|
|
363
|
-
...rawLines.flatMap((line) =>
|
|
364
|
-
renderFrameRow(theme, line, safeWidth, borderTone),
|
|
365
|
-
),
|
|
366
|
-
...renderFrameBottom(theme, footer, safeWidth, borderTone),
|
|
367
|
-
];
|
|
368
|
-
});
|
|
369
|
-
},
|
|
370
|
-
};
|
|
371
|
-
}
|
package/src/tools/index.ts
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExtensionAPI,
|
|
3
|
-
ToolRendererResolver,
|
|
4
|
-
ToolRenderers,
|
|
5
|
-
ToolRenderResultOptions,
|
|
6
|
-
} from '@earendil-works/pi-coding-agent';
|
|
7
|
-
import {
|
|
8
|
-
DEFAULT_RESPECT_PACKAGES,
|
|
9
|
-
type ThemeConfig,
|
|
10
|
-
} from '../shared/config.ts';
|
|
11
|
-
import { createCustomBashTool } from './bash.ts';
|
|
12
|
-
import { createCustomEditTool } from './edit.ts';
|
|
13
|
-
import { createCustomFindTool } from './find.ts';
|
|
14
|
-
import { createGenericTool } from './generic.ts';
|
|
15
|
-
import { createCustomGrepTool } from './grep.ts';
|
|
16
|
-
import { createCustomLsTool } from './ls.ts';
|
|
17
|
-
import { createOwnershipResolver } from './ownership.ts';
|
|
18
|
-
import { createCustomPowerShellTool } from './powershell.ts';
|
|
19
|
-
import { createCustomReadTool } from './read.ts';
|
|
20
|
-
import {
|
|
21
|
-
activateElapsedTickerOwner,
|
|
22
|
-
releaseElapsedTickerOwner,
|
|
23
|
-
stopElapsedTickers,
|
|
24
|
-
} from './ticker.ts';
|
|
25
|
-
import { createCustomWriteTool } from './write.ts';
|
|
26
|
-
|
|
27
|
-
/** Resolve themed renderers using this theme instance's live tool ownership. */
|
|
28
|
-
export function createToolRendererResolver(
|
|
29
|
-
pi: Pick<ExtensionAPI, 'getAllTools'>,
|
|
30
|
-
config: ThemeConfig,
|
|
31
|
-
cwd = process.cwd(),
|
|
32
|
-
): ToolRendererResolver {
|
|
33
|
-
const renderers = new Map<string, ToolRenderers>([
|
|
34
|
-
['read', createCustomReadTool(cwd, config)],
|
|
35
|
-
['bash', createCustomBashTool(cwd, config)],
|
|
36
|
-
['powershell', createCustomPowerShellTool(cwd, config)],
|
|
37
|
-
['ls', createCustomLsTool(cwd, config)],
|
|
38
|
-
['grep', createCustomGrepTool(cwd, config)],
|
|
39
|
-
['find', createCustomFindTool(cwd, config)],
|
|
40
|
-
['edit', createCustomEditTool(cwd, config)],
|
|
41
|
-
['write', createCustomWriteTool(cwd, config)],
|
|
42
|
-
]);
|
|
43
|
-
|
|
44
|
-
const genericRenderers = new Map<string, ToolRenderers>();
|
|
45
|
-
const isOwnedBaseDir = createOwnershipResolver(
|
|
46
|
-
config.tools.respectPackages ?? DEFAULT_RESPECT_PACKAGES,
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
// Tools absent from the registry cannot be attributed and keep respecting
|
|
50
|
-
// their downstream renderers; registered tools are respected only when their
|
|
51
|
-
// package matches tools.respectPackages. Read at each resolution so
|
|
52
|
-
// replacements by another package are honored.
|
|
53
|
-
const canRespectDownstream = (toolName: string): boolean => {
|
|
54
|
-
const tool =
|
|
55
|
-
typeof pi.getAllTools === 'function'
|
|
56
|
-
? pi.getAllTools().find((t) => t.name === toolName)
|
|
57
|
-
: undefined;
|
|
58
|
-
if (!tool) return true;
|
|
59
|
-
const baseDir = tool.sourceInfo?.baseDir;
|
|
60
|
-
return baseDir ? isOwnedBaseDir(baseDir) : false;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
return (toolName, next) => {
|
|
64
|
-
// Built-ins take precedence over Pi 1.0.1's native callbacks.
|
|
65
|
-
const builtIn = renderers.get(toolName);
|
|
66
|
-
if (builtIn) return builtIn;
|
|
67
|
-
|
|
68
|
-
// Respect configured packages' tools that bring their own renderers.
|
|
69
|
-
const downstream = next();
|
|
70
|
-
if (
|
|
71
|
-
(downstream?.renderCall || downstream?.renderResult) &&
|
|
72
|
-
canRespectDownstream(toolName)
|
|
73
|
-
) {
|
|
74
|
-
return downstream;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
let generic = genericRenderers.get(toolName);
|
|
78
|
-
if (!generic) {
|
|
79
|
-
generic = createGenericTool(toolName, config);
|
|
80
|
-
genericRenderers.set(toolName, generic);
|
|
81
|
-
}
|
|
82
|
-
return generic;
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** Register themed renderers and return an idempotent ticker/subscription disposer. */
|
|
87
|
-
export function registerTools(
|
|
88
|
-
pi: ExtensionAPI,
|
|
89
|
-
config: ThemeConfig,
|
|
90
|
-
cwd = process.cwd(),
|
|
91
|
-
owner: object = {},
|
|
92
|
-
resolveToolRenderers?: ToolRendererResolver,
|
|
93
|
-
): () => void {
|
|
94
|
-
if (!config.tools.enabled || typeof pi?.registerToolRenderer !== 'function') {
|
|
95
|
-
return () => {};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
pi.registerToolRenderer(
|
|
99
|
-
resolveToolRenderers ?? createToolRendererResolver(pi, config, cwd),
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
const unsubs: Array<() => void> = [];
|
|
103
|
-
if (typeof pi?.on === 'function') {
|
|
104
|
-
unsubs.push(
|
|
105
|
-
pi.on('agent_end', (_event, ctx) => {
|
|
106
|
-
if (ctx.hasUI) stopElapsedTickers(owner);
|
|
107
|
-
}),
|
|
108
|
-
);
|
|
109
|
-
unsubs.push(
|
|
110
|
-
pi.on('session_shutdown', (_event, ctx) => {
|
|
111
|
-
if (!ctx.hasUI) return;
|
|
112
|
-
stopElapsedTickers(owner);
|
|
113
|
-
releaseElapsedTickerOwner(owner);
|
|
114
|
-
}),
|
|
115
|
-
);
|
|
116
|
-
unsubs.push(
|
|
117
|
-
pi.on('session_start', (_event, ctx) => {
|
|
118
|
-
if (!ctx.hasUI) return;
|
|
119
|
-
stopElapsedTickers(owner);
|
|
120
|
-
activateElapsedTickerOwner(owner);
|
|
121
|
-
}),
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
let disposed = false;
|
|
126
|
-
return () => {
|
|
127
|
-
if (disposed) return;
|
|
128
|
-
disposed = true;
|
|
129
|
-
stopElapsedTickers(owner);
|
|
130
|
-
releaseElapsedTickerOwner(owner);
|
|
131
|
-
for (const unsub of unsubs) unsub();
|
|
132
|
-
unsubs.length = 0;
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
export type { ToolRenderResultOptions };
|