@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/renderer.js
CHANGED
|
@@ -1,35 +1,26 @@
|
|
|
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, } from "@hheei/pi-ext-core";
|
|
4
|
-
import {
|
|
5
|
-
import { previewV4aOperations } from "./apply-patch-preview.js";
|
|
6
|
-
import { applyPatchFacts, formatApplyPatchOperationRow, } from "./apply-patch-view.js";
|
|
7
|
-
import { DiffView, diffStats } from "./diff-view.js";
|
|
3
|
+
import { agentResultText, getRegisteredToolPresentationProvider, getToolPresentationProvider, hasOptionalToolRenderers, } from "@hheei/pi-ext-core";
|
|
4
|
+
import { DiffView } from "./diff-view.js";
|
|
8
5
|
import { NestedCallsView } from "./nested-calls-view.js";
|
|
9
6
|
import { formatToolHeaderText, ToolView, } from "./tool-view.js";
|
|
10
7
|
import { getCapturedNestedCalls, recordToolExecutionEnd, recordToolExecutionStart, } from "./tracker.js";
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"codemode",
|
|
18
|
-
];
|
|
8
|
+
/**
|
|
9
|
+
* Upstream tools whose call and result views pi-ext-ui still frames itself. Concrete tool packages
|
|
10
|
+
* own their own presentation through ext-core's optional renderer point, so their names never
|
|
11
|
+
* appear here.
|
|
12
|
+
*/
|
|
13
|
+
export const SPECIALIZED_TOOL_RENDERERS = ["codemode"];
|
|
19
14
|
/** Web-access tools use the shared folding shell while retaining their native body renderer. */
|
|
20
15
|
export const WEB_ACCESS_TOOLS = ["web_search", "web_fetch", "web_open"];
|
|
21
|
-
import {
|
|
16
|
+
import { callStatsParts, estimateStreamingTokens, extractHeavyInputCommand, extractNestedCalls, extractResultText, extractSummaryArg, formatCostUsd, formatDurationMs, inlinePresentation, isRecord, resolveContainerStatus, toolPath, } from "./payload-parser.js";
|
|
22
17
|
export { estimateStreamingTokens, inlinePresentation, } from "./payload-parser.js";
|
|
23
|
-
const APPLY_PATCH_PREVIEW_STATE_KEY = "__piExtUiApplyPatchPreview";
|
|
24
|
-
const PYTHON_EVAL_TOOL_TITLE = "python_eval";
|
|
25
18
|
const SPECIALIZED_TOOL_SET = new Set(SPECIALIZED_TOOL_RENDERERS);
|
|
26
19
|
const WEB_ACCESS_TOOL_SET = new Set(WEB_ACCESS_TOOLS);
|
|
27
20
|
const CALL_VIEW_STATE_KEY = "__piExtUiCallView";
|
|
28
21
|
function detectCodeLanguage(toolName) {
|
|
29
22
|
if (toolName === "bash")
|
|
30
23
|
return "bash";
|
|
31
|
-
if (toolName === "python_eval")
|
|
32
|
-
return "python";
|
|
33
24
|
if (toolName === "codemode")
|
|
34
25
|
return "javascript";
|
|
35
26
|
return undefined;
|
|
@@ -82,15 +73,12 @@ function rememberCallView(context, view) {
|
|
|
82
73
|
}
|
|
83
74
|
}
|
|
84
75
|
/**
|
|
85
|
-
* A result view takes over from the call view. A container tool yields on any result,
|
|
86
|
-
*
|
|
87
|
-
*
|
|
76
|
+
* A result view takes over from the call view. A container tool yields on any result, because its
|
|
77
|
+
* own row would duplicate the nested rows and output it now carries; every other tool keeps its
|
|
78
|
+
* call row until the final result. A provider states that for its own tool, so no name is involved.
|
|
88
79
|
*/
|
|
89
|
-
function hideCallView(context,
|
|
90
|
-
if (
|
|
91
|
-
toolName !== PYTHON_EVAL_TOOL_NAME &&
|
|
92
|
-
toolName !== APPLY_PATCH_TOOL_NAME &&
|
|
93
|
-
isPartial)
|
|
80
|
+
function hideCallView(context, replaceCallOnPartial, isPartial) {
|
|
81
|
+
if (!replaceCallOnPartial && isPartial)
|
|
94
82
|
return;
|
|
95
83
|
const view = isRecord(context.state) ? context.state[CALL_VIEW_STATE_KEY] : undefined;
|
|
96
84
|
if (view instanceof ToolView)
|
|
@@ -116,358 +104,6 @@ function formatNestedCallRow(call, theme) {
|
|
|
116
104
|
.join(" · "),
|
|
117
105
|
}, theme);
|
|
118
106
|
}
|
|
119
|
-
function renderEditResult(params) {
|
|
120
|
-
const { theme, context, result, renderOptions, defaultLevel, options } = params;
|
|
121
|
-
const path = editPath(context.args);
|
|
122
|
-
const isError = context.isError || result.isError;
|
|
123
|
-
const source = isError ? undefined : editDiffSource(result.details);
|
|
124
|
-
const stats = source === undefined ? undefined : diffStats(source);
|
|
125
|
-
const summaryParts = [
|
|
126
|
-
stats === undefined ? path : `${path} (+${String(stats.added)} -${String(stats.removed)})`,
|
|
127
|
-
];
|
|
128
|
-
const durText = formatDurationMs(context.durationMs);
|
|
129
|
-
if (durText !== "")
|
|
130
|
-
summaryParts.push(durText);
|
|
131
|
-
const summary = summaryParts.join(" · ");
|
|
132
|
-
const output = stripTerminalSequences(extractResultText(EDIT_TOOL_NAME, result).outputText);
|
|
133
|
-
const patch = patchTextForCopy(result.details);
|
|
134
|
-
// A successful edit hides the top-level check mark; the diff body carries the change.
|
|
135
|
-
const status = isError
|
|
136
|
-
? "error"
|
|
137
|
-
: renderOptions.isPartial
|
|
138
|
-
? "running"
|
|
139
|
-
: source !== undefined
|
|
140
|
-
? "none"
|
|
141
|
-
: "ok";
|
|
142
|
-
const sections = source === undefined
|
|
143
|
-
? output === ""
|
|
144
|
-
? []
|
|
145
|
-
: [{ railToken: isError ? "error" : "dim", content: output }]
|
|
146
|
-
: [{ railToken: "dim", content: new DiffView({ theme, source }) }];
|
|
147
|
-
const copy = patch !== undefined ? { primary: patch } : output !== "" ? { primary: output } : undefined;
|
|
148
|
-
return new ToolView({
|
|
149
|
-
theme,
|
|
150
|
-
header: { title: EDIT_TOOL_NAME, status, summary },
|
|
151
|
-
sections,
|
|
152
|
-
copy,
|
|
153
|
-
outputPad: context.outputPad,
|
|
154
|
-
stateHolder: context.state,
|
|
155
|
-
defaultLevel,
|
|
156
|
-
globalExpanded: renderOptions.expanded,
|
|
157
|
-
invalidate: () => context.invalidate(),
|
|
158
|
-
copyToClipboard: options?.copyToClipboard,
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
function renderWriteResult(params) {
|
|
162
|
-
const { theme, context, result, renderOptions, defaultLevel, options } = params;
|
|
163
|
-
const path = toolPath(context.args);
|
|
164
|
-
const isError = context.isError || result.isError;
|
|
165
|
-
const presentation = isError ? undefined : writePresentation(context.args, result.details);
|
|
166
|
-
const output = stripTerminalSequences(extractResultText(WRITE_TOOL_NAME, result).outputText);
|
|
167
|
-
const baseSummary = presentation?.kind === "diff"
|
|
168
|
-
? `${path} (+${String(presentation.added)} -${String(presentation.removed)})`
|
|
169
|
-
: presentation?.kind === "content"
|
|
170
|
-
? `${path} (${presentation.label})`
|
|
171
|
-
: presentation?.kind === "noChange"
|
|
172
|
-
? `${path} (no changes)`
|
|
173
|
-
: path;
|
|
174
|
-
const durText = formatDurationMs(context.durationMs);
|
|
175
|
-
const summaryParts = [baseSummary];
|
|
176
|
-
if (durText !== "")
|
|
177
|
-
summaryParts.push(durText);
|
|
178
|
-
const summary = summaryParts.join(" · ");
|
|
179
|
-
const sections = presentation?.kind === "diff"
|
|
180
|
-
? [{ railToken: "dim", content: new DiffView({ theme, source: presentation.source }) }]
|
|
181
|
-
: presentation?.kind === "content"
|
|
182
|
-
? [{ railToken: "dim", content: presentation.content }]
|
|
183
|
-
: output === ""
|
|
184
|
-
? []
|
|
185
|
-
: [{ railToken: isError ? "error" : "dim", content: output }];
|
|
186
|
-
const copyText = presentation?.kind === "content"
|
|
187
|
-
? presentation.content
|
|
188
|
-
: presentation?.kind === "diff"
|
|
189
|
-
? undefined
|
|
190
|
-
: output;
|
|
191
|
-
return new ToolView({
|
|
192
|
-
theme,
|
|
193
|
-
header: {
|
|
194
|
-
title: WRITE_TOOL_NAME,
|
|
195
|
-
status: isError
|
|
196
|
-
? "error"
|
|
197
|
-
: renderOptions.isPartial
|
|
198
|
-
? "running"
|
|
199
|
-
: presentation?.kind === "diff"
|
|
200
|
-
? "none"
|
|
201
|
-
: presentation?.kind === "noChange"
|
|
202
|
-
? "none"
|
|
203
|
-
: "ok",
|
|
204
|
-
summary,
|
|
205
|
-
},
|
|
206
|
-
sections,
|
|
207
|
-
copy: copyText !== undefined && copyText !== "" ? { primary: copyText } : undefined,
|
|
208
|
-
outputPad: context.outputPad,
|
|
209
|
-
stateHolder: context.state,
|
|
210
|
-
defaultLevel,
|
|
211
|
-
globalExpanded: renderOptions.expanded,
|
|
212
|
-
invalidate: () => context.invalidate(),
|
|
213
|
-
copyToClipboard: options?.copyToClipboard,
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
/** A nested file-tool row keeps its tool name; other tools degrade to name plus bounded args. */
|
|
217
|
-
function formatEvalToolRow(row, theme) {
|
|
218
|
-
const isError = row.error !== undefined;
|
|
219
|
-
const inline = inlinePresentation(row.name, row.args, undefined, isError);
|
|
220
|
-
const summary = typeof row.args === "string" && row.args.trim() !== ""
|
|
221
|
-
? stripTerminalSequences(row.args.trim())
|
|
222
|
-
: "";
|
|
223
|
-
const truncated = summary.length > 72 ? `${summary.slice(0, 69)}…` : summary;
|
|
224
|
-
return formatToolHeaderText({
|
|
225
|
-
status: isError ? "error" : "ok",
|
|
226
|
-
title: row.name,
|
|
227
|
-
summary: [inline?.summary ?? truncated, formatDurationMs(row.durationMs)]
|
|
228
|
-
.filter(Boolean)
|
|
229
|
-
.join(" · "),
|
|
230
|
-
}, theme);
|
|
231
|
-
}
|
|
232
|
-
function renderPythonEvalResult(params) {
|
|
233
|
-
const { theme, context, result, renderOptions, defaultLevel, options } = params;
|
|
234
|
-
const facts = pythonEvalFacts(result.details);
|
|
235
|
-
const isError = (context.isError ?? false) || (result.isError ?? false);
|
|
236
|
-
const toolRows = facts?.rows.filter((row) => row.kind === "tool") ??
|
|
237
|
-
[];
|
|
238
|
-
const anyToolError = toolRows.some((row) => row.error !== undefined);
|
|
239
|
-
const failed = isError || facts?.error !== undefined || anyToolError;
|
|
240
|
-
const status = resolveContainerStatus({
|
|
241
|
-
isError,
|
|
242
|
-
isPartial: renderOptions.isPartial === true,
|
|
243
|
-
hasChildren: toolRows.length > 0,
|
|
244
|
-
selfFailed: failed && !anyToolError,
|
|
245
|
-
});
|
|
246
|
-
const parts = [];
|
|
247
|
-
if (isRecord(context.args)) {
|
|
248
|
-
if (context.args.reset === true)
|
|
249
|
-
parts.push("(reset)");
|
|
250
|
-
if (typeof context.args.timeout_ms === "number")
|
|
251
|
-
parts.push(formatTimeoutFact(context.args.timeout_ms));
|
|
252
|
-
}
|
|
253
|
-
if (facts !== undefined) {
|
|
254
|
-
if (toolRows.length > 0) {
|
|
255
|
-
const failCount = toolRows.filter((row) => row.error !== undefined).length;
|
|
256
|
-
parts.push(...callStatsParts(toolRows.length, failCount));
|
|
257
|
-
}
|
|
258
|
-
else if (facts.rows.length > 0) {
|
|
259
|
-
parts.push(`${String(facts.rows.length)} ${facts.rows.length === 1 ? "row" : "rows"}`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
const durText = formatDurationMs(context.durationMs);
|
|
263
|
-
if (durText !== "")
|
|
264
|
-
parts.push(durText);
|
|
265
|
-
const summary = parts.join(" · ");
|
|
266
|
-
const collapsedRows = toolRows.length > 0 ? toolRows.map((row) => formatEvalToolRow(row, theme)) : undefined;
|
|
267
|
-
const code = extractHeavyInputCommand(PYTHON_EVAL_TOOL_NAME, context.args);
|
|
268
|
-
const inputSection = createInputCodeSection(PYTHON_EVAL_TOOL_NAME, context.args, theme);
|
|
269
|
-
const sections = inputSection !== undefined ? [inputSection] : [];
|
|
270
|
-
if (facts !== undefined) {
|
|
271
|
-
const outputRows = [];
|
|
272
|
-
for (const row of facts.rows) {
|
|
273
|
-
if (row.kind === "tool") {
|
|
274
|
-
outputRows.push(formatEvalToolRow(row, theme));
|
|
275
|
-
}
|
|
276
|
-
else if (row.kind === "state") {
|
|
277
|
-
// The namespace report is metadata about the cell, not its output, so it stays muted.
|
|
278
|
-
outputRows.push(theme.fg("muted", row.text));
|
|
279
|
-
}
|
|
280
|
-
else if (row.kind === "display" || row.kind === "result") {
|
|
281
|
-
const prefix = theme.fg("dim", `${row.kind}:`);
|
|
282
|
-
const lines = row.text.replace(/\r/g, "").split("\n");
|
|
283
|
-
for (let i = 0; i < lines.length; i++) {
|
|
284
|
-
const lineText = theme.fg("dim", lines[i] ?? "");
|
|
285
|
-
outputRows.push(i === 0 ? `${prefix} ${lineText}` : ` ${lineText}`);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
else if (row.text !== "") {
|
|
289
|
-
const lines = row.text.replace(/\r/g, "").split("\n");
|
|
290
|
-
outputRows.push(...lines.map((l) => (isError ? theme.fg("error", l) : theme.fg("dim", l))));
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
if (outputRows.length > 0) {
|
|
294
|
-
sections.push({
|
|
295
|
-
railToken: isError ? "error" : "success",
|
|
296
|
-
content: outputRows,
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
else {
|
|
301
|
-
const output = stripTerminalSequences(extractResultText(PYTHON_EVAL_TOOL_NAME, result).outputText);
|
|
302
|
-
if (output !== "") {
|
|
303
|
-
const rawLines = output.split("\n");
|
|
304
|
-
const outputLines = isError
|
|
305
|
-
? rawLines.map((l) => theme.fg("error", l))
|
|
306
|
-
: rawLines.map((l) => theme.fg("dim", l));
|
|
307
|
-
sections.push({ railToken: isError ? "error" : "success", content: outputLines });
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
const output = stripTerminalSequences(extractResultText(PYTHON_EVAL_TOOL_NAME, result).outputText);
|
|
311
|
-
const copy = code !== undefined && code !== ""
|
|
312
|
-
? { primary: code, full: output !== "" ? `${code}\n---\n${output}` : code }
|
|
313
|
-
: output !== ""
|
|
314
|
-
? { primary: output }
|
|
315
|
-
: undefined;
|
|
316
|
-
return new ToolView({
|
|
317
|
-
theme,
|
|
318
|
-
header: {
|
|
319
|
-
title: PYTHON_EVAL_TOOL_TITLE,
|
|
320
|
-
status,
|
|
321
|
-
summary,
|
|
322
|
-
},
|
|
323
|
-
collapsedRows,
|
|
324
|
-
sections,
|
|
325
|
-
copy,
|
|
326
|
-
outputPad: context.outputPad,
|
|
327
|
-
stateHolder: context.state,
|
|
328
|
-
defaultLevel,
|
|
329
|
-
globalExpanded: renderOptions.expanded,
|
|
330
|
-
invalidate: () => context.invalidate(),
|
|
331
|
-
copyToClipboard: options?.copyToClipboard,
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
/**
|
|
335
|
-
* Streaming call view: operation headers recognized from the patch prefix
|
|
336
|
-
* preview as pending rows. `` only marks a recognized header — it promises
|
|
337
|
-
* neither validation nor application.
|
|
338
|
-
*/
|
|
339
|
-
function renderApplyPatchCallView(params) {
|
|
340
|
-
const { theme, context, defaultLevel, options } = params;
|
|
341
|
-
const patch = isRecord(context.args) && typeof context.args.patch === "string" ? context.args.patch : "";
|
|
342
|
-
const host = isRecord(context.args) &&
|
|
343
|
-
typeof context.args.target === "string" &&
|
|
344
|
-
context.args.target !== "local"
|
|
345
|
-
? context.args.target
|
|
346
|
-
: undefined;
|
|
347
|
-
// The preview cursor lives in the shared render state so each streamed update
|
|
348
|
-
// only processes new lines; an absent state degrades to a one-shot preview.
|
|
349
|
-
const state = isRecord(context.state) ? context.state : undefined;
|
|
350
|
-
const cursor = state?.[APPLY_PATCH_PREVIEW_STATE_KEY];
|
|
351
|
-
const stored = typeof cursor === "object" && cursor !== null ? cursor : undefined;
|
|
352
|
-
const operations = previewV4aOperations(patch, context.argsComplete === true, stored);
|
|
353
|
-
if (state !== undefined && stored !== undefined) {
|
|
354
|
-
state[APPLY_PATCH_PREVIEW_STATE_KEY] = stored;
|
|
355
|
-
}
|
|
356
|
-
const files = new Set(operations.map((operation) => operation.path)).size;
|
|
357
|
-
const summary = files > 0
|
|
358
|
-
? patchSummary(files, operations.reduce((sum, operation) => sum + operation.addedLines, 0), operations.reduce((sum, operation) => sum + operation.removedLines, 0))
|
|
359
|
-
: "";
|
|
360
|
-
const rows = operations.map((operation) => formatApplyPatchOperationRow({
|
|
361
|
-
operationIndex: 0,
|
|
362
|
-
kind: operation.kind,
|
|
363
|
-
path: operation.path,
|
|
364
|
-
addedLines: operation.addedLines,
|
|
365
|
-
removedLines: operation.removedLines,
|
|
366
|
-
status: "pending",
|
|
367
|
-
appliedHunks: undefined,
|
|
368
|
-
totalHunks: undefined,
|
|
369
|
-
partialReason: undefined,
|
|
370
|
-
}, theme, host));
|
|
371
|
-
return new ToolView({
|
|
372
|
-
theme,
|
|
373
|
-
header: {
|
|
374
|
-
title: APPLY_PATCH_TOOL_NAME,
|
|
375
|
-
status: context.isError ? "error" : "none",
|
|
376
|
-
summary,
|
|
377
|
-
},
|
|
378
|
-
collapsedRows: rows,
|
|
379
|
-
sections: [{ railToken: "dim", content: rows }],
|
|
380
|
-
copy: patch !== "" ? { primary: patch } : undefined,
|
|
381
|
-
outputPad: context.outputPad,
|
|
382
|
-
stateHolder: context.state,
|
|
383
|
-
defaultLevel,
|
|
384
|
-
globalExpanded: context.expanded,
|
|
385
|
-
invalidate: () => context.invalidate(),
|
|
386
|
-
copyToClipboard: options?.copyToClipboard,
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
function renderApplyPatchResultView(params) {
|
|
390
|
-
const { theme, context, result, renderOptions, defaultLevel, options } = params;
|
|
391
|
-
const facts = applyPatchFacts(result.details);
|
|
392
|
-
const isError = context.isError || result.isError;
|
|
393
|
-
const host = facts?.target;
|
|
394
|
-
// The aggregated status is the executor's fact; success hides the top glyph.
|
|
395
|
-
const status = facts === undefined
|
|
396
|
-
? isError
|
|
397
|
-
? "error"
|
|
398
|
-
: renderOptions.isPartial
|
|
399
|
-
? "running"
|
|
400
|
-
: "ok"
|
|
401
|
-
: facts.status === "failed"
|
|
402
|
-
? "error"
|
|
403
|
-
: facts.status === "partial"
|
|
404
|
-
? "warn"
|
|
405
|
-
: "none";
|
|
406
|
-
const output = stripTerminalSequences(extractResultText(APPLY_PATCH_TOOL_NAME, result).outputText);
|
|
407
|
-
const parts = [];
|
|
408
|
-
if (facts !== undefined) {
|
|
409
|
-
parts.push(patchSummary(new Set(facts.changedPaths).size, facts.addedLines, facts.removedLines));
|
|
410
|
-
}
|
|
411
|
-
const durText = formatDurationMs(context.durationMs);
|
|
412
|
-
if (durText !== "")
|
|
413
|
-
parts.push(durText);
|
|
414
|
-
const summary = parts.join(" · ");
|
|
415
|
-
const rows = facts === undefined
|
|
416
|
-
? []
|
|
417
|
-
: facts.operations.map((operation) => formatApplyPatchOperationRow(operation, theme, host));
|
|
418
|
-
const sections = rows.length > 0 ? [{ railToken: "dim", content: rows }] : [];
|
|
419
|
-
if (facts === undefined && output !== "") {
|
|
420
|
-
sections.push({ railToken: isError ? "error" : "dim", content: output });
|
|
421
|
-
}
|
|
422
|
-
else if (facts !== undefined) {
|
|
423
|
-
for (const snapshot of facts.snapshots) {
|
|
424
|
-
const hunks = snapshotHunks(snapshot);
|
|
425
|
-
if (hunks !== undefined && hunks.length > 0) {
|
|
426
|
-
sections.push({
|
|
427
|
-
railToken: "dim",
|
|
428
|
-
content: new DiffView({ theme, source: { kind: "hunks", hunks } }),
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
const diagnosis = (entries, glyphStatus) => entries.flatMap((entry) => [
|
|
433
|
-
`${applyPatchGlyph(glyphStatus, theme)} ${entry.paths[0] ?? "<unknown>"} · ${entry.error}`,
|
|
434
|
-
...entry.diagnostics.map((diagnostic) => `${applyPatchGlyph(glyphStatus, theme)} ${entry.paths[0] ?? "<unknown>"} · ${diagnostic.text}`),
|
|
435
|
-
]);
|
|
436
|
-
const failureRows = [
|
|
437
|
-
...diagnosis(facts.rejected, "rejected"),
|
|
438
|
-
...diagnosis(facts.unconfirmed, "unconfirmed"),
|
|
439
|
-
...diagnosis(facts.notApplied, "not_applied"),
|
|
440
|
-
];
|
|
441
|
-
if (failureRows.length > 0) {
|
|
442
|
-
sections.push({ railToken: "error", content: failureRows });
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
const patch = isRecord(context.args) && typeof context.args.patch === "string" ? context.args.patch : "";
|
|
446
|
-
const copy = patch !== ""
|
|
447
|
-
? { primary: patch, full: output !== "" ? `${patch}\n---\n${output}` : patch }
|
|
448
|
-
: output !== ""
|
|
449
|
-
? { primary: output }
|
|
450
|
-
: undefined;
|
|
451
|
-
return new ToolView({
|
|
452
|
-
theme,
|
|
453
|
-
header: { title: APPLY_PATCH_TOOL_NAME, status, summary },
|
|
454
|
-
collapsedRows: rows.length > 0 ? rows : undefined,
|
|
455
|
-
sections,
|
|
456
|
-
copy,
|
|
457
|
-
outputPad: context.outputPad,
|
|
458
|
-
stateHolder: context.state,
|
|
459
|
-
defaultLevel,
|
|
460
|
-
globalExpanded: renderOptions.expanded,
|
|
461
|
-
invalidate: () => context.invalidate(),
|
|
462
|
-
copyToClipboard: options?.copyToClipboard,
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
/** Hunks of one stored before/after snapshot; no file context is invented. */
|
|
466
|
-
function snapshotHunks(snapshot) {
|
|
467
|
-
if (snapshot.before.length === 0 && snapshot.after.length === 0)
|
|
468
|
-
return undefined;
|
|
469
|
-
return lineHunks(snapshot.before.join("\n"), snapshot.after.join("\n"), snapshot.startLine);
|
|
470
|
-
}
|
|
471
107
|
function shouldInterceptTool(toolName, options) {
|
|
472
108
|
if (options?.catchAll === true) {
|
|
473
109
|
return options.match !== undefined ? options.match(toolName) : true;
|
|
@@ -481,18 +117,6 @@ function shouldInterceptTool(toolName, options) {
|
|
|
481
117
|
}
|
|
482
118
|
return SPECIALIZED_TOOL_SET.has(toolName) || WEB_ACCESS_TOOL_SET.has(toolName);
|
|
483
119
|
}
|
|
484
|
-
function renderSpecializedResult(params) {
|
|
485
|
-
const { toolName } = params;
|
|
486
|
-
if (toolName === EDIT_TOOL_NAME)
|
|
487
|
-
return renderEditResult(params);
|
|
488
|
-
if (toolName === WRITE_TOOL_NAME)
|
|
489
|
-
return renderWriteResult(params);
|
|
490
|
-
if (toolName === PYTHON_EVAL_TOOL_NAME)
|
|
491
|
-
return renderPythonEvalResult(params);
|
|
492
|
-
if (toolName === APPLY_PATCH_TOOL_NAME)
|
|
493
|
-
return renderApplyPatchResultView(params);
|
|
494
|
-
return undefined;
|
|
495
|
-
}
|
|
496
120
|
function renderGenericResult(params) {
|
|
497
121
|
const { toolName, theme, context, result, renderOptions, defaultLevel, originalRenderCall, originalRenderResult, resolvePresentation, options, } = params;
|
|
498
122
|
const heavyCmd = extractHeavyInputCommand(toolName, context.args);
|
|
@@ -566,7 +190,7 @@ function renderGenericResult(params) {
|
|
|
566
190
|
if (provider !== undefined) {
|
|
567
191
|
return renderOwnedPresentation({
|
|
568
192
|
toolName: childName,
|
|
569
|
-
provider,
|
|
193
|
+
presentation: provider(childCtx.args, childResult, childTheme, childCtx),
|
|
570
194
|
result: childResult,
|
|
571
195
|
theme: childTheme,
|
|
572
196
|
context: childCtx,
|
|
@@ -574,17 +198,6 @@ function renderGenericResult(params) {
|
|
|
574
198
|
options,
|
|
575
199
|
});
|
|
576
200
|
}
|
|
577
|
-
const specialized = renderSpecializedResult({
|
|
578
|
-
toolName: childName,
|
|
579
|
-
theme: childTheme,
|
|
580
|
-
context: childCtx,
|
|
581
|
-
result: childResult,
|
|
582
|
-
renderOptions: childOptions,
|
|
583
|
-
defaultLevel: childOptions.expanded ? 2 : 1,
|
|
584
|
-
options,
|
|
585
|
-
});
|
|
586
|
-
if (specialized !== undefined)
|
|
587
|
-
return specialized;
|
|
588
201
|
return renderGenericResult({
|
|
589
202
|
toolName: childName,
|
|
590
203
|
theme: childTheme,
|
|
@@ -727,30 +340,40 @@ function renderGenericResult(params) {
|
|
|
727
340
|
copyToClipboard: options?.copyToClipboard,
|
|
728
341
|
});
|
|
729
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* The generic frame around one owner's presentation. The caller already resolved it, so a frame
|
|
345
|
+
* pass never asks the tool to rebuild its rows.
|
|
346
|
+
*/
|
|
730
347
|
function renderOwnedPresentation(params) {
|
|
731
|
-
const { toolName,
|
|
732
|
-
const presentation = provider(context.args, result, theme);
|
|
348
|
+
const { toolName, presentation, result, theme, context, defaultLevel, options } = params;
|
|
733
349
|
const isError = context.isError || result?.isError === true;
|
|
734
350
|
const pending = result === undefined || context.isPartial;
|
|
735
351
|
const isDiff = !isError && !pending && presentation.kind === "diff";
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
:
|
|
743
|
-
?
|
|
744
|
-
|
|
352
|
+
// The tool owns its status: a container says "none" for a child's failure and a partial patch
|
|
353
|
+
// says "warn" even though the host marks the result failed. Only a provider without an opinion
|
|
354
|
+
// falls back to the frame's own reading of the execution.
|
|
355
|
+
const status = presentation.status ??
|
|
356
|
+
(isError
|
|
357
|
+
? "error"
|
|
358
|
+
: pending
|
|
359
|
+
? context.argsComplete
|
|
360
|
+
? "running"
|
|
361
|
+
: "streaming"
|
|
362
|
+
: isDiff
|
|
363
|
+
? "none"
|
|
364
|
+
: "ok");
|
|
745
365
|
const railToken = isError ? "error" : pending ? "accent" : "success";
|
|
746
366
|
const output = isError && result !== undefined
|
|
747
367
|
? agentResultText(result)
|
|
748
368
|
: (presentation.output ?? (result === undefined ? "" : agentResultText(result)));
|
|
749
|
-
const sections = [];
|
|
750
|
-
if (presentation.request) {
|
|
369
|
+
const sections = [...(presentation.sections ?? [])];
|
|
370
|
+
if (presentation.sections === undefined && presentation.request) {
|
|
751
371
|
sections.push({ railToken: "muted", content: stripTerminalSequences(presentation.request) });
|
|
752
372
|
}
|
|
753
|
-
if (
|
|
373
|
+
if (presentation.sections !== undefined) {
|
|
374
|
+
// Tool owns domain rows; this factory owns the frame and interaction only.
|
|
375
|
+
}
|
|
376
|
+
else if (isDiff) {
|
|
754
377
|
if (presentation.diff.trim() !== "") {
|
|
755
378
|
sections.push({
|
|
756
379
|
railToken: "dim",
|
|
@@ -786,12 +409,14 @@ function renderOwnedPresentation(params) {
|
|
|
786
409
|
theme,
|
|
787
410
|
header: { title: toolName, status, summary },
|
|
788
411
|
railToken,
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
412
|
+
// The tool owns the folded rows and their tones; the frame only sanitizes them for the host.
|
|
413
|
+
collapsedRows: presentation.collapsedRows ??
|
|
414
|
+
(isError && output !== ""
|
|
415
|
+
? output
|
|
416
|
+
.split("\n")
|
|
417
|
+
.slice(0, 1)
|
|
418
|
+
.map((line) => theme.fg("error", stripTerminalSequences(line)))
|
|
419
|
+
: undefined),
|
|
795
420
|
sections,
|
|
796
421
|
copy,
|
|
797
422
|
outputPad: context.outputPad,
|
|
@@ -809,6 +434,8 @@ function createRendererResolver(options, resolvePresentation) {
|
|
|
809
434
|
const defaultLevel = options?.defaultCollapsed === false ? 1 : 0;
|
|
810
435
|
return (toolName, next) => {
|
|
811
436
|
const upstream = next();
|
|
437
|
+
if (hasOptionalToolRenderers(upstream))
|
|
438
|
+
return upstream;
|
|
812
439
|
const provider = getToolPresentationProvider(upstream) ?? resolvePresentation?.(toolName);
|
|
813
440
|
if (provider === undefined && !shouldInterceptTool(toolName, options)) {
|
|
814
441
|
return upstream;
|
|
@@ -819,46 +446,18 @@ function createRendererResolver(options, resolvePresentation) {
|
|
|
819
446
|
renderShell: "self",
|
|
820
447
|
renderCall(args, theme, context) {
|
|
821
448
|
if (provider !== undefined) {
|
|
449
|
+
const callContext = { ...context, args };
|
|
822
450
|
const view = renderOwnedPresentation({
|
|
823
451
|
toolName,
|
|
824
|
-
provider,
|
|
825
|
-
theme,
|
|
826
|
-
context: { ...context, args },
|
|
827
|
-
defaultLevel,
|
|
828
|
-
options,
|
|
829
|
-
});
|
|
830
|
-
rememberCallView(context, view);
|
|
831
|
-
return view;
|
|
832
|
-
}
|
|
833
|
-
if (toolName === APPLY_PATCH_TOOL_NAME) {
|
|
834
|
-
const view = renderApplyPatchCallView({
|
|
452
|
+
presentation: provider(args, undefined, theme, callContext),
|
|
835
453
|
theme,
|
|
836
|
-
context:
|
|
454
|
+
context: callContext,
|
|
837
455
|
defaultLevel,
|
|
838
456
|
options,
|
|
839
457
|
});
|
|
840
458
|
rememberCallView(context, view);
|
|
841
459
|
return view;
|
|
842
460
|
}
|
|
843
|
-
if (toolName === EDIT_TOOL_NAME) {
|
|
844
|
-
const path = editPath(args);
|
|
845
|
-
const view = new ToolView({
|
|
846
|
-
theme,
|
|
847
|
-
header: {
|
|
848
|
-
title: EDIT_TOOL_NAME,
|
|
849
|
-
status: context.isError ? "error" : "running",
|
|
850
|
-
summary: path,
|
|
851
|
-
},
|
|
852
|
-
outputPad: context.outputPad,
|
|
853
|
-
stateHolder: context.state,
|
|
854
|
-
defaultLevel,
|
|
855
|
-
globalExpanded: context.expanded,
|
|
856
|
-
invalidate: () => context.invalidate(),
|
|
857
|
-
copyToClipboard: options?.copyToClipboard,
|
|
858
|
-
});
|
|
859
|
-
rememberCallView(context, view);
|
|
860
|
-
return view;
|
|
861
|
-
}
|
|
862
461
|
const isStreamingArgs = context.isPartial && !context.argsComplete;
|
|
863
462
|
const status = context.isError
|
|
864
463
|
? "error"
|
|
@@ -922,33 +521,30 @@ function createRendererResolver(options, resolvePresentation) {
|
|
|
922
521
|
return view;
|
|
923
522
|
},
|
|
924
523
|
renderResult(result, renderOptions, theme, context) {
|
|
925
|
-
|
|
926
|
-
|
|
524
|
+
const resultContext = {
|
|
525
|
+
...context,
|
|
526
|
+
expanded: renderOptions.expanded,
|
|
527
|
+
isPartial: renderOptions.isPartial,
|
|
528
|
+
};
|
|
529
|
+
const presentation = provider === undefined
|
|
530
|
+
? undefined
|
|
531
|
+
: provider(resultContext.args, result, theme, resultContext);
|
|
532
|
+
// Without an owner, a container frame is the only name-based decision left, and it
|
|
533
|
+
// belongs to upstream codemode.
|
|
534
|
+
const replacesCallOnPartial = presentation?.replaceCallOnPartial === true ||
|
|
535
|
+
(presentation === undefined && SPECIALIZED_TOOL_SET.has(toolName));
|
|
536
|
+
hideCallView(context, replacesCallOnPartial, renderOptions.isPartial);
|
|
537
|
+
if (presentation !== undefined) {
|
|
927
538
|
return renderOwnedPresentation({
|
|
928
539
|
toolName,
|
|
929
|
-
|
|
540
|
+
presentation,
|
|
930
541
|
result,
|
|
931
542
|
theme,
|
|
932
|
-
context:
|
|
933
|
-
...context,
|
|
934
|
-
expanded: renderOptions.expanded,
|
|
935
|
-
isPartial: renderOptions.isPartial,
|
|
936
|
-
},
|
|
543
|
+
context: resultContext,
|
|
937
544
|
defaultLevel,
|
|
938
545
|
options,
|
|
939
546
|
});
|
|
940
547
|
}
|
|
941
|
-
const specialized = renderSpecializedResult({
|
|
942
|
-
toolName,
|
|
943
|
-
theme,
|
|
944
|
-
context,
|
|
945
|
-
result,
|
|
946
|
-
renderOptions,
|
|
947
|
-
defaultLevel,
|
|
948
|
-
options,
|
|
949
|
-
});
|
|
950
|
-
if (specialized !== undefined)
|
|
951
|
-
return specialized;
|
|
952
548
|
return renderGenericResult({
|
|
953
549
|
toolName,
|
|
954
550
|
theme,
|
|
@@ -965,6 +561,10 @@ function createRendererResolver(options, resolvePresentation) {
|
|
|
965
561
|
};
|
|
966
562
|
};
|
|
967
563
|
}
|
|
564
|
+
/** UI factory consumed by package-owned direct renderers, independently of host resolver precedence. */
|
|
565
|
+
export function createOptionalToolRendererFactory(pi, options) {
|
|
566
|
+
return (name, native) => createRendererResolver({ ...options, tools: [name] }, (toolName) => getRegisteredToolPresentationProvider(pi, toolName))(name, () => native) ?? native;
|
|
567
|
+
}
|
|
968
568
|
export function registerCollapsibleToolRenderer(pi, options) {
|
|
969
569
|
if (typeof pi.on === "function") {
|
|
970
570
|
pi.on("tool_execution_start", (event) => {
|