@hheei/pi-ext-ui 0.5.2 → 0.7.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 +2 -2
- package/dist/background.d.ts +11 -0
- package/dist/background.js +31 -0
- package/dist/extension.js +71 -7
- package/dist/host-transcript-patch.d.ts +2 -0
- package/dist/host-transcript-patch.js +258 -181
- package/dist/index.d.ts +6 -4
- package/dist/index.js +5 -3
- package/dist/message-view.d.ts +6 -0
- package/dist/message-view.js +38 -8
- package/dist/native-tools.d.ts +69 -0
- package/dist/native-tools.js +414 -0
- package/dist/nested-calls-view.d.ts +6 -2
- package/dist/nested-calls-view.js +98 -99
- package/dist/payload-parser.d.ts +10 -28
- package/dist/payload-parser.js +9 -269
- package/dist/renderer.d.ts +6 -4
- package/dist/renderer.js +280 -174
- package/dist/runtime.d.ts +13 -0
- package/dist/runtime.js +16 -0
- package/dist/settings.d.ts +3 -0
- package/dist/settings.js +18 -1
- package/dist/tool-group-view.d.ts +89 -0
- package/dist/tool-group-view.js +645 -0
- package/dist/tool-view.d.ts +33 -4
- package/dist/tool-view.js +116 -35
- package/dist/tracker.d.ts +36 -17
- package/dist/tracker.js +157 -19
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { copyToClipboard as copyToSystemClipboard, getLanguageFromPath, highlightCode, } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { stripTerminalSequences, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
|
|
3
|
-
import { inlinePresentation } from "./
|
|
3
|
+
import { extractChildToolCopyText, inlinePresentation, nativeRequestCopy } from "./native-tools.js";
|
|
4
4
|
import { formatToolHeaderText, ToolView, toContentRows, transitionFoldLevel, } from "./tool-view.js";
|
|
5
5
|
function truncate(text, width) {
|
|
6
6
|
return truncateToWidth(text, Math.max(1, width));
|
|
@@ -8,53 +8,15 @@ function truncate(text, width) {
|
|
|
8
8
|
function isRecord(value) {
|
|
9
9
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
/**
|
|
12
|
+
* A copy keeps the request half byte-exact and strips only what follows it: the result is display
|
|
13
|
+
* text, while the request is what the model sent and may legitimately contain escapes.
|
|
14
|
+
*/
|
|
15
|
+
function copyText(text, request) {
|
|
16
|
+
if (request === undefined || request === "" || !text.startsWith(request)) {
|
|
17
|
+
return stripTerminalSequences(text);
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
-
? args.command
|
|
20
|
-
: toolName === "codemode" && isRecord(args) && typeof args.code === "string"
|
|
21
|
-
? args.code
|
|
22
|
-
: undefined;
|
|
23
|
-
if (heavyCommand !== undefined && heavyCommand.trim() !== "") {
|
|
24
|
-
if (full !== true)
|
|
25
|
-
return heavyCommand;
|
|
26
|
-
const output = resultText(result);
|
|
27
|
-
return output === undefined ? heavyCommand : `${heavyCommand}\n---\n${output}`;
|
|
28
|
-
}
|
|
29
|
-
if (result && Array.isArray(result.content)) {
|
|
30
|
-
const texts = result.content
|
|
31
|
-
.filter((c) => isRecord(c) && c.type === "text" && typeof c.text === "string")
|
|
32
|
-
.map((c) => c.text);
|
|
33
|
-
if (texts.length > 0)
|
|
34
|
-
return texts.join("\n");
|
|
35
|
-
}
|
|
36
|
-
if (args !== undefined) {
|
|
37
|
-
if (typeof args === "string")
|
|
38
|
-
return args;
|
|
39
|
-
try {
|
|
40
|
-
return JSON.stringify(args, null, 2);
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
return String(args);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
if ("summary" in call && typeof call.summary === "string") {
|
|
47
|
-
return call.summary;
|
|
48
|
-
}
|
|
49
|
-
return "";
|
|
50
|
-
}
|
|
51
|
-
function resultText(result) {
|
|
52
|
-
if (result === undefined || !Array.isArray(result.content))
|
|
53
|
-
return undefined;
|
|
54
|
-
const texts = result.content
|
|
55
|
-
.filter((c) => isRecord(c) && c.type === "text" && typeof c.text === "string")
|
|
56
|
-
.map((c) => c.text);
|
|
57
|
-
return texts.length === 0 ? undefined : texts.join("\n");
|
|
19
|
+
return request + stripTerminalSequences(text.slice(request.length));
|
|
58
20
|
}
|
|
59
21
|
function formatDuration(ms) {
|
|
60
22
|
if (ms === undefined || !Number.isFinite(ms))
|
|
@@ -67,6 +29,7 @@ export class NestedCallsView {
|
|
|
67
29
|
#props;
|
|
68
30
|
#inMemoryLevels = new Map();
|
|
69
31
|
#copiedIndex = undefined;
|
|
32
|
+
#failedIndex = undefined;
|
|
70
33
|
#copiedTimer = undefined;
|
|
71
34
|
#lineMapping = [];
|
|
72
35
|
#renderCache;
|
|
@@ -96,14 +59,11 @@ export class NestedCallsView {
|
|
|
96
59
|
typeof copiedState.until === "number") {
|
|
97
60
|
const remaining = copiedState.until - Date.now();
|
|
98
61
|
if (remaining > 0) {
|
|
99
|
-
|
|
62
|
+
const failed = copiedState.failed === true;
|
|
63
|
+
this.#copiedIndex = failed ? undefined : copiedState.index;
|
|
64
|
+
this.#failedIndex = failed ? copiedState.index : undefined;
|
|
100
65
|
this.#copiedTimer = setTimeout(() => {
|
|
101
|
-
this.#
|
|
102
|
-
this.#renderCache = undefined;
|
|
103
|
-
const s = this.#props.stateHolder;
|
|
104
|
-
if (s !== undefined) {
|
|
105
|
-
delete s[NESTED_CALLS_COPIED_KEY];
|
|
106
|
-
}
|
|
66
|
+
this.#clearCopyFeedback();
|
|
107
67
|
this.#props.invalidate?.();
|
|
108
68
|
}, remaining);
|
|
109
69
|
this.#copiedTimer.unref?.();
|
|
@@ -162,9 +122,9 @@ export class NestedCallsView {
|
|
|
162
122
|
*/
|
|
163
123
|
#callContext(call) {
|
|
164
124
|
const { context } = this.#props;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
125
|
+
// A captured call carries its own id. A persisted record has none, so it falls back to the
|
|
126
|
+
// parent's id — the only identity the host offers, and never a child's guess.
|
|
127
|
+
const toolCallId = ("id" in call ? call.id : undefined) ?? context.toolCallId;
|
|
168
128
|
return {
|
|
169
129
|
...context,
|
|
170
130
|
lastComponent: undefined,
|
|
@@ -182,44 +142,62 @@ export class NestedCallsView {
|
|
|
182
142
|
return;
|
|
183
143
|
const provider = this.#props.resolvePresentation?.(call.toolName);
|
|
184
144
|
const presentation = provider?.("args" in call ? call.args : undefined, "result" in call ? call.result : undefined, this.#props.theme, this.#callContext(call));
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
145
|
+
const chosen = full ? presentation?.copy?.full : presentation?.copy?.primary;
|
|
146
|
+
const text = chosen ?? copyText(extractChildToolCopyText(call, full), nativeRequestCopy(call));
|
|
147
|
+
const durationMs = 1500;
|
|
148
|
+
if (text === "") {
|
|
149
|
+
// Nothing was recorded to copy. A summary is not the request, so the copy reports failure.
|
|
150
|
+
this.#showCopyFeedback(callIndex, durationMs, true);
|
|
188
151
|
return;
|
|
152
|
+
}
|
|
189
153
|
const writeClip = this.#props.copyToClipboard ?? copyToSystemClipboard;
|
|
190
|
-
|
|
191
|
-
|
|
154
|
+
// Feedback starts when the write settles: a rejected clipboard write is not a copy.
|
|
155
|
+
void writeClip(text).then(() => this.#showCopyFeedback(callIndex, durationMs, false), () => this.#showCopyFeedback(callIndex, durationMs, true));
|
|
156
|
+
}
|
|
157
|
+
/** One feedback window on one row: ✓ when the text reached the clipboard, ✗ when it did not. */
|
|
158
|
+
#showCopyFeedback(callIndex, durationMs, failed) {
|
|
192
159
|
const until = Date.now() + durationMs;
|
|
193
|
-
this.#copiedIndex = callIndex;
|
|
160
|
+
this.#copiedIndex = failed ? undefined : callIndex;
|
|
161
|
+
this.#failedIndex = failed ? callIndex : undefined;
|
|
194
162
|
this.#renderCache = undefined;
|
|
195
163
|
const state = this.#props.stateHolder;
|
|
196
164
|
if (state !== undefined) {
|
|
197
165
|
state[NESTED_CALLS_COPIED_KEY] = {
|
|
198
166
|
index: callIndex,
|
|
199
167
|
until,
|
|
168
|
+
...(failed ? { failed: true } : {}),
|
|
200
169
|
};
|
|
201
170
|
}
|
|
202
171
|
if (this.#copiedTimer !== undefined) {
|
|
203
172
|
clearTimeout(this.#copiedTimer);
|
|
204
173
|
}
|
|
205
174
|
this.#copiedTimer = setTimeout(() => {
|
|
206
|
-
this.#
|
|
207
|
-
this.#renderCache = undefined;
|
|
208
|
-
const s = this.#props.stateHolder;
|
|
209
|
-
if (s !== undefined) {
|
|
210
|
-
delete s[NESTED_CALLS_COPIED_KEY];
|
|
211
|
-
}
|
|
175
|
+
this.#clearCopyFeedback();
|
|
212
176
|
this.#props.invalidate?.();
|
|
213
177
|
}, durationMs);
|
|
214
178
|
this.#copiedTimer.unref?.();
|
|
215
179
|
this.#props.invalidate?.();
|
|
216
180
|
}
|
|
181
|
+
/** Drops the copy marker, its timer, and the shared record it left behind. */
|
|
182
|
+
#clearCopyFeedback() {
|
|
183
|
+
if (this.#copiedTimer !== undefined) {
|
|
184
|
+
clearTimeout(this.#copiedTimer);
|
|
185
|
+
this.#copiedTimer = undefined;
|
|
186
|
+
}
|
|
187
|
+
this.#copiedIndex = undefined;
|
|
188
|
+
this.#failedIndex = undefined;
|
|
189
|
+
this.#renderCache = undefined;
|
|
190
|
+
const state = this.#props.stateHolder;
|
|
191
|
+
if (state !== undefined)
|
|
192
|
+
delete state[NESTED_CALLS_COPIED_KEY];
|
|
193
|
+
}
|
|
217
194
|
render(width) {
|
|
218
195
|
const safeWidth = Math.max(1, width);
|
|
219
196
|
const levelsKey = this.#getLevelsKey();
|
|
220
197
|
if (this.#renderCache !== undefined &&
|
|
221
198
|
this.#renderCache.width === safeWidth &&
|
|
222
199
|
this.#renderCache.copiedIndex === this.#copiedIndex &&
|
|
200
|
+
this.#renderCache.failedIndex === this.#failedIndex &&
|
|
223
201
|
this.#renderCache.levelsKey === levelsKey) {
|
|
224
202
|
this.#lineMapping = this.#renderCache.lineMapping;
|
|
225
203
|
return this.#renderCache.lines;
|
|
@@ -240,21 +218,32 @@ export class NestedCallsView {
|
|
|
240
218
|
const isExpanded = expansionLevel !== 0;
|
|
241
219
|
const rawSummary = "summary" in call ? call.summary : undefined;
|
|
242
220
|
const rawArgs = "args" in call ? call.args : undefined;
|
|
243
|
-
|
|
221
|
+
// A record's summary is the fact its owner stated; the arguments only fill in for a capture.
|
|
222
|
+
const inline = inlinePresentation(toolName, rawSummary ?? rawArgs, "result" in call ? call.result?.details : undefined, isError);
|
|
244
223
|
const argText = typeof rawArgs === "string" ? rawArgs : typeof rawSummary === "string" ? rawSummary : "";
|
|
245
224
|
const provider = this.#props.resolvePresentation?.(toolName);
|
|
246
225
|
const presentation = provider?.(rawArgs, "result" in call ? call.result : undefined, theme, this.#callContext(call));
|
|
247
226
|
// The child row is the child's own header minus the fold pin, so it carries the same token
|
|
248
227
|
// assignment as the row the same call would draw at top level.
|
|
249
|
-
const
|
|
250
|
-
|
|
228
|
+
const ownSummary = presentation?.summary === undefined
|
|
229
|
+
? this.#props.headerSummary?.(toolName, rawArgs ?? rawSummary, theme)
|
|
230
|
+
: undefined;
|
|
231
|
+
const childSummary = ownSummary === undefined || ownSummary === ""
|
|
232
|
+
? stripTerminalSequences(presentation?.summary ?? inline?.summary ?? argText)
|
|
233
|
+
: ownSummary;
|
|
251
234
|
const leftContent = formatToolHeaderText({
|
|
252
235
|
status: isRunning ? "running" : isError ? "error" : "ok",
|
|
253
236
|
title: toolName,
|
|
254
|
-
summary:
|
|
237
|
+
summary: childSummary,
|
|
238
|
+
...(ownSummary === undefined || ownSummary === "" ? {} : { summaryStyled: true }),
|
|
239
|
+
meta: formatDuration(call.durationMs),
|
|
255
240
|
}, theme);
|
|
256
241
|
const isCopied = this.#copiedIndex === i;
|
|
257
|
-
const copyIcon =
|
|
242
|
+
const copyIcon = this.#failedIndex === i
|
|
243
|
+
? theme.fg("error", "✗")
|
|
244
|
+
: isCopied
|
|
245
|
+
? theme.fg("success", "✓")
|
|
246
|
+
: theme.fg("dim", "");
|
|
258
247
|
const copySlotWidth = 2 + rightMargin;
|
|
259
248
|
const maxLeft = Math.max(1, width - copySlotWidth);
|
|
260
249
|
const truncatedLeft = truncate(leftContent, maxLeft);
|
|
@@ -290,6 +279,7 @@ export class NestedCallsView {
|
|
|
290
279
|
this.#renderCache = {
|
|
291
280
|
width: safeWidth,
|
|
292
281
|
copiedIndex: this.#copiedIndex,
|
|
282
|
+
failedIndex: this.#failedIndex,
|
|
293
283
|
levelsKey,
|
|
294
284
|
lines,
|
|
295
285
|
lineMapping: this.#lineMapping,
|
|
@@ -304,22 +294,17 @@ export class NestedCallsView {
|
|
|
304
294
|
if (result && resolveChildRenderer) {
|
|
305
295
|
const renderer = resolveChildRenderer(toolName);
|
|
306
296
|
if (renderer?.renderResult) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
return comp.renderBody(width, { noRail: true });
|
|
317
|
-
}
|
|
318
|
-
return comp.render(width);
|
|
319
|
-
}
|
|
320
|
-
catch {
|
|
321
|
-
// Fall through to plain text extraction on renderer failure
|
|
297
|
+
const childContext = {
|
|
298
|
+
...this.#callContext(call),
|
|
299
|
+
state: undefined,
|
|
300
|
+
};
|
|
301
|
+
const comp = renderer.renderResult(result, { expanded: expansionLevel === 2, isPartial: false }, theme, childContext);
|
|
302
|
+
if (comp instanceof ToolView) {
|
|
303
|
+
comp.setFoldLevel(expansionLevel);
|
|
304
|
+
// Render body cleanly via deep headless interface without outer rail or header row
|
|
305
|
+
return comp.renderBody(width, { noRail: true });
|
|
322
306
|
}
|
|
307
|
+
return comp.render(width);
|
|
323
308
|
}
|
|
324
309
|
}
|
|
325
310
|
// Fallback rendering
|
|
@@ -363,11 +348,29 @@ export class NestedCallsView {
|
|
|
363
348
|
if ("error" in call && typeof call.error === "string" && call.error) {
|
|
364
349
|
return call.error.split("\n").map((l) => theme.fg("error", l));
|
|
365
350
|
}
|
|
351
|
+
if ("argsTruncated" in call && call.argsTruncated === true) {
|
|
352
|
+
const bytes = "argsBytes" in call && typeof call.argsBytes === "number" ? call.argsBytes : 0;
|
|
353
|
+
return [
|
|
354
|
+
theme.fg("dim", bytes > 0 ? `(request not recorded: ${String(bytes)} bytes)` : "(request not recorded)"),
|
|
355
|
+
];
|
|
356
|
+
}
|
|
357
|
+
// The request as the model sent it: the command or source, not a JSON wrapper around it.
|
|
358
|
+
const request = nativeRequestCopy(call);
|
|
359
|
+
if (request !== undefined) {
|
|
360
|
+
return stripTerminalSequences(request)
|
|
361
|
+
.split("\n")
|
|
362
|
+
.map((line) => theme.fg("dim", line));
|
|
363
|
+
}
|
|
366
364
|
if (args !== undefined) {
|
|
367
|
-
const text = typeof args === "string" ? args : JSON.stringify(args, null, 2);
|
|
365
|
+
const text = typeof args === "string" ? stripTerminalSequences(args) : JSON.stringify(args, null, 2);
|
|
368
366
|
return text.split("\n").map((l) => theme.fg("dim", l));
|
|
369
367
|
}
|
|
370
|
-
|
|
368
|
+
// A persisted record keeps a summary, not its request: say so instead of implying an empty call.
|
|
369
|
+
return [
|
|
370
|
+
theme.fg("dim", "summary" in call && typeof call.summary === "string" && call.summary !== ""
|
|
371
|
+
? "(request not recorded; summary only)"
|
|
372
|
+
: "(no output)"),
|
|
373
|
+
];
|
|
371
374
|
}
|
|
372
375
|
handleMouse(event) {
|
|
373
376
|
if (event.type !== "click" || event.button !== "left")
|
|
@@ -390,17 +393,13 @@ export class NestedCallsView {
|
|
|
390
393
|
return { handled: true };
|
|
391
394
|
}
|
|
392
395
|
dispose() {
|
|
393
|
-
|
|
394
|
-
clearTimeout(this.#copiedTimer);
|
|
395
|
-
this.#copiedTimer = undefined;
|
|
396
|
-
}
|
|
396
|
+
this.#clearCopyFeedback();
|
|
397
397
|
this.#renderCache = undefined;
|
|
398
398
|
}
|
|
399
399
|
collapseAll() {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
400
|
+
// The marker points at a row that is no longer on screen; leaving it behind would bring a
|
|
401
|
+
// permanent checkmark back when the same view expands again.
|
|
402
|
+
this.#clearCopyFeedback();
|
|
404
403
|
this.#inMemoryLevels.clear();
|
|
405
404
|
const state = this.#props.stateHolder;
|
|
406
405
|
if (state !== undefined) {
|
package/dist/payload-parser.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import type { AgentToolResult } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { type DiffSource } from "./diff-view.js";
|
|
3
2
|
import type { ToolStatusKind } from "./tool-view.js";
|
|
4
|
-
export declare const
|
|
5
|
-
export declare const WRITE_TOOL_NAME = "write";
|
|
6
|
-
export declare const CODEMODE_OPTIONS_PREFIX = "// @options:";
|
|
7
|
-
export declare const CODEMODE_SCRIPT_HEADER: RegExp;
|
|
3
|
+
export declare const RESOLVED_CALLS_KEY = "__piExtUiResolvedCalls";
|
|
8
4
|
export interface NestedCallRecord {
|
|
9
5
|
readonly toolName: string;
|
|
6
|
+
/** The host's call id (`<parent>/<n>`); the key its saved arguments are indexed under. */
|
|
7
|
+
readonly id?: string | undefined;
|
|
8
|
+
/** The request bytes when this process or the session branch still has them. */
|
|
9
|
+
readonly args?: unknown;
|
|
10
|
+
/** The session saved the size of the request but not the request itself. */
|
|
11
|
+
readonly argsTruncated?: boolean | undefined;
|
|
12
|
+
readonly argsBytes?: number | undefined;
|
|
13
|
+
/** Positive fact only: the host proved this call resolved to a real tool. */
|
|
14
|
+
readonly resolved?: boolean | undefined;
|
|
10
15
|
readonly summary?: string | undefined;
|
|
11
16
|
readonly status?: "running" | "ok" | "error" | "cancelled" | undefined;
|
|
12
17
|
readonly isError?: boolean | undefined;
|
|
@@ -21,15 +26,8 @@ export declare function formatDurationMs(ms: number | undefined): string;
|
|
|
21
26
|
export declare function formatCostUsd(cost: number | undefined): string;
|
|
22
27
|
/** One header fact for a deadline, in the same shape codemode's `timeout_ms` renders as. */
|
|
23
28
|
export declare function formatTimeoutFact(timeoutMs: number): string;
|
|
24
|
-
export declare function extractCodemodeHeaderFacts(args: unknown): string;
|
|
25
29
|
export declare function parseCallArgs(raw: unknown): Record<string, unknown> | undefined;
|
|
26
|
-
export declare function extractSummaryArg(toolName: string, args: unknown): string;
|
|
27
|
-
export declare function extractHeavyInputCommand(toolName: string, args: unknown): string | undefined;
|
|
28
30
|
export declare function extractNestedCalls(result: AgentToolResult<unknown> | undefined): readonly NestedCallRecord[];
|
|
29
|
-
export declare function extractResultText(toolName: string, result: AgentToolResult<unknown>): {
|
|
30
|
-
readonly outputText: string;
|
|
31
|
-
readonly wallTimeSeconds: number | undefined;
|
|
32
|
-
};
|
|
33
31
|
export declare function callStatsParts(total: number, failCount: number): string[];
|
|
34
32
|
export declare function resolveContainerStatus(params: {
|
|
35
33
|
readonly isError: boolean;
|
|
@@ -37,19 +35,3 @@ export declare function resolveContainerStatus(params: {
|
|
|
37
35
|
readonly hasChildren: boolean;
|
|
38
36
|
readonly selfFailed: boolean;
|
|
39
37
|
}): ToolStatusKind;
|
|
40
|
-
export declare function toolPath(args: unknown): string;
|
|
41
|
-
/** Compact nested-row result: a one-line summary plus the status it reports. */
|
|
42
|
-
export interface InlinePresentation {
|
|
43
|
-
readonly summary: string;
|
|
44
|
-
readonly status: ToolStatusKind;
|
|
45
|
-
}
|
|
46
|
-
export declare function fileToolPresentation(toolName: string, args: unknown, _details: unknown, isError: boolean): InlinePresentation | undefined;
|
|
47
|
-
/** Pi native edit persists a unified patch plus the same change as a numbered display diff. */
|
|
48
|
-
export declare function editDiffSource(details: unknown): DiffSource | undefined;
|
|
49
|
-
export declare function displayDiffSource(diff: string): DiffSource | undefined;
|
|
50
|
-
/**
|
|
51
|
-
* One compact nested summary for a tool call. Adopts the tool's standard collapsed
|
|
52
|
-
* single-line format when recognized, degrading to undefined so callers can use
|
|
53
|
-
* the default argument rendering.
|
|
54
|
-
*/
|
|
55
|
-
export declare function inlinePresentation(toolName: string, args: unknown, details: unknown, isError: boolean): InlinePresentation | undefined;
|