@better-fs-tools/read 0.0.0-tegami-trusted-publish-setup → 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 +807 -6
- package/dist/authorize/ask-user.d.ts +7 -0
- package/dist/authorize/ask-user.js +25 -0
- package/dist/authorize/authorizers.d.ts +7 -0
- package/dist/authorize/authorizers.js +34 -0
- package/dist/authorize/deny-paths.d.ts +7 -0
- package/dist/authorize/deny-paths.js +25 -0
- package/dist/authorize/glob.d.ts +7 -0
- package/dist/authorize/glob.js +46 -0
- package/dist/authorize/index.d.ts +5 -0
- package/dist/authorize/index.js +5 -0
- package/dist/authorize/shared.d.ts +9 -0
- package/dist/authorize/shared.js +12 -0
- package/dist/authorize/size-ceiling.d.ts +10 -0
- package/dist/authorize/size-ceiling.js +34 -0
- package/dist/budget/chars-per-token.d.ts +9 -0
- package/dist/budget/chars-per-token.js +15 -0
- package/dist/budget/index.d.ts +2 -0
- package/dist/budget/index.js +1 -0
- package/dist/classifiers/binary.d.ts +5 -0
- package/dist/classifiers/binary.js +13 -0
- package/dist/classifiers/extension.d.ts +17 -0
- package/dist/classifiers/extension.js +45 -0
- package/dist/classifiers/image.d.ts +5 -0
- package/dist/classifiers/image.js +22 -0
- package/dist/classifiers/index.d.ts +16 -0
- package/dist/classifiers/index.js +31 -0
- package/dist/classifiers/notebook.d.ts +5 -0
- package/dist/classifiers/notebook.js +14 -0
- package/dist/classifiers/office.d.ts +5 -0
- package/dist/classifiers/office.js +30 -0
- package/dist/classifiers/pdf.d.ts +4 -0
- package/dist/classifiers/pdf.js +8 -0
- package/dist/classifiers/shared.d.ts +17 -0
- package/dist/classifiers/shared.js +57 -0
- package/dist/classifiers/utf8.d.ts +8 -0
- package/dist/classifiers/utf8.js +61 -0
- package/dist/contract/base.d.ts +126 -0
- package/dist/contract/base.js +0 -0
- package/dist/contract/classify.d.ts +50 -0
- package/dist/contract/classify.js +0 -0
- package/dist/contract/context.d.ts +13 -0
- package/dist/contract/context.js +0 -0
- package/dist/contract/deps.d.ts +60 -0
- package/dist/contract/deps.js +0 -0
- package/dist/contract/digest.d.ts +13 -0
- package/dist/contract/digest.js +0 -0
- package/dist/contract/extensions.d.ts +115 -0
- package/dist/contract/extensions.js +0 -0
- package/dist/contract/format.d.ts +16 -0
- package/dist/contract/format.js +0 -0
- package/dist/contract/input.d.ts +16 -0
- package/dist/contract/input.js +0 -0
- package/dist/contract/json.d.ts +6 -0
- package/dist/contract/json.js +0 -0
- package/dist/contract/limits.d.ts +20 -0
- package/dist/contract/limits.js +0 -0
- package/dist/contract/messages.d.ts +110 -0
- package/dist/contract/messages.js +0 -0
- package/dist/contract/result.d.ts +153 -0
- package/dist/contract/result.js +0 -0
- package/dist/contract/state.d.ts +36 -0
- package/dist/contract/state.js +0 -0
- package/dist/converters/directory-listing.d.ts +13 -0
- package/dist/converters/directory-listing.js +63 -0
- package/dist/converters/image.d.ts +7 -0
- package/dist/converters/image.js +25 -0
- package/dist/converters/index.d.ts +8 -0
- package/dist/converters/index.js +4 -0
- package/dist/converters/notebook.d.ts +12 -0
- package/dist/converters/notebook.js +106 -0
- package/dist/converters/shared.d.ts +5 -0
- package/dist/converters/shared.js +18 -0
- package/dist/converters/text.d.ts +12 -0
- package/dist/converters/text.js +24 -0
- package/dist/core/authorize.d.ts +8 -0
- package/dist/core/authorize.js +76 -0
- package/dist/core/budget.d.ts +7 -0
- package/dist/core/budget.js +23 -0
- package/dist/core/call-scope.d.ts +36 -0
- package/dist/core/call-scope.js +112 -0
- package/dist/core/classify.d.ts +9 -0
- package/dist/core/classify.js +27 -0
- package/dist/core/convert-source.d.ts +29 -0
- package/dist/core/convert-source.js +85 -0
- package/dist/core/convert.d.ts +23 -0
- package/dist/core/convert.js +132 -0
- package/dist/core/converted.d.ts +18 -0
- package/dist/core/converted.js +111 -0
- package/dist/core/create-read-tool.d.ts +7 -0
- package/dist/core/create-read-tool.js +18 -0
- package/dist/core/cursor.d.ts +14 -0
- package/dist/core/cursor.js +93 -0
- package/dist/core/deps.d.ts +6 -0
- package/dist/core/deps.js +121 -0
- package/dist/core/directory.d.ts +16 -0
- package/dist/core/directory.js +113 -0
- package/dist/core/extension-error.d.ts +9 -0
- package/dist/core/extension-error.js +18 -0
- package/dist/core/format.d.ts +8 -0
- package/dist/core/format.js +29 -0
- package/dist/core/hooks.d.ts +3 -0
- package/dist/core/hooks.js +140 -0
- package/dist/core/input.d.ts +10 -0
- package/dist/core/input.js +45 -0
- package/dist/core/limits.d.ts +9 -0
- package/dist/core/limits.js +43 -0
- package/dist/core/messages.d.ts +8 -0
- package/dist/core/messages.js +78 -0
- package/dist/core/notes.d.ts +14 -0
- package/dist/core/notes.js +94 -0
- package/dist/core/observation.d.ts +11 -0
- package/dist/core/observation.js +51 -0
- package/dist/core/open.d.ts +16 -0
- package/dist/core/open.js +33 -0
- package/dist/core/outcomes.d.ts +27 -0
- package/dist/core/outcomes.js +172 -0
- package/dist/core/pipeline.d.ts +4 -0
- package/dist/core/pipeline.js +175 -0
- package/dist/core/record.d.ts +3 -0
- package/dist/core/record.js +37 -0
- package/dist/core/resolve.d.ts +15 -0
- package/dist/core/resolve.js +43 -0
- package/dist/core/same.d.ts +2 -0
- package/dist/core/same.js +22 -0
- package/dist/core/sample.d.ts +6 -0
- package/dist/core/sample.js +24 -0
- package/dist/core/scan.d.ts +26 -0
- package/dist/core/scan.js +95 -0
- package/dist/core/scanner.d.ts +29 -0
- package/dist/core/scanner.js +124 -0
- package/dist/core/suggest.d.ts +6 -0
- package/dist/core/suggest.js +41 -0
- package/dist/core/text-outcome.d.ts +19 -0
- package/dist/core/text-outcome.js +72 -0
- package/dist/core/verify.d.ts +7 -0
- package/dist/core/verify.js +15 -0
- package/dist/digest/sha256.d.ts +7 -0
- package/dist/digest/sha256.js +131 -0
- package/dist/formats/deep-agents.d.ts +7 -0
- package/dist/formats/deep-agents.js +23 -0
- package/dist/formats/hashline.d.ts +6 -0
- package/dist/formats/hashline.js +16 -0
- package/dist/formats/hermes.d.ts +9 -0
- package/dist/formats/hermes.js +62 -0
- package/dist/formats/index.d.ts +4 -0
- package/dist/formats/index.js +4 -0
- package/dist/formats/opencode.d.ts +7 -0
- package/dist/formats/opencode.js +35 -0
- package/dist/formats/shared.d.ts +4 -0
- package/dist/formats/shared.js +9 -0
- package/dist/formatters/eof.d.ts +7 -0
- package/dist/formatters/eof.js +20 -0
- package/dist/formatters/file-hash.d.ts +7 -0
- package/dist/formatters/file-hash.js +13 -0
- package/dist/formatters/hashline.d.ts +11 -0
- package/dist/formatters/hashline.js +18 -0
- package/dist/formatters/index.d.ts +10 -0
- package/dist/formatters/index.js +7 -0
- package/dist/formatters/json.d.ts +17 -0
- package/dist/formatters/json.js +36 -0
- package/dist/formatters/layout.d.ts +15 -0
- package/dist/formatters/layout.js +33 -0
- package/dist/formatters/line-number.d.ts +11 -0
- package/dist/formatters/line-number.js +23 -0
- package/dist/formatters/plain.d.ts +7 -0
- package/dist/formatters/plain.js +14 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/redact.d.ts +14 -0
- package/dist/hooks/redact.js +53 -0
- package/dist/hooks/repeat-read-guard.d.ts +13 -0
- package/dist/hooks/repeat-read-guard.js +49 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +15 -0
- package/dist/resolve/expand-home.d.ts +5 -0
- package/dist/resolve/expand-home.js +17 -0
- package/dist/resolve/index.d.ts +5 -0
- package/dist/resolve/index.js +5 -0
- package/dist/resolve/path-resolvers.d.ts +7 -0
- package/dist/resolve/path-resolvers.js +38 -0
- package/dist/resolve/reanchor-leading-slash.d.ts +9 -0
- package/dist/resolve/reanchor-leading-slash.js +23 -0
- package/dist/resolve/strip-prefixes.d.ts +6 -0
- package/dist/resolve/strip-prefixes.js +30 -0
- package/dist/resolve/unicode-repair.d.ts +9 -0
- package/dist/resolve/unicode-repair.js +38 -0
- package/dist/signature/contract.d.ts +18 -0
- package/dist/signature/contract.js +0 -0
- package/dist/signature/index.d.ts +4 -0
- package/dist/signature/index.js +3 -0
- package/dist/signature/line-range.d.ts +7 -0
- package/dist/signature/line-range.js +59 -0
- package/dist/signature/messages.d.ts +4 -0
- package/dist/signature/messages.js +9 -0
- package/dist/signature/offset-limit.d.ts +13 -0
- package/dist/signature/offset-limit.js +59 -0
- package/dist/signature/schema.d.ts +10 -0
- package/dist/signature/schema.js +80 -0
- package/dist/state/index.d.ts +15 -0
- package/dist/state/index.js +77 -0
- package/dist/suggest/canonicalize.d.ts +6 -0
- package/dist/suggest/canonicalize.js +12 -0
- package/dist/suggest/default-suggest.d.ts +3 -0
- package/dist/suggest/default-suggest.js +12 -0
- package/dist/suggest/index.d.ts +3 -0
- package/dist/suggest/index.js +3 -0
- package/dist/suggest/suggest-file-names.d.ts +2 -0
- package/dist/suggest/suggest-file-names.js +46 -0
- package/docs/architecture.md +250 -0
- package/docs/result-schema.md +222 -0
- package/package.json +51 -2
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FileSystem } from "@better-fs-tools/fs";
|
|
2
|
+
import type { ReadDependencies } from "../contract/deps.ts";
|
|
3
|
+
import type { ReadRequest } from "../contract/input.ts";
|
|
4
|
+
import type { ClassificationInfo, ConversionInfo, FileInfo, ReadNote, ReadOk } from "../contract/result.ts";
|
|
5
|
+
import type { ScanOutcome } from "./scan.ts";
|
|
6
|
+
export interface TextOutcomeInput<THost> {
|
|
7
|
+
readonly deps: ReadDependencies<THost>;
|
|
8
|
+
readonly fs: FileSystem | null;
|
|
9
|
+
readonly request: ReadRequest;
|
|
10
|
+
readonly file: FileInfo;
|
|
11
|
+
readonly classification: ClassificationInfo;
|
|
12
|
+
readonly conversion: ConversionInfo | null;
|
|
13
|
+
readonly scan: Extract<ScanOutcome, {
|
|
14
|
+
kind: "scanned";
|
|
15
|
+
}>;
|
|
16
|
+
readonly contentId: string | null;
|
|
17
|
+
readonly notes: readonly ReadNote[];
|
|
18
|
+
}
|
|
19
|
+
export declare function textOutcome<THost>(input: TextOutcomeInput<THost>): ReadOk;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { buildViewNotes, emptyNote } from "./notes.js";
|
|
2
|
+
import { buildObservation } from "./observation.js";
|
|
3
|
+
export function textOutcome(input) {
|
|
4
|
+
const { deps, fs, request, file, classification, conversion, scan } = input;
|
|
5
|
+
const { scanner, scanCapped, reachedEof, scannedBytes } = scan;
|
|
6
|
+
const reasons = [];
|
|
7
|
+
if (scanner.selectionStop === "lines")
|
|
8
|
+
reasons.push("lines");
|
|
9
|
+
if (scanner.selectionStop === "bytes")
|
|
10
|
+
reasons.push("bytes");
|
|
11
|
+
if (scanner.selectionStop === "budget")
|
|
12
|
+
reasons.push("budget");
|
|
13
|
+
if (scanCapped)
|
|
14
|
+
reasons.push("scan-limit");
|
|
15
|
+
if (scanner.clampedLines.length > 0)
|
|
16
|
+
reasons.push("line-length");
|
|
17
|
+
const continuationAvailable = scanner.firstUnshown !== null;
|
|
18
|
+
const wholeFileVisible = request.offset === 1 &&
|
|
19
|
+
!continuationAvailable &&
|
|
20
|
+
!scanCapped &&
|
|
21
|
+
scanner.clampedLines.length === 0;
|
|
22
|
+
return {
|
|
23
|
+
tool: "read",
|
|
24
|
+
status: "ok",
|
|
25
|
+
request,
|
|
26
|
+
file,
|
|
27
|
+
classification,
|
|
28
|
+
conversion,
|
|
29
|
+
view: {
|
|
30
|
+
lines: scanner.lines,
|
|
31
|
+
startLine: request.offset,
|
|
32
|
+
endLine: scanner.lines.length === 0 ? request.offset - 1 : request.offset + scanner.lines.length - 1,
|
|
33
|
+
bytes: scanner.viewBytes,
|
|
34
|
+
partial: request.offset !== 1 || continuationAvailable || reasons.length > 0,
|
|
35
|
+
},
|
|
36
|
+
truncation: { truncated: reasons.length > 0, reasons, primary: reasons[0] ?? null },
|
|
37
|
+
continuation: {
|
|
38
|
+
available: continuationAvailable,
|
|
39
|
+
next: scanner.firstUnshown === null
|
|
40
|
+
? null
|
|
41
|
+
: { path: request.path, offset: scanner.firstUnshown, limit: request.limit },
|
|
42
|
+
},
|
|
43
|
+
totals: {
|
|
44
|
+
lines: scanCapped ? null : scanner.totalLines,
|
|
45
|
+
exact: !scanCapped,
|
|
46
|
+
// Converted text has no size of its own: the count is the text scanned.
|
|
47
|
+
bytes: reachedEof || conversion !== null ? scannedBytes : (file.size ?? scannedBytes),
|
|
48
|
+
},
|
|
49
|
+
observation: fs === null
|
|
50
|
+
? null
|
|
51
|
+
: buildObservation(deps.digest, {
|
|
52
|
+
file,
|
|
53
|
+
contentId: input.contentId,
|
|
54
|
+
view: scanner.viewText,
|
|
55
|
+
observedAt: deps.clock().toISOString(),
|
|
56
|
+
wholeFileVisible,
|
|
57
|
+
}),
|
|
58
|
+
notes: [
|
|
59
|
+
...buildViewNotes({
|
|
60
|
+
scanner,
|
|
61
|
+
request,
|
|
62
|
+
limits: deps.limits,
|
|
63
|
+
messages: deps.messages,
|
|
64
|
+
scanCapped,
|
|
65
|
+
}),
|
|
66
|
+
...input.notes,
|
|
67
|
+
...(conversion === null && reachedEof && scannedBytes === 0
|
|
68
|
+
? [emptyNote(deps.messages, file.displayPath)]
|
|
69
|
+
: []),
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { OpenFile } from "@better-fs-tools/fs";
|
|
2
|
+
import type { ReadRequest } from "../contract/input.ts";
|
|
3
|
+
import type { ReadMessageCatalog } from "../contract/messages.ts";
|
|
4
|
+
import type { FileInfo } from "../contract/result.ts";
|
|
5
|
+
import type { CallScope } from "./call-scope.ts";
|
|
6
|
+
export declare function checkSize(messages: Readonly<ReadMessageCatalog>, request: ReadRequest, file: FileInfo, consumed: number): void;
|
|
7
|
+
export declare function verifyHandle<THost>(handle: OpenFile, messages: Readonly<ReadMessageCatalog>, request: ReadRequest, file: FileInfo, scope: CallScope<THost>): Promise<void>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ReadStop, changedDuringRead, fromFileSystemError } from "./outcomes.js";
|
|
2
|
+
export function checkSize(messages, request, file, consumed) {
|
|
3
|
+
if (file.size !== null && consumed !== file.size) {
|
|
4
|
+
throw new ReadStop(changedDuringRead(messages, request, file));
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export async function verifyHandle(handle, messages, request, file, scope) {
|
|
8
|
+
const verified = await handle.verify();
|
|
9
|
+
if (!verified.ok) {
|
|
10
|
+
throw new ReadStop(fromFileSystemError(messages, request, verified.error, scope.phase, file));
|
|
11
|
+
}
|
|
12
|
+
if (verified.changed)
|
|
13
|
+
throw new ReadStop(changedDuringRead(messages, request, file));
|
|
14
|
+
scope.checkAbort();
|
|
15
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Digest } from "../contract/digest.ts";
|
|
2
|
+
/**
|
|
3
|
+
* SHA-256 in plain JavaScript, with no Node module, so it works in a Cloudflare
|
|
4
|
+
* Worker. It gives the same hashes as `nodeDigest()` from
|
|
5
|
+
* `@better-fs-tools/node`, so both can share one state store.
|
|
6
|
+
*/
|
|
7
|
+
export declare function sha256Digest(): Digest;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/* FIPS 180-4, section 4.2.2: the first 32 bits of the fractional parts of the cube roots of the first 64 primes. */
|
|
2
|
+
const K = new Uint32Array([
|
|
3
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
4
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
5
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
6
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
7
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
8
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
9
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
10
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
11
|
+
]);
|
|
12
|
+
/* Section 5.3.3: the first 32 bits of the fractional parts of the square roots of the first 8 primes. */
|
|
13
|
+
const INITIAL = [
|
|
14
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
15
|
+
];
|
|
16
|
+
const ENCODER = new TextEncoder();
|
|
17
|
+
/**
|
|
18
|
+
* SHA-256 in plain JavaScript, with no Node module, so it works in a Cloudflare
|
|
19
|
+
* Worker. It gives the same hashes as `nodeDigest()` from
|
|
20
|
+
* `@better-fs-tools/node`, so both can share one state store.
|
|
21
|
+
*/
|
|
22
|
+
export function sha256Digest() {
|
|
23
|
+
return Object.freeze({
|
|
24
|
+
id: "sha256",
|
|
25
|
+
create: () => new Sha256Stream(),
|
|
26
|
+
hash(value) {
|
|
27
|
+
const stream = new Sha256Stream();
|
|
28
|
+
stream.update(ENCODER.encode(value));
|
|
29
|
+
return stream.digest();
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
class Sha256Stream {
|
|
34
|
+
state = Uint32Array.from(INITIAL);
|
|
35
|
+
block = new Uint8Array(64);
|
|
36
|
+
words = new Uint32Array(64);
|
|
37
|
+
filled = 0;
|
|
38
|
+
// A double holds every length below 2^53 exactly.
|
|
39
|
+
totalBytes = 0;
|
|
40
|
+
finished = false;
|
|
41
|
+
update(bytes) {
|
|
42
|
+
if (this.finished)
|
|
43
|
+
throw new TypeError("sha256 stream is already finished");
|
|
44
|
+
this.totalBytes += bytes.byteLength;
|
|
45
|
+
let offset = 0;
|
|
46
|
+
if (this.filled > 0) {
|
|
47
|
+
const take = Math.min(64 - this.filled, bytes.byteLength);
|
|
48
|
+
this.block.set(bytes.subarray(0, take), this.filled);
|
|
49
|
+
this.filled += take;
|
|
50
|
+
offset = take;
|
|
51
|
+
if (this.filled < 64)
|
|
52
|
+
return;
|
|
53
|
+
this.compress(this.block, 0);
|
|
54
|
+
this.filled = 0;
|
|
55
|
+
}
|
|
56
|
+
for (; offset + 64 <= bytes.byteLength; offset += 64)
|
|
57
|
+
this.compress(bytes, offset);
|
|
58
|
+
if (offset < bytes.byteLength) {
|
|
59
|
+
this.block.set(bytes.subarray(offset), 0);
|
|
60
|
+
this.filled = bytes.byteLength - offset;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
digest() {
|
|
64
|
+
if (this.finished)
|
|
65
|
+
throw new TypeError("sha256 stream is already finished");
|
|
66
|
+
this.finished = true;
|
|
67
|
+
const bits = this.totalBytes * 8;
|
|
68
|
+
this.block[this.filled] = 0x80;
|
|
69
|
+
this.block.fill(0, this.filled + 1);
|
|
70
|
+
if (this.filled >= 56) {
|
|
71
|
+
this.compress(this.block, 0);
|
|
72
|
+
this.block.fill(0);
|
|
73
|
+
}
|
|
74
|
+
setUint64BigEndian(new DataView(this.block.buffer), 56, bits);
|
|
75
|
+
this.compress(this.block, 0);
|
|
76
|
+
let hex = "sha256:";
|
|
77
|
+
for (const word of this.state)
|
|
78
|
+
hex += word.toString(16).padStart(8, "0");
|
|
79
|
+
return hex;
|
|
80
|
+
}
|
|
81
|
+
compress(bytes, offset) {
|
|
82
|
+
const w = this.words;
|
|
83
|
+
for (let i = 0; i < 16; i++) {
|
|
84
|
+
const j = offset + i * 4;
|
|
85
|
+
w[i] = (bytes[j] << 24) | (bytes[j + 1] << 16) | (bytes[j + 2] << 8) | bytes[j + 3];
|
|
86
|
+
}
|
|
87
|
+
for (let i = 16; i < 64; i++) {
|
|
88
|
+
const a = w[i - 15];
|
|
89
|
+
const b = w[i - 2];
|
|
90
|
+
const s0 = rotr(a, 7) ^ rotr(a, 18) ^ (a >>> 3);
|
|
91
|
+
const s1 = rotr(b, 17) ^ rotr(b, 19) ^ (b >>> 10);
|
|
92
|
+
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) | 0;
|
|
93
|
+
}
|
|
94
|
+
const s = this.state;
|
|
95
|
+
let a = s[0];
|
|
96
|
+
let b = s[1];
|
|
97
|
+
let c = s[2];
|
|
98
|
+
let d = s[3];
|
|
99
|
+
let e = s[4];
|
|
100
|
+
let f = s[5];
|
|
101
|
+
let g = s[6];
|
|
102
|
+
let h = s[7];
|
|
103
|
+
for (let i = 0; i < 64; i++) {
|
|
104
|
+
const t1 = (h + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + ((e & f) ^ (~e & g)) + K[i] + w[i]) | 0;
|
|
105
|
+
const t2 = ((rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) | 0;
|
|
106
|
+
h = g;
|
|
107
|
+
g = f;
|
|
108
|
+
f = e;
|
|
109
|
+
e = (d + t1) | 0;
|
|
110
|
+
d = c;
|
|
111
|
+
c = b;
|
|
112
|
+
b = a;
|
|
113
|
+
a = (t1 + t2) | 0;
|
|
114
|
+
}
|
|
115
|
+
s[0] = s[0] + a;
|
|
116
|
+
s[1] = s[1] + b;
|
|
117
|
+
s[2] = s[2] + c;
|
|
118
|
+
s[3] = s[3] + d;
|
|
119
|
+
s[4] = s[4] + e;
|
|
120
|
+
s[5] = s[5] + f;
|
|
121
|
+
s[6] = s[6] + g;
|
|
122
|
+
s[7] = s[7] + h;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function setUint64BigEndian(view, offset, value) {
|
|
126
|
+
view.setUint32(offset, Math.floor(value / 0x1_0000_0000));
|
|
127
|
+
view.setUint32(offset + 4, value >>> 0);
|
|
128
|
+
}
|
|
129
|
+
function rotr(value, bits) {
|
|
130
|
+
return (value >>> bits) | (value << (32 - bits));
|
|
131
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ReadFormatter } from "../contract/format.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Deep Agents style: the source with no gutter between two range lines,
|
|
4
|
+
* `@@ lines 1-100 of 250 | next offset 101 @@`. The offset is canonical
|
|
5
|
+
* (one-based). The total is left out when the scan stopped before EOF.
|
|
6
|
+
*/
|
|
7
|
+
export declare function deepAgentsFormat(): ReadFormatter<unknown>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { plainFormatter } from "../formatters/plain.js";
|
|
2
|
+
import { isDirectory, preset } from "./shared.js";
|
|
3
|
+
/**
|
|
4
|
+
* Deep Agents style: the source with no gutter between two range lines,
|
|
5
|
+
* `@@ lines 1-100 of 250 | next offset 101 @@`. The offset is canonical
|
|
6
|
+
* (one-based). The total is left out when the scan stopped before EOF.
|
|
7
|
+
*/
|
|
8
|
+
export function deepAgentsFormat() {
|
|
9
|
+
return preset("deep-agents", plainFormatter({ header: range, footer: range }));
|
|
10
|
+
}
|
|
11
|
+
function range(outcome) {
|
|
12
|
+
if (outcome.status !== "ok")
|
|
13
|
+
return null;
|
|
14
|
+
const noun = isDirectory(outcome) ? "entries" : "lines";
|
|
15
|
+
const { lines } = outcome.view;
|
|
16
|
+
const first = lines[0];
|
|
17
|
+
const last = lines.at(-1);
|
|
18
|
+
const shown = first && last ? `${noun} ${first.number}-${last.number}` : `no ${noun}`;
|
|
19
|
+
const total = outcome.totals.lines === null ? "" : ` of ${outcome.totals.lines}`;
|
|
20
|
+
const offset = outcome.continuation.next?.offset;
|
|
21
|
+
const next = offset === undefined ? "" : ` | next offset ${offset}`;
|
|
22
|
+
return `@@ ${shown}${total}${next} @@`;
|
|
23
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ReadFormatter } from "../contract/format.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Hashline style: a file hash line, then `12:a3|text` lines, and an
|
|
4
|
+
* end-of-file line. Directory entries get plain `1|` numbers.
|
|
5
|
+
*/
|
|
6
|
+
export declare function hashlineFormat(): ReadFormatter<unknown>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { eofFooter } from "../formatters/eof.js";
|
|
2
|
+
import { fileHashHeader } from "../formatters/file-hash.js";
|
|
3
|
+
import { hashlineGutter } from "../formatters/hashline.js";
|
|
4
|
+
import { lineNumberFormatter } from "../formatters/line-number.js";
|
|
5
|
+
import { preset } from "./shared.js";
|
|
6
|
+
/**
|
|
7
|
+
* Hashline style: a file hash line, then `12:a3|text` lines, and an
|
|
8
|
+
* end-of-file line. Directory entries get plain `1|` numbers.
|
|
9
|
+
*/
|
|
10
|
+
export function hashlineFormat() {
|
|
11
|
+
return preset("hashline", lineNumberFormatter({
|
|
12
|
+
gutter: hashlineGutter(),
|
|
13
|
+
header: fileHashHeader(),
|
|
14
|
+
footer: eofFooter(),
|
|
15
|
+
}), lineNumberFormatter());
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReadFormatter } from "../contract/format.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Hermes style: one JSON object. Text gives `content` with `12|text` lines,
|
|
4
|
+
* `total_lines`, `file_size`, `truncated` (any cut, a clamped line too),
|
|
5
|
+
* `next_offset` (when more lines follow), and the notes joined as `hint`.
|
|
6
|
+
* Failures and refusals give `error`, and a miss adds `similar_files`. Media
|
|
7
|
+
* gives the JSON, then the media parts. "view" mode leaves out the hint.
|
|
8
|
+
*/
|
|
9
|
+
export declare function hermesFormat(): ReadFormatter<unknown>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { jsonFormatter } from "../formatters/json.js";
|
|
2
|
+
import { isDirectory } from "./shared.js";
|
|
3
|
+
/**
|
|
4
|
+
* Hermes style: one JSON object. Text gives `content` with `12|text` lines,
|
|
5
|
+
* `total_lines`, `file_size`, `truncated` (any cut, a clamped line too),
|
|
6
|
+
* `next_offset` (when more lines follow), and the notes joined as `hint`.
|
|
7
|
+
* Failures and refusals give `error`, and a miss adds `similar_files`. Media
|
|
8
|
+
* gives the JSON, then the media parts. "view" mode leaves out the hint.
|
|
9
|
+
*/
|
|
10
|
+
export function hermesFormat() {
|
|
11
|
+
const json = jsonFormatter({ pick: hermesFields });
|
|
12
|
+
return Object.freeze({
|
|
13
|
+
id: "hermes",
|
|
14
|
+
format: (outcome, ctx) => json.format(ctx.mode === "view" ? withoutNotes(outcome) : outcome, ctx),
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function withoutNotes(outcome) {
|
|
18
|
+
return { ...outcome, notes: [] };
|
|
19
|
+
}
|
|
20
|
+
function hermesFields(outcome) {
|
|
21
|
+
const messages = outcome.notes.map((note) => note.message).join(" ");
|
|
22
|
+
const hint = messages === "" ? {} : { hint: messages };
|
|
23
|
+
const size = outcome.file?.size ?? null;
|
|
24
|
+
const fileSize = size === null ? {} : { file_size: size };
|
|
25
|
+
switch (outcome.status) {
|
|
26
|
+
case "ok": {
|
|
27
|
+
const directory = isDirectory(outcome);
|
|
28
|
+
const content = outcome.view.lines
|
|
29
|
+
.map((line) => directory
|
|
30
|
+
? line.text
|
|
31
|
+
: `${line.number}|${line.text}${line.clamped ? "... [truncated]" : ""}`)
|
|
32
|
+
.join("\n");
|
|
33
|
+
const total = outcome.totals.lines;
|
|
34
|
+
const offset = outcome.continuation.next?.offset;
|
|
35
|
+
return {
|
|
36
|
+
content,
|
|
37
|
+
...(total === null ? {} : { [directory ? "total_entries" : "total_lines"]: total }),
|
|
38
|
+
...fileSize,
|
|
39
|
+
truncated: outcome.truncation.truncated,
|
|
40
|
+
...(offset === undefined ? {} : { next_offset: offset }),
|
|
41
|
+
...hint,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
case "media": {
|
|
45
|
+
const text = outcome.parts.flatMap((part) => (part.type === "text" ? [part.text] : []));
|
|
46
|
+
return {
|
|
47
|
+
...(text.length === 0 ? {} : { content: text.join("\n") }),
|
|
48
|
+
mime_type: outcome.conversion.mimeType,
|
|
49
|
+
...fileSize,
|
|
50
|
+
...hint,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
case "unsupported":
|
|
54
|
+
return { error: messages || outcome.code, ...fileSize };
|
|
55
|
+
case "error":
|
|
56
|
+
return { error: messages || outcome.error.code, ...similarFiles(outcome.notes) };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function similarFiles(notes) {
|
|
60
|
+
const suggestions = notes.find((note) => note.code === "not-found")?.data?.["suggestions"];
|
|
61
|
+
return Array.isArray(suggestions) && suggestions.length > 0 ? { similar_files: suggestions } : {};
|
|
62
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ReadFormatter } from "../contract/format.ts";
|
|
2
|
+
/**
|
|
3
|
+
* OpenCode style: path and type tags, then `1: text` lines in `<content>`,
|
|
4
|
+
* and an end-of-file line. Directory entries go in `<entries>` with no
|
|
5
|
+
* numbers. Notes follow the closing tag.
|
|
6
|
+
*/
|
|
7
|
+
export declare function opencodeFormat(): ReadFormatter<unknown>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { eofFooter } from "../formatters/eof.js";
|
|
2
|
+
import { lineNumberFormatter } from "../formatters/line-number.js";
|
|
3
|
+
import { plainFormatter } from "../formatters/plain.js";
|
|
4
|
+
import { isDirectory, preset } from "./shared.js";
|
|
5
|
+
const eof = eofFooter();
|
|
6
|
+
/**
|
|
7
|
+
* OpenCode style: path and type tags, then `1: text` lines in `<content>`,
|
|
8
|
+
* and an end-of-file line. Directory entries go in `<entries>` with no
|
|
9
|
+
* numbers. Notes follow the closing tag.
|
|
10
|
+
*/
|
|
11
|
+
export function opencodeFormat() {
|
|
12
|
+
const options = { header, footer };
|
|
13
|
+
return preset("opencode", lineNumberFormatter({ ...options, gutter: (line) => `${line.number}: ` }), plainFormatter(options));
|
|
14
|
+
}
|
|
15
|
+
function header(outcome) {
|
|
16
|
+
if (outcome.status === "media") {
|
|
17
|
+
return `<path>${outcome.file.displayPath}</path>\n<type>media</type>`;
|
|
18
|
+
}
|
|
19
|
+
if (outcome.status !== "ok")
|
|
20
|
+
return null;
|
|
21
|
+
const directory = isDirectory(outcome);
|
|
22
|
+
return [
|
|
23
|
+
`<path>${outcome.file.displayPath}</path>`,
|
|
24
|
+
`<type>${directory ? "directory" : "file"}</type>`,
|
|
25
|
+
directory ? "<entries>" : "<content>",
|
|
26
|
+
].join("\n");
|
|
27
|
+
}
|
|
28
|
+
function footer(outcome, ctx) {
|
|
29
|
+
if (outcome.status !== "ok")
|
|
30
|
+
return null;
|
|
31
|
+
if (isDirectory(outcome))
|
|
32
|
+
return "</entries>";
|
|
33
|
+
const end = eof(outcome, ctx);
|
|
34
|
+
return end === null ? "</content>" : `\n${end}\n</content>`;
|
|
35
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ReadFormatter } from "../contract/format.ts";
|
|
2
|
+
import type { ReadReport } from "../contract/result.ts";
|
|
3
|
+
export declare function isDirectory(outcome: ReadReport): boolean;
|
|
4
|
+
export declare function preset(id: string, file: ReadFormatter<unknown>, directory?: ReadFormatter<unknown>): ReadFormatter<unknown>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function isDirectory(outcome) {
|
|
2
|
+
return outcome.status === "ok" && outcome.classification.kind === "directory";
|
|
3
|
+
}
|
|
4
|
+
export function preset(id, file, directory = file) {
|
|
5
|
+
return Object.freeze({
|
|
6
|
+
id,
|
|
7
|
+
format: (outcome, ctx) => (isDirectory(outcome) ? directory : file).format(outcome, ctx),
|
|
8
|
+
});
|
|
9
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { LineNumberFormatterOptions } from "./line-number.ts";
|
|
2
|
+
/**
|
|
3
|
+
* An end-of-file footer, shown only when the view ends at the file's last line
|
|
4
|
+
* and the total is exact. An empty file counts. Directory listings and views a
|
|
5
|
+
* hook emptied do not.
|
|
6
|
+
*/
|
|
7
|
+
export declare function eofFooter(text?: (totalLines: number) => string): NonNullable<LineNumberFormatterOptions["footer"]>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An end-of-file footer, shown only when the view ends at the file's last line
|
|
3
|
+
* and the total is exact. An empty file counts. Directory listings and views a
|
|
4
|
+
* hook emptied do not.
|
|
5
|
+
*/
|
|
6
|
+
export function eofFooter(text = (n) => `(End of file - total ${n} lines)`) {
|
|
7
|
+
return (outcome) => {
|
|
8
|
+
const total = eofTotal(outcome);
|
|
9
|
+
return total === null ? null : text(total);
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function eofTotal(outcome) {
|
|
13
|
+
if (outcome.status !== "ok" || outcome.classification.kind === "directory")
|
|
14
|
+
return null;
|
|
15
|
+
const total = outcome.totals.lines;
|
|
16
|
+
if (!outcome.totals.exact || total === null || outcome.continuation.available)
|
|
17
|
+
return null;
|
|
18
|
+
const last = outcome.view.lines.at(-1)?.number ?? 0;
|
|
19
|
+
return last === total ? total : null;
|
|
20
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { LineNumberFormatterOptions } from "./line-number.ts";
|
|
2
|
+
/**
|
|
3
|
+
* A "file-hash: <contentId>" header, where contentId is the hash of the source
|
|
4
|
+
* bytes. Shows nothing when the hash is unknown: no digest, a scan that stopped
|
|
5
|
+
* before EOF, or an outcome with no observation.
|
|
6
|
+
*/
|
|
7
|
+
export declare function fileHashHeader(): NonNullable<LineNumberFormatterOptions["header"]>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A "file-hash: <contentId>" header, where contentId is the hash of the source
|
|
3
|
+
* bytes. Shows nothing when the hash is unknown: no digest, a scan that stopped
|
|
4
|
+
* before EOF, or an outcome with no observation.
|
|
5
|
+
*/
|
|
6
|
+
export function fileHashHeader() {
|
|
7
|
+
return (outcome) => {
|
|
8
|
+
if (outcome.status !== "ok" && outcome.status !== "media")
|
|
9
|
+
return null;
|
|
10
|
+
const contentId = outcome.observation?.contentId ?? null;
|
|
11
|
+
return contentId === null ? null : `file-hash: ${contentId}`;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { LineNumberFormatterOptions } from "./line-number.ts";
|
|
2
|
+
export interface HashlineGutterOptions {
|
|
3
|
+
/** Characters of the line hash to show. Default 2. */
|
|
4
|
+
width?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* A "12:a3|" gutter: the line number and the start of the line's hash, so the
|
|
8
|
+
* id is stable across reads and changes when the line changes. With no digest
|
|
9
|
+
* the gutter is the number alone: "12|".
|
|
10
|
+
*/
|
|
11
|
+
export declare function hashlineGutter(options?: HashlineGutterOptions): NonNullable<LineNumberFormatterOptions["gutter"]>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A "12:a3|" gutter: the line number and the start of the line's hash, so the
|
|
3
|
+
* id is stable across reads and changes when the line changes. With no digest
|
|
4
|
+
* the gutter is the number alone: "12|".
|
|
5
|
+
*/
|
|
6
|
+
export function hashlineGutter(options = {}) {
|
|
7
|
+
const width = options.width ?? 2;
|
|
8
|
+
if (!Number.isSafeInteger(width) || width < 1) {
|
|
9
|
+
throw new TypeError("hashlineGutter width must be a positive integer");
|
|
10
|
+
}
|
|
11
|
+
return (line, ctx) => {
|
|
12
|
+
if (ctx.digest === null)
|
|
13
|
+
return `${line.number}|`;
|
|
14
|
+
const value = ctx.digest.hash(line.text);
|
|
15
|
+
const afterAlgorithmPrefix = value.lastIndexOf(":") + 1;
|
|
16
|
+
return `${line.number}:${value.slice(afterAlgorithmPrefix, afterAlgorithmPrefix + width)}|`;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { eofFooter } from "./eof.ts";
|
|
2
|
+
export { fileHashHeader } from "./file-hash.ts";
|
|
3
|
+
export { hashlineGutter } from "./hashline.ts";
|
|
4
|
+
export type { HashlineGutterOptions } from "./hashline.ts";
|
|
5
|
+
export { jsonFormatter } from "./json.ts";
|
|
6
|
+
export type { JsonFormatterOptions } from "./json.ts";
|
|
7
|
+
export { defaultNoteLine } from "./layout.ts";
|
|
8
|
+
export { lineNumberFormatter } from "./line-number.ts";
|
|
9
|
+
export type { LineNumberFormatterOptions } from "./line-number.ts";
|
|
10
|
+
export { plainFormatter } from "./plain.ts";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { eofFooter } from "./eof.js";
|
|
2
|
+
export { fileHashHeader } from "./file-hash.js";
|
|
3
|
+
export { hashlineGutter } from "./hashline.js";
|
|
4
|
+
export { jsonFormatter } from "./json.js";
|
|
5
|
+
export { defaultNoteLine } from "./layout.js";
|
|
6
|
+
export { lineNumberFormatter } from "./line-number.js";
|
|
7
|
+
export { plainFormatter } from "./plain.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReadFormatter } from "../contract/format.ts";
|
|
2
|
+
import type { JsonObject } from "../contract/json.ts";
|
|
3
|
+
import type { ReadReport } from "../contract/result.ts";
|
|
4
|
+
export interface JsonFormatterOptions {
|
|
5
|
+
/** Default: the whole outcome. */
|
|
6
|
+
pick?: (outcome: ReadReport) => JsonObject;
|
|
7
|
+
/** "inside" (default) puts notes in the JSON; "after" adds note lines after it. */
|
|
8
|
+
notes?: "inside" | "after";
|
|
9
|
+
space?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* JSON text of pick(outcome), for hosts that render the result themselves.
|
|
13
|
+
* With notes "after", and in "view" mode, the top-level `notes` key is left out.
|
|
14
|
+
* Media bytes are left out of the JSON: each byte array becomes its length.
|
|
15
|
+
* A media outcome returns the JSON text part, then its media parts.
|
|
16
|
+
*/
|
|
17
|
+
export declare function jsonFormatter(options?: JsonFormatterOptions): ReadFormatter<unknown>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { join, noteLines } from "./layout.js";
|
|
2
|
+
/**
|
|
3
|
+
* JSON text of pick(outcome), for hosts that render the result themselves.
|
|
4
|
+
* With notes "after", and in "view" mode, the top-level `notes` key is left out.
|
|
5
|
+
* Media bytes are left out of the JSON: each byte array becomes its length.
|
|
6
|
+
* A media outcome returns the JSON text part, then its media parts.
|
|
7
|
+
*/
|
|
8
|
+
export function jsonFormatter(options = {}) {
|
|
9
|
+
const pick = options.pick ?? ((outcome) => outcome);
|
|
10
|
+
const placement = options.notes ?? "inside";
|
|
11
|
+
const space = options.space ?? 0;
|
|
12
|
+
const stringify = (value) => JSON.stringify(value, withoutBytes, space);
|
|
13
|
+
return Object.freeze({
|
|
14
|
+
id: "json",
|
|
15
|
+
format(outcome, ctx) {
|
|
16
|
+
const picked = pick(outcome);
|
|
17
|
+
let text;
|
|
18
|
+
if (placement === "inside" && ctx.mode === "model") {
|
|
19
|
+
text = stringify(picked);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const json = stringify(withoutNotes(picked));
|
|
23
|
+
text = ctx.mode === "view" ? json : join(json, noteLines(outcome.notes, {}));
|
|
24
|
+
}
|
|
25
|
+
if (outcome.status !== "media")
|
|
26
|
+
return text;
|
|
27
|
+
return [{ type: "text", text }, ...outcome.parts.filter((part) => part.type === "media")];
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function withoutBytes(_key, value) {
|
|
32
|
+
return value instanceof Uint8Array ? { bytes: value.byteLength } : value;
|
|
33
|
+
}
|
|
34
|
+
function withoutNotes(value) {
|
|
35
|
+
return Object.fromEntries(Object.entries(value).filter(([key]) => key !== "notes"));
|
|
36
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ReadFormatContext } from "../contract/format.ts";
|
|
2
|
+
import type { ContentPart, ReadNote, ReadReport } from "../contract/result.ts";
|
|
3
|
+
export interface LayoutOptions {
|
|
4
|
+
header?: (outcome: ReadReport, ctx: ReadFormatContext<unknown>) => string | null;
|
|
5
|
+
footer?: (outcome: ReadReport, ctx: ReadFormatContext<unknown>) => string | null;
|
|
6
|
+
/** Filter or rewrite a note for display. null hides it. */
|
|
7
|
+
notes?: (note: ReadNote) => ReadNote | null;
|
|
8
|
+
/** Default: `[read:${note.code}] ${note.message}`. */
|
|
9
|
+
noteLine?: (note: ReadNote) => string;
|
|
10
|
+
}
|
|
11
|
+
export declare function defaultNoteLine(note: ReadNote): string;
|
|
12
|
+
export declare function layout(outcome: ReadReport, ctx: ReadFormatContext<unknown>, body: string, options: LayoutOptions): string;
|
|
13
|
+
export declare function noteLines(notes: readonly ReadNote[], options: LayoutOptions): string[];
|
|
14
|
+
export declare function join(main: string, lines: readonly string[]): string;
|
|
15
|
+
export declare function withParts(outcome: ReadReport, text: string): string | readonly ContentPart[];
|