pi-readseek 0.6.13 → 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 -0
- package/index.ts +3 -0
- package/package.json +2 -2
- package/prompts/read.md +2 -1
- package/prompts/view.md +14 -0
- package/src/read.ts +35 -5
- package/src/readseek-client.ts +23 -2
- package/src/view.ts +158 -0
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ binary automatically on supported platforms.
|
|
|
18
18
|
|
|
19
19
|
- `readSeek_read`: reads text with `LINE:HASH` anchors; when image modes are
|
|
20
20
|
enabled, images and PDFs can be returned as base64 or analyzed locally.
|
|
21
|
+
PDF reads return one selected page at a time by default.
|
|
21
22
|
- `readSeek_edit`: edits existing text files using fresh `LINE:HASH` anchors.
|
|
22
23
|
- `readSeek_grep`: searches text and returns edit-ready anchors.
|
|
23
24
|
- `readSeek_search`: searches code by structural AST pattern.
|
|
@@ -27,6 +28,7 @@ binary automatically on supported platforms.
|
|
|
27
28
|
- `readSeek_hover`: identifies the cursor token and enclosing symbol.
|
|
28
29
|
- `readSeek_def`: finds structural symbol definitions.
|
|
29
30
|
- `readSeek_check`: checks a source file for parser errors and missing syntax.
|
|
31
|
+
- `readSeek_view`: views an indexed PDF overview or narrows it by page, node, kind, or depth.
|
|
30
32
|
- `readSeek_write`: creates or overwrites whole files and returns anchors.
|
|
31
33
|
|
|
32
34
|
## Settings
|
package/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { registerHoverTool } from "./src/hover.js";
|
|
|
9
9
|
import { registerWriteTool } from "./src/write.js";
|
|
10
10
|
import { registerDefTool } from "./src/def.js";
|
|
11
11
|
import { registerCheckTool } from "./src/check.js";
|
|
12
|
+
import { registerViewTool } from "./src/view.js";
|
|
12
13
|
import { SessionAnchors } from "./src/session-anchors.js";
|
|
13
14
|
import { readSeekBinaryAvailability } from "./src/readseek-client.js";
|
|
14
15
|
import { resolveReadSeekJsonSettings, type ReadSeekSettingsWarning } from "./src/readseek-settings.js";
|
|
@@ -39,6 +40,7 @@ const READSEEK_TOOL_ENTRIES: ReadonlyArray<{ builtIn: ReplacedBuiltIn | null; re
|
|
|
39
40
|
{ builtIn: "write", readSeekName: "readSeek_write" },
|
|
40
41
|
{ builtIn: null, readSeekName: "readSeek_def" },
|
|
41
42
|
{ builtIn: null, readSeekName: "readSeek_check" },
|
|
43
|
+
{ builtIn: null, readSeekName: "readSeek_view" },
|
|
42
44
|
];
|
|
43
45
|
|
|
44
46
|
function formatSettingsWarning(warning: ReadSeekSettingsWarning): string {
|
|
@@ -87,6 +89,7 @@ export default function piReadSeekExtension(pi: ExtensionAPI): void {
|
|
|
87
89
|
registerHoverTool(pi);
|
|
88
90
|
registerDefTool(pi, { onFileAnchored: markAnchored });
|
|
89
91
|
registerCheckTool(pi);
|
|
92
|
+
registerViewTool(pi);
|
|
90
93
|
registerWriteTool(pi, { onFileAnchored: markAnchored, name: writeName });
|
|
91
94
|
|
|
92
95
|
pi.on("session_start", (_event, ctx) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-readseek",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Pi extension for readseek-backed hash-anchored read/edit/grep, structural code maps, structural search, and file exploration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"node": ">=20.0.0"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@jarkkojs/readseek": "^0.
|
|
42
|
+
"@jarkkojs/readseek": "^0.7.0",
|
|
43
43
|
"diff": "^8.0.3",
|
|
44
44
|
"xxhash-wasm": "^1.1.0"
|
|
45
45
|
},
|
package/prompts/read.md
CHANGED
|
@@ -15,8 +15,9 @@ Read anchored text by range, map, or symbol, and process images or PDFs with an
|
|
|
15
15
|
- `symbol` — `Name`, `Class.method`, or `Name@<line>`; incompatible with `offset` / `limit`.
|
|
16
16
|
- `bundle` — only `"local"`; requires `symbol` and excludes `map`.
|
|
17
17
|
- `image` — an exposed image/PDF mode; unavailable when `imageMode` is `"off"`.
|
|
18
|
+
- `page` — one-based PDF page; defaults to page 1 and cannot be combined with `offset` / `limit`.
|
|
18
19
|
|
|
19
|
-
Default cap: {{DEFAULT_MAX_LINES}} lines or {{DEFAULT_MAX_BYTES}}. Omitting `image` skips images and PDFs; when `imageMode` is `"off"`, visual files are always skipped.
|
|
20
|
+
Default cap: {{DEFAULT_MAX_LINES}} lines or {{DEFAULT_MAX_BYTES}}. PDF reads return one page by default. Omitting `image` skips images and PDFs; when `imageMode` is `"off"`, visual files are always skipped.
|
|
20
21
|
|
|
21
22
|
Truncated full-file reads append a map when available. Use its ranges for follow-up reads.
|
|
22
23
|
|
package/prompts/view.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
View the structure or selected content of an indexed PDF. Start with the default overview, then narrow by page or node instead of reading the whole document.
|
|
2
|
+
|
|
3
|
+
## Parameters
|
|
4
|
+
|
|
5
|
+
- `path` — PDF document to view.
|
|
6
|
+
- `node` — optional node ID to use as the view root.
|
|
7
|
+
- `page` — optional one-based source page.
|
|
8
|
+
- `kind` — optional node kind filter.
|
|
9
|
+
- `depth` — optional maximum depth below selected roots.
|
|
10
|
+
- `outline` — return outline nodes only.
|
|
11
|
+
|
|
12
|
+
## Output
|
|
13
|
+
|
|
14
|
+
Returns a bounded text projection with stable node IDs and source page references.
|
package/src/read.ts
CHANGED
|
@@ -32,11 +32,11 @@ import { clampLineToWidth, clampLinesToWidth, linkToolPath, renderPendingResult,
|
|
|
32
32
|
import type { FileAnchoredCallback } from "./tool-types.js";
|
|
33
33
|
import { filePathParam, mapParam, optionalIntOrString, registerReadSeekTool } from "./register-tool.js";
|
|
34
34
|
|
|
35
|
-
|
|
36
35
|
interface ReadParams {
|
|
37
36
|
path: string;
|
|
38
37
|
offset?: number | string;
|
|
39
38
|
limit?: number | string;
|
|
39
|
+
page?: number | string;
|
|
40
40
|
symbol?: string;
|
|
41
41
|
map?: boolean;
|
|
42
42
|
bundle?: string;
|
|
@@ -101,6 +101,12 @@ function formatPdfAnalysis(pdf: ReadSeekPdfOutput): string {
|
|
|
101
101
|
return sections.filter(Boolean).join("\n\n");
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
function truncateDocumentText(text: string): string {
|
|
105
|
+
const truncation = truncateHead(text, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
|
|
106
|
+
if (!truncation.truncated) return truncation.content;
|
|
107
|
+
return `${truncation.content}\n[… document output truncated; use readSeek_view with page or node selectors]`;
|
|
108
|
+
}
|
|
109
|
+
|
|
104
110
|
function skippedVisualFile(path: string): AgentToolResult<any> {
|
|
105
111
|
return {
|
|
106
112
|
content: [{ type: "text", text: `[Skipped image/PDF: ${path}; no image mode selected]` }],
|
|
@@ -128,6 +134,15 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
128
134
|
const message = `Invalid offset: expected a positive integer, received ${offset.value}.`;
|
|
129
135
|
return buildToolErrorResult("read", "invalid-offset", message, { path: rawParams.path });
|
|
130
136
|
}
|
|
137
|
+
|
|
138
|
+
const page = coerceObviousBase10Int(rawParams.page, "page");
|
|
139
|
+
if (!page.ok) {
|
|
140
|
+
return buildToolErrorResult("read", "invalid-page", page.message, { path: rawParams.path });
|
|
141
|
+
}
|
|
142
|
+
if (page.value !== undefined && page.value < 1) {
|
|
143
|
+
const message = `Invalid page: expected a positive integer, received ${page.value}.`;
|
|
144
|
+
return buildToolErrorResult("read", "invalid-page", message, { path: rawParams.path });
|
|
145
|
+
}
|
|
131
146
|
const rawBundle = typeof rawParams.bundle === "string" ? rawParams.bundle.trim() : undefined;
|
|
132
147
|
const requestedMapViaBundle =
|
|
133
148
|
rawBundle === "map" ||
|
|
@@ -136,6 +151,7 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
136
151
|
...rawParams,
|
|
137
152
|
offset: offset.value,
|
|
138
153
|
limit: limit.value,
|
|
154
|
+
page: page.value,
|
|
139
155
|
map: rawParams.map === true || requestedMapViaBundle,
|
|
140
156
|
bundle: requestedMapViaBundle ? undefined : rawBundle,
|
|
141
157
|
};
|
|
@@ -207,6 +223,15 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
207
223
|
});
|
|
208
224
|
}
|
|
209
225
|
if (detection.kind === "image" || detection.type === "application/pdf") {
|
|
226
|
+
if (p.offset !== undefined || p.limit !== undefined) {
|
|
227
|
+
const message = "Cannot combine offset/limit with image or PDF reads.";
|
|
228
|
+
return buildToolErrorResult("read", "invalid-params-combo", message, { path: rawParams.path });
|
|
229
|
+
}
|
|
230
|
+
if (detection.kind !== "pdf" && p.page !== undefined) {
|
|
231
|
+
const message = "The page parameter applies to PDFs only.";
|
|
232
|
+
return buildToolErrorResult("read", "invalid-params-combo", message, { path: rawParams.path });
|
|
233
|
+
}
|
|
234
|
+
const pdfPage = detection.kind === "pdf" ? (p.page ?? 1) : undefined;
|
|
210
235
|
const imageMode = resolveReadSeekImageMode();
|
|
211
236
|
if (imageMode === "off" || p.image === undefined) {
|
|
212
237
|
return skippedVisualFile(rawParams.path);
|
|
@@ -219,8 +244,8 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
219
244
|
if (p.image === "none") {
|
|
220
245
|
try {
|
|
221
246
|
if (detection.kind === "pdf") {
|
|
222
|
-
const pdf = await readSeekPdf(absolutePath, p.image, { signal });
|
|
223
|
-
const content: AgentToolResult<any>["content"] = [{ type: "text", text: pdf.markdown }];
|
|
247
|
+
const pdf = await readSeekPdf(absolutePath, p.image, { page: pdfPage, signal });
|
|
248
|
+
const content: AgentToolResult<any>["content"] = [{ type: "text", text: truncateDocumentText(pdf.markdown) }];
|
|
224
249
|
for (const image of pdf.images) {
|
|
225
250
|
if (image.encoding !== "base64" || image.data === undefined) continue;
|
|
226
251
|
content.push({ type: "text", text: `[PDF page ${image.page} image]` });
|
|
@@ -243,9 +268,9 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
243
268
|
|
|
244
269
|
try {
|
|
245
270
|
if (detection.kind === "pdf") {
|
|
246
|
-
const pdf = await readSeekPdf(absolutePath, p.image, { signal });
|
|
271
|
+
const pdf = await readSeekPdf(absolutePath, p.image, { page: pdfPage, signal });
|
|
247
272
|
return succeed({
|
|
248
|
-
content: [{ type: "text" as const, text: formatPdfAnalysis(pdf) }],
|
|
273
|
+
content: [{ type: "text" as const, text: truncateDocumentText(formatPdfAnalysis(pdf)) }],
|
|
249
274
|
details: {},
|
|
250
275
|
});
|
|
251
276
|
}
|
|
@@ -269,6 +294,10 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
269
294
|
});
|
|
270
295
|
}
|
|
271
296
|
}
|
|
297
|
+
if (p.page !== undefined) {
|
|
298
|
+
const message = "The page parameter applies to PDFs only.";
|
|
299
|
+
return buildToolErrorResult("read", "invalid-params-combo", message, { path: rawParams.path });
|
|
300
|
+
}
|
|
272
301
|
throwIfAborted(signal);
|
|
273
302
|
const rawText = rawBuffer.toString("utf-8");
|
|
274
303
|
const normalized = normalizeToLF(stripBom(rawText).text);
|
|
@@ -548,6 +577,7 @@ export function registerReadTool(pi: ExtensionAPI, options: ReadToolOptions = {}
|
|
|
548
577
|
path: filePathParam(),
|
|
549
578
|
offset: optionalIntOrString("Start line (1-indexed)"),
|
|
550
579
|
limit: optionalIntOrString("Maximum lines to return"),
|
|
580
|
+
page: optionalIntOrString("One-based PDF page; defaults to 1"),
|
|
551
581
|
symbol: Type.Optional(Type.String({ description: "Symbol name to read" })),
|
|
552
582
|
map: mapParam(),
|
|
553
583
|
bundle: Type.Optional(
|
package/src/readseek-client.ts
CHANGED
|
@@ -481,6 +481,25 @@ async function runReadSeek(args: string[], options: RunReadSeekOptions = {}): Pr
|
|
|
481
481
|
return JSON.parse(stdout) as unknown;
|
|
482
482
|
}
|
|
483
483
|
|
|
484
|
+
export interface ReadSeekViewOptions {
|
|
485
|
+
node?: string;
|
|
486
|
+
page?: number;
|
|
487
|
+
kind?: string;
|
|
488
|
+
depth?: number;
|
|
489
|
+
outline?: boolean;
|
|
490
|
+
signal?: AbortSignal;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export async function readSeekView(filePath: string, options: ReadSeekViewOptions = {}): Promise<string> {
|
|
494
|
+
const args = ["view", filePath];
|
|
495
|
+
if (options.node !== undefined) args.push("--node", options.node);
|
|
496
|
+
if (options.page !== undefined) args.push("--page", String(options.page));
|
|
497
|
+
if (options.kind !== undefined) args.push("--kind", options.kind);
|
|
498
|
+
if (options.depth !== undefined) args.push("--depth", String(options.depth));
|
|
499
|
+
if (options.outline) args.push("--outline");
|
|
500
|
+
return runReadSeekRaw(args, { signal: options.signal });
|
|
501
|
+
}
|
|
502
|
+
|
|
484
503
|
let visionInvocationTail = Promise.resolve();
|
|
485
504
|
|
|
486
505
|
/**
|
|
@@ -951,10 +970,12 @@ function parsePdfOutput(value: unknown): ReadSeekPdfOutput {
|
|
|
951
970
|
export async function readSeekPdf(
|
|
952
971
|
filePath: string,
|
|
953
972
|
mode: "none" | ReadSeekImageMode,
|
|
954
|
-
options: { signal?: AbortSignal } = {},
|
|
973
|
+
options: { page?: number; signal?: AbortSignal } = {},
|
|
955
974
|
): Promise<ReadSeekPdfOutput> {
|
|
956
975
|
const run = mode === "none" ? runReadSeek : runReadSeekVision;
|
|
957
|
-
|
|
976
|
+
const args = ["read", "--image", mode, filePath];
|
|
977
|
+
if (options.page !== undefined) args.push("--page", String(options.page));
|
|
978
|
+
return parsePdfOutput(await run(args, { signal: options.signal }));
|
|
958
979
|
}
|
|
959
980
|
|
|
960
981
|
// --- Rename ---
|
package/src/view.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { ExtensionAPI, ToolRenderResultOptions } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_MAX_BYTES,
|
|
4
|
+
DEFAULT_MAX_LINES,
|
|
5
|
+
truncateHead,
|
|
6
|
+
} from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
8
|
+
import { Type } from "@sinclair/typebox";
|
|
9
|
+
|
|
10
|
+
import { coerceObviousBase10Int } from "./coerce-obvious-int.js";
|
|
11
|
+
import { resolveToCwd } from "./path-utils.js";
|
|
12
|
+
import { classifyReadSeekFailure, readSeekView } from "./readseek-client.js";
|
|
13
|
+
import { buildToolErrorResult } from "./readseek-value.js";
|
|
14
|
+
import { filePathParam, optionalIntOrString, registerReadSeekTool } from "./register-tool.js";
|
|
15
|
+
import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
|
|
16
|
+
import {
|
|
17
|
+
clampLineToWidth,
|
|
18
|
+
clampLinesToWidth,
|
|
19
|
+
linkToolPath,
|
|
20
|
+
renderErrorResult,
|
|
21
|
+
renderPendingResult,
|
|
22
|
+
resolveRenderResultContext,
|
|
23
|
+
summaryLine,
|
|
24
|
+
} from "./tui-render-utils.js";
|
|
25
|
+
|
|
26
|
+
const NODE_KINDS = [
|
|
27
|
+
"artifact",
|
|
28
|
+
"footer",
|
|
29
|
+
"header",
|
|
30
|
+
"heading",
|
|
31
|
+
"marginal_label",
|
|
32
|
+
"page",
|
|
33
|
+
"page_number",
|
|
34
|
+
"paragraph",
|
|
35
|
+
"section",
|
|
36
|
+
"structural_section",
|
|
37
|
+
] as const;
|
|
38
|
+
|
|
39
|
+
const VIEW_PROMPT_METADATA = defineToolPromptMetadata({
|
|
40
|
+
promptUrl: new URL("../prompts/view.md", import.meta.url),
|
|
41
|
+
promptSnippet: "View the structure or selected content of an indexed PDF",
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
interface ViewParams {
|
|
45
|
+
path: string;
|
|
46
|
+
node?: string;
|
|
47
|
+
page?: number | string;
|
|
48
|
+
kind?: (typeof NODE_KINDS)[number];
|
|
49
|
+
depth?: number | string;
|
|
50
|
+
outline?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function executeView(opts: {
|
|
54
|
+
params: unknown;
|
|
55
|
+
signal: AbortSignal | undefined;
|
|
56
|
+
cwd: string;
|
|
57
|
+
}): Promise<any> {
|
|
58
|
+
const { params, signal, cwd } = opts;
|
|
59
|
+
const input = params as ViewParams;
|
|
60
|
+
const page = coerceObviousBase10Int(input.page, "page");
|
|
61
|
+
if (!page.ok || (page.value !== undefined && page.value < 1)) {
|
|
62
|
+
const message = page.ok
|
|
63
|
+
? `Invalid page: expected a positive integer, received ${page.value}.`
|
|
64
|
+
: page.message;
|
|
65
|
+
return buildToolErrorResult("view", "invalid-page", message, { path: input.path });
|
|
66
|
+
}
|
|
67
|
+
const depth = coerceObviousBase10Int(input.depth, "depth");
|
|
68
|
+
if (!depth.ok || (depth.value !== undefined && depth.value < 0)) {
|
|
69
|
+
const message = depth.ok
|
|
70
|
+
? `Invalid depth: expected a non-negative integer, received ${depth.value}.`
|
|
71
|
+
: depth.message;
|
|
72
|
+
return buildToolErrorResult("view", "invalid-depth", message, { path: input.path });
|
|
73
|
+
}
|
|
74
|
+
const node = input.node?.trim();
|
|
75
|
+
if (input.node !== undefined && !node) {
|
|
76
|
+
return buildToolErrorResult("view", "invalid-node", "Invalid node: expected a non-empty ID.", {
|
|
77
|
+
path: input.path,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const filePath = resolveToCwd(input.path, cwd);
|
|
82
|
+
try {
|
|
83
|
+
const output = await readSeekView(filePath, {
|
|
84
|
+
node,
|
|
85
|
+
page: page.value,
|
|
86
|
+
kind: input.kind,
|
|
87
|
+
depth: depth.value,
|
|
88
|
+
outline: input.outline,
|
|
89
|
+
signal,
|
|
90
|
+
});
|
|
91
|
+
const truncation = truncateHead(output, {
|
|
92
|
+
maxLines: DEFAULT_MAX_LINES,
|
|
93
|
+
maxBytes: DEFAULT_MAX_BYTES,
|
|
94
|
+
});
|
|
95
|
+
const notice = truncation.truncated
|
|
96
|
+
? "\n[… document view truncated; narrow it with page, node, kind, or depth]"
|
|
97
|
+
: "";
|
|
98
|
+
return {
|
|
99
|
+
content: [{ type: "text", text: `${truncation.content}${notice}` }],
|
|
100
|
+
details: {
|
|
101
|
+
readSeekValue: {
|
|
102
|
+
tool: "view",
|
|
103
|
+
path: filePath,
|
|
104
|
+
truncated: truncation.truncated,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
} catch (error) {
|
|
109
|
+
const failure = classifyReadSeekFailure(error);
|
|
110
|
+
return buildToolErrorResult("view", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function registerViewTool(pi: ExtensionAPI) {
|
|
115
|
+
return registerReadSeekTool(pi, {
|
|
116
|
+
name: "readSeek_view",
|
|
117
|
+
label: "View",
|
|
118
|
+
description: VIEW_PROMPT_METADATA.description,
|
|
119
|
+
promptSnippet: VIEW_PROMPT_METADATA.promptSnippet,
|
|
120
|
+
promptGuidelines: VIEW_PROMPT_METADATA.promptGuidelines,
|
|
121
|
+
parameters: Type.Object({
|
|
122
|
+
path: filePathParam(),
|
|
123
|
+
node: Type.Optional(Type.String({ description: "Node ID to use as the view root" })),
|
|
124
|
+
page: optionalIntOrString("One-based source page"),
|
|
125
|
+
kind: Type.Optional(Type.Union(NODE_KINDS.map((kind) => Type.Literal(kind)), {
|
|
126
|
+
description: "Node kind filter",
|
|
127
|
+
})),
|
|
128
|
+
depth: optionalIntOrString("Maximum depth below the selected roots"),
|
|
129
|
+
outline: Type.Optional(Type.Boolean({ description: "Return outline nodes only" })),
|
|
130
|
+
}),
|
|
131
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
132
|
+
return executeView({ params, signal, cwd: ctx.cwd });
|
|
133
|
+
},
|
|
134
|
+
renderCall(args: any, theme: any, ...rest: any[]) {
|
|
135
|
+
const context = rest[0] ?? {};
|
|
136
|
+
const cwd = context.cwd ?? process.cwd();
|
|
137
|
+
const displayPath = typeof args?.path === "string" ? args.path : "?";
|
|
138
|
+
const text = `${theme.fg("toolTitle", theme.bold("view"))} ${linkToolPath(theme.fg("accent", displayPath), displayPath, cwd)}`;
|
|
139
|
+
return new Text(clampLineToWidth(text, context.width), 0, 0);
|
|
140
|
+
},
|
|
141
|
+
renderResult(result: any, options: ToolRenderResultOptions, theme: any, ...rest: any[]) {
|
|
142
|
+
const { isPartial, isError, expanded, width } = resolveRenderResultContext(options, rest);
|
|
143
|
+
if (isPartial) return renderPendingResult("pending view", width, theme);
|
|
144
|
+
const textContent = result.content?.[0]?.type === "text" ? result.content[0].text : "";
|
|
145
|
+
if (isError || result.isError) {
|
|
146
|
+
return renderErrorResult(textContent, { expanded, width, fallback: "view failed", theme });
|
|
147
|
+
}
|
|
148
|
+
const lines = textContent.split("\n").filter(Boolean).length;
|
|
149
|
+
let text = summaryLine(`loaded ${lines} document ${lines === 1 ? "line" : "lines"}`, {
|
|
150
|
+
hidden: !!textContent && !expanded,
|
|
151
|
+
theme,
|
|
152
|
+
style: "success",
|
|
153
|
+
});
|
|
154
|
+
if (expanded && textContent) text += `\n${textContent}`;
|
|
155
|
+
return new Text(clampLinesToWidth(text.split("\n"), width).join("\n"), 0, 0);
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}
|