@hheei/pi-ext-ui 0.2.0 → 0.2.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/extension.js +12 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/message-view.d.ts +15 -0
- package/dist/message-view.js +130 -0
- package/dist/nested-calls-view.js +24 -25
- package/dist/payload-parser.d.ts +3 -68
- package/dist/payload-parser.js +5 -279
- package/dist/renderer.d.ts +8 -2
- package/dist/renderer.js +72 -472
- package/package.json +6 -6
- package/dist/apply-patch-glyph.d.ts +0 -10
- package/dist/apply-patch-glyph.js +0 -29
- package/dist/apply-patch-preview.d.ts +0 -30
- package/dist/apply-patch-preview.js +0 -89
- package/dist/apply-patch-view.d.ts +0 -47
- package/dist/apply-patch-view.js +0 -198
package/dist/payload-parser.js
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
+
import { formatReadLineRange } from "@earendil-works/pi-coding-agent";
|
|
1
2
|
import { stripTerminalSequences } from "@earendil-works/pi-tui";
|
|
2
|
-
import { previewV4aOperations } from "./apply-patch-preview.js";
|
|
3
|
-
import { applyPatchFacts } from "./apply-patch-view.js";
|
|
4
3
|
import { diffStats } from "./diff-view.js";
|
|
5
|
-
export const APPLY_PATCH_TOOL_NAME = "apply_patch";
|
|
6
|
-
export const PYTHON_EVAL_TOOL_NAME = "python_eval";
|
|
7
|
-
export const PYTHON_EVAL_DETAILS_FORMAT = "pi-ext-tools-python-eval";
|
|
8
4
|
export const EDIT_TOOL_NAME = "edit";
|
|
9
5
|
export const WRITE_TOOL_NAME = "write";
|
|
10
|
-
export const EDIT_VIEW_KEY = "__piExtToolsEditView";
|
|
11
|
-
export const WRITE_VIEW_KEY = "__piExtToolsWriteView";
|
|
12
6
|
export const CODEMODE_OPTIONS_PREFIX = "// @options:";
|
|
13
7
|
export const CODEMODE_SCRIPT_HEADER = /^Script (?:completed|failed)\r?\nWall time ([\d.]+) seconds\r?\nOutput:\r?\n/;
|
|
14
8
|
export function isRecord(value) {
|
|
@@ -257,108 +251,17 @@ export function toolPath(args) {
|
|
|
257
251
|
const val = parsed.path ?? parsed.file_path;
|
|
258
252
|
return typeof val === "string" ? val.trim() : "";
|
|
259
253
|
}
|
|
260
|
-
export function
|
|
261
|
-
return toolPath(args);
|
|
262
|
-
}
|
|
263
|
-
export function numericField(value, key) {
|
|
264
|
-
return isRecord(value) && typeof value[key] === "number" ? value[key] : undefined;
|
|
265
|
-
}
|
|
266
|
-
export function writeStoredDiff(lines) {
|
|
267
|
-
const hunks = [];
|
|
268
|
-
let hunk = [];
|
|
269
|
-
const finish = () => {
|
|
270
|
-
if (hunk.length > 0)
|
|
271
|
-
hunks.push(hunk);
|
|
272
|
-
hunk = [];
|
|
273
|
-
};
|
|
274
|
-
for (const entry of lines) {
|
|
275
|
-
if (!isRecord(entry))
|
|
276
|
-
return undefined;
|
|
277
|
-
if (entry.type === "sep") {
|
|
278
|
-
finish();
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
if (entry.type !== "add" && entry.type !== "del" && entry.type !== "ctx")
|
|
282
|
-
return undefined;
|
|
283
|
-
if (typeof entry.content !== "string")
|
|
284
|
-
return undefined;
|
|
285
|
-
const line = entry.type === "del" ? entry.oldNum : entry.newNum;
|
|
286
|
-
if (typeof line !== "number")
|
|
287
|
-
return undefined;
|
|
288
|
-
hunk.push({ kind: entry.type, line, text: entry.content });
|
|
289
|
-
}
|
|
290
|
-
finish();
|
|
291
|
-
return hunks.length === 0
|
|
292
|
-
? undefined
|
|
293
|
-
: { kind: "hunks", hunks: hunks.map((lines) => ({ lines })) };
|
|
294
|
-
}
|
|
295
|
-
/** Uses only the baseline and content already stored by the write tool. */
|
|
296
|
-
export function writePresentation(args, details) {
|
|
297
|
-
const view = isRecord(details) ? details[WRITE_VIEW_KEY] : undefined;
|
|
298
|
-
if (!isRecord(view))
|
|
299
|
-
return undefined;
|
|
300
|
-
if (view.kind === "noChange")
|
|
301
|
-
return { kind: "noChange" };
|
|
302
|
-
if (view.kind === "diff") {
|
|
303
|
-
const stored = Array.isArray(view.lines) ? writeStoredDiff(view.lines) : undefined;
|
|
304
|
-
if (stored === undefined)
|
|
305
|
-
return undefined;
|
|
306
|
-
const source = stored;
|
|
307
|
-
const stats = diffStats(source);
|
|
308
|
-
return stats === undefined
|
|
309
|
-
? undefined
|
|
310
|
-
: {
|
|
311
|
-
kind: "diff",
|
|
312
|
-
source,
|
|
313
|
-
added: numericField(view, "added") ?? stats.added,
|
|
314
|
-
removed: numericField(view, "removed") ?? stats.removed,
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
if (view.kind !== "new" && view.kind !== "replace")
|
|
318
|
-
return undefined;
|
|
319
|
-
const content = typeof view.content === "string"
|
|
320
|
-
? view.content
|
|
321
|
-
: isRecord(args) && typeof args.content === "string"
|
|
322
|
-
? args.content
|
|
323
|
-
: undefined;
|
|
324
|
-
if (content === undefined)
|
|
325
|
-
return undefined;
|
|
326
|
-
const lines = numericField(view, "lines") ?? content.split("\n").length;
|
|
327
|
-
return {
|
|
328
|
-
kind: "content",
|
|
329
|
-
label: view.kind === "new" ? `new file (${String(lines)} lines)` : `wrote (${String(lines)} lines)`,
|
|
330
|
-
content,
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
export function fileToolPresentation(toolName, args, details, isError) {
|
|
254
|
+
export function fileToolPresentation(toolName, args, _details, isError) {
|
|
334
255
|
const path = toolPath(args);
|
|
335
256
|
if (toolName === "read") {
|
|
336
257
|
if (path === "")
|
|
337
258
|
return undefined;
|
|
338
259
|
const parsed = parseCallArgs(args);
|
|
339
|
-
|
|
340
|
-
if (typeof parsed?.offset === "number" || typeof parsed?.limit === "number") {
|
|
341
|
-
const start = typeof parsed.offset === "number" ? parsed.offset : 1;
|
|
342
|
-
const end = typeof parsed.limit === "number" ? start + parsed.limit - 1 : "";
|
|
343
|
-
range = `:${String(start)}${end ? `-${String(end)}` : ""}`;
|
|
344
|
-
}
|
|
260
|
+
const range = formatReadLineRange(parsed?.offset, parsed?.limit);
|
|
345
261
|
return { summary: `${path}${range}`, status: isError ? "error" : "ok" };
|
|
346
262
|
}
|
|
347
263
|
if (toolName === WRITE_TOOL_NAME) {
|
|
348
|
-
|
|
349
|
-
const delta = presentation?.kind === "diff"
|
|
350
|
-
? ` (+${String(presentation.added)} -${String(presentation.removed)})`
|
|
351
|
-
: presentation?.kind === "content"
|
|
352
|
-
? ` (${presentation.label})`
|
|
353
|
-
: presentation?.kind === "noChange"
|
|
354
|
-
? " (no changes)"
|
|
355
|
-
: "";
|
|
356
|
-
if (path === "" && delta === "")
|
|
357
|
-
return undefined;
|
|
358
|
-
return {
|
|
359
|
-
summary: `${path}${delta}`.trim(),
|
|
360
|
-
status: isError ? "error" : presentation?.kind === "noChange" ? "none" : "ok",
|
|
361
|
-
};
|
|
264
|
+
return path === "" ? undefined : { summary: path, status: isError ? "error" : "ok" };
|
|
362
265
|
}
|
|
363
266
|
return undefined;
|
|
364
267
|
}
|
|
@@ -372,7 +275,7 @@ export function editDiffSource(details) {
|
|
|
372
275
|
if (typeof details.diff === "string" && details.diff.trim() !== "") {
|
|
373
276
|
return displayDiffSource(details.diff);
|
|
374
277
|
}
|
|
375
|
-
return
|
|
278
|
+
return undefined;
|
|
376
279
|
}
|
|
377
280
|
export function displayDiffSource(diff) {
|
|
378
281
|
const hunks = [];
|
|
@@ -401,128 +304,6 @@ export function displayDiffSource(diff) {
|
|
|
401
304
|
hunks.push(current);
|
|
402
305
|
return { kind: "hunks", hunks: hunks.map((lines) => ({ lines })) };
|
|
403
306
|
}
|
|
404
|
-
export function editViewSource(view) {
|
|
405
|
-
const hunks = editViewHunks(view);
|
|
406
|
-
return hunks.length === 0 ? undefined : { kind: "hunks", hunks };
|
|
407
|
-
}
|
|
408
|
-
export function editViewHunks(view) {
|
|
409
|
-
if (!isRecord(view))
|
|
410
|
-
return [];
|
|
411
|
-
const ops = view.kind === "single" && isRecord(view.op)
|
|
412
|
-
? [view.op]
|
|
413
|
-
: view.kind === "multi" && Array.isArray(view.ops)
|
|
414
|
-
? view.ops.filter(isRecord)
|
|
415
|
-
: [];
|
|
416
|
-
return ops.flatMap((op) => hunksForEditOp(op));
|
|
417
|
-
}
|
|
418
|
-
export function hunksForEditOp(op) {
|
|
419
|
-
if (typeof op.oldContent !== "string" || typeof op.newContent !== "string")
|
|
420
|
-
return [];
|
|
421
|
-
const startLine = typeof op.startLine === "number" && op.startLine > 0 ? op.startLine : 1;
|
|
422
|
-
return lineHunks(op.oldContent.replace(/\r\n/g, "\n"), op.newContent.replace(/\r\n/g, "\n"), startLine);
|
|
423
|
-
}
|
|
424
|
-
export function splitFragment(text) {
|
|
425
|
-
if (text === "")
|
|
426
|
-
return [];
|
|
427
|
-
const lines = text.split("\n");
|
|
428
|
-
if (lines.at(-1) === "")
|
|
429
|
-
lines.pop();
|
|
430
|
-
return lines;
|
|
431
|
-
}
|
|
432
|
-
/** Line LCS of one stored before/after fragment. No file context is invented. */
|
|
433
|
-
export function lineHunks(before, after, startLine) {
|
|
434
|
-
const oldLines = splitFragment(before);
|
|
435
|
-
const newLines = splitFragment(after);
|
|
436
|
-
const oldCount = oldLines.length;
|
|
437
|
-
const newCount = newLines.length;
|
|
438
|
-
const lengths = Array.from({ length: oldCount + 1 }, () => Array(newCount + 1).fill(0));
|
|
439
|
-
for (let oldIndex = oldCount - 1; oldIndex >= 0; oldIndex -= 1) {
|
|
440
|
-
const row = lengths[oldIndex];
|
|
441
|
-
const next = lengths[oldIndex + 1];
|
|
442
|
-
if (row === undefined || next === undefined)
|
|
443
|
-
continue;
|
|
444
|
-
for (let newIndex = newCount - 1; newIndex >= 0; newIndex -= 1) {
|
|
445
|
-
row[newIndex] =
|
|
446
|
-
oldLines[oldIndex] === newLines[newIndex]
|
|
447
|
-
? (next[newIndex + 1] ?? 0) + 1
|
|
448
|
-
: Math.max(next[newIndex] ?? 0, row[newIndex + 1] ?? 0);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
const lines = [];
|
|
452
|
-
let oldIndex = 0;
|
|
453
|
-
let newIndex = 0;
|
|
454
|
-
let oldLine = startLine;
|
|
455
|
-
let newLine = startLine;
|
|
456
|
-
while (oldIndex < oldCount || newIndex < newCount) {
|
|
457
|
-
const row = lengths[oldIndex];
|
|
458
|
-
const next = lengths[oldIndex + 1];
|
|
459
|
-
if (oldIndex < oldCount && newIndex < newCount && oldLines[oldIndex] === newLines[newIndex]) {
|
|
460
|
-
lines.push({ kind: "ctx", line: newLine, text: oldLines[oldIndex] ?? "" });
|
|
461
|
-
oldIndex += 1;
|
|
462
|
-
newIndex += 1;
|
|
463
|
-
oldLine += 1;
|
|
464
|
-
newLine += 1;
|
|
465
|
-
}
|
|
466
|
-
else if (newIndex < newCount &&
|
|
467
|
-
(oldIndex === oldCount || (row?.[newIndex + 1] ?? 0) > (next?.[newIndex] ?? 0))) {
|
|
468
|
-
lines.push({ kind: "add", line: newLine, text: newLines[newIndex] ?? "" });
|
|
469
|
-
newIndex += 1;
|
|
470
|
-
newLine += 1;
|
|
471
|
-
}
|
|
472
|
-
else {
|
|
473
|
-
lines.push({ kind: "del", line: oldLine, text: oldLines[oldIndex] ?? "" });
|
|
474
|
-
oldIndex += 1;
|
|
475
|
-
oldLine += 1;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
const changed = lines.some((line) => line.kind !== "ctx");
|
|
479
|
-
return changed ? [{ lines }] : [];
|
|
480
|
-
}
|
|
481
|
-
export function patchTextForCopy(details) {
|
|
482
|
-
if (isRecord(details) && typeof details.patch === "string")
|
|
483
|
-
return details.patch;
|
|
484
|
-
const hunks = editViewHunks(isRecord(details) ? details[EDIT_VIEW_KEY] : undefined);
|
|
485
|
-
if (hunks.length === 0)
|
|
486
|
-
return undefined;
|
|
487
|
-
return (hunks
|
|
488
|
-
.map((hunk) => {
|
|
489
|
-
let oldStart = 0;
|
|
490
|
-
let newStart = 0;
|
|
491
|
-
let oldCount = 0;
|
|
492
|
-
let newCount = 0;
|
|
493
|
-
const body = hunk.lines.map((line) => {
|
|
494
|
-
if (line.kind !== "add") {
|
|
495
|
-
oldCount += 1;
|
|
496
|
-
if (oldStart === 0)
|
|
497
|
-
oldStart = line.line;
|
|
498
|
-
}
|
|
499
|
-
if (line.kind !== "del") {
|
|
500
|
-
newCount += 1;
|
|
501
|
-
if (newStart === 0)
|
|
502
|
-
newStart = line.line;
|
|
503
|
-
}
|
|
504
|
-
const sign = line.kind === "add" ? "+" : line.kind === "del" ? "-" : " ";
|
|
505
|
-
return sign + line.text;
|
|
506
|
-
});
|
|
507
|
-
if (body.length === 0)
|
|
508
|
-
return "";
|
|
509
|
-
const header = "@@ -" +
|
|
510
|
-
String(oldStart || 1) +
|
|
511
|
-
"," +
|
|
512
|
-
String(oldCount) +
|
|
513
|
-
" +" +
|
|
514
|
-
String(newStart || 1) +
|
|
515
|
-
"," +
|
|
516
|
-
String(newCount) +
|
|
517
|
-
" @@";
|
|
518
|
-
return [header, ...body].join("\n");
|
|
519
|
-
})
|
|
520
|
-
.filter((hunk) => hunk !== "")
|
|
521
|
-
.join("\n") || undefined);
|
|
522
|
-
}
|
|
523
|
-
export function patchSummary(files, added, removed) {
|
|
524
|
-
return `modify ${files}${files === 1 ? " file" : " files"} (+${added} -${removed})`;
|
|
525
|
-
}
|
|
526
307
|
/**
|
|
527
308
|
* The name-free part of a summary for tools whose call arguments read well on their own. The tool
|
|
528
309
|
* name is the row's title, so repeating it here would print it twice.
|
|
@@ -564,22 +345,6 @@ export function inlinePresentation(toolName, args, details, isError) {
|
|
|
564
345
|
if (fileTool !== undefined)
|
|
565
346
|
return fileTool;
|
|
566
347
|
const parsed = parseCallArgs(args);
|
|
567
|
-
if (toolName === APPLY_PATCH_TOOL_NAME) {
|
|
568
|
-
const facts = applyPatchFacts(details);
|
|
569
|
-
if (facts !== undefined) {
|
|
570
|
-
return {
|
|
571
|
-
summary: patchSummary(new Set(facts.changedPaths).size, facts.addedLines, facts.removedLines),
|
|
572
|
-
status: isError ? "error" : "ok",
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
if (parsed === undefined || typeof parsed.patch !== "string")
|
|
576
|
-
return undefined;
|
|
577
|
-
const operations = previewV4aOperations(parsed.patch, true, undefined);
|
|
578
|
-
return {
|
|
579
|
-
summary: patchSummary(new Set(operations.map((operation) => operation.path)).size, operations.reduce((sum, operation) => sum + operation.addedLines, 0), operations.reduce((sum, operation) => sum + operation.removedLines, 0)),
|
|
580
|
-
status: isError ? "error" : "ok",
|
|
581
|
-
};
|
|
582
|
-
}
|
|
583
348
|
if (parsed === undefined)
|
|
584
349
|
return undefined;
|
|
585
350
|
if (toolName === EDIT_TOOL_NAME) {
|
|
@@ -603,42 +368,3 @@ export function inlinePresentation(toolName, args, details, isError) {
|
|
|
603
368
|
status: isError ? "error" : "ok",
|
|
604
369
|
};
|
|
605
370
|
}
|
|
606
|
-
/** Reads only the persisted `pi-ext-tools-eval` details; other shapes degrade to plain output. */
|
|
607
|
-
export function pythonEvalFacts(details) {
|
|
608
|
-
if (!isRecord(details) || details.format !== PYTHON_EVAL_DETAILS_FORMAT)
|
|
609
|
-
return undefined;
|
|
610
|
-
const rawRows = Array.isArray(details.rows) ? details.rows : [];
|
|
611
|
-
const rows = [];
|
|
612
|
-
for (const entry of rawRows) {
|
|
613
|
-
if (!isRecord(entry))
|
|
614
|
-
continue;
|
|
615
|
-
if (entry.kind === "text" && typeof entry.text === "string") {
|
|
616
|
-
rows.push({ kind: "text", text: entry.text });
|
|
617
|
-
}
|
|
618
|
-
else if (entry.kind === "display" && typeof entry.text === "string") {
|
|
619
|
-
rows.push({ kind: "display", text: entry.text });
|
|
620
|
-
}
|
|
621
|
-
else if (entry.kind === "result" && typeof entry.text === "string") {
|
|
622
|
-
rows.push({ kind: "result", text: entry.text });
|
|
623
|
-
}
|
|
624
|
-
else if (entry.kind === "state" && typeof entry.text === "string") {
|
|
625
|
-
rows.push({ kind: "state", text: entry.text });
|
|
626
|
-
}
|
|
627
|
-
else if (entry.kind === "tool" && isRecord(entry.trace)) {
|
|
628
|
-
const trace = entry.trace;
|
|
629
|
-
if (typeof trace.name !== "string")
|
|
630
|
-
continue;
|
|
631
|
-
rows.push({
|
|
632
|
-
kind: "tool",
|
|
633
|
-
name: trace.name,
|
|
634
|
-
args: trace.args,
|
|
635
|
-
durationMs: typeof trace.durationMs === "number" ? trace.durationMs : undefined,
|
|
636
|
-
error: typeof trace.error === "string" ? trace.error : undefined,
|
|
637
|
-
});
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
return {
|
|
641
|
-
rows,
|
|
642
|
-
error: typeof details.error === "string" ? details.error : undefined,
|
|
643
|
-
};
|
|
644
|
-
}
|
package/dist/renderer.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { type ExtensionAPI, type ToolRendererResolver, type ToolRenderers } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
export type ToolRenderContext = Parameters<NonNullable<ToolRenderers["renderCall"]>>[2];
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Upstream tools whose call and result views pi-ext-ui still frames itself. Concrete tool packages
|
|
5
|
+
* own their own presentation through ext-core's optional renderer point, so their names never
|
|
6
|
+
* appear here.
|
|
7
|
+
*/
|
|
8
|
+
export declare const SPECIALIZED_TOOL_RENDERERS: readonly ["codemode"];
|
|
5
9
|
export type SpecializedToolRendererName = (typeof SPECIALIZED_TOOL_RENDERERS)[number];
|
|
6
10
|
/** Web-access tools use the shared folding shell while retaining their native body renderer. */
|
|
7
11
|
export declare const WEB_ACCESS_TOOLS: readonly ["web_search", "web_fetch", "web_open"];
|
|
@@ -15,4 +19,6 @@ export interface CollapsibleToolRendererOptions {
|
|
|
15
19
|
readonly copyToClipboard?: ((text: string) => Promise<void>) | undefined;
|
|
16
20
|
}
|
|
17
21
|
export declare function createCollapsibleToolRendererResolver(options?: CollapsibleToolRendererOptions): ToolRendererResolver;
|
|
22
|
+
/** UI factory consumed by package-owned direct renderers, independently of host resolver precedence. */
|
|
23
|
+
export declare function createOptionalToolRendererFactory(pi: ExtensionAPI, options?: CollapsibleToolRendererOptions): import("@hheei/pi-ext-core").ToolRendererFactory;
|
|
18
24
|
export declare function registerCollapsibleToolRenderer(pi: ExtensionAPI, options?: CollapsibleToolRendererOptions): void;
|