@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
package/dist/payload-parser.js
CHANGED
|
@@ -1,18 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { diffStats } from "./diff-view.js";
|
|
3
|
-
export const EDIT_TOOL_NAME = "edit";
|
|
4
|
-
export const WRITE_TOOL_NAME = "write";
|
|
5
|
-
export const CODEMODE_OPTIONS_PREFIX = "// @options:";
|
|
6
|
-
export const CODEMODE_SCRIPT_HEADER = /^Script (?:completed|failed)\r?\nWall time ([\d.]+) seconds\r?\nOutput:\r?\n/;
|
|
7
|
-
// Pi <1.3 has no exported read-range formatter. Keep this UI summary independent of that export.
|
|
8
|
-
function readLineNumber(value) {
|
|
9
|
-
const parsed = typeof value === "number"
|
|
10
|
-
? value
|
|
11
|
-
: typeof value === "string" && value.trim() !== ""
|
|
12
|
-
? Number(value)
|
|
13
|
-
: Number.NaN;
|
|
14
|
-
return Number.isFinite(parsed) ? parsed : undefined;
|
|
15
|
-
}
|
|
1
|
+
export const RESOLVED_CALLS_KEY = "__piExtUiResolvedCalls";
|
|
16
2
|
export function isRecord(value) {
|
|
17
3
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18
4
|
}
|
|
@@ -44,39 +30,6 @@ export function formatTimeoutFact(timeoutMs) {
|
|
|
44
30
|
const label = timeoutMs >= 1000 ? `${String(Math.round(timeoutMs / 1000))}s` : `${String(timeoutMs)}ms`;
|
|
45
31
|
return `(timeout ${label})`;
|
|
46
32
|
}
|
|
47
|
-
export function extractCodemodeHeaderFacts(args) {
|
|
48
|
-
if (!isRecord(args))
|
|
49
|
-
return "";
|
|
50
|
-
const options = {};
|
|
51
|
-
for (const [key, value] of Object.entries(args)) {
|
|
52
|
-
if (key !== "code" && value !== undefined && value !== null) {
|
|
53
|
-
options[key] = value;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
if (typeof args.code === "string") {
|
|
57
|
-
const firstLine = args.code.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
58
|
-
if (firstLine.startsWith(CODEMODE_OPTIONS_PREFIX)) {
|
|
59
|
-
try {
|
|
60
|
-
const parsed = JSON.parse(firstLine.slice(CODEMODE_OPTIONS_PREFIX.length).trim());
|
|
61
|
-
if (isRecord(parsed)) {
|
|
62
|
-
Object.assign(options, parsed);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
catch { }
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const facts = [];
|
|
69
|
-
const timeoutMs = typeof options.timeout_ms === "number" ? options.timeout_ms : undefined;
|
|
70
|
-
if (timeoutMs !== undefined)
|
|
71
|
-
facts.push(formatTimeoutFact(timeoutMs));
|
|
72
|
-
if (typeof options.max_output_tokens === "number") {
|
|
73
|
-
facts.push(`(tokens ${String(options.max_output_tokens)})`);
|
|
74
|
-
}
|
|
75
|
-
if (typeof options.target === "string" && options.target !== "local") {
|
|
76
|
-
facts.push(`(${options.target})`);
|
|
77
|
-
}
|
|
78
|
-
return facts.join(" ");
|
|
79
|
-
}
|
|
80
33
|
export function parseCallArgs(raw) {
|
|
81
34
|
if (isRecord(raw))
|
|
82
35
|
return raw;
|
|
@@ -126,77 +79,6 @@ export function parseCallArgs(raw) {
|
|
|
126
79
|
}
|
|
127
80
|
return undefined;
|
|
128
81
|
}
|
|
129
|
-
export function extractSummaryArg(toolName, args) {
|
|
130
|
-
if (toolName === "codemode") {
|
|
131
|
-
return extractCodemodeHeaderFacts(args);
|
|
132
|
-
}
|
|
133
|
-
const parsed = parseCallArgs(args);
|
|
134
|
-
if (!parsed)
|
|
135
|
-
return "";
|
|
136
|
-
const firstLine = (val) => {
|
|
137
|
-
if (typeof val !== "string" || val.trim() === "")
|
|
138
|
-
return "";
|
|
139
|
-
const clean = stripTerminalSequences(val.trim());
|
|
140
|
-
return clean.split("\n")[0]?.trim() ?? clean;
|
|
141
|
-
};
|
|
142
|
-
switch (toolName) {
|
|
143
|
-
case "bash":
|
|
144
|
-
case "bg_bash_spawn":
|
|
145
|
-
return firstLine(parsed.command);
|
|
146
|
-
case "read":
|
|
147
|
-
case "edit":
|
|
148
|
-
case "write": {
|
|
149
|
-
const path = typeof parsed.path === "string"
|
|
150
|
-
? parsed.path.trim()
|
|
151
|
-
: typeof parsed.file_path === "string"
|
|
152
|
-
? parsed.file_path.trim()
|
|
153
|
-
: "";
|
|
154
|
-
return stripTerminalSequences(path);
|
|
155
|
-
}
|
|
156
|
-
case "grep":
|
|
157
|
-
case "find": {
|
|
158
|
-
const pattern = typeof parsed.pattern === "string" ? parsed.pattern.trim() : "";
|
|
159
|
-
const path = typeof parsed.path === "string" ? parsed.path.trim() : "";
|
|
160
|
-
if (!pattern)
|
|
161
|
-
return path ? stripTerminalSequences(path) : "";
|
|
162
|
-
const target = path && path !== "." ? ` ${path}` : "";
|
|
163
|
-
return `${pattern}${target}`;
|
|
164
|
-
}
|
|
165
|
-
case "python_eval":
|
|
166
|
-
return firstLine(parsed.code);
|
|
167
|
-
case "bg_bash_get":
|
|
168
|
-
case "bg_bash_stop":
|
|
169
|
-
case "agent_send":
|
|
170
|
-
case "agent_get":
|
|
171
|
-
case "agent_stop":
|
|
172
|
-
return typeof parsed.id === "string" ? parsed.id.trim() : "";
|
|
173
|
-
case "agent_spawn": {
|
|
174
|
-
const agent = typeof parsed.agent === "string" ? parsed.agent.trim() : "";
|
|
175
|
-
const task = firstLine(parsed.task);
|
|
176
|
-
if (agent && task)
|
|
177
|
-
return `${agent}: ${task}`;
|
|
178
|
-
return agent || task;
|
|
179
|
-
}
|
|
180
|
-
case "tool_search":
|
|
181
|
-
return typeof parsed.query === "string" ? parsed.query.trim() : "";
|
|
182
|
-
default:
|
|
183
|
-
return "";
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
export function extractHeavyInputCommand(toolName, args) {
|
|
187
|
-
if (!isRecord(args))
|
|
188
|
-
return undefined;
|
|
189
|
-
if (toolName === "bash" && typeof args.command === "string") {
|
|
190
|
-
return args.command.trim();
|
|
191
|
-
}
|
|
192
|
-
if (toolName === "python_eval" && typeof args.code === "string") {
|
|
193
|
-
return args.code.trim();
|
|
194
|
-
}
|
|
195
|
-
if (toolName === "codemode" && typeof args.code === "string") {
|
|
196
|
-
return args.code.trim();
|
|
197
|
-
}
|
|
198
|
-
return undefined;
|
|
199
|
-
}
|
|
200
82
|
export function extractNestedCalls(result) {
|
|
201
83
|
if (result === undefined || !isRecord(result.details))
|
|
202
84
|
return [];
|
|
@@ -207,6 +89,10 @@ export function extractNestedCalls(result) {
|
|
|
207
89
|
: undefined;
|
|
208
90
|
if (rawList === undefined)
|
|
209
91
|
return [];
|
|
92
|
+
const rawResolved = result.details[RESOLVED_CALLS_KEY];
|
|
93
|
+
const resolvedIds = Array.isArray(rawResolved)
|
|
94
|
+
? new Set(rawResolved.filter((id) => typeof id === "string"))
|
|
95
|
+
: undefined;
|
|
210
96
|
const items = [];
|
|
211
97
|
for (const entry of rawList) {
|
|
212
98
|
if (!isRecord(entry))
|
|
@@ -230,8 +116,12 @@ export function extractNestedCalls(result) {
|
|
|
230
116
|
: typeof entry.summary === "string"
|
|
231
117
|
? entry.summary
|
|
232
118
|
: undefined;
|
|
119
|
+
const id = typeof entry.id === "string" ? entry.id : undefined;
|
|
233
120
|
items.push({
|
|
234
121
|
toolName: name,
|
|
122
|
+
...(id === undefined ? {} : { id }),
|
|
123
|
+
// Absent facts stay absent: a legacy record and an unresolved call are both unknown.
|
|
124
|
+
...(id !== undefined && resolvedIds?.has(id) === true ? { resolved: true } : {}),
|
|
235
125
|
summary,
|
|
236
126
|
status,
|
|
237
127
|
isError: entry.isError === true || status === "error",
|
|
@@ -243,23 +133,6 @@ export function extractNestedCalls(result) {
|
|
|
243
133
|
}
|
|
244
134
|
return items;
|
|
245
135
|
}
|
|
246
|
-
export function extractResultText(toolName, result) {
|
|
247
|
-
const parts = [];
|
|
248
|
-
for (const block of result.content) {
|
|
249
|
-
if (block.type === "text" && block.text.trim() !== "") {
|
|
250
|
-
parts.push(block.text);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
const joined = parts.join("\n");
|
|
254
|
-
if (toolName === "codemode") {
|
|
255
|
-
const stripped = stripTerminalSequences(joined);
|
|
256
|
-
const match = CODEMODE_SCRIPT_HEADER.exec(stripped);
|
|
257
|
-
const wallTimeSeconds = match?.[1] !== undefined ? Number.parseFloat(match[1]) : undefined;
|
|
258
|
-
const cleaned = joined.replace(CODEMODE_SCRIPT_HEADER, "").trim();
|
|
259
|
-
return { outputText: cleaned, wallTimeSeconds };
|
|
260
|
-
}
|
|
261
|
-
return { outputText: joined, wallTimeSeconds: undefined };
|
|
262
|
-
}
|
|
263
136
|
export function callStatsParts(total, failCount) {
|
|
264
137
|
if (failCount > 0) {
|
|
265
138
|
return [`${String(total - failCount)} ok`, `${String(failCount)} failed`];
|
|
@@ -275,136 +148,3 @@ export function resolveContainerStatus(params) {
|
|
|
275
148
|
return "running";
|
|
276
149
|
return params.isError ? "error" : "ok";
|
|
277
150
|
}
|
|
278
|
-
export function toolPath(args) {
|
|
279
|
-
const parsed = parseCallArgs(args);
|
|
280
|
-
if (!parsed)
|
|
281
|
-
return "";
|
|
282
|
-
const val = parsed.path ?? parsed.file_path;
|
|
283
|
-
return typeof val === "string" ? val.trim() : "";
|
|
284
|
-
}
|
|
285
|
-
export function fileToolPresentation(toolName, args, _details, isError) {
|
|
286
|
-
const path = toolPath(args);
|
|
287
|
-
if (toolName === "read") {
|
|
288
|
-
if (path === "")
|
|
289
|
-
return undefined;
|
|
290
|
-
const parsed = parseCallArgs(args);
|
|
291
|
-
const offset = parsed?.offset;
|
|
292
|
-
const limit = parsed?.limit;
|
|
293
|
-
const start = offset == null ? 1 : readLineNumber(offset);
|
|
294
|
-
const count = limit == null ? undefined : readLineNumber(limit);
|
|
295
|
-
const end = start !== undefined && count !== undefined ? start + count - 1 : undefined;
|
|
296
|
-
const range = (offset == null && limit == null) ||
|
|
297
|
-
start === undefined ||
|
|
298
|
-
(limit != null && count === undefined)
|
|
299
|
-
? ""
|
|
300
|
-
: `:${start}${end ? `-${end}` : ""}`;
|
|
301
|
-
return { summary: `${path}${range}`, status: isError ? "error" : "ok" };
|
|
302
|
-
}
|
|
303
|
-
if (toolName === WRITE_TOOL_NAME) {
|
|
304
|
-
return path === "" ? undefined : { summary: path, status: isError ? "error" : "ok" };
|
|
305
|
-
}
|
|
306
|
-
return undefined;
|
|
307
|
-
}
|
|
308
|
-
/** Pi native edit persists a unified patch plus the same change as a numbered display diff. */
|
|
309
|
-
export function editDiffSource(details) {
|
|
310
|
-
if (!isRecord(details))
|
|
311
|
-
return undefined;
|
|
312
|
-
if (typeof details.patch === "string" && details.patch.trim() !== "") {
|
|
313
|
-
return { kind: "unifiedPatch", text: details.patch };
|
|
314
|
-
}
|
|
315
|
-
if (typeof details.diff === "string" && details.diff.trim() !== "") {
|
|
316
|
-
return displayDiffSource(details.diff);
|
|
317
|
-
}
|
|
318
|
-
return undefined;
|
|
319
|
-
}
|
|
320
|
-
export function displayDiffSource(diff) {
|
|
321
|
-
const hunks = [];
|
|
322
|
-
let current = [];
|
|
323
|
-
let pendingBreak = false;
|
|
324
|
-
for (const raw of diff.split(/\r?\n/)) {
|
|
325
|
-
if (raw === "")
|
|
326
|
-
continue;
|
|
327
|
-
const sign = raw.charAt(0);
|
|
328
|
-
const match = /^ *(\d+)? ?(.*)$/.exec(raw.slice(1));
|
|
329
|
-
const lineNumber = match?.[1];
|
|
330
|
-
if (lineNumber === undefined) {
|
|
331
|
-
pendingBreak = true;
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
|
-
if (pendingBreak && current.length > 0) {
|
|
335
|
-
hunks.push(current);
|
|
336
|
-
current = [];
|
|
337
|
-
}
|
|
338
|
-
pendingBreak = false;
|
|
339
|
-
const kind = sign === "+" ? "add" : sign === "-" ? "del" : "ctx";
|
|
340
|
-
current.push({ kind, line: Number(lineNumber), text: match?.[2] ?? "" });
|
|
341
|
-
}
|
|
342
|
-
if (current.length === 0)
|
|
343
|
-
return undefined;
|
|
344
|
-
hunks.push(current);
|
|
345
|
-
return { kind: "hunks", hunks: hunks.map((lines) => ({ lines })) };
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* The name-free part of a summary for tools whose call arguments read well on their own. The tool
|
|
349
|
-
* name is the row's title, so repeating it here would print it twice.
|
|
350
|
-
*/
|
|
351
|
-
function formatArgumentSummary(toolName, parsed) {
|
|
352
|
-
const path = typeof parsed.path === "string"
|
|
353
|
-
? parsed.path.trim()
|
|
354
|
-
: typeof parsed.file_path === "string"
|
|
355
|
-
? parsed.file_path.trim()
|
|
356
|
-
: "";
|
|
357
|
-
switch (toolName) {
|
|
358
|
-
case "bash": {
|
|
359
|
-
const cmd = typeof parsed.command === "string" ? parsed.command.trim() : "";
|
|
360
|
-
if (!cmd)
|
|
361
|
-
return undefined;
|
|
362
|
-
return cmd.length > 70 ? `${cmd.slice(0, 67)}…` : cmd;
|
|
363
|
-
}
|
|
364
|
-
case "grep":
|
|
365
|
-
case "find": {
|
|
366
|
-
const pattern = typeof parsed.pattern === "string" ? parsed.pattern.trim() : "";
|
|
367
|
-
if (!pattern)
|
|
368
|
-
return undefined;
|
|
369
|
-
const target = path && path !== "." ? ` ${path}` : "";
|
|
370
|
-
return `${pattern}${target}`;
|
|
371
|
-
}
|
|
372
|
-
default: {
|
|
373
|
-
const keyArg = extractSummaryArg(toolName, parsed);
|
|
374
|
-
return keyArg ? keyArg : undefined;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* One compact nested summary for a tool call. Adopts the tool's standard collapsed
|
|
380
|
-
* single-line format when recognized, degrading to undefined so callers can use
|
|
381
|
-
* the default argument rendering.
|
|
382
|
-
*/
|
|
383
|
-
export function inlinePresentation(toolName, args, details, isError) {
|
|
384
|
-
const fileTool = fileToolPresentation(toolName, args, details, isError);
|
|
385
|
-
if (fileTool !== undefined)
|
|
386
|
-
return fileTool;
|
|
387
|
-
const parsed = parseCallArgs(args);
|
|
388
|
-
if (parsed === undefined)
|
|
389
|
-
return undefined;
|
|
390
|
-
if (toolName === EDIT_TOOL_NAME) {
|
|
391
|
-
const path = toolPath(parsed);
|
|
392
|
-
const source = isError ? undefined : editDiffSource(details);
|
|
393
|
-
const stats = source === undefined ? undefined : diffStats(source);
|
|
394
|
-
if (path === "" && stats === undefined)
|
|
395
|
-
return undefined;
|
|
396
|
-
return {
|
|
397
|
-
summary: stats === undefined
|
|
398
|
-
? path
|
|
399
|
-
: `${path} (+${String(stats.added)} -${String(stats.removed)})`.trim(),
|
|
400
|
-
status: isError ? "error" : "ok",
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
const baseSummary = formatArgumentSummary(toolName, parsed);
|
|
404
|
-
if (baseSummary === undefined)
|
|
405
|
-
return undefined;
|
|
406
|
-
return {
|
|
407
|
-
summary: baseSummary,
|
|
408
|
-
status: isError ? "error" : "ok",
|
|
409
|
-
};
|
|
410
|
-
}
|
package/dist/renderer.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ExtensionAPI, type ToolRendererResolver, type ToolRenderers } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { ToolUiRuntime } from "./runtime.js";
|
|
2
3
|
/** Optional host metadata is absent on early supported Pi versions. */
|
|
3
4
|
export type ToolRenderContext = Parameters<NonNullable<ToolRenderers["renderCall"]>>[2] & {
|
|
4
5
|
readonly durationMs?: number | undefined;
|
|
@@ -11,7 +12,8 @@ export type ToolRenderContext = Parameters<NonNullable<ToolRenderers["renderCall
|
|
|
11
12
|
*/
|
|
12
13
|
export declare const SPECIALIZED_TOOL_RENDERERS: readonly ["codemode"];
|
|
13
14
|
export type SpecializedToolRendererName = (typeof SPECIALIZED_TOOL_RENDERERS)[number];
|
|
14
|
-
export {
|
|
15
|
+
export { extractChildToolCopyText, inlinePresentation } from "./native-tools.js";
|
|
16
|
+
export { estimateStreamingTokens, type NestedCallRecord, } from "./payload-parser.js";
|
|
15
17
|
export interface CollapsibleToolRendererOptions {
|
|
16
18
|
readonly tools?: readonly string[] | undefined;
|
|
17
19
|
readonly match?: ((toolName: string) => boolean) | undefined;
|
|
@@ -20,7 +22,7 @@ export interface CollapsibleToolRendererOptions {
|
|
|
20
22
|
readonly copyToClipboard?: ((text: string) => Promise<void>) | undefined;
|
|
21
23
|
}
|
|
22
24
|
export declare function isMcpTool(toolName: string): boolean;
|
|
23
|
-
export declare function createCollapsibleToolRendererResolver(options?: CollapsibleToolRendererOptions): ToolRendererResolver;
|
|
25
|
+
export declare function createCollapsibleToolRendererResolver(runtime: ToolUiRuntime, options?: CollapsibleToolRendererOptions): ToolRendererResolver;
|
|
24
26
|
/** UI factory consumed by package-owned direct renderers, independently of host resolver precedence. */
|
|
25
|
-
export declare function createOptionalToolRendererFactory(pi: ExtensionAPI, options?: CollapsibleToolRendererOptions): import("@hheei/pi-ext-core").ToolRendererFactory;
|
|
26
|
-
export declare function registerCollapsibleToolRenderer(pi: ExtensionAPI, options?: CollapsibleToolRendererOptions): () => void;
|
|
27
|
+
export declare function createOptionalToolRendererFactory(pi: ExtensionAPI, runtime: ToolUiRuntime, options?: CollapsibleToolRendererOptions): import("@hheei/pi-ext-core").ToolRendererFactory;
|
|
28
|
+
export declare function registerCollapsibleToolRenderer(pi: ExtensionAPI, runtime: ToolUiRuntime, options?: CollapsibleToolRendererOptions): () => void;
|