pi-readseek 0.8.15 → 0.8.16
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 +7 -3
- package/dist/index.ts +57 -36
- package/package.json +2 -2
- package/prompts/read.md +3 -3
package/README.md
CHANGED
|
@@ -56,9 +56,13 @@ Add an optional `readseek` section to `~/.pi/agent/settings.json` (global) or
|
|
|
56
56
|
|
|
57
57
|
- `replacedTools`: built-in tools backed by ReadSeek. Valid values are `"read"`,
|
|
58
58
|
`"edit"`, `"write"`, and `"grep"`.
|
|
59
|
-
- `imageMode`:
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
- `imageMode`: controls standalone images and images embedded in PDFs. `"auto"`
|
|
60
|
+
exposes `none`, `all`, `ocr`, `caption`, and `objects`. When the active model
|
|
61
|
+
accepts images, `"auto"` silently passes prepared images to the model instead
|
|
62
|
+
of running local analysis. `"on"` always runs the selected local analysis and
|
|
63
|
+
omits `none`; `"off"` skips standalone and embedded images. PDF text is always
|
|
64
|
+
read regardless of `imageMode`. Omitting `image` skips standalone images and,
|
|
65
|
+
unless native vision is available in `"auto"` mode, PDF embedded images.
|
|
62
66
|
- `syntaxValidation`: syntax-regression handling for `readSeek_edit`. `"warn"`
|
|
63
67
|
writes with a warning, `"block"` aborts, and `"off"` disables the check.
|
|
64
68
|
- `timeoutMs`: ReadSeek command timeout in milliseconds.
|
package/dist/index.ts
CHANGED
|
@@ -3251,6 +3251,16 @@ ${details.join(`
|
|
|
3251
3251
|
|
|
3252
3252
|
`);
|
|
3253
3253
|
}
|
|
3254
|
+
function preparedPdfContent(pdf) {
|
|
3255
|
+
const content = [{ type: "text", text: truncateDocumentText(pdf.markdown) }];
|
|
3256
|
+
for (const image of pdf.images) {
|
|
3257
|
+
if (image.encoding !== "base64" || image.data === undefined)
|
|
3258
|
+
continue;
|
|
3259
|
+
content.push({ type: "text", text: `[PDF page ${image.page} image]` });
|
|
3260
|
+
content.push({ type: "image", data: image.data, mimeType: image.mime });
|
|
3261
|
+
}
|
|
3262
|
+
return content;
|
|
3263
|
+
}
|
|
3254
3264
|
function truncateDocumentText(text) {
|
|
3255
3265
|
const truncation = truncateHead2(text, { maxLines: DEFAULT_MAX_LINES3, maxBytes: DEFAULT_MAX_BYTES3 });
|
|
3256
3266
|
if (!truncation.truncated)
|
|
@@ -3258,9 +3268,9 @@ function truncateDocumentText(text) {
|
|
|
3258
3268
|
return `${truncation.content}
|
|
3259
3269
|
[… document output truncated; use readSeek_view with page or node selectors]`;
|
|
3260
3270
|
}
|
|
3261
|
-
function
|
|
3271
|
+
function skippedImageFile(path2) {
|
|
3262
3272
|
return {
|
|
3263
|
-
content: [{ type: "text", text: `[Skipped image
|
|
3273
|
+
content: [{ type: "text", text: `[Skipped image: ${path2}; no image mode selected]` }],
|
|
3264
3274
|
details: {}
|
|
3265
3275
|
};
|
|
3266
3276
|
}
|
|
@@ -3361,7 +3371,7 @@ async function executeRead(opts) {
|
|
|
3361
3371
|
path: rawParams.path
|
|
3362
3372
|
});
|
|
3363
3373
|
}
|
|
3364
|
-
if (detection.kind === "image" || detection.
|
|
3374
|
+
if (detection.kind === "image" || detection.kind === "pdf") {
|
|
3365
3375
|
if (p.offset !== undefined || p.limit !== undefined) {
|
|
3366
3376
|
const message = "Cannot combine offset/limit with image or PDF reads.";
|
|
3367
3377
|
return buildToolErrorResult("read", "invalid-params-combo", message, { path: rawParams.path });
|
|
@@ -3370,49 +3380,55 @@ async function executeRead(opts) {
|
|
|
3370
3380
|
const message = "The page parameter applies to PDFs only.";
|
|
3371
3381
|
return buildToolErrorResult("read", "invalid-params-combo", message, { path: rawParams.path });
|
|
3372
3382
|
}
|
|
3373
|
-
const pdfPage = detection.kind === "pdf" ? p.page ?? 1 : undefined;
|
|
3374
3383
|
const imageMode = resolveReadSeekImageMode();
|
|
3375
|
-
|
|
3376
|
-
return skippedVisualFile(rawParams.path);
|
|
3377
|
-
}
|
|
3384
|
+
const useNativeVision = imageMode === "auto" && opts.modelSupportsImages === true;
|
|
3378
3385
|
if (imageMode === "on" && p.image === "none") {
|
|
3379
3386
|
return buildToolErrorResult("read", "invalid-params-combo", 'image="none" is only available when imageMode is "auto".', {
|
|
3380
3387
|
path: rawParams.path
|
|
3381
3388
|
});
|
|
3382
3389
|
}
|
|
3383
|
-
if (
|
|
3390
|
+
if (detection.kind === "pdf") {
|
|
3391
|
+
let pdfImageMode = "none";
|
|
3392
|
+
if (imageMode !== "off" && p.image !== undefined && p.image !== "none" && !useNativeVision) {
|
|
3393
|
+
pdfImageMode = p.image;
|
|
3394
|
+
}
|
|
3384
3395
|
try {
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
content.push({ type: "text", text: `[PDF page ${image2.page} image]` });
|
|
3392
|
-
content.push({ type: "image", data: image2.data, mimeType: image2.mime });
|
|
3393
|
-
}
|
|
3394
|
-
return succeed({ content, details: {} });
|
|
3396
|
+
const pdf = await readSeekPdf(absolutePath, pdfImageMode, { page: p.page ?? 1, signal });
|
|
3397
|
+
if (pdfImageMode !== "none") {
|
|
3398
|
+
return succeed({
|
|
3399
|
+
content: [{ type: "text", text: truncateDocumentText(formatPdfAnalysis(pdf)) }],
|
|
3400
|
+
details: {}
|
|
3401
|
+
});
|
|
3395
3402
|
}
|
|
3396
|
-
const image = await readSeekPreparedImage(absolutePath, { signal });
|
|
3397
3403
|
return succeed({
|
|
3398
|
-
content: [{ type: "
|
|
3404
|
+
content: useNativeVision ? preparedPdfContent(pdf) : [{ type: "text", text: truncateDocumentText(pdf.markdown) }],
|
|
3399
3405
|
details: {}
|
|
3400
3406
|
});
|
|
3401
3407
|
} catch (error) {
|
|
3402
3408
|
const message = error instanceof Error ? error.message : String(error);
|
|
3403
|
-
return buildToolErrorResult("read", "read-error", `
|
|
3409
|
+
return buildToolErrorResult("read", "read-error", `PDF reading unavailable: ${message}`, {
|
|
3404
3410
|
path: rawParams.path
|
|
3405
3411
|
});
|
|
3406
3412
|
}
|
|
3407
3413
|
}
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3414
|
+
if (imageMode === "off" || p.image === undefined) {
|
|
3415
|
+
return skippedImageFile(rawParams.path);
|
|
3416
|
+
}
|
|
3417
|
+
if (useNativeVision || p.image === "none") {
|
|
3418
|
+
try {
|
|
3419
|
+
const image = await readSeekPreparedImage(absolutePath, { signal });
|
|
3411
3420
|
return succeed({
|
|
3412
|
-
content: [{ type: "
|
|
3421
|
+
content: [{ type: "image", data: image.data, mimeType: image.mime }],
|
|
3413
3422
|
details: {}
|
|
3414
3423
|
});
|
|
3424
|
+
} catch (error) {
|
|
3425
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3426
|
+
return buildToolErrorResult("read", "read-error", `Image preprocessing unavailable: ${message}`, {
|
|
3427
|
+
path: rawParams.path
|
|
3428
|
+
});
|
|
3415
3429
|
}
|
|
3430
|
+
}
|
|
3431
|
+
try {
|
|
3416
3432
|
const analysis = await readSeekImage(absolutePath, [p.image], { signal });
|
|
3417
3433
|
const imageAnalysis = formatImageAnalysis(analysis, p.image);
|
|
3418
3434
|
if (imageAnalysis) {
|
|
@@ -3423,8 +3439,7 @@ async function executeRead(opts) {
|
|
|
3423
3439
|
}
|
|
3424
3440
|
} catch (error) {
|
|
3425
3441
|
const message = error instanceof Error ? error.message : String(error);
|
|
3426
|
-
|
|
3427
|
-
return buildToolErrorResult("read", "read-error", `${label} analysis unavailable: ${message}`, {
|
|
3442
|
+
return buildToolErrorResult("read", "read-error", `Image analysis unavailable: ${message}`, {
|
|
3428
3443
|
path: rawParams.path
|
|
3429
3444
|
});
|
|
3430
3445
|
}
|
|
@@ -3663,11 +3678,11 @@ function registerReadTool(pi, options = {}) {
|
|
|
3663
3678
|
const tool = registerReadSeekTool(pi, {
|
|
3664
3679
|
name,
|
|
3665
3680
|
label: "Read",
|
|
3666
|
-
description: imageMode === "off" ? 'Read anchored text by range, map, or symbol;
|
|
3681
|
+
description: imageMode === "off" ? 'Read anchored text by range, map, or symbol; standalone images are skipped because imageMode is "off", while PDFs are read as text.' : promptMetadata.description,
|
|
3667
3682
|
promptSnippet: promptMetadata.promptSnippet,
|
|
3668
3683
|
promptGuidelines: [
|
|
3669
3684
|
...promptMetadata.promptGuidelines,
|
|
3670
|
-
imageMode === "off" ? "The image parameter is unavailable;
|
|
3685
|
+
imageMode === "off" ? "The image parameter is unavailable; standalone images and PDF embedded images are skipped, while PDFs are read as text." : `For an image, explicitly choose image: ${imageModes.join(", ")}; omitting image skips standalone images. PDFs are always read as text, and image controls embedded images.${imageMode === "auto" ? " Native image-capable models bypass local analysis and receive standalone and PDF embedded images directly." : ""}`
|
|
3671
3686
|
],
|
|
3672
3687
|
parameters: Type2.Object({
|
|
3673
3688
|
path: filePathParam(),
|
|
@@ -3681,7 +3696,7 @@ function registerReadTool(pi, options = {}) {
|
|
|
3681
3696
|
})),
|
|
3682
3697
|
...imageMode === "off" ? {} : {
|
|
3683
3698
|
image: Type2.Optional(Type2.Union(imageModes.map((mode) => Type2.Literal(mode)), {
|
|
3684
|
-
description: `
|
|
3699
|
+
description: `Standalone image/PDF embedded-image mode: ${imageModes.join(", ")}. Must be selected explicitly for standalone images.`
|
|
3685
3700
|
}))
|
|
3686
3701
|
}
|
|
3687
3702
|
}),
|
|
@@ -3692,7 +3707,8 @@ function registerReadTool(pi, options = {}) {
|
|
|
3692
3707
|
signal,
|
|
3693
3708
|
onUpdate,
|
|
3694
3709
|
cwd: ctx.cwd,
|
|
3695
|
-
onSuccessfulRead: options.onSuccessfulRead
|
|
3710
|
+
onSuccessfulRead: options.onSuccessfulRead,
|
|
3711
|
+
modelSupportsImages: ctx.model?.input.includes("image") ?? false
|
|
3696
3712
|
});
|
|
3697
3713
|
},
|
|
3698
3714
|
renderCall(args, theme, ...rest) {
|
|
@@ -3726,13 +3742,19 @@ function registerReadTool(pi, options = {}) {
|
|
|
3726
3742
|
const readSeekValue = result.details?.readSeekValue;
|
|
3727
3743
|
if (!readSeekValue) {
|
|
3728
3744
|
const lines = textContent.split(`
|
|
3729
|
-
`).filter(Boolean).length
|
|
3730
|
-
`
|
|
3731
|
-
return new Text2(summaryLine(`loaded ${lines} ${lines === 1 ? "line" : "lines"}`, {
|
|
3745
|
+
`).filter(Boolean).length;
|
|
3746
|
+
let text2 = summaryLine(`loaded ${lines} ${lines === 1 ? "line" : "lines"}`, {
|
|
3732
3747
|
hidden: !!textContent && !expanded,
|
|
3733
3748
|
theme,
|
|
3734
3749
|
style: "success"
|
|
3735
|
-
})
|
|
3750
|
+
});
|
|
3751
|
+
if (expanded && textContent) {
|
|
3752
|
+
text2 += `
|
|
3753
|
+
${textContent}`;
|
|
3754
|
+
}
|
|
3755
|
+
return new Text2(clampLinesToWidth(text2.split(`
|
|
3756
|
+
`), width).join(`
|
|
3757
|
+
`), 0, 0);
|
|
3736
3758
|
}
|
|
3737
3759
|
const info = formatReadResultText({ range: readSeekValue.range, truncation: readSeekValue.truncation, symbol: readSeekValue.symbol, map: readSeekValue.map, warnings: readSeekValue.warnings });
|
|
3738
3760
|
const visibleLines = info.truncated && readSeekValue.truncation ? readSeekValue.truncation.outputLines : readSeekValue.range.endLine - readSeekValue.range.startLine + 1;
|
|
@@ -6124,7 +6146,6 @@ async function statSearchPathOrError(tool, rawPath, searchPath) {
|
|
|
6124
6146
|
try {
|
|
6125
6147
|
return { ok: true, stats: await fsStat2(searchPath) };
|
|
6126
6148
|
} catch (err) {
|
|
6127
|
-
const display = rawPath ?? ".";
|
|
6128
6149
|
const path3 = rawPath ?? searchPath;
|
|
6129
6150
|
const { code, message } = formatFsError(err, "stat-error");
|
|
6130
6151
|
return { ok: false, error: buildToolErrorResult(tool, code, message, { path: path3 }) };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-readseek",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.16",
|
|
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": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"node": ">=20.0.0"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@jarkkojs/readseek": "^0.8.
|
|
43
|
+
"@jarkkojs/readseek": "^0.8.16",
|
|
44
44
|
"diff": "^9.0.0",
|
|
45
45
|
"xxhash-wasm": "^1.1.0"
|
|
46
46
|
},
|
package/prompts/read.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Read anchored text by range, map, or symbol,
|
|
1
|
+
Read anchored text by range, map, or symbol, process images with an explicit mode, and always read PDF text.
|
|
2
2
|
|
|
3
3
|
## Choose the right read
|
|
4
4
|
|
|
@@ -14,10 +14,10 @@ Read anchored text by range, map, or symbol, and process images or PDFs with an
|
|
|
14
14
|
- `map` — full-file map; incompatible with `symbol` or `bundle`.
|
|
15
15
|
- `symbol` — `Name`, `Class.method`, or `Name@<line>`; incompatible with `offset` / `limit`.
|
|
16
16
|
- `bundle` — only `"local"`; requires `symbol` and excludes `map`.
|
|
17
|
-
- `image` — an exposed image/PDF mode; unavailable when `imageMode` is `"off"`.
|
|
17
|
+
- `image` — an exposed standalone-image/PDF embedded-image mode; unavailable when `imageMode` is `"off"`.
|
|
18
18
|
- `page` — one-based PDF page; defaults to page 1 and cannot be combined with `offset` / `limit`.
|
|
19
19
|
|
|
20
|
-
Default cap: {{DEFAULT_MAX_LINES}} lines or {{DEFAULT_MAX_BYTES}}. PDF reads return one page by default. Omitting `image` skips images and
|
|
20
|
+
Default cap: {{DEFAULT_MAX_LINES}} lines or {{DEFAULT_MAX_BYTES}}. PDF reads always return text for one page by default. Omitting `image` skips standalone images and PDF embedded images unless native vision is available in `"auto"` mode. With native vision, prepared standalone and PDF embedded images are passed directly instead of using local analysis.
|
|
21
21
|
|
|
22
22
|
Truncated full-file reads append a map when available. Use its ranges for follow-up reads.
|
|
23
23
|
|