pi-readseek 0.4.30 → 0.5.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/README.md +39 -44
- package/index.ts +45 -7
- package/package.json +2 -2
- package/prompts/def.md +2 -9
- package/prompts/edit.md +13 -13
- package/prompts/grep.md +3 -3
- package/prompts/hover.md +2 -6
- package/prompts/read.md +3 -3
- package/prompts/refs.md +1 -1
- package/prompts/rename.md +3 -7
- package/prompts/sg.md +1 -1
- package/prompts/write.md +5 -5
- package/src/def.ts +12 -19
- package/src/edit-output.ts +3 -3
- package/src/edit-syntax-validate.ts +3 -3
- package/src/edit.ts +18 -25
- package/src/file-map.ts +2 -2
- package/src/grep-budget.ts +7 -51
- package/src/grep-output.ts +4 -4
- package/src/grep.ts +19 -26
- package/src/hover.ts +4 -8
- package/src/read-output.ts +4 -4
- package/src/read.ts +30 -43
- package/src/readseek-client.ts +111 -79
- package/src/readseek-params.ts +1 -1
- package/src/readseek-settings.ts +144 -61
- package/src/readseek-value.ts +4 -4
- package/src/refs-output.ts +3 -3
- package/src/refs.ts +6 -10
- package/src/register-tool.ts +4 -42
- package/src/rename.ts +6 -10
- package/src/replace-symbol.ts +2 -2
- package/src/sg-output.ts +3 -3
- package/src/sg.ts +21 -25
- package/src/syntax-validate-mode.ts +3 -13
- package/src/tool-prompt-metadata.ts +13 -13
- package/src/tui-render-utils.ts +2 -2
- package/src/write.ts +22 -26
package/src/read.ts
CHANGED
|
@@ -26,16 +26,16 @@ import { buildReadOutput } from "./read-output.js";
|
|
|
26
26
|
|
|
27
27
|
import { buildLocalBundle } from "./read-local-bundle.js";
|
|
28
28
|
import { coerceObviousBase10Int } from "./coerce-obvious-int.js";
|
|
29
|
-
import {
|
|
29
|
+
import { readSeekRead, readSeekDetect, readSeekImage, type ReadSeekDetection } from "./readseek-client.js";
|
|
30
30
|
import { formatReadCallText, formatReadResultText } from "./read-render-helpers.js";
|
|
31
|
-
import {
|
|
31
|
+
import { resolveReadSeekImageMode } from "./readseek-settings.js";
|
|
32
32
|
import { clampLineToWidth, clampLinesToWidth, linkToolPath, renderPendingResult, renderToolLabel, resolveRenderResultContext, summaryLine, wrapReadHashlinesForWidthCached } from "./tui-render-utils.js";
|
|
33
33
|
import type { FileAnchoredCallback } from "./tool-types.js";
|
|
34
34
|
import { filePathParam, mapParam, optionalIntOrString, registerReadSeekTool } from "./register-tool.js";
|
|
35
35
|
|
|
36
36
|
const READ_PROMPT_METADATA = defineToolPromptMetadata({
|
|
37
37
|
promptUrl: new URL("../prompts/read.md", import.meta.url),
|
|
38
|
-
promptSnippet: "Read text files or images; text reads include hashline anchors and optional maps/symbol lookup, image reads include the attachment plus OCR text",
|
|
38
|
+
promptSnippet: "Read text files or images; text reads include hashline anchors and optional maps/symbol lookup, image reads include the attachment plus OCR text, an image caption, and detected objects",
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
interface ReadParams {
|
|
@@ -58,16 +58,16 @@ export interface ExecuteReadOptions {
|
|
|
58
58
|
onUpdate: any;
|
|
59
59
|
cwd: string;
|
|
60
60
|
onSuccessfulRead?: FileAnchoredCallback;
|
|
61
|
-
/** Whether the active model accepts image input natively. Used when
|
|
61
|
+
/** Whether the active model accepts image input natively. Used when imageMode is auto. */
|
|
62
62
|
modelSupportsImages?: boolean;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
function hasReadAnchors(result: AgentToolResult<any>): boolean {
|
|
66
66
|
const details = (result as { details?: unknown }).details;
|
|
67
67
|
if (!details || typeof details !== "object") return false;
|
|
68
|
-
const
|
|
69
|
-
if (!
|
|
70
|
-
const lines = (
|
|
68
|
+
const readSeekValue = (details as { readSeekValue?: unknown }).readSeekValue;
|
|
69
|
+
if (!readSeekValue || typeof readSeekValue !== "object") return false;
|
|
70
|
+
const lines = (readSeekValue as { lines?: unknown }).lines;
|
|
71
71
|
return Array.isArray(lines) && lines.length > 0;
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -171,24 +171,18 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
171
171
|
if (hasBinaryContent) {
|
|
172
172
|
let detection: ReadSeekDetection | undefined;
|
|
173
173
|
try {
|
|
174
|
-
detection = await
|
|
174
|
+
detection = await readSeekDetect(absolutePath, { signal });
|
|
175
175
|
} catch {
|
|
176
176
|
}
|
|
177
177
|
if (detection?.kind === "image") {
|
|
178
178
|
const builtinRead = createReadTool(cwd);
|
|
179
179
|
const builtinResult = await builtinRead.execute(toolCallId, p, signal, onUpdate);
|
|
180
|
-
const
|
|
181
|
-
const shouldRunVision =
|
|
180
|
+
const imageMode = resolveReadSeekImageMode();
|
|
181
|
+
const shouldRunVision = imageMode === "force" || (imageMode === "auto" && !opts.modelSupportsImages);
|
|
182
182
|
if (!shouldRunVision) return succeed(builtinResult);
|
|
183
183
|
|
|
184
|
-
let ocrFailed = false;
|
|
185
184
|
try {
|
|
186
|
-
const ocrDetection = await
|
|
187
|
-
ocr: true,
|
|
188
|
-
caption: true,
|
|
189
|
-
objects: true,
|
|
190
|
-
signal,
|
|
191
|
-
});
|
|
185
|
+
const ocrDetection = await readSeekImage(absolutePath, ["ocr", "caption", "objects"], { signal });
|
|
192
186
|
const imageAnalysis = formatImageAnalysis(ocrDetection);
|
|
193
187
|
if (imageAnalysis) {
|
|
194
188
|
return succeed({
|
|
@@ -200,14 +194,11 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
200
194
|
});
|
|
201
195
|
}
|
|
202
196
|
} catch {
|
|
203
|
-
ocrFailed = true;
|
|
204
|
-
}
|
|
205
|
-
if (ocrFailed) {
|
|
206
197
|
return succeed({
|
|
207
198
|
...builtinResult,
|
|
208
199
|
content: [
|
|
209
200
|
...(builtinResult.content ?? []),
|
|
210
|
-
{ type: "text" as const, text: "[Warning: local image analysis
|
|
201
|
+
{ type: "text" as const, text: "[Warning: local image analysis unavailable — showing image attachment only]" },
|
|
211
202
|
],
|
|
212
203
|
});
|
|
213
204
|
}
|
|
@@ -356,33 +347,33 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
356
347
|
}
|
|
357
348
|
|
|
358
349
|
throwIfAborted(signal);
|
|
359
|
-
let
|
|
350
|
+
let readSeekOutput: Awaited<ReturnType<typeof readSeekRead>>;
|
|
360
351
|
try {
|
|
361
|
-
|
|
362
|
-
? await
|
|
363
|
-
: await
|
|
352
|
+
readSeekOutput = total === 0
|
|
353
|
+
? await readSeekRead(absolutePath, undefined, undefined, { signal })
|
|
354
|
+
: await readSeekRead(absolutePath, startLine, endIdx, { signal });
|
|
364
355
|
} catch (err: any) {
|
|
365
356
|
const detail = err?.message ? ` — ${err.message}` : "";
|
|
366
357
|
const message = `readseek failed while reading ${rawPath}${detail}`;
|
|
367
358
|
return buildToolErrorResult("read", "readseek-error", message, { path: rawParams.path, hint: "Ensure @jarkkojs/readseek and its npm platform package are installed.", details: { message: err?.message } });
|
|
368
359
|
}
|
|
369
360
|
const expectedLineCount = Math.max(0, endIdx - startLine + 1);
|
|
370
|
-
const invalidLine =
|
|
371
|
-
if (
|
|
361
|
+
const invalidLine = readSeekOutput.hashlines.find((line, index) => line.line !== startLine + index);
|
|
362
|
+
if (readSeekOutput.hashlines.length !== expectedLineCount || invalidLine) {
|
|
372
363
|
const message = invalidLine
|
|
373
364
|
? `readseek returned non-sequential line ${invalidLine.line} for requested range ${startLine}-${endIdx}`
|
|
374
|
-
: `readseek returned ${
|
|
365
|
+
: `readseek returned ${readSeekOutput.hashlines.length} lines for requested range ${startLine}-${endIdx} (${expectedLineCount} expected)`;
|
|
375
366
|
return buildToolErrorResult("read", "readseek-output-mismatch", message, { path: rawParams.path });
|
|
376
367
|
}
|
|
377
|
-
const
|
|
368
|
+
const readSeekLines: ReadSeekLine[] = readSeekOutput.hashlines.map((line) => ({
|
|
378
369
|
line: line.line,
|
|
379
370
|
hash: line.hash,
|
|
380
371
|
anchor: `${line.line}:${line.hash}`,
|
|
381
372
|
raw: line.text,
|
|
382
373
|
display: escapeControlCharsForDisplay(line.text),
|
|
383
374
|
}));
|
|
384
|
-
const selected =
|
|
385
|
-
const formatted = renderReadSeekLines(
|
|
375
|
+
const selected = readSeekLines.map((line) => line.raw);
|
|
376
|
+
const formatted = renderReadSeekLines(readSeekLines);
|
|
386
377
|
|
|
387
378
|
const truncation = truncateHead(formatted, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
|
|
388
379
|
|
|
@@ -423,7 +414,7 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
423
414
|
endLine: endIdx,
|
|
424
415
|
totalLines: total,
|
|
425
416
|
selectedLines: selected,
|
|
426
|
-
lines:
|
|
417
|
+
lines: readSeekLines,
|
|
427
418
|
warnings: structuredWarnings,
|
|
428
419
|
truncation: truncation.truncated
|
|
429
420
|
? {
|
|
@@ -456,7 +447,7 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
456
447
|
content: [{ type: "text", text: readOutput.text }],
|
|
457
448
|
details: {
|
|
458
449
|
truncation: truncation.truncated ? truncation : undefined,
|
|
459
|
-
|
|
450
|
+
readSeekValue: readOutput.readSeekValue,
|
|
460
451
|
},
|
|
461
452
|
});
|
|
462
453
|
}
|
|
@@ -469,11 +460,7 @@ function splitReadSeekLines(text: string): string[] {
|
|
|
469
460
|
|
|
470
461
|
export function registerReadTool(pi: ExtensionAPI, options: ReadToolOptions = {}) {
|
|
471
462
|
const tool = registerReadSeekTool(pi, {
|
|
472
|
-
|
|
473
|
-
pythonName: "read",
|
|
474
|
-
defaultExposure: "safe-by-default",
|
|
475
|
-
}, {
|
|
476
|
-
name: "read",
|
|
463
|
+
name: "readSeek_read",
|
|
477
464
|
label: "Read",
|
|
478
465
|
description: READ_PROMPT_METADATA.description,
|
|
479
466
|
promptSnippet: READ_PROMPT_METADATA.promptSnippet,
|
|
@@ -529,16 +516,16 @@ export function registerReadTool(pi: ExtensionAPI, options: ReadToolOptions = {}
|
|
|
529
516
|
return new Text(clampLinesToWidth([summaryLine(errorText)], width).join("\n"), 0, 0);
|
|
530
517
|
}
|
|
531
518
|
|
|
532
|
-
const
|
|
533
|
-
if (!
|
|
519
|
+
const readSeekValue = (result.details as any)?.readSeekValue as { range: { startLine: number; endLine: number; totalLines: number }; truncation: any; symbol: any; map: any; warnings: ReadSeekWarning[] } | undefined;
|
|
520
|
+
if (!readSeekValue) {
|
|
534
521
|
const lines = textContent.split("\n").filter(Boolean).length || textContent.split("\n").length;
|
|
535
522
|
return new Text(summaryLine(`loaded ${lines} ${lines === 1 ? "line" : "lines"}`, { hidden: !!textContent && !expanded }), 0, 0);
|
|
536
523
|
}
|
|
537
524
|
|
|
538
|
-
const info = formatReadResultText({ range:
|
|
539
|
-
const visibleLines = info.truncated &&
|
|
525
|
+
const info = formatReadResultText({ range: readSeekValue.range, truncation: readSeekValue.truncation, symbol: readSeekValue.symbol, map: readSeekValue.map, warnings: readSeekValue.warnings });
|
|
526
|
+
const visibleLines = info.truncated && readSeekValue.truncation ? readSeekValue.truncation.outputLines : readSeekValue.range.endLine - readSeekValue.range.startLine + 1;
|
|
540
527
|
const loadedWord = visibleLines === 1 ? "line" : "lines";
|
|
541
|
-
const summaryParts: string[] = [info.truncated ? `loaded ${visibleLines} of ${
|
|
528
|
+
const summaryParts: string[] = [info.truncated ? `loaded ${visibleLines} of ${readSeekValue.truncation?.totalLines ?? readSeekValue.range.totalLines} ${loadedWord} (truncated)` : `loaded ${visibleLines} ${loadedWord}`];
|
|
542
529
|
if (info.symbolBadge) summaryParts.push(info.symbolBadge);
|
|
543
530
|
for (const badge of info.badges) summaryParts.push(badge);
|
|
544
531
|
const summary = summaryParts.join(" • ");
|
package/src/readseek-client.ts
CHANGED
|
@@ -7,6 +7,7 @@ import path from "node:path";
|
|
|
7
7
|
import { DetailLevel } from "./readseek/enums.js";
|
|
8
8
|
import type { FileMap, FileSymbol } from "./readseek/types.js";
|
|
9
9
|
import { SymbolKind } from "./readseek/enums.js";
|
|
10
|
+
import { resolveReadSeekTimeoutMs } from "./readseek-settings.js";
|
|
10
11
|
|
|
11
12
|
export interface ReadSeekHashline {
|
|
12
13
|
line: number;
|
|
@@ -115,6 +116,8 @@ export interface ReadSeekDetectedObject {
|
|
|
115
116
|
bbox: [number, number, number, number];
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
export type ReadSeekImageMode = "ocr" | "caption" | "objects";
|
|
120
|
+
|
|
118
121
|
export type ReadSeekDetection =
|
|
119
122
|
| {
|
|
120
123
|
kind: "source";
|
|
@@ -146,6 +149,8 @@ export type ReadSeekDetection =
|
|
|
146
149
|
mime?: string;
|
|
147
150
|
};
|
|
148
151
|
|
|
152
|
+
type ReadSeekImageDetection = Extract<ReadSeekDetection, { kind: "image" }>;
|
|
153
|
+
|
|
149
154
|
interface ReadSeekSearchOptions {
|
|
150
155
|
language?: string;
|
|
151
156
|
cached?: boolean;
|
|
@@ -154,13 +159,6 @@ interface ReadSeekSearchOptions {
|
|
|
154
159
|
signal?: AbortSignal;
|
|
155
160
|
}
|
|
156
161
|
|
|
157
|
-
interface ReadSeekDetectOptions {
|
|
158
|
-
ocr?: boolean;
|
|
159
|
-
caption?: boolean;
|
|
160
|
-
objects?: boolean;
|
|
161
|
-
signal?: AbortSignal;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
162
|
function normalizeLanguage(language: string): string {
|
|
165
163
|
return language === "java" ? "Java" : language;
|
|
166
164
|
}
|
|
@@ -215,36 +213,43 @@ const require = createRequire(import.meta.url);
|
|
|
215
213
|
const READSEEK_REPO_PACKAGE_NAMES = new Set(["@jarkkojs/readseek", "readseek"]);
|
|
216
214
|
let defaultReadSeekDirInit: Promise<string | null> | undefined;
|
|
217
215
|
|
|
218
|
-
function
|
|
216
|
+
function readSeekPackageDir(): string {
|
|
219
217
|
return path.dirname(require.resolve("@jarkkojs/readseek/package.json"));
|
|
220
218
|
}
|
|
221
219
|
|
|
222
|
-
|
|
223
|
-
|
|
220
|
+
const READSEEK_PLATFORM_PACKAGES: Record<string, string> = {
|
|
221
|
+
"darwin-arm64": "@jarkkojs/readseek-darwin-arm64",
|
|
222
|
+
"linux-x64": "@jarkkojs/readseek-linux-x64",
|
|
223
|
+
"win32-x64": "@jarkkojs/readseek-win32-x64",
|
|
224
|
+
};
|
|
224
225
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
})();
|
|
226
|
+
function readSeekPlatform(): string {
|
|
227
|
+
return `${process.platform}-${process.arch}`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function readSeekBinaryPath(): string {
|
|
231
|
+
const platform = readSeekPlatform();
|
|
232
|
+
const platformPackage = READSEEK_PLATFORM_PACKAGES[platform];
|
|
233
|
+
if (!platformPackage) {
|
|
234
|
+
const supported = Object.keys(READSEEK_PLATFORM_PACKAGES).join(", ");
|
|
235
|
+
throw new Error(`@jarkkojs/readseek ships no binary for ${platform}; it supports ${supported}`);
|
|
236
|
+
}
|
|
237
237
|
|
|
238
|
-
const packageJson = require.resolve(`${platformPackage}/package.json`, { paths: [
|
|
238
|
+
const packageJson = require.resolve(`${platformPackage}/package.json`, { paths: [readSeekPackageDir()] });
|
|
239
239
|
return path.join(path.dirname(packageJson), "bin", process.platform === "win32" ? "readseek.exe" : "readseek");
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
|
|
242
|
+
/**
|
|
243
|
+
* Report whether a readseek binary can be resolved for the running platform,
|
|
244
|
+
* along with the reason when it cannot. Used to keep the readseek tools out of
|
|
245
|
+
* the active set on hosts that readseek publishes no binary for.
|
|
246
|
+
*/
|
|
247
|
+
export function readSeekBinaryAvailability(): { available: true } | { available: false; reason: string } {
|
|
243
248
|
try {
|
|
244
|
-
|
|
245
|
-
return true;
|
|
246
|
-
} catch {
|
|
247
|
-
return false;
|
|
249
|
+
readSeekBinaryPath();
|
|
250
|
+
return { available: true };
|
|
251
|
+
} catch (err) {
|
|
252
|
+
return { available: false, reason: classifyReadSeekFailure(err).message };
|
|
248
253
|
}
|
|
249
254
|
}
|
|
250
255
|
|
|
@@ -317,13 +322,18 @@ function defaultReadSeekDir(): string | null {
|
|
|
317
322
|
|
|
318
323
|
const DEFAULT_READSEEK_TIMEOUT_MS = 120_000;
|
|
319
324
|
|
|
320
|
-
function
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
325
|
+
function readSeekTimeoutMs(): number {
|
|
326
|
+
return resolveReadSeekTimeoutMs() ?? DEFAULT_READSEEK_TIMEOUT_MS;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const READSEEK_USAGE_HINT = /\n\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
|
|
330
|
+
|
|
331
|
+
function readSeekErrorMessage(stderr: string): string {
|
|
332
|
+
return stderr
|
|
333
|
+
.replace(READSEEK_USAGE_HINT, "")
|
|
334
|
+
.replace(/^error:\s*/i, "")
|
|
335
|
+
.replace(/\s*\n\s*/g, " ")
|
|
336
|
+
.trim();
|
|
327
337
|
}
|
|
328
338
|
|
|
329
339
|
async function spawnReadSeekRaw(args: string[], options: RunReadSeekOptions = {}): Promise<string> {
|
|
@@ -345,8 +355,8 @@ async function spawnReadSeekRaw(args: string[], options: RunReadSeekOptions = {}
|
|
|
345
355
|
|
|
346
356
|
const stdin = options.stdin;
|
|
347
357
|
const stdio: StdioOptions = [stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"];
|
|
348
|
-
const child = spawn(
|
|
349
|
-
const timeoutMs = options.timeoutMs ??
|
|
358
|
+
const child = spawn(readSeekBinaryPath(), args, { stdio, signal: options.signal });
|
|
359
|
+
const timeoutMs = options.timeoutMs ?? readSeekTimeoutMs();
|
|
350
360
|
timeout = setTimeout(() => {
|
|
351
361
|
child.kill("SIGKILL");
|
|
352
362
|
fail(new Error(`readseek timed out after ${timeoutMs} ms`));
|
|
@@ -391,8 +401,8 @@ async function spawnReadSeekRaw(args: string[], options: RunReadSeekOptions = {}
|
|
|
391
401
|
const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
392
402
|
const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
|
|
393
403
|
if (code === 0) succeed(stdout);
|
|
394
|
-
else if (signal) fail(new Error((stderr || `readseek killed by signal ${signal}`)
|
|
395
|
-
else fail(new Error((stderr || `readseek exited with status ${code}`)
|
|
404
|
+
else if (signal) fail(new Error(readSeekErrorMessage(stderr) || `readseek killed by signal ${signal}`));
|
|
405
|
+
else fail(new Error(readSeekErrorMessage(stderr) || `readseek exited with status ${code}`));
|
|
396
406
|
});
|
|
397
407
|
});
|
|
398
408
|
}
|
|
@@ -411,11 +421,11 @@ async function ensureDefaultReadSeekDir(): Promise<string | null> {
|
|
|
411
421
|
return defaultReadSeekDirInit;
|
|
412
422
|
}
|
|
413
423
|
|
|
414
|
-
async function
|
|
424
|
+
async function readSeekInvocationArgs(args: string[]): Promise<string[]> {
|
|
415
425
|
if (isOwnReadSeekRepository()) return args;
|
|
416
426
|
|
|
417
|
-
const
|
|
418
|
-
return
|
|
427
|
+
const readSeekDir = await ensureDefaultReadSeekDir();
|
|
428
|
+
return readSeekDir ? ["--readseek-dir", readSeekDir, ...args] : args;
|
|
419
429
|
}
|
|
420
430
|
|
|
421
431
|
interface RunReadSeekOptions {
|
|
@@ -425,7 +435,7 @@ interface RunReadSeekOptions {
|
|
|
425
435
|
}
|
|
426
436
|
|
|
427
437
|
async function runReadSeekRaw(args: string[], options: RunReadSeekOptions = {}): Promise<string> {
|
|
428
|
-
return spawnReadSeekRaw(await
|
|
438
|
+
return spawnReadSeekRaw(await readSeekInvocationArgs(args), options);
|
|
429
439
|
}
|
|
430
440
|
|
|
431
441
|
async function runReadSeek(args: string[], options: RunReadSeekOptions = {}): Promise<unknown> {
|
|
@@ -548,14 +558,13 @@ function parseSearchOutput(value: unknown): ReadSeekSearchOutput {
|
|
|
548
558
|
};
|
|
549
559
|
}
|
|
550
560
|
|
|
551
|
-
export async function
|
|
561
|
+
export async function readSeekRead(
|
|
552
562
|
filePath: string,
|
|
553
563
|
startLine?: number,
|
|
554
564
|
endLine?: number,
|
|
555
565
|
options: { signal?: AbortSignal } = {},
|
|
556
566
|
): Promise<ReadSeekReadOutput> {
|
|
557
|
-
const args = ["read", filePath];
|
|
558
|
-
if (startLine !== undefined) args.push("--start", String(startLine));
|
|
567
|
+
const args = ["read", startLine === undefined ? filePath : `${filePath}:${startLine}`];
|
|
559
568
|
if (endLine !== undefined) args.push("--end", String(endLine));
|
|
560
569
|
return parseReadOutput(await runReadSeek(args, { signal: options.signal }));
|
|
561
570
|
}
|
|
@@ -572,7 +581,7 @@ function fileMapFromReadSeekOutput(output: ReadSeekMapOutput, filePath: string,
|
|
|
572
581
|
};
|
|
573
582
|
}
|
|
574
583
|
|
|
575
|
-
export async function
|
|
584
|
+
export async function readSeekMap(
|
|
576
585
|
filePath: string,
|
|
577
586
|
totalBytes: number,
|
|
578
587
|
options: { signal?: AbortSignal } = {},
|
|
@@ -581,7 +590,7 @@ export async function readseekMap(
|
|
|
581
590
|
return fileMapFromReadSeekOutput(output, filePath, totalBytes);
|
|
582
591
|
}
|
|
583
592
|
|
|
584
|
-
export async function
|
|
593
|
+
export async function readSeekSearch(
|
|
585
594
|
target: string,
|
|
586
595
|
pattern: string,
|
|
587
596
|
options: ReadSeekSearchOptions = {},
|
|
@@ -594,13 +603,13 @@ export async function readseekSearch(
|
|
|
594
603
|
return parseSearchOutput(await runReadSeek(args, { signal: options.signal })).results;
|
|
595
604
|
}
|
|
596
605
|
|
|
597
|
-
export async function
|
|
606
|
+
export async function readSeekMapContent(
|
|
598
607
|
filePath: string,
|
|
599
608
|
content: string,
|
|
600
609
|
options: { signal?: AbortSignal } = {},
|
|
601
610
|
): Promise<FileMap | null> {
|
|
602
611
|
const output = parseMapOutput(
|
|
603
|
-
await runReadSeek(["map",
|
|
612
|
+
await runReadSeek(["map", `stdin:${filePath}`], { signal: options.signal, stdin: content }),
|
|
604
613
|
);
|
|
605
614
|
return fileMapFromReadSeekOutput(output, filePath, Buffer.byteLength(content, "utf8"));
|
|
606
615
|
}
|
|
@@ -635,7 +644,7 @@ function parseRefsOutput(value: unknown): ReadSeekRefsOutput {
|
|
|
635
644
|
};
|
|
636
645
|
}
|
|
637
646
|
|
|
638
|
-
export async function
|
|
647
|
+
export async function readSeekRefs(
|
|
639
648
|
target: string,
|
|
640
649
|
name: string,
|
|
641
650
|
options: ReadSeekRefsOptions = {},
|
|
@@ -675,13 +684,13 @@ function parseCheckOutput(value: unknown): ReadSeekCheckOutput {
|
|
|
675
684
|
};
|
|
676
685
|
}
|
|
677
686
|
|
|
678
|
-
export async function
|
|
687
|
+
export async function readSeekCheck(
|
|
679
688
|
filePath: string,
|
|
680
689
|
content: string,
|
|
681
690
|
options: { signal?: AbortSignal } = {},
|
|
682
691
|
): Promise<ReadSeekCheckOutput> {
|
|
683
692
|
return parseCheckOutput(
|
|
684
|
-
await runReadSeek(["check",
|
|
693
|
+
await runReadSeek(["check", `stdin:${filePath}`], { signal: options.signal, stdin: content }),
|
|
685
694
|
);
|
|
686
695
|
}
|
|
687
696
|
|
|
@@ -746,16 +755,48 @@ function parseDetectOutput(value: unknown): ReadSeekDetection {
|
|
|
746
755
|
return { kind: "text", type, file, mime };
|
|
747
756
|
}
|
|
748
757
|
|
|
749
|
-
export async function
|
|
758
|
+
export async function readSeekDetect(
|
|
750
759
|
filePath: string,
|
|
751
|
-
options:
|
|
760
|
+
options: { signal?: AbortSignal } = {},
|
|
752
761
|
): Promise<ReadSeekDetection> {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
762
|
+
return parseDetectOutput(await runReadSeek(["detect", filePath], { signal: options.signal }));
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Analyze an image with each requested vision mode and merge the payloads into
|
|
767
|
+
* a single detection. readseek accepts one `--image` mode per invocation, so
|
|
768
|
+
* modes that fail are dropped as long as at least one produced a payload;
|
|
769
|
+
* otherwise the first failure is rethrown.
|
|
770
|
+
*/
|
|
771
|
+
export async function readSeekImage(
|
|
772
|
+
filePath: string,
|
|
773
|
+
modes: ReadSeekImageMode[],
|
|
774
|
+
options: { signal?: AbortSignal } = {},
|
|
775
|
+
): Promise<ReadSeekDetection> {
|
|
776
|
+
const results = await Promise.allSettled(
|
|
777
|
+
modes.map(async (mode) =>
|
|
778
|
+
parseDetectOutput(await runReadSeek(["read", "--image", mode, filePath], { signal: options.signal })),
|
|
779
|
+
),
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
let merged: ReadSeekImageDetection | undefined;
|
|
783
|
+
for (const result of results) {
|
|
784
|
+
if (result.status !== "fulfilled" || result.value.kind !== "image") continue;
|
|
785
|
+
const detection = result.value;
|
|
786
|
+
merged = merged === undefined
|
|
787
|
+
? detection
|
|
788
|
+
: {
|
|
789
|
+
...merged,
|
|
790
|
+
ocr: detection.ocr ?? merged.ocr,
|
|
791
|
+
caption: detection.caption ?? merged.caption,
|
|
792
|
+
objects: detection.objects ?? merged.objects,
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
if (merged !== undefined) return merged;
|
|
796
|
+
|
|
797
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
798
|
+
if (failure?.status === "rejected") throw failure.reason;
|
|
799
|
+
throw new Error(`readseek returned no image analysis for ${filePath}`);
|
|
759
800
|
}
|
|
760
801
|
|
|
761
802
|
// --- Rename ---
|
|
@@ -875,7 +916,7 @@ function parseRenameOutput(value: unknown): RenameOutput {
|
|
|
875
916
|
};
|
|
876
917
|
}
|
|
877
918
|
|
|
878
|
-
export async function
|
|
919
|
+
export async function readSeekRename(
|
|
879
920
|
filePath: string,
|
|
880
921
|
options: RenameOptions,
|
|
881
922
|
): Promise<RenameOutput> {
|
|
@@ -962,13 +1003,13 @@ function parseIdentifyOutput(value: unknown): IdentifyOutput {
|
|
|
962
1003
|
};
|
|
963
1004
|
}
|
|
964
1005
|
|
|
965
|
-
export async function
|
|
1006
|
+
export async function readSeekIdentify(
|
|
966
1007
|
filePath: string,
|
|
967
1008
|
content: string,
|
|
968
1009
|
options: IdentifyOptions = {},
|
|
969
1010
|
): Promise<IdentifyOutput> {
|
|
970
|
-
const
|
|
971
|
-
|
|
1011
|
+
const target = options.line === undefined ? `stdin:${filePath}` : `stdin:${filePath}:${options.line}`;
|
|
1012
|
+
const args = ["identify", target];
|
|
972
1013
|
if (options.column !== undefined) args.push("--column", String(options.column));
|
|
973
1014
|
if (options.language) args.push("--language", options.language);
|
|
974
1015
|
return parseIdentifyOutput(await runReadSeek(args, { signal: options.signal, stdin: content }));
|
|
@@ -988,9 +1029,7 @@ export interface DefLocation {
|
|
|
988
1029
|
}
|
|
989
1030
|
|
|
990
1031
|
interface DefOptions {
|
|
991
|
-
name
|
|
992
|
-
fromIdentify?: boolean;
|
|
993
|
-
identifyInput?: string;
|
|
1032
|
+
name: string;
|
|
994
1033
|
language?: string;
|
|
995
1034
|
cached?: boolean;
|
|
996
1035
|
others?: boolean;
|
|
@@ -1019,21 +1058,14 @@ function parseDefCompact(value: unknown): DefLocation[] {
|
|
|
1019
1058
|
});
|
|
1020
1059
|
}
|
|
1021
1060
|
|
|
1022
|
-
export async function
|
|
1061
|
+
export async function readSeekDef(
|
|
1023
1062
|
target: string,
|
|
1024
|
-
options: DefOptions
|
|
1063
|
+
options: DefOptions,
|
|
1025
1064
|
): Promise<DefLocation[]> {
|
|
1026
|
-
const args = ["def", target, "--format", "plain"];
|
|
1027
|
-
if (options.fromIdentify) {
|
|
1028
|
-
args.push("--from-identify");
|
|
1029
|
-
}
|
|
1030
|
-
if (options.name) {
|
|
1031
|
-
args.push(options.name);
|
|
1032
|
-
}
|
|
1065
|
+
const args = ["def", target, "--format", "plain", options.name];
|
|
1033
1066
|
if (options.language) args.push("--language", options.language);
|
|
1034
1067
|
if (options.cached) args.push("--cached");
|
|
1035
1068
|
if (options.others) args.push("--others");
|
|
1036
1069
|
if (options.ignored) args.push("--ignored");
|
|
1037
|
-
|
|
1038
|
-
return parseDefCompact(await runReadSeek(args, { signal: options.signal, stdin }));
|
|
1070
|
+
return parseDefCompact(await runReadSeek(args, { signal: options.signal }));
|
|
1039
1071
|
}
|
package/src/readseek-params.ts
CHANGED
|
@@ -16,7 +16,7 @@ export function langParam() {
|
|
|
16
16
|
* TypeBox fragment for the Git-scope parameters shared by every readseek
|
|
17
17
|
* search tool. Returns fresh schema instances so each tool spreads its own.
|
|
18
18
|
*/
|
|
19
|
-
export function
|
|
19
|
+
export function readSeekGitSearchParams() {
|
|
20
20
|
return {
|
|
21
21
|
cached: Type.Optional(Type.Boolean({ description: "In a Git repository, search tracked/indexed files" })),
|
|
22
22
|
others: Type.Optional(Type.Boolean({ description: "In a Git repository, search untracked files" })),
|