@agent-sh/harness-read 0.2.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/LICENSE +21 -0
- package/README.md +34 -0
- package/dist/index.cjs +590 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +141 -0
- package/dist/index.d.ts +141 -0
- package/dist/index.js +544 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
3
|
+
import path3 from 'path';
|
|
4
|
+
import { toolError, defaultNodeOperations, withFileLock, matchesAnyPattern, isInsideAnyRoot } from '@agent-sh/harness-core';
|
|
5
|
+
import * as v from 'valibot';
|
|
6
|
+
|
|
7
|
+
// src/read.ts
|
|
8
|
+
|
|
9
|
+
// src/constants.ts
|
|
10
|
+
var DEFAULT_LIMIT = 2e3;
|
|
11
|
+
var MAX_LINE_LENGTH = 2e3;
|
|
12
|
+
var MAX_LINE_SUFFIX = `... (line truncated to ${MAX_LINE_LENGTH} chars)`;
|
|
13
|
+
var MAX_BYTES = 50 * 1024;
|
|
14
|
+
var MAX_BYTES_LABEL = `${MAX_BYTES / 1024} KB`;
|
|
15
|
+
var MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
16
|
+
var BINARY_SAMPLE_BYTES = 4096;
|
|
17
|
+
var FUZZY_SUGGESTION_LIMIT = 3;
|
|
18
|
+
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
19
|
+
".zip",
|
|
20
|
+
".tar",
|
|
21
|
+
".gz",
|
|
22
|
+
".exe",
|
|
23
|
+
".dll",
|
|
24
|
+
".so",
|
|
25
|
+
".class",
|
|
26
|
+
".jar",
|
|
27
|
+
".war",
|
|
28
|
+
".7z",
|
|
29
|
+
".doc",
|
|
30
|
+
".docx",
|
|
31
|
+
".xls",
|
|
32
|
+
".xlsx",
|
|
33
|
+
".ppt",
|
|
34
|
+
".pptx",
|
|
35
|
+
".odt",
|
|
36
|
+
".ods",
|
|
37
|
+
".odp",
|
|
38
|
+
".bin",
|
|
39
|
+
".dat",
|
|
40
|
+
".obj",
|
|
41
|
+
".o",
|
|
42
|
+
".a",
|
|
43
|
+
".lib",
|
|
44
|
+
".wasm",
|
|
45
|
+
".pyc",
|
|
46
|
+
".pyo"
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
// src/binary.ts
|
|
50
|
+
function isBinaryByExtension(filepath) {
|
|
51
|
+
return BINARY_EXTENSIONS.has(path3.extname(filepath).toLowerCase());
|
|
52
|
+
}
|
|
53
|
+
function isBinaryByContent(sample) {
|
|
54
|
+
if (sample.length === 0) return false;
|
|
55
|
+
let nonPrintable = 0;
|
|
56
|
+
for (let i = 0; i < sample.length; i++) {
|
|
57
|
+
const b = sample[i];
|
|
58
|
+
if (b === 0) return true;
|
|
59
|
+
if (b < 9 || b > 13 && b < 32) nonPrintable++;
|
|
60
|
+
}
|
|
61
|
+
return nonPrintable / sample.length > 0.3;
|
|
62
|
+
}
|
|
63
|
+
function isBinary(filepath, sample) {
|
|
64
|
+
return isBinaryByExtension(filepath) || isBinaryByContent(sample);
|
|
65
|
+
}
|
|
66
|
+
function isImageMime(mime) {
|
|
67
|
+
if (!mime.startsWith("image/")) return false;
|
|
68
|
+
if (mime === "image/svg+xml") return false;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
function isPdfMime(mime) {
|
|
72
|
+
return mime === "application/pdf";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/format.ts
|
|
76
|
+
function formatText(params) {
|
|
77
|
+
const { path: path4, offset, lines, totalLines, more, byteCap } = params;
|
|
78
|
+
const header = `<path>${path4}</path>
|
|
79
|
+
<type>file</type>
|
|
80
|
+
<content>`;
|
|
81
|
+
if (lines.length === 0 && totalLines === 0) {
|
|
82
|
+
return `${header}
|
|
83
|
+
(File exists but is empty)
|
|
84
|
+
</content>`;
|
|
85
|
+
}
|
|
86
|
+
const body = lines.map((line, i) => `${offset + i}: ${line}`).join("\n");
|
|
87
|
+
const last = offset + lines.length - 1;
|
|
88
|
+
const next = last + 1;
|
|
89
|
+
let hint;
|
|
90
|
+
if (byteCap) {
|
|
91
|
+
const pct = totalLines > 0 ? Math.round(last / totalLines * 100) : 0;
|
|
92
|
+
const remaining = Math.max(totalLines - last, 0);
|
|
93
|
+
hint = `(Output capped at ${MAX_BYTES_LABEL}. Showing lines ${offset}-${last} of ${totalLines} \xB7 ${pct}% covered \xB7 ${remaining} lines remaining. Next offset: ${next}.)`;
|
|
94
|
+
} else if (more) {
|
|
95
|
+
const pct = Math.round(last / totalLines * 100);
|
|
96
|
+
const remaining = Math.max(totalLines - last, 0);
|
|
97
|
+
hint = `(Showing lines ${offset}-${last} of ${totalLines} \xB7 ${pct}% covered \xB7 ${remaining} lines remaining. Next offset: ${next}.)`;
|
|
98
|
+
} else {
|
|
99
|
+
hint = `(End of file \xB7 ${totalLines} lines total)`;
|
|
100
|
+
}
|
|
101
|
+
return `${header}
|
|
102
|
+
${body}
|
|
103
|
+
|
|
104
|
+
${hint}
|
|
105
|
+
</content>`;
|
|
106
|
+
}
|
|
107
|
+
function formatDirectory(params) {
|
|
108
|
+
const { path: path4, entries, offset, totalEntries, more } = params;
|
|
109
|
+
const header = `<path>${path4}</path>
|
|
110
|
+
<type>directory</type>
|
|
111
|
+
<entries>`;
|
|
112
|
+
const body = entries.join("\n");
|
|
113
|
+
const last = offset + entries.length - 1;
|
|
114
|
+
const next = last + 1;
|
|
115
|
+
const remaining = Math.max(totalEntries - last, 0);
|
|
116
|
+
const hint = more ? `(Showing ${entries.length} of ${totalEntries} entries \xB7 ${remaining} remaining. Next offset: ${next}.)` : `(${totalEntries} entries)`;
|
|
117
|
+
return `${header}
|
|
118
|
+
${body}
|
|
119
|
+
|
|
120
|
+
${hint}
|
|
121
|
+
</entries>`;
|
|
122
|
+
}
|
|
123
|
+
function formatAttachment(kind) {
|
|
124
|
+
return `${kind} read successfully`;
|
|
125
|
+
}
|
|
126
|
+
async function streamLines(ops, path4, opts) {
|
|
127
|
+
const maxBytes = opts.maxBytes ?? MAX_BYTES;
|
|
128
|
+
const maxLineLen = opts.maxLineLength ?? MAX_LINE_LENGTH;
|
|
129
|
+
const start = opts.offset - 1;
|
|
130
|
+
const out = [];
|
|
131
|
+
let bytes = 0;
|
|
132
|
+
let totalLines = 0;
|
|
133
|
+
let more = false;
|
|
134
|
+
let byteCap = false;
|
|
135
|
+
const signalOpt = {};
|
|
136
|
+
if (opts.signal !== void 0) signalOpt.signal = opts.signal;
|
|
137
|
+
const iter = ops.openLineStream(path4, signalOpt);
|
|
138
|
+
for await (const raw of iter) {
|
|
139
|
+
totalLines += 1;
|
|
140
|
+
if (totalLines <= start) continue;
|
|
141
|
+
if (out.length >= opts.limit) {
|
|
142
|
+
more = true;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const truncated = raw.length > maxLineLen ? raw.substring(0, maxLineLen) + MAX_LINE_SUFFIX : raw;
|
|
146
|
+
const size = Buffer.byteLength(truncated, "utf8") + (out.length > 0 ? 1 : 0);
|
|
147
|
+
if (bytes + size > maxBytes) {
|
|
148
|
+
byteCap = true;
|
|
149
|
+
more = true;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
out.push(truncated);
|
|
153
|
+
bytes += size;
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
lines: out,
|
|
157
|
+
totalLines,
|
|
158
|
+
offset: opts.offset,
|
|
159
|
+
more,
|
|
160
|
+
byteCap
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
var ReadParamsSchema = v.object({
|
|
164
|
+
path: v.pipe(v.string(), v.minLength(1, "path must not be empty")),
|
|
165
|
+
offset: v.optional(
|
|
166
|
+
v.pipe(v.number(), v.integer(), v.minValue(1, "offset must be >= 1"))
|
|
167
|
+
),
|
|
168
|
+
limit: v.optional(
|
|
169
|
+
v.pipe(v.number(), v.integer(), v.minValue(1, "limit must be >= 1"))
|
|
170
|
+
)
|
|
171
|
+
});
|
|
172
|
+
function parseReadParams(input) {
|
|
173
|
+
return v.parse(ReadParamsSchema, input);
|
|
174
|
+
}
|
|
175
|
+
function safeParseReadParams(input) {
|
|
176
|
+
const result = v.safeParse(ReadParamsSchema, input);
|
|
177
|
+
if (result.success) return { ok: true, value: result.output };
|
|
178
|
+
return { ok: false, issues: result.issues };
|
|
179
|
+
}
|
|
180
|
+
var READ_TOOL_NAME = "read";
|
|
181
|
+
var READ_TOOL_DESCRIPTION = `Read a file or directory from the local filesystem.
|
|
182
|
+
|
|
183
|
+
Usage:
|
|
184
|
+
- The path parameter should be an absolute path. If relative, it resolves against the session working directory.
|
|
185
|
+
- By default, returns up to 2000 lines from the start of the file.
|
|
186
|
+
- The offset parameter is the 1-indexed line number to start from.
|
|
187
|
+
- For later sections, call this tool again with a larger offset.
|
|
188
|
+
- Use the grep tool for content search in large files; glob to locate files by pattern.
|
|
189
|
+
- Contents are returned with each line prefixed by its line number as "<line>: <content>".
|
|
190
|
+
- Any line longer than 2000 characters is truncated.
|
|
191
|
+
- Call this tool in parallel when reading multiple files.
|
|
192
|
+
- Avoid tiny repeated slices (under 30 lines). Read a larger window instead.
|
|
193
|
+
- Images and PDFs are returned as file attachments.
|
|
194
|
+
- Binary files are refused; use specialized tools.`;
|
|
195
|
+
var readToolDefinition = {
|
|
196
|
+
name: READ_TOOL_NAME,
|
|
197
|
+
description: READ_TOOL_DESCRIPTION,
|
|
198
|
+
inputSchema: {
|
|
199
|
+
type: "object",
|
|
200
|
+
properties: {
|
|
201
|
+
path: {
|
|
202
|
+
type: "string",
|
|
203
|
+
description: "Absolute path (preferred) or path relative to the session cwd."
|
|
204
|
+
},
|
|
205
|
+
offset: {
|
|
206
|
+
type: "integer",
|
|
207
|
+
minimum: 1,
|
|
208
|
+
description: "1-indexed line number to start reading from."
|
|
209
|
+
},
|
|
210
|
+
limit: {
|
|
211
|
+
type: "integer",
|
|
212
|
+
minimum: 1,
|
|
213
|
+
description: "Maximum number of lines to read. Defaults to 2000."
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
required: ["path"],
|
|
217
|
+
additionalProperties: false
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
async function suggestSiblings(ops, missingPath) {
|
|
221
|
+
const dir = path3.dirname(missingPath);
|
|
222
|
+
const base = path3.basename(missingPath).toLowerCase();
|
|
223
|
+
let entries;
|
|
224
|
+
try {
|
|
225
|
+
entries = await ops.readDirectory(dir);
|
|
226
|
+
} catch {
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
const scored = [];
|
|
230
|
+
for (const entry of entries) {
|
|
231
|
+
const lower = entry.toLowerCase();
|
|
232
|
+
const score = similarity(base, lower);
|
|
233
|
+
if (score > 0) scored.push({ p: path3.join(dir, entry), score });
|
|
234
|
+
}
|
|
235
|
+
scored.sort((a, b) => b.score - a.score);
|
|
236
|
+
return scored.slice(0, FUZZY_SUGGESTION_LIMIT).map((s) => s.p);
|
|
237
|
+
}
|
|
238
|
+
function similarity(a, b) {
|
|
239
|
+
if (a === b) return 1e3;
|
|
240
|
+
if (a.length === 0 || b.length === 0) return 0;
|
|
241
|
+
if (a.includes(b) || b.includes(a)) return 500;
|
|
242
|
+
const prefix = commonPrefix(a, b);
|
|
243
|
+
if (prefix >= 3) return 200 + prefix;
|
|
244
|
+
if (prefix >= 2 && Math.abs(a.length - b.length) <= 2) return 100 + prefix;
|
|
245
|
+
const aExt = extOf(a);
|
|
246
|
+
const bExt = extOf(b);
|
|
247
|
+
if (aExt && aExt === bExt) return 10;
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
function commonPrefix(a, b) {
|
|
251
|
+
const n = Math.min(a.length, b.length);
|
|
252
|
+
let i = 0;
|
|
253
|
+
while (i < n && a[i] === b[i]) i++;
|
|
254
|
+
return i;
|
|
255
|
+
}
|
|
256
|
+
function extOf(name) {
|
|
257
|
+
const dot = name.lastIndexOf(".");
|
|
258
|
+
return dot > 0 ? name.slice(dot) : "";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/read.ts
|
|
262
|
+
function err(error) {
|
|
263
|
+
return { kind: "error", error };
|
|
264
|
+
}
|
|
265
|
+
async function read(input, session) {
|
|
266
|
+
const parsed = safeParseReadParams(input);
|
|
267
|
+
if (!parsed.ok) {
|
|
268
|
+
const messages = parsed.issues.map((i) => i.message).join("; ");
|
|
269
|
+
return err(toolError("INVALID_PARAM", messages, { cause: parsed.issues }));
|
|
270
|
+
}
|
|
271
|
+
const params = parsed.value;
|
|
272
|
+
const ops = session.ops ?? defaultNodeOperations();
|
|
273
|
+
const resolvedPath = await resolvePath(ops, session.cwd, params.path);
|
|
274
|
+
const fence = await fencePath(ops, session, resolvedPath);
|
|
275
|
+
if (fence !== void 0) return err(fence);
|
|
276
|
+
return withFileLock(
|
|
277
|
+
resolvedPath,
|
|
278
|
+
() => executeRead(ops, session, resolvedPath, params)
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
async function resolvePath(ops, cwd, input) {
|
|
282
|
+
const absolute = path3.isAbsolute(input) ? input : path3.resolve(cwd, input);
|
|
283
|
+
try {
|
|
284
|
+
return await ops.realpath(absolute);
|
|
285
|
+
} catch {
|
|
286
|
+
return absolute;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
async function fencePath(ops, session, resolvedPath) {
|
|
290
|
+
const { permissions } = session;
|
|
291
|
+
const isSensitive = matchesAnyPattern(
|
|
292
|
+
resolvedPath,
|
|
293
|
+
permissions.sensitivePatterns
|
|
294
|
+
);
|
|
295
|
+
const insideWorkspace = isInsideAnyRoot(resolvedPath, permissions.roots);
|
|
296
|
+
const needsAsk = isSensitive || !insideWorkspace && permissions.bypassWorkspaceGuard !== true;
|
|
297
|
+
if (isSensitive && permissions.hook === void 0) {
|
|
298
|
+
return toolError(
|
|
299
|
+
"SENSITIVE",
|
|
300
|
+
`Refusing to read sensitive path: ${resolvedPath}`,
|
|
301
|
+
{ meta: { path: resolvedPath } }
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
if (!insideWorkspace && permissions.bypassWorkspaceGuard !== true && permissions.hook === void 0) {
|
|
305
|
+
if (process.env.E2E_DEBUG_PERMISSIONS) {
|
|
306
|
+
console.error(
|
|
307
|
+
`[read fencePath] OUTSIDE_WORKSPACE reject: resolvedPath=${JSON.stringify(resolvedPath)} roots=${JSON.stringify(permissions.roots)}`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
return toolError(
|
|
311
|
+
"OUTSIDE_WORKSPACE",
|
|
312
|
+
`Path is outside all configured workspace roots: ${resolvedPath}`,
|
|
313
|
+
{ meta: { path: resolvedPath, roots: permissions.roots } }
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
if (permissions.hook !== void 0) {
|
|
317
|
+
const reason = isSensitive ? "sensitive" : !insideWorkspace ? "outside_workspace" : "in_workspace";
|
|
318
|
+
const alwaysPatterns = needsAsk ? [path3.dirname(resolvedPath) + "/*"] : ["*"];
|
|
319
|
+
const decision = await permissions.hook({
|
|
320
|
+
tool: "read",
|
|
321
|
+
path: resolvedPath,
|
|
322
|
+
action: "read",
|
|
323
|
+
always_patterns: alwaysPatterns,
|
|
324
|
+
metadata: { reason }
|
|
325
|
+
});
|
|
326
|
+
if (decision === "deny") {
|
|
327
|
+
return toolError(
|
|
328
|
+
"PERMISSION_DENIED",
|
|
329
|
+
`Read denied by user: ${resolvedPath}`,
|
|
330
|
+
{ meta: { path: resolvedPath } }
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return void 0;
|
|
335
|
+
}
|
|
336
|
+
async function executeRead(ops, session, resolvedPath, params) {
|
|
337
|
+
let stat;
|
|
338
|
+
try {
|
|
339
|
+
stat = await ops.stat(resolvedPath);
|
|
340
|
+
} catch (e) {
|
|
341
|
+
return err(
|
|
342
|
+
toolError("IO_ERROR", `stat failed: ${e.message}`, {
|
|
343
|
+
cause: e
|
|
344
|
+
})
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
if (!stat) {
|
|
348
|
+
const suggestions = await suggestSiblings(ops, resolvedPath);
|
|
349
|
+
const msg = suggestions.length > 0 ? `File not found: ${resolvedPath}
|
|
350
|
+
|
|
351
|
+
Did you mean one of these?
|
|
352
|
+
${suggestions.join("\n")}` : `File not found: ${resolvedPath}`;
|
|
353
|
+
return err(
|
|
354
|
+
toolError("NOT_FOUND", msg, { meta: { path: resolvedPath, suggestions } })
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
if (stat.type === "directory") {
|
|
358
|
+
return readDirectory(ops, resolvedPath, params);
|
|
359
|
+
}
|
|
360
|
+
const maxSize = session.maxFileSize ?? MAX_FILE_SIZE;
|
|
361
|
+
if (stat.size > maxSize) {
|
|
362
|
+
return err(
|
|
363
|
+
toolError(
|
|
364
|
+
"TOO_LARGE",
|
|
365
|
+
`File size ${stat.size} exceeds max ${maxSize}. Use a narrower offset/limit or grep first.`,
|
|
366
|
+
{ meta: { path: resolvedPath, size: stat.size, maxSize } }
|
|
367
|
+
)
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
const half = session.modelContextTokens ? Math.floor(session.modelContextTokens / 2) : void 0;
|
|
371
|
+
const tokensPerByte = session.tokensPerByte ?? 0.3;
|
|
372
|
+
if (half !== void 0 && stat.size * tokensPerByte > half) {
|
|
373
|
+
return err(
|
|
374
|
+
toolError(
|
|
375
|
+
"TOO_LARGE",
|
|
376
|
+
`File would consume more than half of the model context (~${Math.floor(stat.size * tokensPerByte)} tokens > ${half}). Use offset/limit or grep first.`,
|
|
377
|
+
{ meta: { path: resolvedPath, size: stat.size, half } }
|
|
378
|
+
)
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
const mime = ops.mimeType(resolvedPath);
|
|
382
|
+
if (isImageMime(mime) || isPdfMime(mime)) {
|
|
383
|
+
return readAttachment(ops, resolvedPath, mime, stat.size);
|
|
384
|
+
}
|
|
385
|
+
const sample = await readSample(ops, resolvedPath, stat.size);
|
|
386
|
+
if (isBinary(resolvedPath, sample)) {
|
|
387
|
+
return err(
|
|
388
|
+
toolError("BINARY", `Cannot read binary file: ${resolvedPath}`, {
|
|
389
|
+
meta: { path: resolvedPath }
|
|
390
|
+
})
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
return readText(ops, session, resolvedPath, stat, params);
|
|
394
|
+
}
|
|
395
|
+
async function readSample(ops, p, size) {
|
|
396
|
+
if (size === 0) return new Uint8Array();
|
|
397
|
+
const bytes = await ops.readFile(p);
|
|
398
|
+
return bytes.length > BINARY_SAMPLE_BYTES ? bytes.subarray(0, BINARY_SAMPLE_BYTES) : bytes;
|
|
399
|
+
}
|
|
400
|
+
async function readDirectory(ops, resolvedPath, params) {
|
|
401
|
+
const entries = await ops.readDirectoryEntries(resolvedPath);
|
|
402
|
+
const named = await Promise.all(
|
|
403
|
+
entries.map(async (e) => {
|
|
404
|
+
if (e.type === "directory") return e.name + "/";
|
|
405
|
+
if (e.type !== "symlink") return e.name;
|
|
406
|
+
const target = await ops.stat(path3.join(resolvedPath, e.name)).catch(() => void 0);
|
|
407
|
+
return target?.type === "directory" ? e.name + "/" : e.name;
|
|
408
|
+
})
|
|
409
|
+
);
|
|
410
|
+
named.sort((a, b) => a.localeCompare(b, void 0, { sensitivity: "base" }));
|
|
411
|
+
const offset = params.offset ?? 1;
|
|
412
|
+
const limit = params.limit ?? DEFAULT_LIMIT;
|
|
413
|
+
const start = offset - 1;
|
|
414
|
+
const sliced = named.slice(start, start + limit);
|
|
415
|
+
const more = start + sliced.length < named.length;
|
|
416
|
+
const output = formatDirectory({
|
|
417
|
+
path: resolvedPath,
|
|
418
|
+
entries: sliced,
|
|
419
|
+
offset,
|
|
420
|
+
totalEntries: named.length,
|
|
421
|
+
more
|
|
422
|
+
});
|
|
423
|
+
return {
|
|
424
|
+
kind: "directory",
|
|
425
|
+
output,
|
|
426
|
+
meta: {
|
|
427
|
+
path: resolvedPath,
|
|
428
|
+
totalEntries: named.length,
|
|
429
|
+
returnedEntries: sliced.length,
|
|
430
|
+
offset,
|
|
431
|
+
limit,
|
|
432
|
+
more
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
async function readAttachment(ops, resolvedPath, mime, size) {
|
|
437
|
+
const bytes = await ops.readFile(resolvedPath);
|
|
438
|
+
const kind = mime === "application/pdf" ? "PDF" : "Image";
|
|
439
|
+
const dataUrl = `data:${mime};base64,${Buffer.from(bytes).toString("base64")}`;
|
|
440
|
+
return {
|
|
441
|
+
kind: "attachment",
|
|
442
|
+
output: formatAttachment(kind),
|
|
443
|
+
attachments: [{ mime, dataUrl }],
|
|
444
|
+
meta: { path: resolvedPath, mime, size_bytes: size }
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
async function readText(ops, session, resolvedPath, stat, params) {
|
|
448
|
+
const offset = params.offset ?? 1;
|
|
449
|
+
const limit = params.limit ?? session.defaultLimit ?? DEFAULT_LIMIT;
|
|
450
|
+
if (session.cache) {
|
|
451
|
+
const cached = session.cache.get({
|
|
452
|
+
path: resolvedPath,
|
|
453
|
+
mtime_ms: stat.mtime_ms,
|
|
454
|
+
size_bytes: stat.size,
|
|
455
|
+
offset,
|
|
456
|
+
limit
|
|
457
|
+
});
|
|
458
|
+
if (cached) {
|
|
459
|
+
if (session.ledger) {
|
|
460
|
+
session.ledger.record({
|
|
461
|
+
path: resolvedPath,
|
|
462
|
+
sha256: cached.meta.sha256,
|
|
463
|
+
mtime_ms: stat.mtime_ms,
|
|
464
|
+
size_bytes: stat.size,
|
|
465
|
+
lines_returned: cached.meta.returnedLines,
|
|
466
|
+
offset,
|
|
467
|
+
limit,
|
|
468
|
+
timestamp_ms: Date.now()
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
return cached;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
const lineStreamOpts = { offset, limit };
|
|
475
|
+
if (session.maxBytes !== void 0) lineStreamOpts.maxBytes = session.maxBytes;
|
|
476
|
+
if (session.maxLineLength !== void 0)
|
|
477
|
+
lineStreamOpts.maxLineLength = session.maxLineLength;
|
|
478
|
+
if (session.signal !== void 0) lineStreamOpts.signal = session.signal;
|
|
479
|
+
const result = await streamLines(ops, resolvedPath, lineStreamOpts);
|
|
480
|
+
if (result.totalLines > 0 && offset > result.totalLines) {
|
|
481
|
+
return err(
|
|
482
|
+
toolError(
|
|
483
|
+
"INVALID_PARAM",
|
|
484
|
+
`Offset ${offset} is out of range for this file (${result.totalLines} lines)`,
|
|
485
|
+
{ meta: { path: resolvedPath, totalLines: result.totalLines } }
|
|
486
|
+
)
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
const bytes = await ops.readFile(resolvedPath);
|
|
490
|
+
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
491
|
+
const output = formatText({
|
|
492
|
+
path: resolvedPath,
|
|
493
|
+
offset,
|
|
494
|
+
lines: result.lines,
|
|
495
|
+
totalLines: result.totalLines,
|
|
496
|
+
more: result.more,
|
|
497
|
+
byteCap: result.byteCap
|
|
498
|
+
});
|
|
499
|
+
const textResult = {
|
|
500
|
+
kind: "text",
|
|
501
|
+
output,
|
|
502
|
+
meta: {
|
|
503
|
+
path: resolvedPath,
|
|
504
|
+
totalLines: result.totalLines,
|
|
505
|
+
returnedLines: result.lines.length,
|
|
506
|
+
offset,
|
|
507
|
+
limit,
|
|
508
|
+
byteCap: result.byteCap,
|
|
509
|
+
more: result.more,
|
|
510
|
+
sha256,
|
|
511
|
+
mtime_ms: stat.mtime_ms,
|
|
512
|
+
size_bytes: stat.size
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
if (session.cache) {
|
|
516
|
+
session.cache.set(
|
|
517
|
+
{
|
|
518
|
+
path: resolvedPath,
|
|
519
|
+
mtime_ms: stat.mtime_ms,
|
|
520
|
+
size_bytes: stat.size,
|
|
521
|
+
offset,
|
|
522
|
+
limit
|
|
523
|
+
},
|
|
524
|
+
textResult
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
if (session.ledger) {
|
|
528
|
+
session.ledger.record({
|
|
529
|
+
path: resolvedPath,
|
|
530
|
+
sha256,
|
|
531
|
+
mtime_ms: stat.mtime_ms,
|
|
532
|
+
size_bytes: stat.size,
|
|
533
|
+
lines_returned: result.lines.length,
|
|
534
|
+
offset,
|
|
535
|
+
limit,
|
|
536
|
+
timestamp_ms: Date.now()
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
return textResult;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export { BINARY_EXTENSIONS, DEFAULT_LIMIT, MAX_BYTES, MAX_FILE_SIZE, MAX_LINE_LENGTH, READ_TOOL_DESCRIPTION, READ_TOOL_NAME, ReadParamsSchema, formatAttachment, formatDirectory, formatText, isBinary, isBinaryByContent, isBinaryByExtension, isImageMime, isPdfMime, parseReadParams, read, readToolDefinition, safeParseReadParams, streamLines, suggestSiblings };
|
|
543
|
+
//# sourceMappingURL=index.js.map
|
|
544
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/binary.ts","../src/format.ts","../src/lines.ts","../src/schema.ts","../src/suggest.ts","../src/read.ts"],"names":["path","Buffer"],"mappings":"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","file":"index.js","sourcesContent":["export const DEFAULT_LIMIT = 2000;\nexport const MAX_LINE_LENGTH = 2000;\nexport const MAX_LINE_SUFFIX = `... (line truncated to ${MAX_LINE_LENGTH} chars)`;\nexport const MAX_BYTES = 50 * 1024;\nexport const MAX_BYTES_LABEL = `${MAX_BYTES / 1024} KB`;\nexport const MAX_FILE_SIZE = 5 * 1024 * 1024;\nexport const BINARY_SAMPLE_BYTES = 4096;\nexport const FUZZY_SUGGESTION_LIMIT = 3;\n\nexport const BINARY_EXTENSIONS: ReadonlySet<string> = new Set([\n \".zip\",\n \".tar\",\n \".gz\",\n \".exe\",\n \".dll\",\n \".so\",\n \".class\",\n \".jar\",\n \".war\",\n \".7z\",\n \".doc\",\n \".docx\",\n \".xls\",\n \".xlsx\",\n \".ppt\",\n \".pptx\",\n \".odt\",\n \".ods\",\n \".odp\",\n \".bin\",\n \".dat\",\n \".obj\",\n \".o\",\n \".a\",\n \".lib\",\n \".wasm\",\n \".pyc\",\n \".pyo\",\n]);\n","import path from \"node:path\";\nimport { BINARY_EXTENSIONS } from \"./constants.js\";\n\nexport function isBinaryByExtension(filepath: string): boolean {\n return BINARY_EXTENSIONS.has(path.extname(filepath).toLowerCase());\n}\n\nexport function isBinaryByContent(sample: Uint8Array): boolean {\n if (sample.length === 0) return false;\n let nonPrintable = 0;\n for (let i = 0; i < sample.length; i++) {\n const b = sample[i] as number;\n if (b === 0) return true;\n if (b < 9 || (b > 13 && b < 32)) nonPrintable++;\n }\n return nonPrintable / sample.length > 0.3;\n}\n\nexport function isBinary(filepath: string, sample: Uint8Array): boolean {\n return isBinaryByExtension(filepath) || isBinaryByContent(sample);\n}\n\nexport function isImageMime(mime: string): boolean {\n if (!mime.startsWith(\"image/\")) return false;\n if (mime === \"image/svg+xml\") return false;\n return true;\n}\n\nexport function isPdfMime(mime: string): boolean {\n return mime === \"application/pdf\";\n}\n","import { MAX_BYTES_LABEL } from \"./constants.js\";\nimport type {\n AttachmentReadResult,\n DirReadResult,\n TextReadResult,\n} from \"./types.js\";\n\nexport function formatText(\n params: {\n path: string;\n offset: number;\n lines: readonly string[];\n totalLines: number;\n more: boolean;\n byteCap: boolean;\n },\n): string {\n const { path, offset, lines, totalLines, more, byteCap } = params;\n const header = `<path>${path}</path>\\n<type>file</type>\\n<content>`;\n\n if (lines.length === 0 && totalLines === 0) {\n return `${header}\\n(File exists but is empty)\\n</content>`;\n }\n\n const body = lines.map((line, i) => `${offset + i}: ${line}`).join(\"\\n\");\n const last = offset + lines.length - 1;\n const next = last + 1;\n\n let hint: string;\n if (byteCap) {\n const pct = totalLines > 0 ? Math.round((last / totalLines) * 100) : 0;\n const remaining = Math.max(totalLines - last, 0);\n hint = `(Output capped at ${MAX_BYTES_LABEL}. Showing lines ${offset}-${last} of ${totalLines} · ${pct}% covered · ${remaining} lines remaining. Next offset: ${next}.)`;\n } else if (more) {\n const pct = Math.round((last / totalLines) * 100);\n const remaining = Math.max(totalLines - last, 0);\n hint = `(Showing lines ${offset}-${last} of ${totalLines} · ${pct}% covered · ${remaining} lines remaining. Next offset: ${next}.)`;\n } else {\n hint = `(End of file · ${totalLines} lines total)`;\n }\n\n return `${header}\\n${body}\\n\\n${hint}\\n</content>`;\n}\n\nexport function formatDirectory(params: {\n path: string;\n entries: readonly string[];\n offset: number;\n totalEntries: number;\n more: boolean;\n}): string {\n const { path, entries, offset, totalEntries, more } = params;\n const header = `<path>${path}</path>\\n<type>directory</type>\\n<entries>`;\n const body = entries.join(\"\\n\");\n const last = offset + entries.length - 1;\n const next = last + 1;\n const remaining = Math.max(totalEntries - last, 0);\n const hint = more\n ? `(Showing ${entries.length} of ${totalEntries} entries · ${remaining} remaining. Next offset: ${next}.)`\n : `(${totalEntries} entries)`;\n return `${header}\\n${body}\\n\\n${hint}\\n</entries>`;\n}\n\nexport function formatAttachment(kind: \"Image\" | \"PDF\"): string {\n return `${kind} read successfully`;\n}\n\nexport function asTextResult(x: TextReadResult): TextReadResult {\n return x;\n}\nexport function asDirResult(x: DirReadResult): DirReadResult {\n return x;\n}\nexport function asAttachResult(x: AttachmentReadResult): AttachmentReadResult {\n return x;\n}\n","import { Buffer } from \"node:buffer\";\nimport type { ReadOperations } from \"@agent-sh/harness-core\";\nimport {\n MAX_BYTES,\n MAX_LINE_LENGTH,\n MAX_LINE_SUFFIX,\n} from \"./constants.js\";\n\nexport interface StreamLinesOptions {\n readonly offset: number;\n readonly limit: number;\n readonly maxBytes?: number;\n readonly maxLineLength?: number;\n readonly signal?: AbortSignal;\n}\n\nexport interface StreamLinesResult {\n readonly lines: readonly string[];\n readonly totalLines: number;\n readonly offset: number;\n readonly more: boolean;\n readonly byteCap: boolean;\n}\n\nexport async function streamLines(\n ops: ReadOperations,\n path: string,\n opts: StreamLinesOptions,\n): Promise<StreamLinesResult> {\n const maxBytes = opts.maxBytes ?? MAX_BYTES;\n const maxLineLen = opts.maxLineLength ?? MAX_LINE_LENGTH;\n const start = opts.offset - 1;\n\n const out: string[] = [];\n let bytes = 0;\n let totalLines = 0;\n let more = false;\n let byteCap = false;\n\n const signalOpt: { signal?: AbortSignal } = {};\n if (opts.signal !== undefined) signalOpt.signal = opts.signal;\n\n const iter = ops.openLineStream(path, signalOpt);\n\n for await (const raw of iter) {\n totalLines += 1;\n if (totalLines <= start) continue;\n\n if (out.length >= opts.limit) {\n more = true;\n continue;\n }\n\n const truncated =\n raw.length > maxLineLen ? raw.substring(0, maxLineLen) + MAX_LINE_SUFFIX : raw;\n\n const size = Buffer.byteLength(truncated, \"utf8\") + (out.length > 0 ? 1 : 0);\n if (bytes + size > maxBytes) {\n byteCap = true;\n more = true;\n break;\n }\n\n out.push(truncated);\n bytes += size;\n }\n\n return {\n lines: out,\n totalLines,\n offset: opts.offset,\n more,\n byteCap,\n };\n}\n","import * as v from \"valibot\";\nimport type { ToolDefinition } from \"@agent-sh/harness-core\";\nimport type { ReadParams } from \"./types.js\";\n\nexport const ReadParamsSchema = v.object({\n path: v.pipe(v.string(), v.minLength(1, \"path must not be empty\")),\n offset: v.optional(\n v.pipe(v.number(), v.integer(), v.minValue(1, \"offset must be >= 1\")),\n ),\n limit: v.optional(\n v.pipe(v.number(), v.integer(), v.minValue(1, \"limit must be >= 1\")),\n ),\n});\n\nexport type ParsedReadParams = v.InferOutput<typeof ReadParamsSchema>;\n\nexport function parseReadParams(input: unknown): ParsedReadParams {\n return v.parse(ReadParamsSchema, input);\n}\n\nexport function safeParseReadParams(input: unknown):\n | { ok: true; value: ReadParams }\n | { ok: false; issues: v.BaseIssue<unknown>[] } {\n const result = v.safeParse(ReadParamsSchema, input);\n if (result.success) return { ok: true, value: result.output };\n return { ok: false, issues: result.issues };\n}\n\nexport const READ_TOOL_NAME = \"read\";\n\nexport const READ_TOOL_DESCRIPTION = `Read a file or directory from the local filesystem.\n\nUsage:\n- The path parameter should be an absolute path. If relative, it resolves against the session working directory.\n- By default, returns up to 2000 lines from the start of the file.\n- The offset parameter is the 1-indexed line number to start from.\n- For later sections, call this tool again with a larger offset.\n- Use the grep tool for content search in large files; glob to locate files by pattern.\n- Contents are returned with each line prefixed by its line number as \"<line>: <content>\".\n- Any line longer than 2000 characters is truncated.\n- Call this tool in parallel when reading multiple files.\n- Avoid tiny repeated slices (under 30 lines). Read a larger window instead.\n- Images and PDFs are returned as file attachments.\n- Binary files are refused; use specialized tools.`;\n\nexport const readToolDefinition: ToolDefinition = {\n name: READ_TOOL_NAME,\n description: READ_TOOL_DESCRIPTION,\n inputSchema: {\n type: \"object\",\n properties: {\n path: {\n type: \"string\",\n description:\n \"Absolute path (preferred) or path relative to the session cwd.\",\n },\n offset: {\n type: \"integer\",\n minimum: 1,\n description: \"1-indexed line number to start reading from.\",\n },\n limit: {\n type: \"integer\",\n minimum: 1,\n description: \"Maximum number of lines to read. Defaults to 2000.\",\n },\n },\n required: [\"path\"],\n additionalProperties: false,\n },\n};\n","import path from \"node:path\";\nimport type { ReadOperations } from \"@agent-sh/harness-core\";\nimport { FUZZY_SUGGESTION_LIMIT } from \"./constants.js\";\n\nexport async function suggestSiblings(\n ops: ReadOperations,\n missingPath: string,\n): Promise<readonly string[]> {\n const dir = path.dirname(missingPath);\n const base = path.basename(missingPath).toLowerCase();\n let entries: readonly string[];\n try {\n entries = await ops.readDirectory(dir);\n } catch {\n return [];\n }\n const scored: { p: string; score: number }[] = [];\n for (const entry of entries) {\n const lower = entry.toLowerCase();\n const score = similarity(base, lower);\n if (score > 0) scored.push({ p: path.join(dir, entry), score });\n }\n scored.sort((a, b) => b.score - a.score);\n return scored.slice(0, FUZZY_SUGGESTION_LIMIT).map((s) => s.p);\n}\n\nfunction similarity(a: string, b: string): number {\n if (a === b) return 1000;\n if (a.length === 0 || b.length === 0) return 0;\n if (a.includes(b) || b.includes(a)) return 500;\n const prefix = commonPrefix(a, b);\n if (prefix >= 3) return 200 + prefix;\n if (prefix >= 2 && Math.abs(a.length - b.length) <= 2) return 100 + prefix;\n const aExt = extOf(a);\n const bExt = extOf(b);\n if (aExt && aExt === bExt) return 10;\n return 0;\n}\n\nfunction commonPrefix(a: string, b: string): number {\n const n = Math.min(a.length, b.length);\n let i = 0;\n while (i < n && a[i] === b[i]) i++;\n return i;\n}\n\nfunction extOf(name: string): string {\n const dot = name.lastIndexOf(\".\");\n return dot > 0 ? name.slice(dot) : \"\";\n}\n","import { Buffer } from \"node:buffer\";\nimport { createHash } from \"node:crypto\";\nimport path from \"node:path\";\nimport {\n defaultNodeOperations,\n isInsideAnyRoot,\n matchesAnyPattern,\n toolError,\n withFileLock,\n type ReadOperations,\n type ToolError,\n} from \"@agent-sh/harness-core\";\nimport { isBinary, isImageMime, isPdfMime } from \"./binary.js\";\nimport {\n BINARY_SAMPLE_BYTES,\n DEFAULT_LIMIT,\n MAX_FILE_SIZE,\n} from \"./constants.js\";\nimport {\n formatAttachment,\n formatDirectory,\n formatText,\n} from \"./format.js\";\nimport { streamLines } from \"./lines.js\";\nimport { safeParseReadParams } from \"./schema.js\";\nimport { suggestSiblings } from \"./suggest.js\";\nimport type {\n AttachmentReadResult,\n DirReadResult,\n ErrorReadResult,\n ReadParams,\n ReadResult,\n ReadSessionConfig,\n TextReadResult,\n} from \"./types.js\";\n\nfunction err(error: ToolError): ErrorReadResult {\n return { kind: \"error\", error };\n}\n\nexport async function read(\n input: unknown,\n session: ReadSessionConfig,\n): Promise<ReadResult> {\n const parsed = safeParseReadParams(input);\n if (!parsed.ok) {\n const messages = parsed.issues.map((i) => i.message).join(\"; \");\n return err(toolError(\"INVALID_PARAM\", messages, { cause: parsed.issues }));\n }\n const params = parsed.value;\n\n const ops = session.ops ?? defaultNodeOperations();\n\n const resolvedPath = await resolvePath(ops, session.cwd, params.path);\n const fence = await fencePath(ops, session, resolvedPath);\n if (fence !== undefined) return err(fence);\n\n return withFileLock(resolvedPath, () =>\n executeRead(ops, session, resolvedPath, params),\n );\n}\n\nasync function resolvePath(\n ops: ReadOperations,\n cwd: string,\n input: string,\n): Promise<string> {\n const absolute = path.isAbsolute(input) ? input : path.resolve(cwd, input);\n try {\n return await ops.realpath(absolute);\n } catch {\n return absolute;\n }\n}\n\nasync function fencePath(\n ops: ReadOperations,\n session: ReadSessionConfig,\n resolvedPath: string,\n): Promise<ToolError | undefined> {\n const { permissions } = session;\n\n const isSensitive = matchesAnyPattern(\n resolvedPath,\n permissions.sensitivePatterns,\n );\n const insideWorkspace = isInsideAnyRoot(resolvedPath, permissions.roots);\n const needsAsk =\n isSensitive ||\n (!insideWorkspace && permissions.bypassWorkspaceGuard !== true);\n\n if (isSensitive && permissions.hook === undefined) {\n return toolError(\n \"SENSITIVE\",\n `Refusing to read sensitive path: ${resolvedPath}`,\n { meta: { path: resolvedPath } },\n );\n }\n\n if (\n !insideWorkspace &&\n permissions.bypassWorkspaceGuard !== true &&\n permissions.hook === undefined\n ) {\n if (process.env.E2E_DEBUG_PERMISSIONS) {\n // eslint-disable-next-line no-console\n console.error(\n `[read fencePath] OUTSIDE_WORKSPACE reject: resolvedPath=${JSON.stringify(resolvedPath)} roots=${JSON.stringify(permissions.roots)}`,\n );\n }\n return toolError(\n \"OUTSIDE_WORKSPACE\",\n `Path is outside all configured workspace roots: ${resolvedPath}`,\n { meta: { path: resolvedPath, roots: permissions.roots } },\n );\n }\n\n if (permissions.hook !== undefined) {\n const reason = isSensitive\n ? \"sensitive\"\n : !insideWorkspace\n ? \"outside_workspace\"\n : \"in_workspace\";\n const alwaysPatterns = needsAsk\n ? [path.dirname(resolvedPath) + \"/*\"]\n : [\"*\"];\n const decision = await permissions.hook({\n tool: \"read\",\n path: resolvedPath,\n action: \"read\",\n always_patterns: alwaysPatterns,\n metadata: { reason },\n });\n if (decision === \"deny\") {\n return toolError(\n \"PERMISSION_DENIED\",\n `Read denied by user: ${resolvedPath}`,\n { meta: { path: resolvedPath } },\n );\n }\n }\n\n return undefined;\n}\n\nasync function executeRead(\n ops: ReadOperations,\n session: ReadSessionConfig,\n resolvedPath: string,\n params: ReadParams,\n): Promise<ReadResult> {\n let stat;\n try {\n stat = await ops.stat(resolvedPath);\n } catch (e) {\n return err(\n toolError(\"IO_ERROR\", `stat failed: ${(e as Error).message}`, {\n cause: e,\n }),\n );\n }\n\n if (!stat) {\n const suggestions = await suggestSiblings(ops, resolvedPath);\n const msg =\n suggestions.length > 0\n ? `File not found: ${resolvedPath}\\n\\nDid you mean one of these?\\n${suggestions.join(\"\\n\")}`\n : `File not found: ${resolvedPath}`;\n return err(\n toolError(\"NOT_FOUND\", msg, { meta: { path: resolvedPath, suggestions } }),\n );\n }\n\n if (stat.type === \"directory\") {\n return readDirectory(ops, resolvedPath, params);\n }\n\n const maxSize = session.maxFileSize ?? MAX_FILE_SIZE;\n if (stat.size > maxSize) {\n return err(\n toolError(\n \"TOO_LARGE\",\n `File size ${stat.size} exceeds max ${maxSize}. Use a narrower offset/limit or grep first.`,\n { meta: { path: resolvedPath, size: stat.size, maxSize } },\n ),\n );\n }\n\n const half = session.modelContextTokens\n ? Math.floor(session.modelContextTokens / 2)\n : undefined;\n const tokensPerByte = session.tokensPerByte ?? 0.3;\n if (half !== undefined && stat.size * tokensPerByte > half) {\n return err(\n toolError(\n \"TOO_LARGE\",\n `File would consume more than half of the model context (~${Math.floor(stat.size * tokensPerByte)} tokens > ${half}). Use offset/limit or grep first.`,\n { meta: { path: resolvedPath, size: stat.size, half } },\n ),\n );\n }\n\n const mime = ops.mimeType(resolvedPath);\n if (isImageMime(mime) || isPdfMime(mime)) {\n return readAttachment(ops, resolvedPath, mime, stat.size);\n }\n\n const sample = await readSample(ops, resolvedPath, stat.size);\n if (isBinary(resolvedPath, sample)) {\n return err(\n toolError(\"BINARY\", `Cannot read binary file: ${resolvedPath}`, {\n meta: { path: resolvedPath },\n }),\n );\n }\n\n return readText(ops, session, resolvedPath, stat, params);\n}\n\nasync function readSample(\n ops: ReadOperations,\n p: string,\n size: number,\n): Promise<Uint8Array> {\n if (size === 0) return new Uint8Array();\n const bytes = await ops.readFile(p);\n return bytes.length > BINARY_SAMPLE_BYTES\n ? bytes.subarray(0, BINARY_SAMPLE_BYTES)\n : bytes;\n}\n\nasync function readDirectory(\n ops: ReadOperations,\n resolvedPath: string,\n params: ReadParams,\n): Promise<DirReadResult> {\n const entries = await ops.readDirectoryEntries(resolvedPath);\n const named = await Promise.all(\n entries.map(async (e) => {\n if (e.type === \"directory\") return e.name + \"/\";\n if (e.type !== \"symlink\") return e.name;\n const target = await ops\n .stat(path.join(resolvedPath, e.name))\n .catch(() => undefined);\n return target?.type === \"directory\" ? e.name + \"/\" : e.name;\n }),\n );\n named.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: \"base\" }));\n\n const offset = params.offset ?? 1;\n const limit = params.limit ?? DEFAULT_LIMIT;\n const start = offset - 1;\n const sliced = named.slice(start, start + limit);\n const more = start + sliced.length < named.length;\n\n const output = formatDirectory({\n path: resolvedPath,\n entries: sliced,\n offset,\n totalEntries: named.length,\n more,\n });\n\n return {\n kind: \"directory\",\n output,\n meta: {\n path: resolvedPath,\n totalEntries: named.length,\n returnedEntries: sliced.length,\n offset,\n limit,\n more,\n },\n };\n}\n\nasync function readAttachment(\n ops: ReadOperations,\n resolvedPath: string,\n mime: string,\n size: number,\n): Promise<AttachmentReadResult> {\n const bytes = await ops.readFile(resolvedPath);\n const kind: \"Image\" | \"PDF\" = mime === \"application/pdf\" ? \"PDF\" : \"Image\";\n const dataUrl = `data:${mime};base64,${Buffer.from(bytes).toString(\"base64\")}`;\n return {\n kind: \"attachment\",\n output: formatAttachment(kind),\n attachments: [{ mime, dataUrl }],\n meta: { path: resolvedPath, mime, size_bytes: size },\n };\n}\n\nasync function readText(\n ops: ReadOperations,\n session: ReadSessionConfig,\n resolvedPath: string,\n stat: { size: number; mtime_ms: number },\n params: ReadParams,\n): Promise<ReadResult> {\n const offset = params.offset ?? 1;\n const limit = params.limit ?? session.defaultLimit ?? DEFAULT_LIMIT;\n\n if (session.cache) {\n const cached = session.cache.get({\n path: resolvedPath,\n mtime_ms: stat.mtime_ms,\n size_bytes: stat.size,\n offset,\n limit,\n });\n if (cached) {\n if (session.ledger) {\n session.ledger.record({\n path: resolvedPath,\n sha256: cached.meta.sha256,\n mtime_ms: stat.mtime_ms,\n size_bytes: stat.size,\n lines_returned: cached.meta.returnedLines,\n offset,\n limit,\n timestamp_ms: Date.now(),\n });\n }\n return cached;\n }\n }\n\n const lineStreamOpts: {\n offset: number;\n limit: number;\n maxBytes?: number;\n maxLineLength?: number;\n signal?: AbortSignal;\n } = { offset, limit };\n if (session.maxBytes !== undefined) lineStreamOpts.maxBytes = session.maxBytes;\n if (session.maxLineLength !== undefined)\n lineStreamOpts.maxLineLength = session.maxLineLength;\n if (session.signal !== undefined) lineStreamOpts.signal = session.signal;\n\n const result = await streamLines(ops, resolvedPath, lineStreamOpts);\n\n if (result.totalLines > 0 && offset > result.totalLines) {\n return err(\n toolError(\n \"INVALID_PARAM\",\n `Offset ${offset} is out of range for this file (${result.totalLines} lines)`,\n { meta: { path: resolvedPath, totalLines: result.totalLines } },\n ),\n );\n }\n\n const bytes = await ops.readFile(resolvedPath);\n const sha256 = createHash(\"sha256\").update(bytes).digest(\"hex\");\n\n const output = formatText({\n path: resolvedPath,\n offset,\n lines: result.lines,\n totalLines: result.totalLines,\n more: result.more,\n byteCap: result.byteCap,\n });\n\n const textResult: TextReadResult = {\n kind: \"text\",\n output,\n meta: {\n path: resolvedPath,\n totalLines: result.totalLines,\n returnedLines: result.lines.length,\n offset,\n limit,\n byteCap: result.byteCap,\n more: result.more,\n sha256,\n mtime_ms: stat.mtime_ms,\n size_bytes: stat.size,\n },\n };\n\n if (session.cache) {\n session.cache.set(\n {\n path: resolvedPath,\n mtime_ms: stat.mtime_ms,\n size_bytes: stat.size,\n offset,\n limit,\n },\n textResult,\n );\n }\n\n if (session.ledger) {\n session.ledger.record({\n path: resolvedPath,\n sha256,\n mtime_ms: stat.mtime_ms,\n size_bytes: stat.size,\n lines_returned: result.lines.length,\n offset,\n limit,\n timestamp_ms: Date.now(),\n });\n }\n\n return textResult;\n}\n"]}
|