@f5xc-salesdemos/xcsh 18.40.1 → 18.41.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/package.json +7 -7
- package/src/autoresearch/tools/init-experiment.ts +10 -5
- package/src/autoresearch/tools/log-experiment.ts +20 -13
- package/src/autoresearch/tools/run-experiment.ts +9 -8
- package/src/edit/renderer.ts +24 -17
- package/src/exa/render.ts +168 -173
- package/src/internal-urls/api-spec-resolve.ts +54 -3
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/lsp/render.ts +77 -68
- package/src/mcp/render.ts +98 -93
- package/src/tools/calculator.ts +18 -13
- package/src/tools/debug.ts +21 -24
- package/src/tools/inspect-image-renderer.ts +51 -41
- package/src/tools/python.ts +48 -38
- package/src/tools/resolve.ts +21 -17
- package/src/tools/search-tool-bm25.ts +10 -5
- package/src/tools/write.ts +19 -12
- package/src/web/search/render.ts +75 -57
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5xc-salesdemos/xcsh",
|
|
4
|
-
"version": "18.
|
|
4
|
+
"version": "18.41.0",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/f5xc-salesdemos/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
50
50
|
"@mozilla/readability": "^0.6",
|
|
51
|
-
"@f5xc-salesdemos/xcsh-stats": "18.
|
|
52
|
-
"@f5xc-salesdemos/pi-agent-core": "18.
|
|
53
|
-
"@f5xc-salesdemos/pi-ai": "18.
|
|
54
|
-
"@f5xc-salesdemos/pi-natives": "18.
|
|
55
|
-
"@f5xc-salesdemos/pi-tui": "18.
|
|
56
|
-
"@f5xc-salesdemos/pi-utils": "18.
|
|
51
|
+
"@f5xc-salesdemos/xcsh-stats": "18.41.0",
|
|
52
|
+
"@f5xc-salesdemos/pi-agent-core": "18.41.0",
|
|
53
|
+
"@f5xc-salesdemos/pi-ai": "18.41.0",
|
|
54
|
+
"@f5xc-salesdemos/pi-natives": "18.41.0",
|
|
55
|
+
"@f5xc-salesdemos/pi-tui": "18.41.0",
|
|
56
|
+
"@f5xc-salesdemos/pi-utils": "18.41.0",
|
|
57
57
|
"@sinclair/typebox": "^0.34",
|
|
58
58
|
"@xterm/headless": "^6.0",
|
|
59
59
|
"ajv": "^8.18",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { StringEnum } from "@f5xc-salesdemos/pi-ai";
|
|
4
|
-
import { Text } from "@f5xc-salesdemos/pi-tui";
|
|
4
|
+
import { type Component, Text } from "@f5xc-salesdemos/pi-tui";
|
|
5
5
|
import { Type } from "@sinclair/typebox";
|
|
6
6
|
import type { ToolDefinition } from "../../extensibility/extensions";
|
|
7
7
|
import type { Theme } from "../../modes/theme/theme";
|
|
@@ -370,8 +370,13 @@ export function createInitExperimentTool(
|
|
|
370
370
|
details: { state: cloneExperimentState(state) },
|
|
371
371
|
};
|
|
372
372
|
},
|
|
373
|
-
renderCall(args, _options, theme):
|
|
374
|
-
return
|
|
373
|
+
renderCall(args, _options, theme): Component {
|
|
374
|
+
return {
|
|
375
|
+
render(width: number): string[] {
|
|
376
|
+
return [renderInitCall(args.name, theme, width)];
|
|
377
|
+
},
|
|
378
|
+
invalidate() {},
|
|
379
|
+
};
|
|
375
380
|
},
|
|
376
381
|
renderResult(result): Text {
|
|
377
382
|
const text = replaceTabs(result.content.find(part => part.type === "text")?.text ?? "");
|
|
@@ -380,8 +385,8 @@ export function createInitExperimentTool(
|
|
|
380
385
|
};
|
|
381
386
|
}
|
|
382
387
|
|
|
383
|
-
function renderInitCall(name: string, theme: Theme): string {
|
|
384
|
-
return `${theme.fg("toolTitle", theme.bold("init_experiment"))} ${theme.fg("contentAccent", truncateToWidth(replaceTabs(name), 100))}`;
|
|
388
|
+
function renderInitCall(name: string, theme: Theme, width?: number): string {
|
|
389
|
+
return `${theme.fg("toolTitle", theme.bold("init_experiment"))} ${theme.fg("contentAccent", truncateToWidth(replaceTabs(name), Math.max(20, (width ?? 100) - 20)))}`;
|
|
385
390
|
}
|
|
386
391
|
|
|
387
392
|
function collectLoggedRunNumbers(results: ExperimentState["results"]): Set<number> {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { StringEnum } from "@f5xc-salesdemos/pi-ai";
|
|
4
|
-
import { Text } from "@f5xc-salesdemos/pi-tui";
|
|
4
|
+
import { type Component, Text } from "@f5xc-salesdemos/pi-tui";
|
|
5
5
|
import { logger } from "@f5xc-salesdemos/pi-utils";
|
|
6
6
|
import { Type } from "@sinclair/typebox";
|
|
7
7
|
import type { ToolDefinition } from "../../extensibility/extensions";
|
|
@@ -358,22 +358,29 @@ export function createLogExperimentTool(
|
|
|
358
358
|
},
|
|
359
359
|
};
|
|
360
360
|
},
|
|
361
|
-
renderCall(args, _options, theme):
|
|
361
|
+
renderCall(args, _options, theme): Component {
|
|
362
362
|
const color = args.status === "keep" ? "success" : args.status === "discard" ? "warning" : "error";
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
363
|
+
return {
|
|
364
|
+
render(width: number): string[] {
|
|
365
|
+
const description = truncateToWidth(replaceTabs(args.description), Math.max(20, width - 30));
|
|
366
|
+
return [
|
|
367
|
+
`${theme.fg("toolTitle", theme.bold("log_experiment"))} ${theme.fg(color, args.status)} ${theme.fg("muted", description)}`,
|
|
368
|
+
];
|
|
369
|
+
},
|
|
370
|
+
invalidate() {},
|
|
371
|
+
};
|
|
369
372
|
},
|
|
370
|
-
renderResult(result, _options, theme):
|
|
373
|
+
renderResult(result, _options, theme): Component {
|
|
371
374
|
const details = result.details;
|
|
372
375
|
if (!details) {
|
|
373
376
|
return new Text(replaceTabs(result.content.find(part => part.type === "text")?.text ?? ""), 0, 0);
|
|
374
377
|
}
|
|
375
|
-
|
|
376
|
-
|
|
378
|
+
return {
|
|
379
|
+
render(width: number): string[] {
|
|
380
|
+
return [renderSummary(details, theme, width)];
|
|
381
|
+
},
|
|
382
|
+
invalidate() {},
|
|
383
|
+
};
|
|
377
384
|
},
|
|
378
385
|
};
|
|
379
386
|
}
|
|
@@ -763,10 +770,10 @@ function truncateAsiValue(value: ASIData[string]): string {
|
|
|
763
770
|
return text.length > 120 ? `${text.slice(0, 117)}...` : text;
|
|
764
771
|
}
|
|
765
772
|
|
|
766
|
-
function renderSummary(details: LogDetails, theme: Theme): string {
|
|
773
|
+
function renderSummary(details: LogDetails, theme: Theme, width?: number): string {
|
|
767
774
|
const { experiment, state } = details;
|
|
768
775
|
const color = experiment.status === "keep" ? "success" : experiment.status === "discard" ? "warning" : "error";
|
|
769
|
-
let summary = `${theme.fg(color, experiment.status.toUpperCase())} ${theme.fg("muted", truncateToWidth(replaceTabs(experiment.description), 100))}`;
|
|
776
|
+
let summary = `${theme.fg(color, experiment.status.toUpperCase())} ${theme.fg("muted", truncateToWidth(replaceTabs(experiment.description), Math.max(20, (width ?? 100) - 30)))}`;
|
|
770
777
|
summary += ` ${theme.fg("contentAccent", `${state.metricName}=${formatNum(experiment.metric, state.metricUnit)}`)}`;
|
|
771
778
|
if (state.bestMetric !== null) {
|
|
772
779
|
summary += ` ${theme.fg("dim", `baseline ${formatNum(state.bestMetric, state.metricUnit)}`)}`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as childProcess from "node:child_process";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { Text } from "@f5xc-salesdemos/pi-tui";
|
|
4
|
+
import { type Component, Text } from "@f5xc-salesdemos/pi-tui";
|
|
5
5
|
import { formatBytes } from "@f5xc-salesdemos/pi-utils";
|
|
6
6
|
import { Type } from "@sinclair/typebox";
|
|
7
7
|
import type { ToolDefinition } from "../../extensibility/extensions";
|
|
@@ -380,13 +380,14 @@ export function createRunExperimentTool(
|
|
|
380
380
|
details: resultDetails,
|
|
381
381
|
};
|
|
382
382
|
},
|
|
383
|
-
renderCall(args, _options, theme):
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
383
|
+
renderCall(args, _options, theme): Component {
|
|
384
|
+
return {
|
|
385
|
+
render(width: number): string[] {
|
|
386
|
+
const commandPreview = truncateToWidth(replaceTabs(args.command), Math.max(20, width - 20));
|
|
387
|
+
return [`${theme.fg("toolTitle", theme.bold("run_experiment"))} ${theme.fg("muted", commandPreview)}`];
|
|
388
|
+
},
|
|
389
|
+
invalidate() {},
|
|
390
|
+
};
|
|
390
391
|
},
|
|
391
392
|
renderResult(result, options, theme): Text {
|
|
392
393
|
if (isProgressDetails(result.details)) {
|
package/src/edit/renderer.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { ToolCallContext } from "@f5xc-salesdemos/pi-agent-core";
|
|
5
5
|
import type { Component } from "@f5xc-salesdemos/pi-tui";
|
|
6
|
-
import {
|
|
6
|
+
import { visibleWidth, wrapTextWithAnsi } from "@f5xc-salesdemos/pi-tui";
|
|
7
7
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
8
8
|
import type { FileDiagnosticsResult } from "../lsp";
|
|
9
9
|
import { renderDiff as renderDiffColored } from "../modes/components/diff";
|
|
@@ -145,8 +145,6 @@ export interface EditRenderContext {
|
|
|
145
145
|
|
|
146
146
|
const EDIT_STREAMING_PREVIEW_LINES = 12;
|
|
147
147
|
const CALL_TEXT_PREVIEW_LINES = 6;
|
|
148
|
-
const CALL_TEXT_PREVIEW_WIDTH = 80;
|
|
149
|
-
const STREAMING_EDIT_PREVIEW_WIDTH = 120;
|
|
150
148
|
const STREAMING_EDIT_PREVIEW_LIMIT = 4;
|
|
151
149
|
const STREAMING_EDIT_PREVIEW_DST_LINE_LIMIT = 8;
|
|
152
150
|
|
|
@@ -207,11 +205,11 @@ function formatEditDescription(
|
|
|
207
205
|
};
|
|
208
206
|
}
|
|
209
207
|
|
|
210
|
-
function renderPlainTextPreview(text: string, uiTheme: Theme): string {
|
|
208
|
+
function renderPlainTextPreview(text: string, uiTheme: Theme, width: number): string {
|
|
211
209
|
const previewLines = text.split("\n");
|
|
212
210
|
let preview = "\n\n";
|
|
213
211
|
for (const line of previewLines.slice(0, CALL_TEXT_PREVIEW_LINES)) {
|
|
214
|
-
preview += `${uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(line),
|
|
212
|
+
preview += `${uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(line), width))}\n`;
|
|
215
213
|
}
|
|
216
214
|
if (previewLines.length > CALL_TEXT_PREVIEW_LINES) {
|
|
217
215
|
preview += uiTheme.fg("dim", `… ${previewLines.length - CALL_TEXT_PREVIEW_LINES} more lines`);
|
|
@@ -299,7 +297,11 @@ function formatChunkStreamingEdit(edit: Partial<ChunkToolEdit>): FormattedStream
|
|
|
299
297
|
return { srcLabel: `\u2022 edit ${target}`, dst: "" };
|
|
300
298
|
}
|
|
301
299
|
|
|
302
|
-
function formatStreamingHashlineEdits(
|
|
300
|
+
function formatStreamingHashlineEdits(
|
|
301
|
+
edits: Partial<HashlineToolEdit | ChunkToolEdit>[],
|
|
302
|
+
uiTheme: Theme,
|
|
303
|
+
width: number,
|
|
304
|
+
): string {
|
|
303
305
|
let text = "\n\n";
|
|
304
306
|
|
|
305
307
|
// Detect whether these are chunk edits (target field) or hashline edits (loc field)
|
|
@@ -314,17 +316,17 @@ function formatStreamingHashlineEdits(edits: Partial<HashlineToolEdit | ChunkToo
|
|
|
314
316
|
shownEdits++;
|
|
315
317
|
if (shownEdits > STREAMING_EDIT_PREVIEW_LIMIT) break;
|
|
316
318
|
const formatted = formatEdit(edit as never);
|
|
317
|
-
text += uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(formatted.srcLabel),
|
|
319
|
+
text += uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(formatted.srcLabel), width));
|
|
318
320
|
text += "\n";
|
|
319
321
|
if (formatted.dst === "") {
|
|
320
|
-
text += uiTheme.fg("dim", truncateToWidth(" (delete)",
|
|
322
|
+
text += uiTheme.fg("dim", truncateToWidth(" (delete)", width));
|
|
321
323
|
text += "\n";
|
|
322
324
|
continue;
|
|
323
325
|
}
|
|
324
326
|
for (const dstLine of formatted.dst.split("\n")) {
|
|
325
327
|
shownDstLines++;
|
|
326
328
|
if (shownDstLines > STREAMING_EDIT_PREVIEW_DST_LINE_LIMIT) break;
|
|
327
|
-
text += uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(`+ ${dstLine}`),
|
|
329
|
+
text += uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(`+ ${dstLine}`), width));
|
|
328
330
|
text += "\n";
|
|
329
331
|
}
|
|
330
332
|
if (shownDstLines > STREAMING_EDIT_PREVIEW_DST_LINE_LIMIT) break;
|
|
@@ -347,7 +349,7 @@ function formatMetadataLine(lineCount: number | null, language: string | undefin
|
|
|
347
349
|
return uiTheme.fg("dim", `${icon}`);
|
|
348
350
|
}
|
|
349
351
|
|
|
350
|
-
function getCallPreview(args: EditRenderArgs, rawPath: string, uiTheme: Theme): string {
|
|
352
|
+
function getCallPreview(args: EditRenderArgs, rawPath: string, uiTheme: Theme, width: number): string {
|
|
351
353
|
if (args.previewDiff) {
|
|
352
354
|
return formatStreamingDiff(args.previewDiff, rawPath, uiTheme, "preview");
|
|
353
355
|
}
|
|
@@ -358,14 +360,14 @@ function getCallPreview(args: EditRenderArgs, rawPath: string, uiTheme: Theme):
|
|
|
358
360
|
// Only show hashline/chunk streaming edits — replace/patch use previewDiff above
|
|
359
361
|
const first = args.edits[0];
|
|
360
362
|
if (first && typeof first === "object" && ("loc" in first || isChunkStreamingEdit(first))) {
|
|
361
|
-
return formatStreamingHashlineEdits(args.edits, uiTheme);
|
|
363
|
+
return formatStreamingHashlineEdits(args.edits, uiTheme, width);
|
|
362
364
|
}
|
|
363
365
|
}
|
|
364
366
|
if (args.diff) {
|
|
365
|
-
return renderPlainTextPreview(args.diff, uiTheme);
|
|
367
|
+
return renderPlainTextPreview(args.diff, uiTheme, width);
|
|
366
368
|
}
|
|
367
369
|
if (args.newText || args.patch) {
|
|
368
|
-
return renderPlainTextPreview(args.newText ?? args.patch ?? "", uiTheme);
|
|
370
|
+
return renderPlainTextPreview(args.newText ?? args.patch ?? "", uiTheme, width);
|
|
369
371
|
}
|
|
370
372
|
return "";
|
|
371
373
|
}
|
|
@@ -445,15 +447,20 @@ export const editToolRenderer = {
|
|
|
445
447
|
const { description } = formatEditDescription(rawPath, uiTheme, { rename });
|
|
446
448
|
const spinner =
|
|
447
449
|
options?.spinnerFrame !== undefined ? formatStatusIcon("running", uiTheme, options.spinnerFrame) : "";
|
|
448
|
-
let
|
|
450
|
+
let header = `${formatTitle(getOperationTitle(op), uiTheme)} ${spinner ? `${spinner} ` : ""}${description}`;
|
|
449
451
|
// Show file count hint for multi-file edits
|
|
450
452
|
const fileCount = Array.isArray(args.edits) ? countEditFiles(args.edits as any[]) : 0;
|
|
451
453
|
if (fileCount > 1) {
|
|
452
|
-
|
|
454
|
+
header += uiTheme.fg("dim", ` (+${fileCount - 1} more)`);
|
|
453
455
|
}
|
|
454
|
-
text += getCallPreview(args, rawPath, uiTheme);
|
|
455
456
|
|
|
456
|
-
return
|
|
457
|
+
return {
|
|
458
|
+
render(width: number) {
|
|
459
|
+
const text = header + getCallPreview(args, rawPath, uiTheme, width);
|
|
460
|
+
return text.split("\n");
|
|
461
|
+
},
|
|
462
|
+
invalidate() {},
|
|
463
|
+
};
|
|
457
464
|
},
|
|
458
465
|
|
|
459
466
|
renderResult(
|
package/src/exa/render.ts
CHANGED
|
@@ -15,17 +15,12 @@ import {
|
|
|
15
15
|
getDomain,
|
|
16
16
|
getPreviewLines,
|
|
17
17
|
PREVIEW_LIMITS,
|
|
18
|
-
TRUNCATE_LENGTHS,
|
|
19
18
|
truncateToWidth,
|
|
20
19
|
} from "../tools/render-utils";
|
|
21
20
|
import type { ExaRenderDetails } from "./types";
|
|
22
21
|
|
|
23
22
|
const COLLAPSED_PREVIEW_LINES = PREVIEW_LIMITS.COLLAPSED_LINES;
|
|
24
|
-
const COLLAPSED_PREVIEW_LINE_LEN = TRUNCATE_LENGTHS.LONG;
|
|
25
23
|
const EXPANDED_TEXT_LINES = 5;
|
|
26
|
-
const EXPANDED_TEXT_LINE_LEN = 90;
|
|
27
|
-
const MAX_TITLE_LEN = TRUNCATE_LENGTHS.TITLE;
|
|
28
|
-
const MAX_HIGHLIGHT_LEN = TRUNCATE_LENGTHS.CONTENT;
|
|
29
24
|
|
|
30
25
|
function renderErrorMessage(message: string, theme: Theme): Text {
|
|
31
26
|
const clean = message.replace(/^Error:\s*/, "").trim();
|
|
@@ -42,7 +37,6 @@ export function renderExaResult(
|
|
|
42
37
|
options: RenderResultOptions,
|
|
43
38
|
uiTheme: Theme,
|
|
44
39
|
): Component {
|
|
45
|
-
const { expanded } = options;
|
|
46
40
|
const details = result.details;
|
|
47
41
|
|
|
48
42
|
if (details?.error) {
|
|
@@ -51,187 +45,188 @@ export function renderExaResult(
|
|
|
51
45
|
}
|
|
52
46
|
|
|
53
47
|
const response = details?.response;
|
|
54
|
-
if (!response) {
|
|
55
|
-
if (details?.raw) {
|
|
56
|
-
const rawText = typeof details.raw === "string" ? details.raw : JSON.stringify(details.raw, null, 2);
|
|
57
|
-
const rawLines = rawText.split("\n").filter(l => l.trim());
|
|
58
|
-
const maxLines = expanded ? rawLines.length : Math.min(rawLines.length, COLLAPSED_PREVIEW_LINES);
|
|
59
|
-
const displayLines = rawLines.slice(0, maxLines);
|
|
60
|
-
const remaining = rawLines.length - maxLines;
|
|
61
|
-
const expandHint = formatExpandHint(uiTheme, expanded, remaining > 0);
|
|
62
|
-
|
|
63
|
-
let text = `${uiTheme.fg("dim", "Raw response")}${expandHint}`;
|
|
64
|
-
|
|
65
|
-
for (let i = 0; i < displayLines.length; i++) {
|
|
66
|
-
const isLast = i === displayLines.length - 1 && remaining === 0;
|
|
67
|
-
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
68
|
-
text += `\n ${uiTheme.fg("dim", branch)} ${uiTheme.fg(
|
|
69
|
-
"toolOutput",
|
|
70
|
-
truncateToWidth(displayLines[i], COLLAPSED_PREVIEW_LINE_LEN),
|
|
71
|
-
)}`;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (remaining > 0) {
|
|
75
|
-
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg(
|
|
76
|
-
"muted",
|
|
77
|
-
formatMoreItems(remaining, "line"),
|
|
78
|
-
)}`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return new Text(text, 0, 0);
|
|
82
|
-
}
|
|
48
|
+
if (!response && !details?.raw) {
|
|
83
49
|
return renderEmptyMessage("No response data", uiTheme);
|
|
84
50
|
}
|
|
85
51
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const safePreviewLines = previewLines.length > 0 ? previewLines : ["No preview text"];
|
|
118
|
-
const totalLines = previewText.split("\n").filter(l => l.trim()).length;
|
|
119
|
-
const remainingLines = Math.max(0, totalLines - previewLines.length);
|
|
120
|
-
const extraItems: string[] = [];
|
|
121
|
-
if (remainingLines > 0) {
|
|
122
|
-
extraItems.push(formatMoreItems(remainingLines, "line"));
|
|
123
|
-
}
|
|
124
|
-
if (resultCount > 1) {
|
|
125
|
-
extraItems.push(formatMoreItems(resultCount - 1, "result"));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
for (let i = 0; i < safePreviewLines.length; i++) {
|
|
129
|
-
const isLast = i === safePreviewLines.length - 1 && extraItems.length === 0;
|
|
130
|
-
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
131
|
-
const line = safePreviewLines[i];
|
|
132
|
-
const color = line === "No preview text" ? "muted" : "toolOutput";
|
|
133
|
-
text += `\n ${uiTheme.fg("dim", branch)} ${uiTheme.fg(color, line)}`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
for (let i = 0; i < extraItems.length; i++) {
|
|
137
|
-
const isLast = i === extraItems.length - 1;
|
|
138
|
-
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
139
|
-
text += `\n ${uiTheme.fg("dim", branch)} ${uiTheme.fg("muted", extraItems[i])}`;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return new Text(text, 0, 0);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (resultCount === 0) {
|
|
146
|
-
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg("muted", "No results")}`;
|
|
147
|
-
return new Text(text, 0, 0);
|
|
148
|
-
}
|
|
52
|
+
return {
|
|
53
|
+
render(width: number): string[] {
|
|
54
|
+
const { expanded } = options;
|
|
55
|
+
const contentWidth = Math.max(20, width - 6);
|
|
56
|
+
|
|
57
|
+
if (!response) {
|
|
58
|
+
const rawText = typeof details?.raw === "string" ? details.raw : JSON.stringify(details?.raw, null, 2);
|
|
59
|
+
const rawLines = rawText.split("\n").filter(l => l.trim());
|
|
60
|
+
const maxLines = expanded ? rawLines.length : Math.min(rawLines.length, COLLAPSED_PREVIEW_LINES);
|
|
61
|
+
const displayLines = rawLines.slice(0, maxLines);
|
|
62
|
+
const remaining = rawLines.length - maxLines;
|
|
63
|
+
const expandHint = formatExpandHint(uiTheme, expanded, remaining > 0);
|
|
64
|
+
|
|
65
|
+
const lines: string[] = [`${uiTheme.fg("dim", "Raw response")}${expandHint}`];
|
|
66
|
+
|
|
67
|
+
for (let i = 0; i < displayLines.length; i++) {
|
|
68
|
+
const isLast = i === displayLines.length - 1 && remaining === 0;
|
|
69
|
+
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
70
|
+
lines.push(
|
|
71
|
+
` ${uiTheme.fg("dim", branch)} ${uiTheme.fg("toolOutput", truncateToWidth(displayLines[i], contentWidth))}`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (remaining > 0) {
|
|
76
|
+
lines.push(
|
|
77
|
+
` ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg("muted", formatMoreItems(remaining, "line"))}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return lines;
|
|
82
|
+
}
|
|
149
83
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
res.url,
|
|
166
|
-
)}`;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (res.author) {
|
|
170
|
-
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg(
|
|
171
|
-
"muted",
|
|
172
|
-
`Author: ${res.author}`,
|
|
173
|
-
)}`;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (res.publishedDate) {
|
|
177
|
-
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg(
|
|
178
|
-
"muted",
|
|
179
|
-
`Published: ${res.publishedDate}`,
|
|
180
|
-
)}`;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (res.text) {
|
|
184
|
-
const textLines = res.text.split("\n").filter(l => l.trim());
|
|
185
|
-
const displayLines = textLines.slice(0, EXPANDED_TEXT_LINES);
|
|
186
|
-
for (const line of displayLines) {
|
|
187
|
-
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg(
|
|
188
|
-
"toolOutput",
|
|
189
|
-
truncateToWidth(line.trim(), EXPANDED_TEXT_LINE_LEN),
|
|
190
|
-
)}`;
|
|
84
|
+
const results = response.results ?? [];
|
|
85
|
+
const resultCount = results.length;
|
|
86
|
+
const cost = response.costDollars?.total;
|
|
87
|
+
const time = response.searchTime;
|
|
88
|
+
|
|
89
|
+
const metaParts = [formatCount("result", resultCount)];
|
|
90
|
+
if (cost !== undefined) metaParts.push(`cost:$${cost.toFixed(4)}`);
|
|
91
|
+
if (time !== undefined) metaParts.push(`time:${time.toFixed(2)}s`);
|
|
92
|
+
const summaryText = metaParts.join(uiTheme.sep.dot);
|
|
93
|
+
|
|
94
|
+
let hasMorePreview = false;
|
|
95
|
+
if (!expanded && resultCount > 0) {
|
|
96
|
+
const previewText = results[0].text ?? results[0].title ?? "";
|
|
97
|
+
const totalLines = previewText.split("\n").filter(l => l.trim()).length;
|
|
98
|
+
hasMorePreview = totalLines > COLLAPSED_PREVIEW_LINES || resultCount > 1;
|
|
191
99
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
100
|
+
const expandHint = formatExpandHint(uiTheme, expanded, hasMorePreview);
|
|
101
|
+
|
|
102
|
+
const lines: string[] = [`${uiTheme.fg("dim", summaryText)}${expandHint}`];
|
|
103
|
+
|
|
104
|
+
if (!expanded) {
|
|
105
|
+
if (resultCount === 0) {
|
|
106
|
+
lines.push(` ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg("muted", "No results")}`);
|
|
107
|
+
return lines;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const first = results[0];
|
|
111
|
+
const previewText = first.text ?? first.title ?? "";
|
|
112
|
+
const previewLines = previewText ? getPreviewLines(previewText, COLLAPSED_PREVIEW_LINES, contentWidth) : [];
|
|
113
|
+
const safePreviewLines = previewLines.length > 0 ? previewLines : ["No preview text"];
|
|
114
|
+
const totalLines = previewText.split("\n").filter(l => l.trim()).length;
|
|
115
|
+
const remainingLines = Math.max(0, totalLines - previewLines.length);
|
|
116
|
+
const extraItems: string[] = [];
|
|
117
|
+
if (remainingLines > 0) {
|
|
118
|
+
extraItems.push(formatMoreItems(remainingLines, "line"));
|
|
119
|
+
}
|
|
120
|
+
if (resultCount > 1) {
|
|
121
|
+
extraItems.push(formatMoreItems(resultCount - 1, "result"));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (let i = 0; i < safePreviewLines.length; i++) {
|
|
125
|
+
const isLast = i === safePreviewLines.length - 1 && extraItems.length === 0;
|
|
126
|
+
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
127
|
+
const line = safePreviewLines[i];
|
|
128
|
+
const color = line === "No preview text" ? "muted" : "toolOutput";
|
|
129
|
+
lines.push(` ${uiTheme.fg("dim", branch)} ${uiTheme.fg(color, line)}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (let i = 0; i < extraItems.length; i++) {
|
|
133
|
+
const isLast = i === extraItems.length - 1;
|
|
134
|
+
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
135
|
+
lines.push(` ${uiTheme.fg("dim", branch)} ${uiTheme.fg("muted", extraItems[i])}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return lines;
|
|
197
139
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
"contentAccent",
|
|
203
|
-
"Highlights",
|
|
204
|
-
)}`;
|
|
205
|
-
const maxHighlights = Math.min(res.highlights.length, 3);
|
|
206
|
-
for (let j = 0; j < maxHighlights; j++) {
|
|
207
|
-
const h = res.highlights[j];
|
|
208
|
-
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg(
|
|
209
|
-
"muted",
|
|
210
|
-
`${uiTheme.format.dash} ${truncateToWidth(h, MAX_HIGHLIGHT_LEN)}`,
|
|
211
|
-
)}`;
|
|
140
|
+
|
|
141
|
+
if (resultCount === 0) {
|
|
142
|
+
lines.push(` ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg("muted", "No results")}`);
|
|
143
|
+
return lines;
|
|
212
144
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
145
|
+
|
|
146
|
+
for (let i = 0; i < results.length; i++) {
|
|
147
|
+
const res = results[i];
|
|
148
|
+
const isLast = i === results.length - 1;
|
|
149
|
+
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
150
|
+
const cont = isLast ? " " : uiTheme.tree.vertical;
|
|
151
|
+
|
|
152
|
+
const title = truncateToWidth(res.title ?? "Untitled", contentWidth);
|
|
153
|
+
const domain = res.url ? getDomain(res.url) : "";
|
|
154
|
+
const domainPart = domain ? uiTheme.fg("dim", ` (${domain})`) : "";
|
|
155
|
+
|
|
156
|
+
lines.push(` ${uiTheme.fg("dim", branch)} ${uiTheme.fg("contentAccent", title)}${domainPart}`);
|
|
157
|
+
|
|
158
|
+
if (res.url) {
|
|
159
|
+
lines.push(
|
|
160
|
+
` ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg("mdLinkUrl", res.url)}`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (res.author) {
|
|
165
|
+
lines.push(
|
|
166
|
+
` ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg("muted", `Author: ${res.author}`)}`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (res.publishedDate) {
|
|
171
|
+
lines.push(
|
|
172
|
+
` ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg("muted", `Published: ${res.publishedDate}`)}`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (res.text) {
|
|
177
|
+
const textLines = res.text.split("\n").filter(l => l.trim());
|
|
178
|
+
const displayLines = textLines.slice(0, EXPANDED_TEXT_LINES);
|
|
179
|
+
for (const line of displayLines) {
|
|
180
|
+
lines.push(
|
|
181
|
+
` ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg("toolOutput", truncateToWidth(line.trim(), contentWidth))}`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
if (textLines.length > EXPANDED_TEXT_LINES) {
|
|
185
|
+
lines.push(
|
|
186
|
+
` ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg("muted", formatMoreItems(textLines.length - EXPANDED_TEXT_LINES, "line"))}`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (res.highlights?.length) {
|
|
192
|
+
lines.push(
|
|
193
|
+
` ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg("contentAccent", "Highlights")}`,
|
|
194
|
+
);
|
|
195
|
+
const maxHighlights = Math.min(res.highlights.length, 3);
|
|
196
|
+
for (let j = 0; j < maxHighlights; j++) {
|
|
197
|
+
const h = res.highlights[j];
|
|
198
|
+
lines.push(
|
|
199
|
+
` ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg("muted", `${uiTheme.format.dash} ${truncateToWidth(h, contentWidth)}`)}`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (res.highlights.length > maxHighlights) {
|
|
203
|
+
lines.push(
|
|
204
|
+
` ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg("muted", formatMoreItems(res.highlights.length - maxHighlights, "highlight"))}`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
218
208
|
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
209
|
|
|
222
|
-
|
|
210
|
+
return lines;
|
|
211
|
+
},
|
|
212
|
+
invalidate() {},
|
|
213
|
+
};
|
|
223
214
|
}
|
|
224
215
|
|
|
225
216
|
/** Render Exa call (query/args preview) */
|
|
226
217
|
export function renderExaCall(args: Record<string, unknown>, toolName: string, uiTheme: Theme): Component {
|
|
227
218
|
const toolLabel = toolName || "Exa Search";
|
|
228
|
-
const query = typeof args.query === "string" ? truncateToWidth(args.query, 80) : "?";
|
|
229
219
|
const numResults = typeof args.num_results === "number" ? args.num_results : undefined;
|
|
230
220
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
221
|
+
return {
|
|
222
|
+
render(width: number): string[] {
|
|
223
|
+
const query = typeof args.query === "string" ? truncateToWidth(args.query, Math.max(20, width - 30)) : "?";
|
|
224
|
+
let text = `${uiTheme.fg("toolTitle", toolLabel)} ${uiTheme.fg("contentAccent", query)}`;
|
|
225
|
+
if (numResults !== undefined) {
|
|
226
|
+
text += ` ${uiTheme.fg("muted", `results:${numResults}`)}`;
|
|
227
|
+
}
|
|
228
|
+
return [truncateToWidth(text, width)];
|
|
229
|
+
},
|
|
230
|
+
invalidate() {},
|
|
231
|
+
};
|
|
237
232
|
}
|