@hheei/pi-ext-ui 0.3.0 → 0.4.1
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/dist/diff-view.js +5 -2
- package/dist/extension.js +4 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/message-view.js +9 -2
- package/dist/nested-calls-view.js +19 -9
- package/dist/payload-parser.js +46 -23
- package/dist/renderer.d.ts +2 -4
- package/dist/renderer.js +188 -82
- package/dist/tool-view.d.ts +2 -0
- package/dist/tool-view.js +118 -19
- package/dist/tracker.d.ts +19 -0
- package/dist/tracker.js +75 -60
- package/package.json +2 -2
- package/dist/turn-copy.d.ts +0 -11
- package/dist/turn-copy.js +0 -20
package/dist/diff-view.js
CHANGED
|
@@ -153,9 +153,12 @@ export class DiffView {
|
|
|
153
153
|
if (!this.#parsed.ok)
|
|
154
154
|
rows.push(this.#theme.fg("warning", `! ${this.#parsed.diagnostic}`));
|
|
155
155
|
for (const raw of (this.#rawText ?? "").split(/\r?\n/)) {
|
|
156
|
-
|
|
156
|
+
const wrapped = wrapTextWithAnsi(raw, width);
|
|
157
|
+
for (const line of wrapped.length > 0 ? wrapped : [""]) {
|
|
158
|
+
rows.push(this.#theme.fg("dim", line));
|
|
159
|
+
}
|
|
157
160
|
}
|
|
158
|
-
return rows
|
|
161
|
+
return rows;
|
|
159
162
|
}
|
|
160
163
|
#renderDiff(width, hunks) {
|
|
161
164
|
const numberCol = numberWidth(hunks);
|
package/dist/extension.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { messageViewPoint, registerExtensionHook, registerExtensionLifecycle, toolRendererPoint, } from "@hheei/pi-ext-core";
|
|
2
2
|
import { createMessageView } from "./message-view.js";
|
|
3
3
|
import { createOptionalToolRendererFactory, registerCollapsibleToolRenderer } from "./renderer.js";
|
|
4
|
+
import { clearCapturedNestedCalls } from "./tracker.js";
|
|
4
5
|
export default function (pi) {
|
|
5
6
|
registerCollapsibleToolRenderer(pi);
|
|
6
7
|
registerExtensionLifecycle(pi, {
|
|
7
8
|
key: "@hheei/pi-ext-ui",
|
|
8
9
|
start(context) {
|
|
10
|
+
context.resources.add("tracker-cleanup", () => {
|
|
11
|
+
clearCapturedNestedCalls();
|
|
12
|
+
});
|
|
9
13
|
const toolRenderers = registerExtensionHook(pi, toolRendererPoint, createOptionalToolRendererFactory(pi));
|
|
10
14
|
context.resources.add("tool-renderer", () => toolRenderers.dispose());
|
|
11
15
|
const handle = registerExtensionHook(context.pi, messageViewPoint, createMessageView);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export { type DiffHunk, type DiffLine, type DiffSource, type DiffStats, DiffView, type DiffViewOptions, diffStats, } from "./diff-view.js";
|
|
2
2
|
export { createMessageView, type MessageRows, type MessageTone, MessageView, type MessageViewHeader, type MessageViewSpec, } from "./message-view.js";
|
|
3
3
|
export { extractChildToolCopyText, NestedCallsView, type NestedCallsViewProps, } from "./nested-calls-view.js";
|
|
4
|
-
export { type CollapsibleToolRendererOptions, createCollapsibleToolRendererResolver, estimateStreamingTokens, inlinePresentation, type NestedCallRecord, registerCollapsibleToolRenderer, SPECIALIZED_TOOL_RENDERERS, type SpecializedToolRendererName, type ToolRenderContext,
|
|
4
|
+
export { type CollapsibleToolRendererOptions, createCollapsibleToolRendererResolver, estimateStreamingTokens, inlinePresentation, isMcpTool, type NestedCallRecord, registerCollapsibleToolRenderer, SPECIALIZED_TOOL_RENDERERS, type SpecializedToolRendererName, type ToolRenderContext, } from "./renderer.js";
|
|
5
5
|
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";
|
|
6
6
|
export { type CapturedToolCall, clearCapturedNestedCalls, getCapturedNestedCalls, recordToolExecutionEnd, recordToolExecutionStart, } from "./tracker.js";
|
|
7
|
-
export { formatTurnFlow, type TurnFlowData, type TurnToolCallSummary, } from "./turn-copy.js";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export { DiffView, diffStats, } from "./diff-view.js";
|
|
2
2
|
export { createMessageView, MessageView, } from "./message-view.js";
|
|
3
3
|
export { extractChildToolCopyText, NestedCallsView, } from "./nested-calls-view.js";
|
|
4
|
-
export { createCollapsibleToolRendererResolver, estimateStreamingTokens, inlinePresentation, registerCollapsibleToolRenderer, SPECIALIZED_TOOL_RENDERERS,
|
|
4
|
+
export { createCollapsibleToolRendererResolver, estimateStreamingTokens, inlinePresentation, isMcpTool, registerCollapsibleToolRenderer, SPECIALIZED_TOOL_RENDERERS, } from "./renderer.js";
|
|
5
5
|
export { createToolView, formatStatusGlyph, formatToolHeaderText, isCollapsible, ToolView, transitionFoldLevel, } from "./tool-view.js";
|
|
6
6
|
export { clearCapturedNestedCalls, getCapturedNestedCalls, recordToolExecutionEnd, recordToolExecutionStart, } from "./tracker.js";
|
|
7
|
-
export { formatTurnFlow, } from "./turn-copy.js";
|
package/dist/message-view.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { truncateToWidth, } from "@earendil-works/pi-tui";
|
|
1
|
+
import { truncateToWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
|
|
2
2
|
import { toContentRows, toSingleRow } from "./tool-view.js";
|
|
3
3
|
/**
|
|
4
4
|
* A transcript notice, not a tool execution.
|
|
@@ -38,7 +38,14 @@ function layRows(source, width) {
|
|
|
38
38
|
const raw = isComponent(source)
|
|
39
39
|
? source.render(inner).flatMap((line) => toContentRows(line))
|
|
40
40
|
: (typeof source === "string" ? [source] : source).flatMap((line) => toContentRows(line));
|
|
41
|
-
|
|
41
|
+
const rows = [];
|
|
42
|
+
for (const line of raw) {
|
|
43
|
+
const wrapped = wrapTextWithAnsi(line, inner);
|
|
44
|
+
for (const chunk of wrapped.length > 0 ? wrapped : [""]) {
|
|
45
|
+
rows.push(truncate(` ${chunk}`, width));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return rows;
|
|
42
49
|
}
|
|
43
50
|
function invalidateRows(source) {
|
|
44
51
|
if (source !== undefined && isComponent(source))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { copyToClipboard as copyToSystemClipboard, getLanguageFromPath, highlightCode, } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { stripTerminalSequences, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
|
|
2
|
+
import { stripTerminalSequences, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
|
|
3
3
|
import { inlinePresentation } from "./renderer.js";
|
|
4
4
|
import { formatToolHeaderText, ToolView, toContentRows, transitionFoldLevel, } from "./tool-view.js";
|
|
5
5
|
function truncate(text, width) {
|
|
@@ -268,15 +268,20 @@ export class NestedCallsView {
|
|
|
268
268
|
copyRegion: { startX: width - copySlotWidth - 1, endX: width },
|
|
269
269
|
};
|
|
270
270
|
if (isExpanded) {
|
|
271
|
-
const
|
|
271
|
+
const childWidth = Math.max(1, width - 2);
|
|
272
|
+
const bodyLines = this.#renderChildBody(call, childWidth, expansionLevel);
|
|
272
273
|
for (const rawLine of bodyLines) {
|
|
273
274
|
for (const bodyLine of toContentRows(rawLine)) {
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
275
|
+
const wrapped = wrapTextWithAnsi(bodyLine, childWidth);
|
|
276
|
+
const chunks = wrapped.length > 0 ? wrapped : [""];
|
|
277
|
+
for (const chunk of chunks) {
|
|
278
|
+
const bodyLineIdx = lines.length;
|
|
279
|
+
lines.push(truncate(` ${chunk}`, width));
|
|
280
|
+
this.#lineMapping[bodyLineIdx] = {
|
|
281
|
+
callIndex: i,
|
|
282
|
+
isHeader: false,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
280
285
|
}
|
|
281
286
|
}
|
|
282
287
|
}
|
|
@@ -343,7 +348,12 @@ export class NestedCallsView {
|
|
|
343
348
|
else {
|
|
344
349
|
lines = text.split("\n");
|
|
345
350
|
}
|
|
346
|
-
if (expansionLevel === 2
|
|
351
|
+
if (expansionLevel === 2) {
|
|
352
|
+
if (lines.length <= 500)
|
|
353
|
+
return lines;
|
|
354
|
+
return [...lines.slice(0, 500), `... (${lines.length - 500} more lines)`];
|
|
355
|
+
}
|
|
356
|
+
if (lines.length <= 20) {
|
|
347
357
|
return lines;
|
|
348
358
|
}
|
|
349
359
|
return [...lines.slice(0, 20), `... (${lines.length - 20} more lines)`];
|
package/dist/payload-parser.js
CHANGED
|
@@ -125,32 +125,55 @@ export function extractSummaryArg(toolName, args) {
|
|
|
125
125
|
const parsed = parseCallArgs(args);
|
|
126
126
|
if (!parsed)
|
|
127
127
|
return "";
|
|
128
|
-
|
|
129
|
-
"
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
"
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
|
|
138
|
-
"
|
|
139
|
-
"
|
|
140
|
-
"
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return stripTerminalSequences(
|
|
128
|
+
const firstLine = (val) => {
|
|
129
|
+
if (typeof val !== "string" || val.trim() === "")
|
|
130
|
+
return "";
|
|
131
|
+
const clean = stripTerminalSequences(val.trim());
|
|
132
|
+
return clean.split("\n")[0]?.trim() ?? clean;
|
|
133
|
+
};
|
|
134
|
+
switch (toolName) {
|
|
135
|
+
case "bash":
|
|
136
|
+
case "bg_bash_spawn":
|
|
137
|
+
return firstLine(parsed.command);
|
|
138
|
+
case "read":
|
|
139
|
+
case "edit":
|
|
140
|
+
case "write": {
|
|
141
|
+
const path = typeof parsed.path === "string"
|
|
142
|
+
? parsed.path.trim()
|
|
143
|
+
: typeof parsed.file_path === "string"
|
|
144
|
+
? parsed.file_path.trim()
|
|
145
|
+
: "";
|
|
146
|
+
return stripTerminalSequences(path);
|
|
147
|
+
}
|
|
148
|
+
case "grep":
|
|
149
|
+
case "find": {
|
|
150
|
+
const pattern = typeof parsed.pattern === "string" ? parsed.pattern.trim() : "";
|
|
151
|
+
const path = typeof parsed.path === "string" ? parsed.path.trim() : "";
|
|
152
|
+
if (!pattern)
|
|
153
|
+
return path ? stripTerminalSequences(path) : "";
|
|
154
|
+
const target = path && path !== "." ? ` ${path}` : "";
|
|
155
|
+
return `${pattern}${target}`;
|
|
147
156
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
157
|
+
case "python_eval":
|
|
158
|
+
return firstLine(parsed.code);
|
|
159
|
+
case "bg_bash_get":
|
|
160
|
+
case "bg_bash_stop":
|
|
161
|
+
case "agent_send":
|
|
162
|
+
case "agent_get":
|
|
163
|
+
case "agent_stop":
|
|
164
|
+
return typeof parsed.id === "string" ? parsed.id.trim() : "";
|
|
165
|
+
case "agent_spawn": {
|
|
166
|
+
const agent = typeof parsed.agent === "string" ? parsed.agent.trim() : "";
|
|
167
|
+
const task = firstLine(parsed.task);
|
|
168
|
+
if (agent && task)
|
|
169
|
+
return `${agent}: ${task}`;
|
|
170
|
+
return agent || task;
|
|
151
171
|
}
|
|
172
|
+
case "tool_search":
|
|
173
|
+
return typeof parsed.query === "string" ? parsed.query.trim() : "";
|
|
174
|
+
default:
|
|
175
|
+
return "";
|
|
152
176
|
}
|
|
153
|
-
return "";
|
|
154
177
|
}
|
|
155
178
|
export function extractHeavyInputCommand(toolName, args) {
|
|
156
179
|
if (!isRecord(args))
|
package/dist/renderer.d.ts
CHANGED
|
@@ -7,9 +7,6 @@ export type ToolRenderContext = Parameters<NonNullable<ToolRenderers["renderCall
|
|
|
7
7
|
*/
|
|
8
8
|
export declare const SPECIALIZED_TOOL_RENDERERS: readonly ["codemode"];
|
|
9
9
|
export type SpecializedToolRendererName = (typeof SPECIALIZED_TOOL_RENDERERS)[number];
|
|
10
|
-
/** Web-access tools use the shared folding shell while retaining their native body renderer. */
|
|
11
|
-
export declare const WEB_ACCESS_TOOLS: readonly ["web_search", "web_fetch", "web_open"];
|
|
12
|
-
export type WebAccessToolName = (typeof WEB_ACCESS_TOOLS)[number];
|
|
13
10
|
export { estimateStreamingTokens, inlinePresentation, type NestedCallRecord, } from "./payload-parser.js";
|
|
14
11
|
export interface CollapsibleToolRendererOptions {
|
|
15
12
|
readonly tools?: readonly string[] | undefined;
|
|
@@ -18,7 +15,8 @@ export interface CollapsibleToolRendererOptions {
|
|
|
18
15
|
readonly defaultCollapsed?: boolean | undefined;
|
|
19
16
|
readonly copyToClipboard?: ((text: string) => Promise<void>) | undefined;
|
|
20
17
|
}
|
|
18
|
+
export declare function isMcpTool(toolName: string): boolean;
|
|
21
19
|
export declare function createCollapsibleToolRendererResolver(options?: CollapsibleToolRendererOptions): ToolRendererResolver;
|
|
22
20
|
/** UI factory consumed by package-owned direct renderers, independently of host resolver precedence. */
|
|
23
21
|
export declare function createOptionalToolRendererFactory(pi: ExtensionAPI, options?: CollapsibleToolRendererOptions): import("@hheei/pi-ext-core").ToolRendererFactory;
|
|
24
|
-
export declare function registerCollapsibleToolRenderer(pi: ExtensionAPI, options?: CollapsibleToolRendererOptions): void;
|
|
22
|
+
export declare function registerCollapsibleToolRenderer(pi: ExtensionAPI, options?: CollapsibleToolRendererOptions): () => void;
|
package/dist/renderer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getLanguageFromPath, highlightCode, } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { stripTerminalSequences } from "@earendil-works/pi-tui";
|
|
3
|
-
import { agentResultText, getRegisteredToolPresentationProvider, getToolPresentationProvider, hasOptionalToolRenderers, } from "@hheei/pi-ext-core";
|
|
3
|
+
import { agentResultText, getRegisteredToolPresentationProvider, getRegisteredToolRenderers, getToolPresentationProvider, hasOptionalToolRenderers, } from "@hheei/pi-ext-core";
|
|
4
4
|
import { DiffView } from "./diff-view.js";
|
|
5
5
|
import { NestedCallsView } from "./nested-calls-view.js";
|
|
6
6
|
import { formatToolHeaderText, ToolView, } from "./tool-view.js";
|
|
@@ -11,18 +11,17 @@ import { getCapturedNestedCalls, recordToolExecutionEnd, recordToolExecutionStar
|
|
|
11
11
|
* appear here.
|
|
12
12
|
*/
|
|
13
13
|
export const SPECIALIZED_TOOL_RENDERERS = ["codemode"];
|
|
14
|
-
|
|
15
|
-
export const WEB_ACCESS_TOOLS = ["web_search", "web_fetch", "web_open"];
|
|
16
|
-
import { callStatsParts, estimateStreamingTokens, extractHeavyInputCommand, extractNestedCalls, extractResultText, extractSummaryArg, formatCostUsd, formatDurationMs, inlinePresentation, isRecord, resolveContainerStatus, toolPath, } from "./payload-parser.js";
|
|
14
|
+
import { callStatsParts, estimateStreamingTokens, extractCodemodeHeaderFacts, extractHeavyInputCommand, extractNestedCalls, extractResultText, extractSummaryArg, formatCostUsd, formatDurationMs, inlinePresentation, isRecord, resolveContainerStatus, } from "./payload-parser.js";
|
|
17
15
|
export { estimateStreamingTokens, inlinePresentation, } from "./payload-parser.js";
|
|
18
16
|
const SPECIALIZED_TOOL_SET = new Set(SPECIALIZED_TOOL_RENDERERS);
|
|
19
|
-
const WEB_ACCESS_TOOL_SET = new Set(WEB_ACCESS_TOOLS);
|
|
20
17
|
const CALL_VIEW_STATE_KEY = "__piExtUiCallView";
|
|
21
18
|
function detectCodeLanguage(toolName) {
|
|
22
19
|
if (toolName === "bash")
|
|
23
20
|
return "bash";
|
|
24
21
|
if (toolName === "codemode")
|
|
25
22
|
return "javascript";
|
|
23
|
+
if (toolName === "python_eval")
|
|
24
|
+
return "python";
|
|
26
25
|
return undefined;
|
|
27
26
|
}
|
|
28
27
|
function createInputCodeSection(toolName, args, theme, railToken = "muted") {
|
|
@@ -46,6 +45,39 @@ function createInputCodeSection(toolName, args, theme, railToken = "muted") {
|
|
|
46
45
|
}
|
|
47
46
|
return { railToken, content: lines };
|
|
48
47
|
}
|
|
48
|
+
function resolveToolCallSummary(params) {
|
|
49
|
+
const { toolName, args, originalRenderCall, theme, context } = params;
|
|
50
|
+
if (toolName === "codemode") {
|
|
51
|
+
return extractCodemodeHeaderFacts(args);
|
|
52
|
+
}
|
|
53
|
+
// Tier 1: Host Native RenderCall Delegation
|
|
54
|
+
if (originalRenderCall !== undefined) {
|
|
55
|
+
try {
|
|
56
|
+
const upstreamLines = originalRenderCall(args, theme, context).render(120);
|
|
57
|
+
const firstNonEmpty = upstreamLines
|
|
58
|
+
.map((line) => stripTerminalSequences(line).trim())
|
|
59
|
+
.find((line) => line !== "");
|
|
60
|
+
if (firstNonEmpty !== undefined) {
|
|
61
|
+
if (firstNonEmpty.startsWith("$ ")) {
|
|
62
|
+
return firstNonEmpty.slice(2).trim();
|
|
63
|
+
}
|
|
64
|
+
if (firstNonEmpty === toolName) {
|
|
65
|
+
return "";
|
|
66
|
+
}
|
|
67
|
+
if (firstNonEmpty.startsWith(toolName) &&
|
|
68
|
+
/^[\s(:=/[]/.test(firstNonEmpty.slice(toolName.length))) {
|
|
69
|
+
return firstNonEmpty.slice(toolName.length).trim();
|
|
70
|
+
}
|
|
71
|
+
return firstNonEmpty;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Upstream threw, fall through to fallback
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Tier 2: Contractual Fallback for built-in tools
|
|
79
|
+
return extractSummaryArg(toolName, args);
|
|
80
|
+
}
|
|
49
81
|
function formatArgsPreview(args) {
|
|
50
82
|
if (args === undefined || args === null)
|
|
51
83
|
return "";
|
|
@@ -104,6 +136,12 @@ function formatNestedCallRow(call, theme) {
|
|
|
104
136
|
.join(" · "),
|
|
105
137
|
}, theme);
|
|
106
138
|
}
|
|
139
|
+
export function isMcpTool(toolName) {
|
|
140
|
+
return (toolName.startsWith("mcp__") ||
|
|
141
|
+
toolName === "read_mcp_resource" ||
|
|
142
|
+
toolName === "list_mcp_resources" ||
|
|
143
|
+
toolName === "list_mcp_resource_templates");
|
|
144
|
+
}
|
|
107
145
|
function shouldInterceptTool(toolName, options) {
|
|
108
146
|
if (options?.catchAll === true) {
|
|
109
147
|
return options.match !== undefined ? options.match(toolName) : true;
|
|
@@ -115,10 +153,10 @@ function shouldInterceptTool(toolName, options) {
|
|
|
115
153
|
if (toolList !== undefined) {
|
|
116
154
|
return toolList.includes(toolName);
|
|
117
155
|
}
|
|
118
|
-
return SPECIALIZED_TOOL_SET.has(toolName) ||
|
|
156
|
+
return SPECIALIZED_TOOL_SET.has(toolName) || isMcpTool(toolName);
|
|
119
157
|
}
|
|
120
158
|
function renderGenericResult(params) {
|
|
121
|
-
const { toolName, theme, context, result, renderOptions, defaultLevel, originalRenderCall, originalRenderResult, resolvePresentation, options, } = params;
|
|
159
|
+
const { toolName, theme, context, result, renderOptions, defaultLevel, originalRenderCall, originalRenderResult, resolvePresentation, resolveNativeRenderer, options, } = params;
|
|
122
160
|
const heavyCmd = extractHeavyInputCommand(toolName, context.args);
|
|
123
161
|
const nestedCalls = extractNestedCalls(result);
|
|
124
162
|
const captured = toolName === "codemode"
|
|
@@ -150,19 +188,15 @@ function renderGenericResult(params) {
|
|
|
150
188
|
? "running"
|
|
151
189
|
: "ok";
|
|
152
190
|
const summaryParts = [];
|
|
153
|
-
const argSummary =
|
|
154
|
-
|
|
191
|
+
const argSummary = resolveToolCallSummary({
|
|
192
|
+
toolName,
|
|
193
|
+
args: context.args,
|
|
194
|
+
originalRenderCall,
|
|
195
|
+
theme,
|
|
196
|
+
context,
|
|
197
|
+
});
|
|
198
|
+
if (argSummary !== "")
|
|
155
199
|
summaryParts.push(argSummary);
|
|
156
|
-
}
|
|
157
|
-
else if (originalRenderCall !== undefined && toolName !== "codemode") {
|
|
158
|
-
const upstreamLines = originalRenderCall(context.args, theme, context).render(120);
|
|
159
|
-
const firstLine = stripTerminalSequences(upstreamLines[0] ?? "").trim();
|
|
160
|
-
const stripped = firstLine.startsWith(toolName)
|
|
161
|
-
? firstLine.slice(toolName.length).trim()
|
|
162
|
-
: firstLine;
|
|
163
|
-
if (stripped !== "")
|
|
164
|
-
summaryParts.push(stripped);
|
|
165
|
-
}
|
|
166
200
|
if (isMetaTool && childCalls.length > 0) {
|
|
167
201
|
const failCount = childCalls.filter((c) => Boolean(c.isError || ("status" in c && c.status === "error"))).length;
|
|
168
202
|
summaryParts.push(...callStatsParts(childCalls.length, failCount));
|
|
@@ -195,9 +229,12 @@ function renderGenericResult(params) {
|
|
|
195
229
|
theme: childTheme,
|
|
196
230
|
context: childCtx,
|
|
197
231
|
defaultLevel: childOptions.expanded ? 2 : 1,
|
|
232
|
+
resolvePresentation,
|
|
233
|
+
resolveNativeRenderer,
|
|
198
234
|
options,
|
|
199
235
|
});
|
|
200
236
|
}
|
|
237
|
+
const registered = resolveNativeRenderer?.(childName);
|
|
201
238
|
return renderGenericResult({
|
|
202
239
|
toolName: childName,
|
|
203
240
|
theme: childTheme,
|
|
@@ -205,9 +242,10 @@ function renderGenericResult(params) {
|
|
|
205
242
|
result: childResult,
|
|
206
243
|
renderOptions: childOptions,
|
|
207
244
|
defaultLevel: childOptions.expanded ? 2 : 1,
|
|
208
|
-
originalRenderCall:
|
|
209
|
-
originalRenderResult:
|
|
245
|
+
originalRenderCall: registered?.renderCall,
|
|
246
|
+
originalRenderResult: registered?.renderResult,
|
|
210
247
|
resolvePresentation,
|
|
248
|
+
resolveNativeRenderer,
|
|
211
249
|
options,
|
|
212
250
|
});
|
|
213
251
|
},
|
|
@@ -231,7 +269,16 @@ function renderGenericResult(params) {
|
|
|
231
269
|
: "success";
|
|
232
270
|
// Upper room (input code) uses neutral/muted rail; lower room (output) uses toolRailToken (success/error)
|
|
233
271
|
const inputSection = createInputCodeSection(toolName, context.args, theme, "muted");
|
|
234
|
-
const sections =
|
|
272
|
+
const sections = [];
|
|
273
|
+
if (inputSection !== undefined) {
|
|
274
|
+
sections.push(inputSection);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
const preview = formatArgsPreview(context.args);
|
|
278
|
+
if (preview !== "" && preview !== "{}") {
|
|
279
|
+
sections.push({ railToken: "muted", content: preview });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
235
282
|
if (toolName === "codemode") {
|
|
236
283
|
// Nested tool calls belong only to collapsedRows (Level 0), hidden when expanded
|
|
237
284
|
if (rawOutput !== "") {
|
|
@@ -267,36 +314,14 @@ function renderGenericResult(params) {
|
|
|
267
314
|
sections.push({
|
|
268
315
|
railToken: toolRailToken,
|
|
269
316
|
content: lazyUpstreamComponent,
|
|
270
|
-
maxLines: effectiveExpanded ? Number.POSITIVE_INFINITY :
|
|
317
|
+
maxLines: effectiveExpanded ? Number.POSITIVE_INFINITY : undefined,
|
|
271
318
|
});
|
|
272
319
|
}
|
|
273
320
|
else if (rawOutput !== "") {
|
|
274
321
|
const rawLines = rawOutput.split("\n");
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
else if (toolName === "read" || toolPath(context.args) !== "") {
|
|
280
|
-
const filePath = toolPath(context.args);
|
|
281
|
-
const lang = filePath !== "" ? getLanguageFromPath(filePath) : undefined;
|
|
282
|
-
if (lang !== undefined) {
|
|
283
|
-
try {
|
|
284
|
-
outputLines = highlightCode(rawOutput, lang);
|
|
285
|
-
}
|
|
286
|
-
catch {
|
|
287
|
-
outputLines = rawLines;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
else {
|
|
291
|
-
outputLines = rawLines;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
else if (toolName === "bash") {
|
|
295
|
-
outputLines = rawLines.map((line) => theme.fg("dim", line));
|
|
296
|
-
}
|
|
297
|
-
else {
|
|
298
|
-
outputLines = rawLines;
|
|
299
|
-
}
|
|
322
|
+
const outputLines = isError
|
|
323
|
+
? rawLines.map((line) => theme.fg("error", line))
|
|
324
|
+
: rawLines.map((line) => theme.fg("dim", line));
|
|
300
325
|
sections.push({
|
|
301
326
|
railToken: toolRailToken,
|
|
302
327
|
content: outputLines,
|
|
@@ -345,7 +370,7 @@ function renderGenericResult(params) {
|
|
|
345
370
|
* pass never asks the tool to rebuild its rows.
|
|
346
371
|
*/
|
|
347
372
|
function renderOwnedPresentation(params) {
|
|
348
|
-
const { toolName, presentation, result, theme, context, defaultLevel, options } = params;
|
|
373
|
+
const { toolName, presentation, result, theme, context, defaultLevel, resolvePresentation, resolveNativeRenderer, options, } = params;
|
|
349
374
|
const isError = context.isError || result?.isError === true;
|
|
350
375
|
const pending = result === undefined || context.isPartial;
|
|
351
376
|
const isDiff = !isError && !pending && presentation.kind === "diff";
|
|
@@ -385,10 +410,36 @@ function renderOwnedPresentation(params) {
|
|
|
385
410
|
}
|
|
386
411
|
}
|
|
387
412
|
else if (output !== "") {
|
|
388
|
-
const
|
|
413
|
+
const rawLines = stripTerminalSequences(output).split("\n");
|
|
414
|
+
let styledLines;
|
|
415
|
+
if (isError) {
|
|
416
|
+
styledLines = rawLines.map((line) => theme.fg("error", line));
|
|
417
|
+
}
|
|
418
|
+
else if (toolName === "read") {
|
|
419
|
+
const rawPath = isRecord(context.args) && typeof context.args.path === "string"
|
|
420
|
+
? context.args.path
|
|
421
|
+
: isRecord(context.args) && typeof context.args.file_path === "string"
|
|
422
|
+
? context.args.file_path
|
|
423
|
+
: "";
|
|
424
|
+
const lang = rawPath ? getLanguageFromPath(rawPath) : undefined;
|
|
425
|
+
if (lang !== undefined) {
|
|
426
|
+
try {
|
|
427
|
+
styledLines = highlightCode(output.replace(/\t/g, " "), lang);
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
styledLines = rawLines.map((line) => theme.fg("toolOutput", line));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
styledLines = rawLines.map((line) => theme.fg("toolOutput", line));
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
styledLines = rawLines.map((line) => theme.fg("toolOutput", line));
|
|
439
|
+
}
|
|
389
440
|
sections.push({
|
|
390
441
|
railToken,
|
|
391
|
-
content:
|
|
442
|
+
content: styledLines,
|
|
392
443
|
});
|
|
393
444
|
}
|
|
394
445
|
const duration = result === undefined ? "" : formatDurationMs(context.durationMs);
|
|
@@ -410,13 +461,68 @@ function renderOwnedPresentation(params) {
|
|
|
410
461
|
header: { title: toolName, status, summary },
|
|
411
462
|
railToken,
|
|
412
463
|
// The tool owns the folded rows and their tones; the frame only sanitizes them for the host.
|
|
413
|
-
collapsedRows:
|
|
414
|
-
(
|
|
464
|
+
collapsedRows: (() => {
|
|
465
|
+
if (presentation.collapsedRows !== undefined) {
|
|
466
|
+
return presentation.collapsedRows;
|
|
467
|
+
}
|
|
468
|
+
const captured = result !== undefined
|
|
469
|
+
? (getCapturedNestedCalls(context.toolCallId) ??
|
|
470
|
+
(isRecord(result.details) && Array.isArray(result.details.__piExtUiCapturedCalls)
|
|
471
|
+
? result.details.__piExtUiCapturedCalls
|
|
472
|
+
: undefined))
|
|
473
|
+
: undefined;
|
|
474
|
+
const nestedCalls = extractNestedCalls(result);
|
|
475
|
+
const childCalls = captured ?? nestedCalls;
|
|
476
|
+
if (childCalls.length > 0) {
|
|
477
|
+
return new NestedCallsView({
|
|
478
|
+
calls: childCalls,
|
|
479
|
+
theme,
|
|
480
|
+
context,
|
|
481
|
+
resolveChildRenderer: (childName) => ({
|
|
482
|
+
renderResult: (childResult, childOptions, childTheme, childCtx) => {
|
|
483
|
+
const childProvider = resolvePresentation?.(childName);
|
|
484
|
+
if (childProvider !== undefined) {
|
|
485
|
+
return renderOwnedPresentation({
|
|
486
|
+
toolName: childName,
|
|
487
|
+
presentation: childProvider(childCtx.args, childResult, childTheme, childCtx),
|
|
488
|
+
result: childResult,
|
|
489
|
+
theme: childTheme,
|
|
490
|
+
context: childCtx,
|
|
491
|
+
defaultLevel: childOptions.expanded ? 2 : 1,
|
|
492
|
+
resolvePresentation,
|
|
493
|
+
resolveNativeRenderer,
|
|
494
|
+
options,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
const registered = resolveNativeRenderer?.(childName);
|
|
498
|
+
return renderGenericResult({
|
|
499
|
+
toolName: childName,
|
|
500
|
+
theme: childTheme,
|
|
501
|
+
context: childCtx,
|
|
502
|
+
result: childResult,
|
|
503
|
+
renderOptions: childOptions,
|
|
504
|
+
defaultLevel: childOptions.expanded ? 2 : 1,
|
|
505
|
+
originalRenderCall: registered?.renderCall,
|
|
506
|
+
originalRenderResult: registered?.renderResult,
|
|
507
|
+
resolvePresentation,
|
|
508
|
+
resolveNativeRenderer,
|
|
509
|
+
options,
|
|
510
|
+
});
|
|
511
|
+
},
|
|
512
|
+
}),
|
|
513
|
+
copyToClipboard: options?.copyToClipboard,
|
|
514
|
+
stateHolder: context.state,
|
|
515
|
+
resolvePresentation,
|
|
516
|
+
invalidate: () => context.invalidate(),
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
return isError && output !== ""
|
|
415
520
|
? output
|
|
416
521
|
.split("\n")
|
|
417
522
|
.slice(0, 1)
|
|
418
523
|
.map((line) => theme.fg("error", stripTerminalSequences(line)))
|
|
419
|
-
: undefined
|
|
524
|
+
: undefined;
|
|
525
|
+
})(),
|
|
420
526
|
sections,
|
|
421
527
|
copy,
|
|
422
528
|
outputPad: context.outputPad,
|
|
@@ -430,7 +536,7 @@ function renderOwnedPresentation(params) {
|
|
|
430
536
|
export function createCollapsibleToolRendererResolver(options) {
|
|
431
537
|
return createRendererResolver(options);
|
|
432
538
|
}
|
|
433
|
-
function createRendererResolver(options, resolvePresentation) {
|
|
539
|
+
function createRendererResolver(options, resolvePresentation, resolveNativeRenderer) {
|
|
434
540
|
const defaultLevel = options?.defaultCollapsed === false ? 1 : 0;
|
|
435
541
|
return (toolName, next) => {
|
|
436
542
|
const upstream = next();
|
|
@@ -440,8 +546,9 @@ function createRendererResolver(options, resolvePresentation) {
|
|
|
440
546
|
if (provider === undefined && !shouldInterceptTool(toolName, options)) {
|
|
441
547
|
return upstream;
|
|
442
548
|
}
|
|
443
|
-
const
|
|
444
|
-
const
|
|
549
|
+
const registeredNative = resolveNativeRenderer?.(toolName);
|
|
550
|
+
const originalRenderCall = upstream?.renderCall ?? registeredNative?.renderCall;
|
|
551
|
+
const originalRenderResult = upstream?.renderResult ?? registeredNative?.renderResult;
|
|
445
552
|
return {
|
|
446
553
|
renderShell: "self",
|
|
447
554
|
renderCall(args, theme, context) {
|
|
@@ -453,6 +560,8 @@ function createRendererResolver(options, resolvePresentation) {
|
|
|
453
560
|
theme,
|
|
454
561
|
context: callContext,
|
|
455
562
|
defaultLevel,
|
|
563
|
+
resolvePresentation,
|
|
564
|
+
resolveNativeRenderer,
|
|
456
565
|
options,
|
|
457
566
|
});
|
|
458
567
|
rememberCallView(context, view);
|
|
@@ -472,14 +581,13 @@ function createRendererResolver(options, resolvePresentation) {
|
|
|
472
581
|
summary = tokens > 0 ? `· ${String(tokens)} tokens` : "";
|
|
473
582
|
}
|
|
474
583
|
else {
|
|
475
|
-
summary =
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
584
|
+
summary = resolveToolCallSummary({
|
|
585
|
+
toolName,
|
|
586
|
+
args,
|
|
587
|
+
originalRenderCall,
|
|
588
|
+
theme,
|
|
589
|
+
context,
|
|
590
|
+
});
|
|
483
591
|
}
|
|
484
592
|
const heavyCmd = extractHeavyInputCommand(toolName, args);
|
|
485
593
|
const sections = [];
|
|
@@ -542,6 +650,7 @@ function createRendererResolver(options, resolvePresentation) {
|
|
|
542
650
|
theme,
|
|
543
651
|
context: resultContext,
|
|
544
652
|
defaultLevel,
|
|
653
|
+
resolvePresentation,
|
|
545
654
|
options,
|
|
546
655
|
});
|
|
547
656
|
}
|
|
@@ -555,6 +664,7 @@ function createRendererResolver(options, resolvePresentation) {
|
|
|
555
664
|
originalRenderCall,
|
|
556
665
|
originalRenderResult,
|
|
557
666
|
resolvePresentation,
|
|
667
|
+
resolveNativeRenderer,
|
|
558
668
|
options,
|
|
559
669
|
});
|
|
560
670
|
},
|
|
@@ -563,29 +673,25 @@ function createRendererResolver(options, resolvePresentation) {
|
|
|
563
673
|
}
|
|
564
674
|
/** UI factory consumed by package-owned direct renderers, independently of host resolver precedence. */
|
|
565
675
|
export function createOptionalToolRendererFactory(pi, options) {
|
|
566
|
-
return (name, native) => createRendererResolver({ ...options, tools: [name] }, (toolName) => getRegisteredToolPresentationProvider(pi, toolName))(name, () => native) ?? native;
|
|
676
|
+
return (name, native) => createRendererResolver({ ...options, tools: [name] }, (toolName) => getRegisteredToolPresentationProvider(pi, toolName), (toolName) => getRegisteredToolRenderers(pi, toolName))(name, () => native) ?? native;
|
|
567
677
|
}
|
|
568
678
|
export function registerCollapsibleToolRenderer(pi, options) {
|
|
679
|
+
const disposers = [];
|
|
569
680
|
if (typeof pi.on === "function") {
|
|
570
|
-
pi.on("tool_execution_start", (event) => {
|
|
681
|
+
const unbindStart = pi.on("tool_execution_start", (event) => {
|
|
571
682
|
recordToolExecutionStart(event);
|
|
572
683
|
});
|
|
573
|
-
|
|
684
|
+
if (typeof unbindStart === "function")
|
|
685
|
+
disposers.push(unbindStart);
|
|
686
|
+
const unbindEnd = pi.on("tool_execution_end", (event) => {
|
|
574
687
|
recordToolExecutionEnd(event);
|
|
575
688
|
});
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
if (captured && captured.length > 0) {
|
|
579
|
-
const currentDetails = isRecord(event.details) ? event.details : {};
|
|
580
|
-
return {
|
|
581
|
-
details: {
|
|
582
|
-
...currentDetails,
|
|
583
|
-
__piExtUiCapturedCalls: captured,
|
|
584
|
-
},
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
return undefined;
|
|
588
|
-
});
|
|
689
|
+
if (typeof unbindEnd === "function")
|
|
690
|
+
disposers.push(unbindEnd);
|
|
589
691
|
}
|
|
590
|
-
pi.registerToolRenderer(createRendererResolver(options, (name) => getRegisteredToolPresentationProvider(pi, name)));
|
|
692
|
+
pi.registerToolRenderer(createRendererResolver(options, (name) => getRegisteredToolPresentationProvider(pi, name), (name) => getRegisteredToolRenderers(pi, name)));
|
|
693
|
+
return () => {
|
|
694
|
+
for (const dispose of disposers)
|
|
695
|
+
dispose();
|
|
696
|
+
};
|
|
591
697
|
}
|
package/dist/tool-view.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ export interface ToolViewSpec {
|
|
|
43
43
|
}
|
|
44
44
|
/** Public composition entry point for extensions that need the shared view behavior. */
|
|
45
45
|
export declare function createToolView(spec: ToolViewSpec): ToolView;
|
|
46
|
+
export declare const MAX_LEVEL2_SECTION_LINES = 500;
|
|
47
|
+
export declare const MAX_LEVEL2_BYTES: number;
|
|
46
48
|
export declare function transitionFoldLevel(current: FoldLevel, ctrlKey: boolean): FoldLevel;
|
|
47
49
|
export declare function formatStatusGlyph(status: ToolStatusKind | undefined, theme: Theme): string;
|
|
48
50
|
/**
|
package/dist/tool-view.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
1
2
|
import { copyToClipboard as copyToSystemClipboard, } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
|
|
3
|
+
import { truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
|
|
3
4
|
export function isCollapsible(value) {
|
|
4
5
|
return (typeof value === "object" &&
|
|
5
6
|
value !== null &&
|
|
@@ -14,6 +15,8 @@ const FOLD_LEVEL_STATE_KEY = "__piExtUiFoldLevel";
|
|
|
14
15
|
const COPIED_UNTIL_STATE_KEY = "__piExtUiCopiedUntil";
|
|
15
16
|
const COPY_FEEDBACK_TIMER_KEY = "__piExtUiCopyTimer";
|
|
16
17
|
const DEFAULT_MAX_SECTION_LINES = 9;
|
|
18
|
+
export const MAX_LEVEL2_SECTION_LINES = 500;
|
|
19
|
+
export const MAX_LEVEL2_BYTES = 64 * 1024;
|
|
17
20
|
const DEFAULT_COPY_FEEDBACK_MS = 1200;
|
|
18
21
|
function isRecord(value) {
|
|
19
22
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -107,20 +110,97 @@ export function toContentRows(text) {
|
|
|
107
110
|
}
|
|
108
111
|
return text.replace(/\r/g, "").replace(/\t/g, " ").split("\n");
|
|
109
112
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
function truncateUtf8(str, maxBytes) {
|
|
114
|
+
const buf = Buffer.from(str, "utf8");
|
|
115
|
+
if (buf.length <= maxBytes)
|
|
116
|
+
return str;
|
|
117
|
+
let end = maxBytes;
|
|
118
|
+
while (end > 0) {
|
|
119
|
+
const b = buf[end];
|
|
120
|
+
if (b !== undefined && (b & 0xc0) === 0x80) {
|
|
121
|
+
end--;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (end > 0) {
|
|
128
|
+
const lead = buf[end - 1];
|
|
129
|
+
if (lead !== undefined) {
|
|
130
|
+
let seqLen = 1;
|
|
131
|
+
if ((lead & 0xe0) === 0xc0)
|
|
132
|
+
seqLen = 2;
|
|
133
|
+
else if ((lead & 0xf0) === 0xe0)
|
|
134
|
+
seqLen = 3;
|
|
135
|
+
else if ((lead & 0xf8) === 0xf0)
|
|
136
|
+
seqLen = 4;
|
|
137
|
+
if (seqLen > 1 && end - 1 + seqLen > maxBytes) {
|
|
138
|
+
end = end - 1;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return buf.subarray(0, end).toString("utf8");
|
|
143
|
+
}
|
|
116
144
|
function resolveRows(source, width, normalize) {
|
|
117
145
|
if (typeof source === "string") {
|
|
118
|
-
|
|
146
|
+
if (source === "")
|
|
147
|
+
return { rows: [], isByteTruncated: false, totalBytes: 0 };
|
|
148
|
+
const totalBytes = Buffer.byteLength(source, "utf8");
|
|
149
|
+
const isByteTruncated = totalBytes > MAX_LEVEL2_BYTES;
|
|
150
|
+
const bounded = isByteTruncated ? truncateUtf8(source, MAX_LEVEL2_BYTES) : source;
|
|
151
|
+
return { rows: normalize(bounded), isByteTruncated, totalBytes };
|
|
119
152
|
}
|
|
120
153
|
if (Array.isArray(source)) {
|
|
121
|
-
|
|
154
|
+
const totalBytes = source.reduce((acc, item) => (typeof item === "string" ? acc + Buffer.byteLength(item, "utf8") : acc), 0);
|
|
155
|
+
const isByteTruncated = totalBytes > MAX_LEVEL2_BYTES;
|
|
156
|
+
const rows = [];
|
|
157
|
+
let currentBytes = 0;
|
|
158
|
+
for (const item of source) {
|
|
159
|
+
if (typeof item !== "string")
|
|
160
|
+
continue;
|
|
161
|
+
if (currentBytes >= MAX_LEVEL2_BYTES)
|
|
162
|
+
continue;
|
|
163
|
+
const itemBytes = Buffer.byteLength(item, "utf8");
|
|
164
|
+
if (currentBytes + itemBytes > MAX_LEVEL2_BYTES) {
|
|
165
|
+
const remaining = MAX_LEVEL2_BYTES - currentBytes;
|
|
166
|
+
if (remaining > 0) {
|
|
167
|
+
for (const row of normalize(truncateUtf8(item, remaining)))
|
|
168
|
+
rows.push(row);
|
|
169
|
+
}
|
|
170
|
+
currentBytes = MAX_LEVEL2_BYTES;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
currentBytes += itemBytes;
|
|
174
|
+
for (const row of normalize(item))
|
|
175
|
+
rows.push(row);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return { rows, isByteTruncated, totalBytes };
|
|
179
|
+
}
|
|
180
|
+
const rendered = source.render(width);
|
|
181
|
+
const totalBytes = rendered.reduce((acc, row) => acc + Buffer.byteLength(row, "utf8"), 0);
|
|
182
|
+
const isByteTruncated = totalBytes > MAX_LEVEL2_BYTES;
|
|
183
|
+
const rows = [];
|
|
184
|
+
let currentBytes = 0;
|
|
185
|
+
for (const row of rendered) {
|
|
186
|
+
if (currentBytes >= MAX_LEVEL2_BYTES)
|
|
187
|
+
continue;
|
|
188
|
+
const rowBytes = Buffer.byteLength(row, "utf8");
|
|
189
|
+
if (currentBytes + rowBytes > MAX_LEVEL2_BYTES) {
|
|
190
|
+
const remaining = MAX_LEVEL2_BYTES - currentBytes;
|
|
191
|
+
if (remaining > 0) {
|
|
192
|
+
for (const norm of toContentRows(truncateUtf8(row, remaining)))
|
|
193
|
+
rows.push(norm);
|
|
194
|
+
}
|
|
195
|
+
currentBytes = MAX_LEVEL2_BYTES;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
currentBytes += rowBytes;
|
|
199
|
+
for (const norm of toContentRows(row))
|
|
200
|
+
rows.push(norm);
|
|
201
|
+
}
|
|
122
202
|
}
|
|
123
|
-
return
|
|
203
|
+
return { rows, isByteTruncated, totalBytes };
|
|
124
204
|
}
|
|
125
205
|
export class ToolView {
|
|
126
206
|
#spec;
|
|
@@ -290,7 +370,7 @@ export class ToolView {
|
|
|
290
370
|
if (collapsedRows !== undefined) {
|
|
291
371
|
const railColor = this.#spec.header.status === "error" ? "error" : "dim";
|
|
292
372
|
const rail = noRail ? "" : `${theme.fg(railColor, "▎")} `;
|
|
293
|
-
const rows = resolveRows(collapsedRows, innerWidth, (row) => [toSingleRow(row)]);
|
|
373
|
+
const { rows } = resolveRows(collapsedRows, innerWidth, (row) => [toSingleRow(row)]);
|
|
294
374
|
for (const row of rows) {
|
|
295
375
|
output.push(truncate(rail + row, safeWidth));
|
|
296
376
|
}
|
|
@@ -302,15 +382,34 @@ export class ToolView {
|
|
|
302
382
|
}
|
|
303
383
|
for (const section of sections) {
|
|
304
384
|
const rail = noRail ? "" : `${theme.fg(section.railToken, "▎")} `;
|
|
305
|
-
const rawLines = resolveRows(section.content, innerWidth, toContentRows);
|
|
306
|
-
const cap = level === 1 ? (section.maxLines ?? DEFAULT_MAX_SECTION_LINES) :
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
for (const line of
|
|
310
|
-
|
|
385
|
+
const { rows: rawLines, isByteTruncated, totalBytes, } = resolveRows(section.content, innerWidth, toContentRows);
|
|
386
|
+
const cap = level === 1 ? (section.maxLines ?? DEFAULT_MAX_SECTION_LINES) : MAX_LEVEL2_SECTION_LINES;
|
|
387
|
+
const sectionRows = [];
|
|
388
|
+
let logicalLinesCount = 0;
|
|
389
|
+
for (const line of rawLines) {
|
|
390
|
+
const wrapped = wrapTextWithAnsi(line, innerWidth);
|
|
391
|
+
const chunks = wrapped.length > 0 ? wrapped : [""];
|
|
392
|
+
if (Number.isFinite(cap) && sectionRows.length + chunks.length > cap) {
|
|
393
|
+
const canTake = Math.max(0, cap - sectionRows.length);
|
|
394
|
+
for (let i = 0; i < canTake; i++) {
|
|
395
|
+
const chunk = chunks[i];
|
|
396
|
+
if (chunk !== undefined) {
|
|
397
|
+
sectionRows.push(chunk);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
sectionRows.push(...chunks);
|
|
403
|
+
logicalLinesCount++;
|
|
404
|
+
}
|
|
405
|
+
const omittedCount = rawLines.length - logicalLinesCount;
|
|
406
|
+
for (const row of sectionRows) {
|
|
407
|
+
output.push(truncate(rail + row, safeWidth));
|
|
311
408
|
}
|
|
312
|
-
if (omittedCount > 0) {
|
|
313
|
-
const hint =
|
|
409
|
+
if (omittedCount > 0 || isByteTruncated) {
|
|
410
|
+
const hint = isByteTruncated
|
|
411
|
+
? theme.fg("dim", `… [Output truncated: showing 64KB of ${Math.round(totalBytes / 1024)}KB]`)
|
|
412
|
+
: theme.fg("dim", `… (${String(omittedCount)} more lines)`);
|
|
314
413
|
output.push(truncate(rail + hint, safeWidth));
|
|
315
414
|
}
|
|
316
415
|
}
|
package/dist/tracker.d.ts
CHANGED
|
@@ -8,6 +8,25 @@ export interface CapturedToolCall {
|
|
|
8
8
|
readonly durationMs?: number | undefined;
|
|
9
9
|
readonly cost?: number | undefined;
|
|
10
10
|
}
|
|
11
|
+
export declare class ToolExecutionTracker {
|
|
12
|
+
#private;
|
|
13
|
+
recordStart(event: {
|
|
14
|
+
toolCallId: string;
|
|
15
|
+
toolName: string;
|
|
16
|
+
args: unknown;
|
|
17
|
+
parentToolCallId?: string | undefined;
|
|
18
|
+
}): void;
|
|
19
|
+
recordEnd(event: {
|
|
20
|
+
toolCallId: string;
|
|
21
|
+
toolName: string;
|
|
22
|
+
result: unknown;
|
|
23
|
+
isError: boolean;
|
|
24
|
+
durationMs?: number | undefined;
|
|
25
|
+
parentToolCallId?: string | undefined;
|
|
26
|
+
}): void;
|
|
27
|
+
getCaptured(parentToolCallId: string | undefined): readonly CapturedToolCall[] | undefined;
|
|
28
|
+
clear(): void;
|
|
29
|
+
}
|
|
11
30
|
export declare function recordToolExecutionStart(event: {
|
|
12
31
|
toolCallId: string;
|
|
13
32
|
toolName: string;
|
package/dist/tracker.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const capturedByParentId = new Map();
|
|
2
1
|
const MAX_STORED_PARENTS = 200;
|
|
3
2
|
const MAX_CAPTURED_PAYLOAD_CHARS = 32_768;
|
|
4
3
|
function boundCapturedValue(val) {
|
|
@@ -9,73 +8,89 @@ function boundCapturedValue(val) {
|
|
|
9
8
|
}
|
|
10
9
|
return val;
|
|
11
10
|
}
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
capturedByParentId.
|
|
11
|
+
export class ToolExecutionTracker {
|
|
12
|
+
#capturedByParentId = new Map();
|
|
13
|
+
recordStart(event) {
|
|
14
|
+
if (!event.parentToolCallId)
|
|
15
|
+
return;
|
|
16
|
+
let list = this.#capturedByParentId.get(event.parentToolCallId);
|
|
17
|
+
if (!list) {
|
|
18
|
+
if (this.#capturedByParentId.size >= MAX_STORED_PARENTS) {
|
|
19
|
+
const firstKey = this.#capturedByParentId.keys().next().value;
|
|
20
|
+
if (firstKey !== undefined)
|
|
21
|
+
this.#capturedByParentId.delete(firstKey);
|
|
22
|
+
}
|
|
23
|
+
list = [];
|
|
24
|
+
this.#capturedByParentId.set(event.parentToolCallId, list);
|
|
21
25
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
list.push(entry);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
export function recordToolExecutionEnd(event) {
|
|
39
|
-
if (!event.parentToolCallId)
|
|
40
|
-
return;
|
|
41
|
-
let list = capturedByParentId.get(event.parentToolCallId);
|
|
42
|
-
if (!list) {
|
|
43
|
-
if (capturedByParentId.size >= MAX_STORED_PARENTS) {
|
|
44
|
-
const firstKey = capturedByParentId.keys().next().value;
|
|
45
|
-
if (firstKey !== undefined)
|
|
46
|
-
capturedByParentId.delete(firstKey);
|
|
26
|
+
const idx = list.findIndex((c) => c.id === event.toolCallId);
|
|
27
|
+
const entry = {
|
|
28
|
+
id: event.toolCallId,
|
|
29
|
+
toolName: event.toolName,
|
|
30
|
+
args: boundCapturedValue(event.args),
|
|
31
|
+
};
|
|
32
|
+
if (idx >= 0) {
|
|
33
|
+
list[idx] = { ...list[idx], ...entry };
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
list.push(entry);
|
|
47
37
|
}
|
|
48
|
-
list = [];
|
|
49
|
-
capturedByParentId.set(event.parentToolCallId, list);
|
|
50
38
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (idx >= 0) {
|
|
54
|
-
const prev = list[idx];
|
|
55
|
-
if (!prev)
|
|
39
|
+
recordEnd(event) {
|
|
40
|
+
if (!event.parentToolCallId)
|
|
56
41
|
return;
|
|
57
|
-
list
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
42
|
+
let list = this.#capturedByParentId.get(event.parentToolCallId);
|
|
43
|
+
if (!list) {
|
|
44
|
+
if (this.#capturedByParentId.size >= MAX_STORED_PARENTS) {
|
|
45
|
+
const firstKey = this.#capturedByParentId.keys().next().value;
|
|
46
|
+
if (firstKey !== undefined)
|
|
47
|
+
this.#capturedByParentId.delete(firstKey);
|
|
48
|
+
}
|
|
49
|
+
list = [];
|
|
50
|
+
this.#capturedByParentId.set(event.parentToolCallId, list);
|
|
51
|
+
}
|
|
52
|
+
const idx = list.findIndex((c) => c.id === event.toolCallId);
|
|
53
|
+
const typedResult = event.result;
|
|
54
|
+
if (idx >= 0) {
|
|
55
|
+
const prev = list[idx];
|
|
56
|
+
if (!prev)
|
|
57
|
+
return;
|
|
58
|
+
list[idx] = {
|
|
59
|
+
...prev,
|
|
60
|
+
result: typedResult,
|
|
61
|
+
isError: event.isError,
|
|
62
|
+
durationMs: event.durationMs,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
list.push({
|
|
67
|
+
id: event.toolCallId,
|
|
68
|
+
toolName: event.toolName,
|
|
69
|
+
result: typedResult,
|
|
70
|
+
isError: event.isError,
|
|
71
|
+
durationMs: event.durationMs,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
63
74
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
result: typedResult,
|
|
69
|
-
isError: event.isError,
|
|
70
|
-
durationMs: event.durationMs,
|
|
71
|
-
});
|
|
75
|
+
getCaptured(parentToolCallId) {
|
|
76
|
+
if (!parentToolCallId)
|
|
77
|
+
return undefined;
|
|
78
|
+
return this.#capturedByParentId.get(parentToolCallId);
|
|
72
79
|
}
|
|
80
|
+
clear() {
|
|
81
|
+
this.#capturedByParentId.clear();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const defaultTracker = new ToolExecutionTracker();
|
|
85
|
+
export function recordToolExecutionStart(event) {
|
|
86
|
+
defaultTracker.recordStart(event);
|
|
87
|
+
}
|
|
88
|
+
export function recordToolExecutionEnd(event) {
|
|
89
|
+
defaultTracker.recordEnd(event);
|
|
73
90
|
}
|
|
74
91
|
export function getCapturedNestedCalls(parentToolCallId) {
|
|
75
|
-
|
|
76
|
-
return undefined;
|
|
77
|
-
return capturedByParentId.get(parentToolCallId);
|
|
92
|
+
return defaultTracker.getCaptured(parentToolCallId);
|
|
78
93
|
}
|
|
79
94
|
export function clearCapturedNestedCalls() {
|
|
80
|
-
|
|
95
|
+
defaultTracker.clear();
|
|
81
96
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hheei/pi-ext-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Collapsible tool rendering frame and tool renderer pilot for Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@hheei/pi-ext-core": "^0.
|
|
39
|
+
"@hheei/pi-ext-core": "^0.6.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@earendil-works/pi-coding-agent": ">=1.3.0",
|
package/dist/turn-copy.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export interface TurnToolCallSummary {
|
|
2
|
-
readonly toolName: string;
|
|
3
|
-
readonly commandOrArgs?: string | undefined;
|
|
4
|
-
readonly summary: string;
|
|
5
|
-
}
|
|
6
|
-
export interface TurnFlowData {
|
|
7
|
-
readonly userPrompt: string;
|
|
8
|
-
readonly toolCalls?: readonly TurnToolCallSummary[] | undefined;
|
|
9
|
-
readonly assistantReply: string;
|
|
10
|
-
}
|
|
11
|
-
export declare function formatTurnFlow(data: TurnFlowData): string;
|
package/dist/turn-copy.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { stripTerminalSequences } from "@earendil-works/pi-tui";
|
|
2
|
-
export function formatTurnFlow(data) {
|
|
3
|
-
const blocks = [];
|
|
4
|
-
const cleanUser = stripTerminalSequences(data.userPrompt).trim();
|
|
5
|
-
blocks.push(`[User]\n${cleanUser}`);
|
|
6
|
-
if (data.toolCalls !== undefined && data.toolCalls.length > 0) {
|
|
7
|
-
const toolLines = [];
|
|
8
|
-
for (const call of data.toolCalls) {
|
|
9
|
-
const header = call.commandOrArgs !== undefined && call.commandOrArgs.trim() !== ""
|
|
10
|
-
? `${call.toolName} ${stripTerminalSequences(call.commandOrArgs).trim()}`
|
|
11
|
-
: call.toolName;
|
|
12
|
-
const detail = stripTerminalSequences(call.summary).trim();
|
|
13
|
-
toolLines.push(detail !== "" ? `${header}\n${detail}` : header);
|
|
14
|
-
}
|
|
15
|
-
blocks.push(`[Tools]\n${toolLines.join("\n\n")}`);
|
|
16
|
-
}
|
|
17
|
-
const cleanAssistant = stripTerminalSequences(data.assistantReply).trim();
|
|
18
|
-
blocks.push(`[Assistant]\n${cleanAssistant}`);
|
|
19
|
-
return blocks.join("\n\n");
|
|
20
|
-
}
|