@better-fs-tools/read 0.1.0 → 0.3.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 +119 -84
- package/dist/classifiers/index.d.ts +4 -3
- package/dist/classifiers/index.js +4 -5
- package/dist/contract/base.d.ts +31 -3
- package/dist/contract/deps.d.ts +9 -14
- package/dist/contract/limits.d.ts +1 -1
- package/dist/contract/messages.d.ts +0 -10
- package/dist/contract/result.d.ts +10 -0
- package/dist/contract/state.d.ts +10 -0
- package/dist/core/convert.js +3 -3
- package/dist/core/create-read-tool.d.ts +3 -3
- package/dist/core/create-read-tool.js +3 -3
- package/dist/core/deps.d.ts +2 -2
- package/dist/core/deps.js +17 -6
- package/dist/core/format.js +1 -15
- package/dist/core/limits.js +1 -1
- package/dist/core/messages.js +5 -8
- package/dist/core/notes.d.ts +0 -2
- package/dist/core/notes.js +0 -20
- package/dist/core/observation.d.ts +2 -1
- package/dist/core/observation.js +2 -0
- package/dist/core/record.js +59 -6
- package/dist/core/seen-lines.d.ts +7 -0
- package/dist/core/seen-lines.js +39 -0
- package/dist/core/text-outcome.js +8 -3
- package/dist/index.d.ts +3 -3
- package/dist/signature/definition.d.ts +30 -0
- package/dist/signature/definition.js +48 -0
- package/dist/signature/index.d.ts +2 -0
- package/dist/signature/index.js +1 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/suggest/suggest-file-names.d.ts +5 -1
- package/dist/suggest/suggest-file-names.js +5 -1
- package/docs/architecture.md +19 -17
- package/docs/result-schema.md +24 -21
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Most hosts also install one adapter package. Each adapter lists its own peers:
|
|
|
18
18
|
| -------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
|
|
19
19
|
| `@better-fs-tools/read` | The core, the helpers, and the `./signature` and `./formats` subpaths | `npm install @better-fs-tools/read` |
|
|
20
20
|
| `@better-fs-tools/fs` | The `FileSystem` contract, `memoryFileSystem`, and the conformance suite | `npm install @better-fs-tools/fs` |
|
|
21
|
-
| `@better-fs-tools/node` |
|
|
21
|
+
| `@better-fs-tools/node` | The local filesystem on Node 24 or Bun, and `nodeDigest()` | `npm install @better-fs-tools/node @better-fs-tools/read` |
|
|
22
22
|
| `@better-fs-tools/ai-sdk` | An AI SDK 7 tool | `npm install @better-fs-tools/ai-sdk @better-fs-tools/read ai` |
|
|
23
23
|
| `@better-fs-tools/pi` | A Pi tool and a Pi extension | `npm install @better-fs-tools/pi @better-fs-tools/read @earendil-works/pi-coding-agent typebox` |
|
|
24
24
|
| `@better-fs-tools/cloudflare-shell` | A filesystem over a Cloudflare Shell Workspace | `npm install @better-fs-tools/cloudflare-shell @better-fs-tools/read` |
|
|
@@ -29,14 +29,14 @@ All packages have the same version. Install matching versions.
|
|
|
29
29
|
|
|
30
30
|
## Quick start
|
|
31
31
|
|
|
32
|
-
On Node or Bun, `
|
|
32
|
+
On Node or Bun, give `createReadTool()` a `nodeFileSystem()`:
|
|
33
33
|
|
|
34
34
|
```ts
|
|
35
|
-
import {
|
|
36
|
-
import { textOf } from "@better-fs-tools/read";
|
|
35
|
+
import { nodeFileSystem } from "@better-fs-tools/node";
|
|
36
|
+
import { createReadTool, textOf } from "@better-fs-tools/read";
|
|
37
37
|
|
|
38
38
|
// Rooted at process.cwd(), with SHA-256 observations.
|
|
39
|
-
const read =
|
|
39
|
+
const read = createReadTool({ fs: nodeFileSystem({ allowedRoots: [process.cwd()] }) });
|
|
40
40
|
|
|
41
41
|
const result = await read({ path: "package.json", limit: 2 });
|
|
42
42
|
console.log(result.status); // "ok"
|
|
@@ -47,8 +47,6 @@ console.log(textOf(result));
|
|
|
47
47
|
// [read:continue] Output stopped at the line limit. Continue with {"path":"package.json","offset":3,"limit":2}.
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
`createNodeReadTool()` uses `nodeFileSystem` rooted at `process.cwd()`, and `nodeDigest()` for content hashes. It passes every other option to `createReadTool()`.
|
|
51
|
-
|
|
52
50
|
`createReadTool()` is the core. It needs a filesystem, and it works with any `FileSystem`:
|
|
53
51
|
|
|
54
52
|
```ts
|
|
@@ -107,7 +105,7 @@ read({ path, offset, limit }, ctx)
|
|
|
107
105
|
│ ├─ on a miss: suggest one bounded listing, names only
|
|
108
106
|
│ └─ on a directory .... a directory converter lists it, else NOT_A_FILE
|
|
109
107
|
│ authorize ............... host policy on the open file, before the core reads content
|
|
110
|
-
│ sample + classifiers .... text, image, PDF,
|
|
108
|
+
│ sample + classifiers .... text, image, PDF, Office, binary, ...
|
|
111
109
|
│ converters .............. other formats to text or media parts
|
|
112
110
|
│ scan .................... lines, clamping, view bytes, token budget, scan limit
|
|
113
111
|
│ verify .................. size check and change detection
|
|
@@ -118,38 +116,39 @@ read({ path, offset, limit }, ctx)
|
|
|
118
116
|
ReadResult
|
|
119
117
|
```
|
|
120
118
|
|
|
121
|
-
You cannot remove the input validation, the scan, the limits, the type check, or change detection. Every other step is
|
|
119
|
+
You cannot remove the input validation, the scan, the limits, the type check, or change detection. Every other step is an option that you can replace. [docs/architecture.md](docs/architecture.md) explains each stage.
|
|
122
120
|
|
|
123
121
|
## What you can change
|
|
124
122
|
|
|
125
|
-
Pass
|
|
126
|
-
|
|
127
|
-
| Dimension |
|
|
128
|
-
| ------------------------------------------------------ | --------------------------- |
|
|
129
|
-
| Tool name, parameters, range model, descriptions | `signature` (adapters only) | `defaultReadSignature()`: `read` with `path`, `offset`, `limit`
|
|
130
|
-
| Filesystem, allowed roots, deny roots, symlinks | `fs` | Required. `
|
|
131
|
-
| Path rewrites (`~`, `file://`, `@`, Unicode repair) | `resolve` | `null` (the path is used as given)
|
|
132
|
-
| Names to suggest when a file is missing | `suggest` | `defaultSuggest()`
|
|
133
|
-
| Per-path policy, size ceiling, user approval | `authorize` | `null` (allow)
|
|
134
|
-
| Binary and format detection | `classifiers` | `defaultClassifiers()`
|
|
135
|
-
| File conversion, images, directories | `converters` | `[]`
|
|
136
|
-
| Line, byte, scan, conversion, and media limits | `limits` | `defaultReadLimits`
|
|
137
|
-
| Token limit | `budget` | `null`
|
|
138
|
-
| Redaction, repeat-read guard, your own after-read code | `hooks` | `[]`
|
|
139
|
-
| Output text or content parts | `formatter` | `lineNumberFormatter()`
|
|
140
|
-
| Note and message wording | `messages` | `defaultReadMessages`
|
|
141
|
-
| Read records for later tools | `state` | `null`
|
|
142
|
-
| Content hashes and observations | `digest` | `
|
|
143
|
-
| Timestamps | `clock` | `() => new Date()`
|
|
144
|
-
|
|
145
|
-
`limits` and `messages` merge over their defaults key by key. Every other
|
|
123
|
+
Pass options to `createReadTool()`, `readToolDefinition()`, or an integration factory (`createAiSdkReadTool()`, `createPiReadTool()`, `createAgentCoreReadTool()`). Every integration takes the same options with the same defaults, and passes them to the core.
|
|
124
|
+
|
|
125
|
+
| Dimension | Option | Default |
|
|
126
|
+
| ------------------------------------------------------ | --------------------------- | -------------------------------------------------------------------------------------------- |
|
|
127
|
+
| Tool name, parameters, range model, descriptions | `signature` (adapters only) | `defaultReadSignature()`: `read` with `path`, `offset`, `limit` |
|
|
128
|
+
| Filesystem, allowed roots, deny roots, symlinks | `fs` | Required. `nodeFileSystem()` from `@better-fs-tools/node` reads the local disk |
|
|
129
|
+
| Path rewrites (`~`, `file://`, `@`, Unicode repair) | `resolve` | `null` (the path is used as given) |
|
|
130
|
+
| Names to suggest when a file is missing | `suggest` | `defaultSuggest()` |
|
|
131
|
+
| Per-path policy, size ceiling, user approval | `authorize` | `null` (allow) |
|
|
132
|
+
| Binary and format detection | `classifiers` | `defaultClassifiers()` |
|
|
133
|
+
| File conversion, images, directories | `converters` | `[imageConverter()]`: an image becomes a media part. `[]` turns images off |
|
|
134
|
+
| Line, byte, scan, conversion, and media limits | `limits` | `defaultReadLimits` |
|
|
135
|
+
| Token limit | `budget` | `null` |
|
|
136
|
+
| Redaction, repeat-read guard, your own after-read code | `hooks` | `[]` |
|
|
137
|
+
| Output text or content parts | `formatter` | `lineNumberFormatter()` |
|
|
138
|
+
| Note and message wording | `messages` | `defaultReadMessages` |
|
|
139
|
+
| Read records for later tools | `state` | `null` |
|
|
140
|
+
| Content hashes and observations | `digest` | `sha256Digest()`, which runs anywhere. `nodeDigest()` is faster on Node. `null` turns it off |
|
|
141
|
+
| Timestamps | `clock` | `() => new Date()` |
|
|
142
|
+
|
|
143
|
+
`limits` and `messages` merge over their defaults key by key. Every other option replaces its default. To add to a list, include the default yourself:
|
|
146
144
|
|
|
147
145
|
```ts
|
|
148
|
-
import {
|
|
149
|
-
import { defaultClassifiers, extensionClassifier } from "@better-fs-tools/read";
|
|
146
|
+
import { nodeFileSystem } from "@better-fs-tools/node";
|
|
147
|
+
import { createReadTool, defaultClassifiers, extensionClassifier } from "@better-fs-tools/read";
|
|
150
148
|
|
|
151
|
-
export const read =
|
|
152
|
-
|
|
149
|
+
export const read = createReadTool({
|
|
150
|
+
fs: nodeFileSystem({ allowedRoots: [process.cwd()] }),
|
|
151
|
+
// A list option replaces the default. Include the default to add to it.
|
|
153
152
|
classifiers: [
|
|
154
153
|
extensionClassifier({ unsupported: { ".parquet": { code: "BINARY" } } }),
|
|
155
154
|
...defaultClassifiers(),
|
|
@@ -160,7 +159,7 @@ export const read = createNodeReadTool({
|
|
|
160
159
|
});
|
|
161
160
|
```
|
|
162
161
|
|
|
163
|
-
`createReadTool()` checks the
|
|
162
|
+
`createReadTool()` checks the options once, when you call it. An unknown key, an empty classifier list, a limit that is not a positive safe integer, or a message that is not a function throws `TypeError`.
|
|
164
163
|
|
|
165
164
|
## Tool signature and docs
|
|
166
165
|
|
|
@@ -172,7 +171,7 @@ The model sees a tool name, a description, a parameter schema, and parameter des
|
|
|
172
171
|
- `param(name)` gives the host name of a canonical parameter, or `""` when the signature has none.
|
|
173
172
|
- `fromInput(retry)` maps a canonical retry back to the model's names. Notes use it when they tell the model how to continue. Only the read signature has it.
|
|
174
173
|
|
|
175
|
-
Two builders
|
|
174
|
+
Two builders make the signature. `defaultReadSignature()` gives `read({ path, offset?, limit? })`. `lineRangeSignature()` gives `read({ path, start?, end? })`, with one-based and inclusive lines. Both take the shared `SignatureDocs` options: `name`, `description`, `describe`, and `names`. `describe` and `names` use the builder's own parameter names as keys.
|
|
176
175
|
|
|
177
176
|
```ts
|
|
178
177
|
import { defaultReadSignature, lineRangeSignature } from "@better-fs-tools/read/signature";
|
|
@@ -196,7 +195,9 @@ export const lineRange = lineRangeSignature({
|
|
|
196
195
|
});
|
|
197
196
|
```
|
|
198
197
|
|
|
199
|
-
With `
|
|
198
|
+
With `renamed`, a truncated read tells the model to continue with `{"file_path":"src/a.ts","start":101,"max_lines":100}`. With `lineRange`, it says `{"file_path":"src/a.ts","start_line":101,"end_line":200}`. The structured result stays canonical: `result.continuation.next` is still `{ path, offset, limit }`.
|
|
199
|
+
|
|
200
|
+
For another input shape, write your own `ReadSignature`. It needs `name`, `description`, `schema`, `toInput`, `param`, and `fromInput`.
|
|
200
201
|
|
|
201
202
|
`createAiSdkReadTool()` and `createPiReadTool()` wire the retry wording for you with `readSignatureMessages(signature)`. If you call the core yourself behind a signature, pass `messages: readSignatureMessages(signature)`. Otherwise the retry text uses `path`, `offset`, and `limit`.
|
|
202
203
|
|
|
@@ -210,13 +211,19 @@ The tool reads one path for each call. For multi-file reads or globs, call the c
|
|
|
210
211
|
|
|
211
212
|
```ts
|
|
212
213
|
import { homedir } from "node:os";
|
|
213
|
-
import {
|
|
214
|
-
import {
|
|
214
|
+
import { nodeFileSystem } from "@better-fs-tools/node";
|
|
215
|
+
import {
|
|
216
|
+
createReadTool,
|
|
217
|
+
expandHome,
|
|
218
|
+
pathResolvers,
|
|
219
|
+
stripPrefixes,
|
|
220
|
+
unicodeRepair,
|
|
221
|
+
} from "@better-fs-tools/read";
|
|
215
222
|
|
|
216
223
|
const cwd = process.cwd();
|
|
217
224
|
const home = homedir();
|
|
218
225
|
|
|
219
|
-
export const read =
|
|
226
|
+
export const read = createReadTool({
|
|
220
227
|
fs: nodeFileSystem({ cwd, allowedRoots: [cwd, `${home}/.config/app`] }),
|
|
221
228
|
resolve: pathResolvers(
|
|
222
229
|
stripPrefixes(), // "file:///x" and "@src/x"
|
|
@@ -285,13 +292,20 @@ The filesystem owns the root policy: allowed roots, deny roots, and symlinks. `a
|
|
|
285
292
|
What `open()` has fetched before `authorize` runs depends on the backend. `@better-fs-tools/cloudflare-shell` and `@better-fs-tools/just-bash` buffer the whole file inside `open()`, so those bytes have left the backend. `@better-fs-tools/cloudflare-computer` does not buffer: its `open()` starts a `readFile` stream, so the request reaches the backend, but it reads no chunk before `authorize`. `nodeFileSystem` streams, and `memoryFileSystem` already holds the bytes. In every case the core passes no byte to a classifier, a converter, or the model until `authorize` allows it. If a denied read must not reach the backend, refuse the path in the filesystem with a deny root, or leave it out of the allowed roots.
|
|
286
293
|
|
|
287
294
|
```ts
|
|
288
|
-
import {
|
|
289
|
-
import {
|
|
295
|
+
import { nodeFileSystem } from "@better-fs-tools/node";
|
|
296
|
+
import {
|
|
297
|
+
askUser,
|
|
298
|
+
createReadTool,
|
|
299
|
+
denyPaths,
|
|
300
|
+
readAuthorizers,
|
|
301
|
+
sizeCeiling,
|
|
302
|
+
} from "@better-fs-tools/read";
|
|
290
303
|
|
|
291
304
|
// Your own prompt, for example a dialog in your UI.
|
|
292
305
|
declare function confirmInUi(question: string): Promise<boolean>;
|
|
293
306
|
|
|
294
|
-
export const read =
|
|
307
|
+
export const read = createReadTool({
|
|
308
|
+
fs: nodeFileSystem({ allowedRoots: [process.cwd()] }),
|
|
295
309
|
authorize: readAuthorizers(
|
|
296
310
|
denyPaths(["**/.env", "**/.env.*", "**/*.pem"]),
|
|
297
311
|
sizeCeiling({ maxBytes: 256 * 1024, unrangedOnly: true }),
|
|
@@ -343,7 +357,9 @@ While a prompt waits, the file stays open. If the file changes during the wait,
|
|
|
343
357
|
|
|
344
358
|
`classifiers` is an ordered list. Each classifier sees a bounded sample of the file (`limits.sampleBytes`). The first classifier with an opinion wins.
|
|
345
359
|
|
|
346
|
-
The default chain detects images, PDFs, Office documents,
|
|
360
|
+
The default chain detects images, PDFs, Office documents, binary content, and text encodings. It returns these `unsupported` codes: `IMAGE`, `PDF`, `OFFICE_DOCUMENT`, `BINARY`, and `UNKNOWN_ENCODING`. Each refusal has a note with code `unsupported-<code>`, for example `unsupported-pdf`.
|
|
361
|
+
|
|
362
|
+
A Jupyter notebook is JSON, so the default chain reads it as text. `notebookClassifier()` is exported but not in the chain. Put it first, as `classifiers: [notebookClassifier(), ...defaultClassifiers()]`, to refuse notebooks with code `NOTEBOOK`, or to convert them with `notebookConverter()`. The write tools use the same classifiers, so the same list makes them refuse a notebook too.
|
|
347
363
|
|
|
348
364
|
To detect by extension, or to change a refusal message:
|
|
349
365
|
|
|
@@ -370,13 +386,16 @@ A classifier only decides what a file is. A converter changes the content.
|
|
|
370
386
|
|
|
371
387
|
## File conversion, images, and directories
|
|
372
388
|
|
|
373
|
-
`converters` is a list. It runs after classification. The first file converter whose `accepts` returns `true` handles the file. The first directory converter handles directories. With no converters, text is read, other formats are `unsupported`, and a directory gives `NOT_A_FILE`.
|
|
389
|
+
`converters` is a list. It runs after classification. The first file converter whose `accepts` returns `true` handles the file. The first directory converter handles directories. With no converters, text is read, other formats are `unsupported`, and a directory gives `NOT_A_FILE`. The core has no converters by default. The Pi and AI SDK hosts add `imageConverter()` when you pass no `converters`.
|
|
374
390
|
|
|
375
391
|
```ts
|
|
376
|
-
import {
|
|
392
|
+
import { nodeFileSystem } from "@better-fs-tools/node";
|
|
377
393
|
import {
|
|
394
|
+
createReadTool,
|
|
395
|
+
defaultClassifiers,
|
|
378
396
|
directoryListing,
|
|
379
397
|
imageConverter,
|
|
398
|
+
notebookClassifier,
|
|
380
399
|
notebookConverter,
|
|
381
400
|
textConverter,
|
|
382
401
|
} from "@better-fs-tools/read";
|
|
@@ -384,7 +403,11 @@ import {
|
|
|
384
403
|
// Your own PDF extraction, for example a pdftotext process that reads stdin.
|
|
385
404
|
declare function pdfToText(source: AsyncIterable<Uint8Array>): AsyncIterable<string>;
|
|
386
405
|
|
|
387
|
-
export const read =
|
|
406
|
+
export const read = createReadTool({
|
|
407
|
+
fs: nodeFileSystem({ allowedRoots: [process.cwd()] }),
|
|
408
|
+
// The default chain reads a notebook as JSON text. notebookClassifier()
|
|
409
|
+
// marks it NOTEBOOK, so notebookConverter() can take it.
|
|
410
|
+
classifiers: [notebookClassifier(), ...defaultClassifiers()],
|
|
388
411
|
converters: [
|
|
389
412
|
imageConverter(),
|
|
390
413
|
notebookConverter({ outputs: false }),
|
|
@@ -400,12 +423,12 @@ export const read = createNodeReadTool({
|
|
|
400
423
|
});
|
|
401
424
|
```
|
|
402
425
|
|
|
403
|
-
| Converter | Result
|
|
404
|
-
| ----------------------------------------------- |
|
|
405
|
-
| `imageConverter({ transform })` | Accepts code `IMAGE`. Gives a `media` result with the image as one content part. `transform` can resize it.
|
|
406
|
-
| `notebookConverter({ outputs })` | Accepts code `NOTEBOOK`. Gives the cells as text. `outputs` defaults to `true`. Broken notebook JSON is refused as `INVALID_NOTEBOOK`. |
|
|
407
|
-
| `textConverter({ id, accepts, mimeType, run })` | Builds a text converter from a stream function, for example `pdftotext`, `pandoc`, or `markitdown` in your host.
|
|
408
|
-
| `directoryListing({ trailingSlash, sort })` | A directory becomes text, one entry for each line. `sort` is `"name"` (the default) or `"type-then-name"`.
|
|
426
|
+
| Converter | Result |
|
|
427
|
+
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
428
|
+
| `imageConverter({ transform })` | Accepts code `IMAGE`. Gives a `media` result with the image as one content part. `transform` can resize it. |
|
|
429
|
+
| `notebookConverter({ outputs })` | Accepts code `NOTEBOOK`, so it needs `notebookClassifier()`. Gives the cells as text. `outputs` defaults to `true`. Broken notebook JSON is refused as `INVALID_NOTEBOOK`. |
|
|
430
|
+
| `textConverter({ id, accepts, mimeType, run })` | Builds a text converter from a stream function, for example `pdftotext`, `pandoc`, or `markitdown` in your host. |
|
|
431
|
+
| `directoryListing({ trailingSlash, sort })` | A directory becomes text, one entry for each line. `sort` is `"name"` (the default) or `"type-then-name"`. |
|
|
409
432
|
|
|
410
433
|
A converter returns one of these outcomes:
|
|
411
434
|
|
|
@@ -428,7 +451,7 @@ Conversion runs again on each continuation read. Cache inside your converter if
|
|
|
428
451
|
| Limit | Default | Meaning |
|
|
429
452
|
| --------------------- | ------- | ----------------------------------------------------- |
|
|
430
453
|
| `maxLines` | 2,000 | Lines in the view. Also the ceiling for `limit`. |
|
|
431
|
-
| `maxViewBytes` |
|
|
454
|
+
| `maxViewBytes` | 50 KiB | UTF-8 bytes of source text in the view |
|
|
432
455
|
| `maxCharsPerLine` | 2,000 | Characters in one line before it is clamped |
|
|
433
456
|
| `maxScanBytes` | 64 MiB | Bytes scanned before totals and content identity stop |
|
|
434
457
|
| `sampleBytes` | 8 KiB | Bytes given to classifiers. At most `maxScanBytes`. |
|
|
@@ -446,10 +469,11 @@ Every tool has one parse helper with the same pattern: `parseReadInput`, `parseE
|
|
|
446
469
|
For a token limit, set `budget`. The scanner stops at the last whole line that fits, and the result has a normal continuation. The truncation reason is `"budget"`.
|
|
447
470
|
|
|
448
471
|
```ts
|
|
449
|
-
import {
|
|
450
|
-
import { charsPerToken } from "@better-fs-tools/read";
|
|
472
|
+
import { nodeFileSystem } from "@better-fs-tools/node";
|
|
473
|
+
import { charsPerToken, createReadTool } from "@better-fs-tools/read";
|
|
451
474
|
|
|
452
|
-
export const read =
|
|
475
|
+
export const read = createReadTool({
|
|
476
|
+
fs: nodeFileSystem({ allowedRoots: [process.cwd()] }),
|
|
453
477
|
limits: { maxLines: 1_000, maxViewBytes: 64 * 1024 },
|
|
454
478
|
budget: charsPerToken({ ratio: 4, max: 25_000 }),
|
|
455
479
|
});
|
|
@@ -464,11 +488,12 @@ For a size ceiling that refuses a whole file, use the `sizeCeiling` authorizer.
|
|
|
464
488
|
`hooks` is an ordered list of `afterRead` functions. They run after the scan and change detection, and before the result is recorded and formatted.
|
|
465
489
|
|
|
466
490
|
```ts
|
|
467
|
-
import {
|
|
468
|
-
import { memoryStore, redact, repeatReadGuard } from "@better-fs-tools/read";
|
|
491
|
+
import { nodeFileSystem } from "@better-fs-tools/node";
|
|
492
|
+
import { createReadTool, memoryStore, redact, repeatReadGuard } from "@better-fs-tools/read";
|
|
469
493
|
|
|
470
|
-
//
|
|
471
|
-
export const read =
|
|
494
|
+
// repeatReadGuard needs a digest: the default sha256Digest() is one.
|
|
495
|
+
export const read = createReadTool({
|
|
496
|
+
fs: nodeFileSystem({ allowedRoots: [process.cwd()] }),
|
|
472
497
|
state: memoryStore(),
|
|
473
498
|
hooks: [repeatReadGuard(), redact({ patterns: [/AKIA[0-9A-Z]{16}/g] })],
|
|
474
499
|
});
|
|
@@ -506,7 +531,7 @@ export const agentsNote: ReadHook<unknown> = {
|
|
|
506
531
|
};
|
|
507
532
|
```
|
|
508
533
|
|
|
509
|
-
A repeat read that the guard empties
|
|
534
|
+
A repeat read that the guard empties has `observation.wholeFileVisible: false`. The stored record keeps the earlier read's `wholeFileVisible` and line ranges when the content is the same. Redaction works line by line, so a secret split across two lines is not matched.
|
|
510
535
|
|
|
511
536
|
## Output format
|
|
512
537
|
|
|
@@ -535,20 +560,22 @@ Its options:
|
|
|
535
560
|
Helpers for common formats:
|
|
536
561
|
|
|
537
562
|
```ts
|
|
538
|
-
import {
|
|
563
|
+
import { nodeFileSystem } from "@better-fs-tools/node";
|
|
539
564
|
import {
|
|
565
|
+
createReadTool,
|
|
540
566
|
eofFooter,
|
|
541
567
|
fileHashHeader,
|
|
542
568
|
hashlineGutter,
|
|
543
569
|
lineNumberFormatter,
|
|
544
570
|
} from "@better-fs-tools/read";
|
|
545
571
|
|
|
546
|
-
export const read =
|
|
572
|
+
export const read = createReadTool({
|
|
573
|
+
fs: nodeFileSystem({ allowedRoots: [process.cwd()] }),
|
|
547
574
|
formatter: lineNumberFormatter({
|
|
548
575
|
gutter: hashlineGutter({ width: 2 }), // "12:a3|text"
|
|
549
576
|
header: fileHashHeader(), // "file-hash: sha256:..."
|
|
550
577
|
footer: eofFooter((n) => `(End of file - total ${n} lines)`),
|
|
551
|
-
notes: (note) => (note.
|
|
578
|
+
notes: (note) => (note.severity === "info" ? null : note),
|
|
552
579
|
}),
|
|
553
580
|
});
|
|
554
581
|
```
|
|
@@ -562,13 +589,17 @@ export const read = createNodeReadTool({
|
|
|
562
589
|
Presets that copy the read output of other harnesses are in `@better-fs-tools/read/formats`: `opencodeFormat()`, `deepAgentsFormat()`, `hashlineFormat()`, and `hermesFormat()`. They take no options.
|
|
563
590
|
|
|
564
591
|
```ts
|
|
565
|
-
import {
|
|
592
|
+
import { nodeFileSystem } from "@better-fs-tools/node";
|
|
593
|
+
import { createReadTool } from "@better-fs-tools/read";
|
|
566
594
|
import { opencodeFormat } from "@better-fs-tools/read/formats";
|
|
567
595
|
|
|
568
|
-
export const read =
|
|
596
|
+
export const read = createReadTool({
|
|
597
|
+
fs: nodeFileSystem({ allowedRoots: [process.cwd()] }),
|
|
598
|
+
formatter: opencodeFormat(),
|
|
599
|
+
});
|
|
569
600
|
```
|
|
570
601
|
|
|
571
|
-
A custom formatter implements `format(outcome, ctx)`. It is synchronous and pure over the outcome. `ctx` has the `digest`, the `limits`, the `call`, and a `mode`. The mode is `"model"` for model output and `"view"` for the body only, with no notes, header, or footer. The Pi adapter uses `"view"` for its truncation details. If your formatter returns parts in `"view"` mode, Pi leaves those details out. A formatter sees only the finished outcome. It cannot read the file. If the formatter throws or returns neither a string nor an array, the call still succeeds: the core
|
|
602
|
+
A custom formatter implements `format(outcome, ctx)`. It is synchronous and pure over the outcome. `ctx` has the `digest`, the `limits`, the `call`, and a `mode`. The mode is `"model"` for model output and `"view"` for the body only, with no notes, header, or footer. The Pi adapter uses `"view"` for its truncation details. If your formatter returns parts in `"view"` mode, Pi leaves those details out. A formatter sees only the finished outcome. It cannot read the file. If the formatter throws or returns neither a string nor an array, the call still succeeds: the core formats the result with `lineNumberFormatter()` and adds no note.
|
|
572
603
|
|
|
573
604
|
## Messages and notes
|
|
574
605
|
|
|
@@ -602,7 +633,7 @@ No default message names `offset` or `limit`. Every message that suggests a retr
|
|
|
602
633
|
| `unsupported` | A classifier or converter refused the format. `code` is open, for example `PDF` or `TOO_LARGE`. |
|
|
603
634
|
| `error` | `error.code` is one of thirteen `ReadErrorCode` values. |
|
|
604
635
|
|
|
605
|
-
The error codes are `INVALID_INPUT`, `NOT_FOUND`, `NOT_A_FILE`, `DANGEROUS_PATH`, `OUTSIDE_ALLOWED_ROOTS`, `PERMISSION_DENIED`, `DENIED`, `TOO_LARGE`, `CHANGED_DURING_READ`, `ABORTED`, `UNSUPPORTED_BACKEND`, `EXTENSION_FAILED`, and `IO_ERROR`. `TOO_LARGE` is an `error` when a backend byte ceiling refuses the file, and an `unsupported` code when a converter or media limit stops it. `EXTENSION_FAILED` means that host code threw or broke a rule. Its note data names the
|
|
636
|
+
The error codes are `INVALID_INPUT`, `NOT_FOUND`, `NOT_A_FILE`, `DANGEROUS_PATH`, `OUTSIDE_ALLOWED_ROOTS`, `PERMISSION_DENIED`, `DENIED`, `TOO_LARGE`, `CHANGED_DURING_READ`, `ABORTED`, `UNSUPPORTED_BACKEND`, `EXTENSION_FAILED`, and `IO_ERROR`. `TOO_LARGE` is an `error` when a backend byte ceiling refuses the file, and an `unsupported` code when a converter or media limit stops it. `EXTENSION_FAILED` means that host code threw or broke a rule. Its note data names the option and the stage, for example `{ extension: "hooks", phase: "hooks", id: "redact" }`.
|
|
606
637
|
|
|
607
638
|
An `ok` result has structured lines, and an `error` result has its error:
|
|
608
639
|
|
|
@@ -669,36 +700,39 @@ await read({ path: "/a.txt" }, { host: session, callId: "call-1" });
|
|
|
669
700
|
|
|
670
701
|
- `fs` can be a function of the call. It runs once for each read, before `resolve`. Use it to choose a backend for each call.
|
|
671
702
|
- `sha256Digest()` is SHA-256 in plain JavaScript. It is synchronous and needs no Node module, so it works in a Cloudflare Worker. Its id and its values (`sha256:<hex>`) are the same as `nodeDigest()`, so records from either one match.
|
|
672
|
-
- `state` needs a `digest`, because a record names the digest that made it. `
|
|
703
|
+
- `state` needs a `digest`, because a record names the digest that made it. `digest` defaults to `sha256Digest()`, so a `state` alone works. `createReadTool` throws `TypeError` for a `state` with `digest: null`, and the options type (`StateNeedsDigest`) refuses it at compile time.
|
|
673
704
|
- `state` can be a function of the call. It runs at most once for each read, and only when the core needs the store. Return `null` for no store. A factory that throws gives `EXTENSION_FAILED`. A store whose `get` or `put` fails never fails the read.
|
|
674
|
-
- The record key is `file.resolvedPath`. A read stores a `ReadRecord` with `schema: 2` and `origin: "read"`. It holds the backend `version`, the `digest` id, the content and view ids, and the read
|
|
705
|
+
- The record key is `file.resolvedPath`. A read stores a `ReadRecord` with `schema: 2` and `origin: "read"`. It holds the backend `version`, the `digest` id, the content and view ids, the read range, and `seenLines`. `seenLines` joins the ranges that reads of the same content showed in full. When they run from line 1 to the last line, the record is `wholeFileVisible`, so reads in ranges count as a read of the whole file. The write tools store records with `origin: "write"` and `request: null` after a commit. A record of another schema counts as absent.
|
|
675
706
|
- `result.file.version` is the backend's change token from `open()`, or `null`. It is kept when the backend has no identity capability.
|
|
676
707
|
- `memoryStore()` from `@better-fs-tools/read` keeps records in memory, with a size cap and a TTL. Its `clock` option is the same `Clock` (`() => Date`) that the tools take, so one fake clock fits both. It sits on the root of the package that owns `ReadStateStore`, as `memoryLocks()` sits on the root of `@better-fs-tools/write`, which owns `LockManager`.
|
|
677
708
|
- The helpers in this package are typed with `unknown` for the host, so they fit a tool with any host type. `askUser`, `imageConverter`, and `textConverter` can take your host type, as `askUser<Session>` does above.
|
|
678
709
|
|
|
679
710
|
## Security guarantees
|
|
680
711
|
|
|
681
|
-
Your
|
|
712
|
+
Your options can narrow access. They cannot widen it.
|
|
682
713
|
|
|
683
714
|
- The filesystem is the only place that grants access. It checks roots, deny roots, the realpath, and the type in one `open()` call. `resolve` runs before it and `authorize` runs after it.
|
|
684
715
|
- Each read opens at most one file. A resolver only changes the path that goes into that open.
|
|
685
716
|
- `nodeFileSystem` opens files with `O_NOFOLLOW | O_NONBLOCK` and checks the type first. It refuses directories, FIFOs, sockets, and devices before any content read. It refuses `/dev`, `/proc`, and `/sys` before it touches the filesystem.
|
|
686
|
-
- The core reads no content byte before `authorize` allows it, so no content reaches a classifier, a converter, or the model. `@better-fs-tools/cloudflare-shell` and `@better-fs-tools/just-bash`
|
|
717
|
+
- The core reads no content byte before `authorize` allows it, so no content reaches a classifier, a converter, or the model. `@better-fs-tools/cloudflare-shell` and `@better-fs-tools/just-bash` have already fetched the whole file in `open()`. `@better-fs-tools/cloudflare-computer` has started a `readFile` stream but read no chunk. If a denied read must not reach the backend, use a deny root, or leave the path out of the filesystem's allowed roots.
|
|
687
718
|
- The scan is bounded. Converters get a capped stream from the open file, never a path.
|
|
688
719
|
- Change detection runs after the scan and after conversion. A file that changed returns `CHANGED_DURING_READ`.
|
|
689
720
|
- A suggested name is never opened.
|
|
690
721
|
- A hook cannot turn a failure into a success. A hook that changes the view marks the observation as partial.
|
|
691
722
|
- A `DENIED` result does not show the file behind the path.
|
|
692
723
|
|
|
693
|
-
##
|
|
724
|
+
## Integrations
|
|
725
|
+
|
|
726
|
+
Each integration takes the options of `readToolDefinition()`: every core option, with the same default, plus `signature`. `fs` is required in every one. The integration owns abort forwarding and the mapping from `result.content` to the framework's content types.
|
|
694
727
|
|
|
695
|
-
|
|
728
|
+
| Package | Factory | Notes |
|
|
729
|
+
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
|
|
730
|
+
| [`@better-fs-tools/ai-sdk`](https://www.npmjs.com/package/@better-fs-tools/ai-sdk) | `createAiSdkReadTool(options)` | `execute` returns the whole `ReadResult`. `toModelOutput` maps media parts to AI SDK `file` parts. |
|
|
731
|
+
| [`@better-fs-tools/pi`](https://www.npmjs.com/package/@better-fs-tools/pi) | `createPiReadTool(options)` | Adds the Pi prompt options. `piCwdFileSystem()` from `@better-fs-tools/pi/node` roots each call at Pi's `ctx.cwd`. |
|
|
732
|
+
| [`@better-fs-tools/pi`](https://www.npmjs.com/package/@better-fs-tools/pi) | `createAgentCoreReadTool(options)` from `./agent-core` | An `AgentTool` for `@earendil-works/pi-agent-core`. |
|
|
733
|
+
| [`@better-fs-tools/pi-durable`](https://www.npmjs.com/package/@better-fs-tools/pi-durable) | `createDurableFsTools(options)` | A bundle only: the tools register inside a pi-durable extension. |
|
|
696
734
|
|
|
697
|
-
|
|
698
|
-
| ---------------------------------------------------------------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
699
|
-
| [`@better-fs-tools/ai-sdk`](https://www.npmjs.com/package/@better-fs-tools/ai-sdk) | `createAiSdkReadTool(options)` | `execute` returns the whole `ReadResult`. `toModelOutput` maps media parts to AI SDK `file` parts. You pass `fs`. |
|
|
700
|
-
| [`@better-fs-tools/pi`](https://www.npmjs.com/package/@better-fs-tools/pi) | `createPiReadTool(options)` | Each call is confined to Pi's `ctx.cwd`. `fs`, `cwd`, and `allowedRoots` are refused. The `pi.extensions` entry registers `read`, `edit`, `write`, and `apply_patch`. |
|
|
701
|
-
| [`@better-fs-tools/node`](https://www.npmjs.com/package/@better-fs-tools/node) | `createNodeReadTool(options?)` | The core with Node defaults, for your own tool. |
|
|
735
|
+
For your own framework, call `readToolDefinition()` from `./signature`. It returns the name, description, and JSON Schema to declare, a `validate` for model input, and an `execute` that maps model input and runs the tool.
|
|
702
736
|
|
|
703
737
|
Cloudflare Agents hosts use `@better-fs-tools/ai-sdk` with `cloudflareShellFileSystem` from [`@better-fs-tools/cloudflare-shell`](https://www.npmjs.com/package/@better-fs-tools/cloudflare-shell). [`@better-fs-tools/cloudflare-computer`](https://www.npmjs.com/package/@better-fs-tools/cloudflare-computer) and [`@better-fs-tools/just-bash`](https://www.npmjs.com/package/@better-fs-tools/just-bash) are filesystems. Use them with `createReadTool()` or with an adapter.
|
|
704
738
|
|
|
@@ -723,7 +757,8 @@ This Pi host sets most dimensions. You will usually need only a few of these opt
|
|
|
723
757
|
```ts
|
|
724
758
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
725
759
|
import { createPiReadTool } from "@better-fs-tools/pi";
|
|
726
|
-
import {
|
|
760
|
+
import { piCwdFileSystem } from "@better-fs-tools/pi/node";
|
|
761
|
+
import { defaultReadSignature } from "@better-fs-tools/read/signature";
|
|
727
762
|
import {
|
|
728
763
|
askUser,
|
|
729
764
|
denyPaths,
|
|
@@ -744,9 +779,9 @@ import {
|
|
|
744
779
|
const stores = new Map<string, ReturnType<typeof memoryStore>>();
|
|
745
780
|
|
|
746
781
|
export const read = createPiReadTool({
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
names: { path: "file_path",
|
|
782
|
+
fs: piCwdFileSystem(),
|
|
783
|
+
signature: defaultReadSignature({
|
|
784
|
+
names: { path: "file_path", offset: "start_line", limit: "max_lines" },
|
|
750
785
|
}),
|
|
751
786
|
resolve: pathResolvers(stripPrefixes(), unicodeRepair({ note: false })),
|
|
752
787
|
authorize: readAuthorizers(
|
|
@@ -8,8 +8,9 @@ export { officeClassifier } from "./office.ts";
|
|
|
8
8
|
export { pdfClassifier } from "./pdf.ts";
|
|
9
9
|
export { utf8Classifier } from "./utf8.ts";
|
|
10
10
|
/**
|
|
11
|
-
* The built-in chain in evaluation order: image, pdf, office,
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* The built-in chain in evaluation order: image, pdf, office, binary, utf8.
|
|
12
|
+
* The first classifier with an opinion wins. utf8Classifier runs last and is
|
|
13
|
+
* the one that returns text. A notebook is JSON text here. Put
|
|
14
|
+
* notebookClassifier() before the chain to refuse or convert notebooks.
|
|
14
15
|
*/
|
|
15
16
|
export declare function defaultClassifiers(options?: DefaultClassifierOptions): Classifier[];
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { binaryClassifier } from "./binary.js";
|
|
2
2
|
import { imageClassifier } from "./image.js";
|
|
3
|
-
import { notebookClassifier } from "./notebook.js";
|
|
4
3
|
import { officeClassifier } from "./office.js";
|
|
5
4
|
import { pdfClassifier } from "./pdf.js";
|
|
6
5
|
import { utf8Classifier } from "./utf8.js";
|
|
@@ -12,9 +11,10 @@ export { officeClassifier } from "./office.js";
|
|
|
12
11
|
export { pdfClassifier } from "./pdf.js";
|
|
13
12
|
export { utf8Classifier } from "./utf8.js";
|
|
14
13
|
/**
|
|
15
|
-
* The built-in chain in evaluation order: image, pdf, office,
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* The built-in chain in evaluation order: image, pdf, office, binary, utf8.
|
|
15
|
+
* The first classifier with an opinion wins. utf8Classifier runs last and is
|
|
16
|
+
* the one that returns text. A notebook is JSON text here. Put
|
|
17
|
+
* notebookClassifier() before the chain to refuse or convert notebooks.
|
|
18
18
|
*/
|
|
19
19
|
export function defaultClassifiers(options = {}) {
|
|
20
20
|
const notes = options.notes ?? {};
|
|
@@ -22,7 +22,6 @@ export function defaultClassifiers(options = {}) {
|
|
|
22
22
|
imageClassifier(withNote(notes.IMAGE)),
|
|
23
23
|
pdfClassifier(withNote(notes.PDF)),
|
|
24
24
|
officeClassifier(withNote(notes.OFFICE_DOCUMENT)),
|
|
25
|
-
notebookClassifier(withNote(notes.NOTEBOOK)),
|
|
26
25
|
binaryClassifier(withNote(notes.BINARY)),
|
|
27
26
|
utf8Classifier(withNote(notes.UNKNOWN_ENCODING)),
|
|
28
27
|
];
|
package/dist/contract/base.d.ts
CHANGED
|
@@ -107,15 +107,43 @@ export interface ToolSignature<TInput, TParam extends string = string> {
|
|
|
107
107
|
* that names it.
|
|
108
108
|
*/
|
|
109
109
|
param(name: TParam): string;
|
|
110
|
-
/**
|
|
110
|
+
/** A Lark grammar for hosts with grammar tools. freeformPatchSignature() sets CODEX_PATCH_GRAMMAR. */
|
|
111
111
|
readonly grammar?: {
|
|
112
112
|
readonly lark: string;
|
|
113
113
|
};
|
|
114
114
|
}
|
|
115
115
|
/**
|
|
116
|
-
*
|
|
116
|
+
* A core tool bound to a signature: what a framework adapter needs to declare
|
|
117
|
+
* the tool and run it. The xxxToolDefinition() helpers in each `./signature`
|
|
118
|
+
* subpath build one, and every integration uses them.
|
|
119
|
+
*/
|
|
120
|
+
export interface ToolDefinition<TInput, TResult, THost = undefined, TParam extends string = string> {
|
|
121
|
+
/** The tool name the model sees: `signature.name`. */
|
|
122
|
+
readonly name: string;
|
|
123
|
+
readonly description: string;
|
|
124
|
+
/** Plain JSON Schema of the model input: `signature.schema`. */
|
|
125
|
+
readonly inputSchema: JsonObject;
|
|
126
|
+
/** Set when the signature has a grammar. */
|
|
127
|
+
readonly grammar?: {
|
|
128
|
+
readonly lark: string;
|
|
129
|
+
};
|
|
130
|
+
readonly signature: ToolSignature<TInput, TParam>;
|
|
131
|
+
/**
|
|
132
|
+
* Checks model input with the signature and with the core input check at the
|
|
133
|
+
* tool's limits, and returns the canonical input. Throws TypeError that names
|
|
134
|
+
* the signature's parameters.
|
|
135
|
+
*/
|
|
136
|
+
validate(modelInput: unknown): TInput;
|
|
137
|
+
/**
|
|
138
|
+
* Maps model input with `signature.toInput` and runs the core tool. Bad input
|
|
139
|
+
* rejects with TypeError. A tool error is a result, not a rejection.
|
|
140
|
+
*/
|
|
141
|
+
execute(modelInput: unknown, ctx: ToolCallContext<THost>): Promise<TResult>;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The options every default signature takes. `TParam` is the signature's own
|
|
117
145
|
* parameter names. `names` gives a parameter another name in the schema, and
|
|
118
|
-
* `describe` replaces its description. Both are keyed by the
|
|
146
|
+
* `describe` replaces its description. Both are keyed by the signature's names.
|
|
119
147
|
*/
|
|
120
148
|
export interface SignatureDocs<TParam extends string> {
|
|
121
149
|
/** The tool name. */
|
package/dist/contract/deps.d.ts
CHANGED
|
@@ -20,7 +20,10 @@ export interface ReadDependencies<THost = undefined> {
|
|
|
20
20
|
readonly suggest: Suggest<THost> | null;
|
|
21
21
|
/** Host access policy for the opened file ("read") and before every listing ("list"). null allows everything. */
|
|
22
22
|
readonly authorize: ReadAuthorizer<THost> | null;
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* File converters in order: the first that accepts runs. The first directory
|
|
25
|
+
* converter lists directories. Default [imageConverter()]. [] turns images off.
|
|
26
|
+
*/
|
|
24
27
|
readonly converters: readonly Converter<THost>[];
|
|
25
28
|
/** Stops the view early, at a line boundary, when the next line would pass `max`. null has no budget. */
|
|
26
29
|
readonly budget: ViewBudget | null;
|
|
@@ -28,32 +31,24 @@ export interface ReadDependencies<THost = undefined> {
|
|
|
28
31
|
readonly hooks: readonly ReadHook<THost>[];
|
|
29
32
|
/** A store, or a factory called at most once for each read, and only when needed. Needs `digest`. */
|
|
30
33
|
readonly state: ReadStateStore | ((call: ReadContext<THost>) => ReadStateStore | null) | null;
|
|
34
|
+
/** Names content for the read state and the observations. Default sha256Digest(). null turns both off. */
|
|
31
35
|
readonly digest: Digest | null;
|
|
32
36
|
readonly clock: Clock;
|
|
33
37
|
readonly formatter: ReadFormatter<THost>;
|
|
34
38
|
}
|
|
35
39
|
/**
|
|
36
40
|
* `state` needs a `digest`, because a stored record names the digest that made
|
|
37
|
-
* it.
|
|
38
|
-
*
|
|
41
|
+
* it. Leave `digest` out to get sha256Digest(). `digest: null` turns it off,
|
|
42
|
+
* and then `state` must be left out or null. A `state` with `digest: null`
|
|
43
|
+
* does not type-check, and throws TypeError at run time.
|
|
39
44
|
*/
|
|
40
45
|
export type StateNeedsDigest = {
|
|
41
46
|
readonly state?: null;
|
|
42
|
-
} | {
|
|
43
|
-
readonly digest: Digest;
|
|
44
|
-
};
|
|
45
|
-
/**
|
|
46
|
-
* The same pairing for a factory with its own default digest, such as the
|
|
47
|
-
* Node and Pi factories. Leave `digest` out to get the default. `digest: null`
|
|
48
|
-
* turns it off, and then `state` must be left out or null.
|
|
49
|
-
*/
|
|
50
|
-
export type StateNeedsDigestOrDefault = {
|
|
51
|
-
readonly state?: null;
|
|
52
47
|
readonly digest: null;
|
|
53
48
|
} | {
|
|
54
49
|
readonly digest?: Digest;
|
|
55
50
|
};
|
|
56
|
-
export type
|
|
51
|
+
export type ReadToolOptions<THost = undefined> = {
|
|
57
52
|
readonly fs: ReadDependencies<THost>["fs"];
|
|
58
53
|
readonly limits?: Partial<ReadLimits>;
|
|
59
54
|
readonly messages?: Partial<ReadMessageCatalog>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export interface ReadLimits {
|
|
2
2
|
/** Lines in the view. Default 2_000. */
|
|
3
3
|
readonly maxLines: number;
|
|
4
|
-
/** UTF-8 bytes of source text in the view. Default
|
|
4
|
+
/** UTF-8 bytes of source text in the view. Default 50 KiB (51 200). */
|
|
5
5
|
readonly maxViewBytes: number;
|
|
6
6
|
/** Characters per line before clamping. Default 2_000. */
|
|
7
7
|
readonly maxCharsPerLine: number;
|
|
@@ -107,14 +107,4 @@ export interface ReadMessageCatalog extends ToolMessages {
|
|
|
107
107
|
viewModified(c: {
|
|
108
108
|
hook: string;
|
|
109
109
|
}): string;
|
|
110
|
-
weakIdentity(c: {
|
|
111
|
-
backend: string;
|
|
112
|
-
}): string;
|
|
113
|
-
bufferedBackend(c: {
|
|
114
|
-
backend: string;
|
|
115
|
-
}): string;
|
|
116
|
-
/** The formatter threw or returned neither a string nor an array. */
|
|
117
|
-
formatterFailed(c: {
|
|
118
|
-
formatter: string;
|
|
119
|
-
}): string;
|
|
120
110
|
}
|