@hheei/pi-ext-ui 0.0.0-stage → 0.2.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.
@@ -0,0 +1,4 @@
1
+ import { registerCollapsibleToolRenderer } from "./renderer.js";
2
+ export default function (pi) {
3
+ registerCollapsibleToolRenderer(pi);
4
+ }
@@ -0,0 +1,6 @@
1
+ export { type DiffHunk, type DiffLine, type DiffSource, type DiffStats, DiffView, type DiffViewOptions, diffStats, } from "./diff-view.js";
2
+ export { extractChildToolCopyText, NestedCallsView, type NestedCallsViewProps, } from "./nested-calls-view.js";
3
+ export { type CollapsibleToolRendererOptions, createCollapsibleToolRendererResolver, estimateStreamingTokens, inlinePresentation, type NestedCallRecord, registerCollapsibleToolRenderer, SPECIALIZED_TOOL_RENDERERS, type SpecializedToolRendererName, type ToolRenderContext, WEB_ACCESS_TOOLS, type WebAccessToolName, } from "./renderer.js";
4
+ export { type Collapsible, createToolView, type FoldLevel, formatStatusGlyph, formatToolHeaderText, isCollapsible, type RailColorToken, type RailSection, type ToolStatusKind, ToolView, type ToolViewCopySpec, type ToolViewHeaderSpec, type ToolViewSpec, transitionFoldLevel, } from "./tool-view.js";
5
+ export { type CapturedToolCall, clearCapturedNestedCalls, getCapturedNestedCalls, recordToolExecutionEnd, recordToolExecutionStart, } from "./tracker.js";
6
+ export { formatTurnFlow, type TurnFlowData, type TurnToolCallSummary, } from "./turn-copy.js";
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { DiffView, diffStats, } from "./diff-view.js";
2
+ export { extractChildToolCopyText, NestedCallsView, } from "./nested-calls-view.js";
3
+ export { createCollapsibleToolRendererResolver, estimateStreamingTokens, inlinePresentation, registerCollapsibleToolRenderer, SPECIALIZED_TOOL_RENDERERS, WEB_ACCESS_TOOLS, } from "./renderer.js";
4
+ export { createToolView, formatStatusGlyph, formatToolHeaderText, isCollapsible, ToolView, transitionFoldLevel, } from "./tool-view.js";
5
+ export { clearCapturedNestedCalls, getCapturedNestedCalls, recordToolExecutionEnd, recordToolExecutionStart, } from "./tracker.js";
6
+ export { formatTurnFlow, } from "./turn-copy.js";
@@ -0,0 +1,28 @@
1
+ import { type Theme, type ToolRenderers } from "@earendil-works/pi-coding-agent";
2
+ import { type Component, type TuiMouseEvent, type TuiMouseEventResult } from "@earendil-works/pi-tui";
3
+ import type { ToolPresentationProvider } from "@hheei/pi-ext-core";
4
+ import { type NestedCallRecord, type ToolRenderContext } from "./renderer.js";
5
+ import { type Collapsible } from "./tool-view.js";
6
+ import type { CapturedToolCall } from "./tracker.js";
7
+ export declare function extractChildToolCopyText(call: CapturedToolCall | NestedCallRecord, full?: boolean): string;
8
+ export interface NestedCallsViewProps {
9
+ readonly resolvePresentation?: ((toolName: string) => ToolPresentationProvider | undefined) | undefined;
10
+ readonly calls: readonly (CapturedToolCall | NestedCallRecord)[];
11
+ readonly theme: Theme;
12
+ readonly context: ToolRenderContext;
13
+ readonly resolveChildRenderer?: ((toolName: string) => ToolRenderers | undefined) | undefined;
14
+ readonly copyToClipboard?: ((text: string) => Promise<void>) | undefined;
15
+ readonly stateHolder?: Record<string, unknown> | undefined;
16
+ readonly invalidate?: (() => void) | undefined;
17
+ }
18
+ export declare const NESTED_CALLS_EXPANDED_KEY = "__pi_ext_ui_nested_expanded__";
19
+ export declare const NESTED_CALLS_COPIED_KEY = "__pi_ext_ui_nested_copied__";
20
+ export declare class NestedCallsView implements Component, Collapsible {
21
+ #private;
22
+ constructor(props: NestedCallsViewProps);
23
+ render(width: number): string[];
24
+ handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined;
25
+ dispose(): void;
26
+ collapseAll(): void;
27
+ invalidate(): void;
28
+ }
@@ -0,0 +1,406 @@
1
+ import { copyToClipboard as copyToSystemClipboard, getLanguageFromPath, highlightCode, } from "@earendil-works/pi-coding-agent";
2
+ import { stripTerminalSequences, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
3
+ import { inlinePresentation } from "./renderer.js";
4
+ import { formatToolHeaderText, ToolView, toContentRows, transitionFoldLevel, } from "./tool-view.js";
5
+ function truncate(text, width) {
6
+ return truncateToWidth(text, Math.max(1, width));
7
+ }
8
+ function isRecord(value) {
9
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10
+ }
11
+ export function extractChildToolCopyText(call, full) {
12
+ const toolName = "toolName" in call ? call.toolName : call.name;
13
+ const result = "result" in call ? call.result : undefined;
14
+ const args = "args" in call ? call.args : undefined;
15
+ if (toolName === "write" && isRecord(args) && typeof args.content === "string") {
16
+ return args.content;
17
+ }
18
+ if (toolName === "apply_patch" && isRecord(args) && typeof args.patch === "string") {
19
+ return args.patch;
20
+ }
21
+ const heavyCommand = toolName === "bash" && isRecord(args) && typeof args.command === "string"
22
+ ? args.command
23
+ : (toolName === "python_eval" || toolName === "codemode") &&
24
+ isRecord(args) &&
25
+ typeof args.code === "string"
26
+ ? args.code
27
+ : undefined;
28
+ if (heavyCommand !== undefined && heavyCommand.trim() !== "") {
29
+ if (full !== true)
30
+ return heavyCommand;
31
+ const output = resultText(result);
32
+ return output === undefined ? heavyCommand : `${heavyCommand}\n---\n${output}`;
33
+ }
34
+ if (toolName === "edit" && result && isRecord(result.details)) {
35
+ const editView = result.details.__piExtToolsEditView;
36
+ if (isRecord(editView) && Array.isArray(editView.ops)) {
37
+ const opsText = editView.ops
38
+ .map((op) => isRecord(op) && typeof op.newContent === "string" ? op.newContent : "")
39
+ .filter(Boolean)
40
+ .join("\n---\n");
41
+ if (opsText)
42
+ return opsText;
43
+ }
44
+ }
45
+ if (result && Array.isArray(result.content)) {
46
+ const texts = result.content
47
+ .filter((c) => isRecord(c) && c.type === "text" && typeof c.text === "string")
48
+ .map((c) => c.text);
49
+ if (texts.length > 0)
50
+ return texts.join("\n");
51
+ }
52
+ if (args !== undefined) {
53
+ if (typeof args === "string")
54
+ return args;
55
+ try {
56
+ return JSON.stringify(args, null, 2);
57
+ }
58
+ catch {
59
+ return String(args);
60
+ }
61
+ }
62
+ if ("summary" in call && typeof call.summary === "string") {
63
+ return call.summary;
64
+ }
65
+ return "";
66
+ }
67
+ function resultText(result) {
68
+ if (result === undefined || !Array.isArray(result.content))
69
+ return undefined;
70
+ const texts = result.content
71
+ .filter((c) => isRecord(c) && c.type === "text" && typeof c.text === "string")
72
+ .map((c) => c.text);
73
+ return texts.length === 0 ? undefined : texts.join("\n");
74
+ }
75
+ function formatDuration(ms) {
76
+ if (ms === undefined || !Number.isFinite(ms))
77
+ return "";
78
+ return ms < 1000 ? `${String(Math.round(ms))}ms` : `${(ms / 1000).toFixed(1)}s`;
79
+ }
80
+ export const NESTED_CALLS_EXPANDED_KEY = "__pi_ext_ui_nested_expanded__";
81
+ export const NESTED_CALLS_COPIED_KEY = "__pi_ext_ui_nested_copied__";
82
+ export class NestedCallsView {
83
+ #props;
84
+ #inMemoryLevels = new Map();
85
+ #copiedIndex = undefined;
86
+ #copiedTimer = undefined;
87
+ #lineMapping = [];
88
+ #renderCache;
89
+ constructor(props) {
90
+ this.#props = props;
91
+ const state = props.stateHolder;
92
+ if (state !== undefined) {
93
+ const stored = state[NESTED_CALLS_EXPANDED_KEY];
94
+ if (isRecord(stored)) {
95
+ for (const [k, v] of Object.entries(stored)) {
96
+ const idx = Number(k);
97
+ if (!Number.isNaN(idx) && (v === 1 || v === 2)) {
98
+ this.#inMemoryLevels.set(idx, v);
99
+ }
100
+ }
101
+ }
102
+ else if (Array.isArray(stored)) {
103
+ for (const item of stored) {
104
+ if (typeof item === "number") {
105
+ this.#inMemoryLevels.set(item, 1);
106
+ }
107
+ }
108
+ }
109
+ const copiedState = state[NESTED_CALLS_COPIED_KEY];
110
+ if (isRecord(copiedState) &&
111
+ typeof copiedState.index === "number" &&
112
+ typeof copiedState.until === "number") {
113
+ const remaining = copiedState.until - Date.now();
114
+ if (remaining > 0) {
115
+ this.#copiedIndex = copiedState.index;
116
+ this.#copiedTimer = setTimeout(() => {
117
+ this.#copiedIndex = undefined;
118
+ this.#renderCache = undefined;
119
+ const s = this.#props.stateHolder;
120
+ if (s !== undefined) {
121
+ delete s[NESTED_CALLS_COPIED_KEY];
122
+ }
123
+ this.#props.invalidate?.();
124
+ }, remaining);
125
+ this.#copiedTimer.unref?.();
126
+ }
127
+ else {
128
+ delete state[NESTED_CALLS_COPIED_KEY];
129
+ }
130
+ }
131
+ }
132
+ }
133
+ #getExpansionLevel(index) {
134
+ const state = this.#props.stateHolder;
135
+ if (state !== undefined) {
136
+ const stored = state[NESTED_CALLS_EXPANDED_KEY];
137
+ if (isRecord(stored)) {
138
+ const val = stored[String(index)];
139
+ if (val === 1 || val === 2)
140
+ return val;
141
+ }
142
+ else if (Array.isArray(stored)) {
143
+ if (stored.includes(index))
144
+ return 1;
145
+ }
146
+ }
147
+ return this.#inMemoryLevels.get(index) ?? 0;
148
+ }
149
+ #getLevelsKey() {
150
+ const parts = [];
151
+ for (let i = 0; i < this.#props.calls.length; i++) {
152
+ const level = this.#getExpansionLevel(i);
153
+ if (level !== 0)
154
+ parts.push(`${i}:${level}`);
155
+ }
156
+ return parts.join(",");
157
+ }
158
+ #setExpansionLevel(index, level) {
159
+ if (level === 0) {
160
+ this.#inMemoryLevels.delete(index);
161
+ }
162
+ else {
163
+ this.#inMemoryLevels.set(index, level);
164
+ }
165
+ this.#renderCache = undefined;
166
+ const state = this.#props.stateHolder;
167
+ if (state !== undefined) {
168
+ const record = {};
169
+ for (const [k, v] of this.#inMemoryLevels.entries()) {
170
+ record[String(k)] = v;
171
+ }
172
+ state[NESTED_CALLS_EXPANDED_KEY] = record;
173
+ }
174
+ }
175
+ #triggerCopy(callIndex, full) {
176
+ const call = this.#props.calls[callIndex];
177
+ if (!call)
178
+ return;
179
+ const provider = this.#props.resolvePresentation?.(call.toolName);
180
+ const presentation = provider?.("args" in call ? call.args : undefined, "result" in call ? call.result : undefined, this.#props.theme);
181
+ const text = stripTerminalSequences((full ? presentation?.copy?.full : presentation?.copy?.primary) ??
182
+ extractChildToolCopyText(call, full));
183
+ if (!text)
184
+ return;
185
+ const writeClip = this.#props.copyToClipboard ?? copyToSystemClipboard;
186
+ void writeClip(text).catch(() => { });
187
+ const durationMs = 1500;
188
+ const until = Date.now() + durationMs;
189
+ this.#copiedIndex = callIndex;
190
+ this.#renderCache = undefined;
191
+ const state = this.#props.stateHolder;
192
+ if (state !== undefined) {
193
+ state[NESTED_CALLS_COPIED_KEY] = {
194
+ index: callIndex,
195
+ until,
196
+ };
197
+ }
198
+ if (this.#copiedTimer !== undefined) {
199
+ clearTimeout(this.#copiedTimer);
200
+ }
201
+ this.#copiedTimer = setTimeout(() => {
202
+ this.#copiedIndex = undefined;
203
+ this.#renderCache = undefined;
204
+ const s = this.#props.stateHolder;
205
+ if (s !== undefined) {
206
+ delete s[NESTED_CALLS_COPIED_KEY];
207
+ }
208
+ this.#props.invalidate?.();
209
+ }, durationMs);
210
+ this.#copiedTimer.unref?.();
211
+ this.#props.invalidate?.();
212
+ }
213
+ render(width) {
214
+ const safeWidth = Math.max(1, width);
215
+ const levelsKey = this.#getLevelsKey();
216
+ if (this.#renderCache !== undefined &&
217
+ this.#renderCache.width === safeWidth &&
218
+ this.#renderCache.copiedIndex === this.#copiedIndex &&
219
+ this.#renderCache.levelsKey === levelsKey) {
220
+ this.#lineMapping = this.#renderCache.lineMapping;
221
+ return this.#renderCache.lines;
222
+ }
223
+ this.#lineMapping = [];
224
+ const { calls, theme } = this.#props;
225
+ const lines = [];
226
+ const rightMargin = 1;
227
+ for (let i = 0; i < calls.length; i++) {
228
+ const call = calls[i];
229
+ if (!call)
230
+ continue;
231
+ const toolName = "toolName" in call ? call.toolName : call.name;
232
+ const isError = Boolean(call.isError || ("status" in call && call.status === "error"));
233
+ const isRunning = ("status" in call && call.status === "running") ||
234
+ (!("status" in call) && call.result === undefined);
235
+ const expansionLevel = this.#getExpansionLevel(i);
236
+ const isExpanded = expansionLevel !== 0;
237
+ const rawSummary = "summary" in call ? call.summary : undefined;
238
+ const rawArgs = "args" in call ? call.args : undefined;
239
+ const inline = inlinePresentation(toolName, rawArgs ?? rawSummary, "result" in call ? call.result?.details : undefined, isError);
240
+ const argText = typeof rawArgs === "string" ? rawArgs : typeof rawSummary === "string" ? rawSummary : "";
241
+ const provider = this.#props.resolvePresentation?.(toolName);
242
+ const presentation = provider?.(rawArgs, "result" in call ? call.result : undefined, theme);
243
+ // The child row is the child's own header minus the fold pin, so it carries the same token
244
+ // assignment as the row the same call would draw at top level.
245
+ const childSummary = stripTerminalSequences(presentation?.summary ?? inline?.summary ?? argText);
246
+ const duration = formatDuration(call.durationMs);
247
+ const leftContent = formatToolHeaderText({
248
+ status: isRunning ? "running" : isError ? "error" : "ok",
249
+ title: toolName,
250
+ summary: [childSummary, duration].filter(Boolean).join(" · "),
251
+ }, theme);
252
+ const isCopied = this.#copiedIndex === i;
253
+ const copyIcon = isCopied ? theme.fg("success", "✓") : theme.fg("dim", "󰆏");
254
+ const copySlotWidth = 2 + rightMargin;
255
+ const maxLeft = Math.max(1, width - copySlotWidth);
256
+ const truncatedLeft = truncate(leftContent, maxLeft);
257
+ const leftVis = visibleWidth(truncatedLeft);
258
+ const gap = " ".repeat(Math.max(1, width - leftVis - rightMargin - 1));
259
+ const headerLine = truncate(truncatedLeft + gap + copyIcon + " ".repeat(rightMargin), width);
260
+ const headerLineIdx = lines.length;
261
+ lines.push(headerLine);
262
+ this.#lineMapping[headerLineIdx] = {
263
+ callIndex: i,
264
+ isHeader: true,
265
+ copyRegion: { startX: width - copySlotWidth - 1, endX: width },
266
+ };
267
+ if (isExpanded) {
268
+ const bodyLines = this.#renderChildBody(call, Math.max(1, width - 2), expansionLevel);
269
+ for (const rawLine of bodyLines) {
270
+ for (const bodyLine of toContentRows(rawLine)) {
271
+ const bodyLineIdx = lines.length;
272
+ lines.push(truncate(` ${bodyLine}`, width));
273
+ this.#lineMapping[bodyLineIdx] = {
274
+ callIndex: i,
275
+ isHeader: false,
276
+ };
277
+ }
278
+ }
279
+ }
280
+ }
281
+ this.#renderCache = {
282
+ width: safeWidth,
283
+ copiedIndex: this.#copiedIndex,
284
+ levelsKey,
285
+ lines,
286
+ lineMapping: this.#lineMapping,
287
+ };
288
+ return lines;
289
+ }
290
+ #renderChildBody(call, width, expansionLevel = 1) {
291
+ const { theme, context, resolveChildRenderer } = this.#props;
292
+ const toolName = "toolName" in call ? call.toolName : call.name;
293
+ const result = "result" in call ? call.result : undefined;
294
+ const args = "args" in call ? call.args : undefined;
295
+ if (result && resolveChildRenderer) {
296
+ const renderer = resolveChildRenderer(toolName);
297
+ if (renderer?.renderResult) {
298
+ try {
299
+ const childContext = {
300
+ ...context,
301
+ toolCallId: "toolCallId" in call ? call.toolCallId : context.toolCallId,
302
+ args,
303
+ durationMs: call.durationMs,
304
+ isError: Boolean(call.isError || ("status" in call && call.status === "error")),
305
+ state: undefined,
306
+ };
307
+ const comp = renderer.renderResult(result, { expanded: expansionLevel === 2, isPartial: false }, theme, childContext);
308
+ if (comp instanceof ToolView) {
309
+ comp.setFoldLevel(expansionLevel);
310
+ // Render body cleanly via deep headless interface without outer rail or header row
311
+ return comp.renderBody(width, { noRail: true });
312
+ }
313
+ return comp.render(width);
314
+ }
315
+ catch {
316
+ // Fall through to plain text extraction on renderer failure
317
+ }
318
+ }
319
+ }
320
+ // Fallback rendering
321
+ if (result && Array.isArray(result.content)) {
322
+ const text = result.content
323
+ .filter((c) => isRecord(c) && c.type === "text" && typeof c.text === "string")
324
+ .map((c) => c.text)
325
+ .join("\n")
326
+ .trim();
327
+ if (text) {
328
+ let lines;
329
+ const filePath = typeof args === "object" &&
330
+ args !== null &&
331
+ "path" in args &&
332
+ typeof args.path === "string"
333
+ ? args.path
334
+ : "";
335
+ const lang = filePath ? getLanguageFromPath(filePath) : undefined;
336
+ if (lang) {
337
+ try {
338
+ lines = highlightCode(text, lang);
339
+ }
340
+ catch {
341
+ lines = text.split("\n");
342
+ }
343
+ }
344
+ else {
345
+ lines = text.split("\n");
346
+ }
347
+ if (expansionLevel === 2 || lines.length <= 20) {
348
+ return lines;
349
+ }
350
+ return [...lines.slice(0, 20), `... (${lines.length - 20} more lines)`];
351
+ }
352
+ }
353
+ if ("error" in call && typeof call.error === "string" && call.error) {
354
+ return call.error.split("\n").map((l) => theme.fg("error", l));
355
+ }
356
+ if (args !== undefined) {
357
+ const text = typeof args === "string" ? args : JSON.stringify(args, null, 2);
358
+ return text.split("\n").map((l) => theme.fg("dim", l));
359
+ }
360
+ return [theme.fg("dim", "(no output)")];
361
+ }
362
+ handleMouse(event) {
363
+ if (event.type !== "click" || event.button !== "left")
364
+ return undefined;
365
+ const mapping = this.#lineMapping[event.y];
366
+ if (!mapping)
367
+ return { handled: true };
368
+ if (mapping.isHeader) {
369
+ if (mapping.copyRegion && event.x >= mapping.copyRegion.startX) {
370
+ this.#triggerCopy(mapping.callIndex, event.shift === true);
371
+ return { handled: true, render: true };
372
+ }
373
+ const isCtrl = event.ctrl === true;
374
+ const currentLevel = this.#getExpansionLevel(mapping.callIndex);
375
+ const nextLevel = transitionFoldLevel(currentLevel, isCtrl);
376
+ this.#setExpansionLevel(mapping.callIndex, nextLevel);
377
+ this.#props.invalidate?.();
378
+ return { handled: true, render: true };
379
+ }
380
+ return { handled: true };
381
+ }
382
+ dispose() {
383
+ if (this.#copiedTimer !== undefined) {
384
+ clearTimeout(this.#copiedTimer);
385
+ this.#copiedTimer = undefined;
386
+ }
387
+ this.#renderCache = undefined;
388
+ }
389
+ collapseAll() {
390
+ if (this.#copiedTimer !== undefined) {
391
+ clearTimeout(this.#copiedTimer);
392
+ this.#copiedTimer = undefined;
393
+ }
394
+ this.#inMemoryLevels.clear();
395
+ const state = this.#props.stateHolder;
396
+ if (state !== undefined) {
397
+ state[NESTED_CALLS_EXPANDED_KEY] = {};
398
+ }
399
+ this.#lineMapping = [];
400
+ this.#renderCache = undefined;
401
+ }
402
+ invalidate() {
403
+ this.#lineMapping = [];
404
+ this.#renderCache = undefined;
405
+ }
406
+ }
@@ -0,0 +1,120 @@
1
+ import type { AgentToolResult } from "@earendil-works/pi-coding-agent";
2
+ import { type DiffLine, type DiffSource } from "./diff-view.js";
3
+ import type { ToolStatusKind } from "./tool-view.js";
4
+ export declare const APPLY_PATCH_TOOL_NAME = "apply_patch";
5
+ export declare const PYTHON_EVAL_TOOL_NAME = "python_eval";
6
+ export declare const PYTHON_EVAL_DETAILS_FORMAT = "pi-ext-tools-python-eval";
7
+ export declare const EDIT_TOOL_NAME = "edit";
8
+ export declare const WRITE_TOOL_NAME = "write";
9
+ export declare const EDIT_VIEW_KEY = "__piExtToolsEditView";
10
+ export declare const WRITE_VIEW_KEY = "__piExtToolsWriteView";
11
+ export declare const CODEMODE_OPTIONS_PREFIX = "// @options:";
12
+ export declare const CODEMODE_SCRIPT_HEADER: RegExp;
13
+ export interface NestedCallRecord {
14
+ readonly toolName: string;
15
+ readonly summary?: string | undefined;
16
+ readonly status?: "running" | "ok" | "error" | "cancelled" | undefined;
17
+ readonly isError?: boolean | undefined;
18
+ readonly isWarning?: boolean | undefined;
19
+ readonly durationMs?: number | undefined;
20
+ readonly cost?: number | undefined;
21
+ readonly error?: string | undefined;
22
+ }
23
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
24
+ export declare function estimateStreamingTokens(args: unknown): number;
25
+ export declare function formatDurationMs(ms: number | undefined): string;
26
+ export declare function formatCostUsd(cost: number | undefined): string;
27
+ /** One header fact for a deadline, in the same shape codemode's `timeout_ms` renders as. */
28
+ export declare function formatTimeoutFact(timeoutMs: number): string;
29
+ export declare function extractCodemodeHeaderFacts(args: unknown): string;
30
+ export declare function parseCallArgs(raw: unknown): Record<string, unknown> | undefined;
31
+ export declare function extractSummaryArg(toolName: string, args: unknown): string;
32
+ export declare function extractHeavyInputCommand(toolName: string, args: unknown): string | undefined;
33
+ export declare function extractNestedCalls(result: AgentToolResult<unknown> | undefined): readonly NestedCallRecord[];
34
+ export declare function extractResultText(toolName: string, result: AgentToolResult<unknown>): {
35
+ readonly outputText: string;
36
+ readonly wallTimeSeconds: number | undefined;
37
+ };
38
+ export declare function callStatsParts(total: number, failCount: number): string[];
39
+ export declare function resolveContainerStatus(params: {
40
+ readonly isError: boolean;
41
+ readonly isPartial: boolean;
42
+ readonly hasChildren: boolean;
43
+ readonly selfFailed: boolean;
44
+ }): ToolStatusKind;
45
+ export declare function toolPath(args: unknown): string;
46
+ export declare function editPath(args: unknown): string;
47
+ export declare function numericField(value: unknown, key: string): number | undefined;
48
+ export type WritePresentation = {
49
+ readonly kind: "diff";
50
+ readonly source: DiffSource;
51
+ readonly added: number;
52
+ readonly removed: number;
53
+ } | {
54
+ readonly kind: "content";
55
+ readonly label: string;
56
+ readonly content: string;
57
+ } | {
58
+ readonly kind: "noChange";
59
+ };
60
+ export declare function writeStoredDiff(lines: readonly unknown[]): DiffSource | undefined;
61
+ /** Uses only the baseline and content already stored by the write tool. */
62
+ export declare function writePresentation(args: unknown, details: unknown): WritePresentation | undefined;
63
+ /** Compact nested-row result: a one-line summary plus the status it reports. */
64
+ export interface InlinePresentation {
65
+ readonly summary: string;
66
+ readonly status: ToolStatusKind;
67
+ }
68
+ export declare function fileToolPresentation(toolName: string, args: unknown, details: unknown, isError: boolean): InlinePresentation | undefined;
69
+ /** Pi native edit persists a unified patch plus the same change as a numbered display diff. */
70
+ export declare function editDiffSource(details: unknown): DiffSource | undefined;
71
+ export declare function displayDiffSource(diff: string): DiffSource | undefined;
72
+ export declare function editViewSource(view: unknown): DiffSource | undefined;
73
+ export declare function editViewHunks(view: unknown): readonly {
74
+ readonly lines: readonly DiffLine[];
75
+ }[];
76
+ export declare function hunksForEditOp(op: Record<string, unknown>): readonly {
77
+ readonly lines: readonly DiffLine[];
78
+ }[];
79
+ export declare function splitFragment(text: string): readonly string[];
80
+ /** Line LCS of one stored before/after fragment. No file context is invented. */
81
+ export declare function lineHunks(before: string, after: string, startLine: number): readonly {
82
+ readonly lines: readonly DiffLine[];
83
+ }[];
84
+ export declare function patchTextForCopy(details: unknown): string | undefined;
85
+ export declare function patchSummary(files: number, added: number, removed: number): string;
86
+ /**
87
+ * One compact nested summary for a tool call. Adopts the tool's standard collapsed
88
+ * single-line format when recognized, degrading to undefined so callers can use
89
+ * the default argument rendering.
90
+ */
91
+ export declare function inlinePresentation(toolName: string, args: unknown, details: unknown, isError: boolean): InlinePresentation | undefined;
92
+ /**
93
+ * One row of the eval cell transcript: printed text, display value, nested call, the final
94
+ * value, or the kernel's namespace report.
95
+ */
96
+ export type EvalRow = {
97
+ readonly kind: "text";
98
+ readonly text: string;
99
+ } | {
100
+ readonly kind: "display";
101
+ readonly text: string;
102
+ } | {
103
+ readonly kind: "tool";
104
+ readonly name: string;
105
+ readonly args: unknown;
106
+ readonly durationMs: number | undefined;
107
+ readonly error: string | undefined;
108
+ } | {
109
+ readonly kind: "result";
110
+ readonly text: string;
111
+ } | {
112
+ readonly kind: "state";
113
+ readonly text: string;
114
+ };
115
+ export interface EvalFacts {
116
+ readonly rows: readonly EvalRow[];
117
+ readonly error: string | undefined;
118
+ }
119
+ /** Reads only the persisted `pi-ext-tools-eval` details; other shapes degrade to plain output. */
120
+ export declare function pythonEvalFacts(details: unknown): EvalFacts | undefined;