@earendil-works/pi-durable 1.2.0 → 1.4.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/CHANGELOG.md +56 -0
- package/README.md +128 -3
- package/dist/codemode/codemode.d.ts.map +1 -1
- package/dist/codemode/codemode.js +12 -4
- package/dist/codemode/codemode.js.map +1 -1
- package/dist/codemode/models.d.ts.map +1 -1
- package/dist/codemode/models.js +2 -0
- package/dist/codemode/models.js.map +1 -1
- package/dist/documents.d.ts +1 -1
- package/dist/documents.d.ts.map +1 -1
- package/dist/documents.js.map +1 -1
- package/dist/env/index.d.ts +1 -0
- package/dist/env/index.d.ts.map +1 -1
- package/dist/env/index.js.map +1 -1
- package/dist/env/node.d.ts.map +1 -1
- package/dist/env/node.js +11 -3
- package/dist/env/node.js.map +1 -1
- package/dist/harness/agent.d.ts +13 -2
- package/dist/harness/agent.d.ts.map +1 -1
- package/dist/harness/agent.js +39 -5
- package/dist/harness/agent.js.map +1 -1
- package/dist/harness/compaction.d.ts +3 -1
- package/dist/harness/compaction.d.ts.map +1 -1
- package/dist/harness/compaction.js +19 -4
- package/dist/harness/compaction.js.map +1 -1
- package/dist/harness/context.d.ts +5 -0
- package/dist/harness/context.d.ts.map +1 -1
- package/dist/harness/context.js +46 -12
- package/dist/harness/context.js.map +1 -1
- package/dist/harness/generation.d.ts.map +1 -1
- package/dist/harness/generation.js +67 -27
- package/dist/harness/generation.js.map +1 -1
- package/dist/harness/harness.d.ts +5 -2
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +78 -13
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/inbox.d.ts +2 -2
- package/dist/harness/inbox.d.ts.map +1 -1
- package/dist/harness/inbox.js +3 -3
- package/dist/harness/inbox.js.map +1 -1
- package/dist/harness/prompt.d.ts +1 -1
- package/dist/harness/prompt.d.ts.map +1 -1
- package/dist/harness/prompt.js +1 -1
- package/dist/harness/prompt.js.map +1 -1
- package/dist/harness/scheduler.d.ts +55 -10
- package/dist/harness/scheduler.d.ts.map +1 -1
- package/dist/harness/scheduler.js +617 -124
- package/dist/harness/scheduler.js.map +1 -1
- package/dist/harness/submissions.d.ts +7 -2
- package/dist/harness/submissions.d.ts.map +1 -1
- package/dist/harness/submissions.js +48 -17
- package/dist/harness/submissions.js.map +1 -1
- package/dist/harness/task-graph.d.ts +18 -1
- package/dist/harness/task-graph.d.ts.map +1 -1
- package/dist/harness/task-graph.js +50 -11
- package/dist/harness/task-graph.js.map +1 -1
- package/dist/harness/telemetry.d.ts +307 -0
- package/dist/harness/telemetry.d.ts.map +1 -0
- package/dist/harness/telemetry.js +136 -0
- package/dist/harness/telemetry.js.map +1 -0
- package/dist/harness/tool.d.ts.map +1 -1
- package/dist/harness/tool.js +54 -8
- package/dist/harness/tool.js.map +1 -1
- package/dist/harness/types.d.ts +110 -15
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/util.d.ts +4 -0
- package/dist/harness/util.d.ts.map +1 -1
- package/dist/harness/util.js +4 -0
- package/dist/harness/util.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/session/session.d.ts +17 -6
- package/dist/session/session.d.ts.map +1 -1
- package/dist/session/session.js +64 -32
- package/dist/session/session.js.map +1 -1
- package/dist/session/transaction.d.ts +5 -1
- package/dist/session/transaction.d.ts.map +1 -1
- package/dist/session/transaction.js +26 -1
- package/dist/session/transaction.js.map +1 -1
- package/dist/storage/indexeddb.d.ts +60 -0
- package/dist/storage/indexeddb.d.ts.map +1 -0
- package/dist/storage/indexeddb.js +831 -0
- package/dist/storage/indexeddb.js.map +1 -0
- package/dist/storage/jsonl/storage.d.ts +16 -0
- package/dist/storage/jsonl/storage.d.ts.map +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/memory.js.map +1 -1
- package/dist/storage/sqlite/cloudflare.d.ts +6 -43
- package/dist/storage/sqlite/cloudflare.d.ts.map +1 -1
- package/dist/storage/sqlite/cloudflare.js +6 -101
- package/dist/storage/sqlite/cloudflare.js.map +1 -1
- package/dist/storage/sqlite/database.d.ts +1 -36
- package/dist/storage/sqlite/database.d.ts.map +1 -1
- package/dist/storage/sqlite/database.js.map +1 -1
- package/dist/storage/sqlite/index.d.ts +2 -2
- package/dist/storage/sqlite/index.d.ts.map +1 -1
- package/dist/storage/sqlite/index.js.map +1 -1
- package/dist/storage/sqlite/migrations.d.ts +7 -6
- package/dist/storage/sqlite/migrations.d.ts.map +1 -1
- package/dist/storage/sqlite/migrations.js +116 -106
- package/dist/storage/sqlite/migrations.js.map +1 -1
- package/dist/storage/sqlite/node.d.ts +6 -39
- package/dist/storage/sqlite/node.d.ts.map +1 -1
- package/dist/storage/sqlite/node.js +8 -175
- package/dist/storage/sqlite/node.js.map +1 -1
- package/dist/storage/sqlite/schema.d.ts +45 -0
- package/dist/storage/sqlite/schema.d.ts.map +1 -0
- package/dist/storage/sqlite/schema.js +50 -0
- package/dist/storage/sqlite/schema.js.map +1 -0
- package/dist/storage/sqlite/storage.d.ts +10 -2
- package/dist/storage/sqlite/storage.d.ts.map +1 -1
- package/dist/storage/sqlite/storage.js +46 -43
- package/dist/storage/sqlite/storage.js.map +1 -1
- package/dist/testing/storage-conformance.d.ts.map +1 -1
- package/dist/testing/storage-conformance.js +17 -0
- package/dist/testing/storage-conformance.js.map +1 -1
- package/dist/timers.d.ts +21 -0
- package/dist/timers.d.ts.map +1 -0
- package/dist/timers.js +72 -0
- package/dist/timers.js.map +1 -0
- package/dist/tools/image-processor.d.ts +4 -1
- package/dist/tools/image-processor.d.ts.map +1 -1
- package/dist/tools/image-processor.js +8 -18
- package/dist/tools/image-processor.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +1 -1
- package/dist/tools/read.js.map +1 -1
- package/dist/truncate.d.ts +3 -2
- package/dist/truncate.d.ts.map +1 -1
- package/dist/truncate.js +4 -34
- package/dist/truncate.js.map +1 -1
- package/dist/types.d.ts +130 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +15 -5
- package/dist/harness/output.d.ts +0 -69
- package/dist/harness/output.d.ts.map +0 -1
- package/dist/harness/output.js +0 -318
- package/dist/harness/output.js.map +0 -1
package/dist/timers.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SetTimer } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The default `HarnessOptions.setTimer`: `setTimeout`, waiting out delays beyond its limit in steps. A background timer
|
|
4
|
+
* is unreferenced, so it never keeps the process alive. It is declined in Cloudflare Workers and wherever timers cannot
|
|
5
|
+
* be unreferenced.
|
|
6
|
+
*/
|
|
7
|
+
export declare const nativeTimer: SetTimer;
|
|
8
|
+
/** Timers the Session and Harness arm through the host's `setTimer`. */
|
|
9
|
+
export type Timers = {
|
|
10
|
+
/** A timer that paces or resumes work; when the host throws or declines it, `setTimeout` arms it instead. */
|
|
11
|
+
foreground(callback: () => void, delayMs: number): () => void;
|
|
12
|
+
/** A timer that only frees memory; `undefined` when the host declines it. */
|
|
13
|
+
background(callback: () => void, delayMs: number): (() => void) | undefined;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Wrap a host `setTimer`. A foreground timer must fire, or work stalls: one the host throws on or declines is armed
|
|
17
|
+
* with `setTimeout`, and the first such failure is reported. A background one the host throws on is reported and
|
|
18
|
+
* declined.
|
|
19
|
+
*/
|
|
20
|
+
export declare function hostTimers(setTimer: SetTimer | undefined, report: (error: unknown) => void): Timers;
|
|
21
|
+
//# sourceMappingURL=timers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timers.d.ts","sourceRoot":"","sources":["../src/timers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAW3C;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,QAmBzB,CAAC;AAEF,wEAAwE;AACxE,MAAM,MAAM,MAAM,GAAG;IACpB,6GAA6G;IAC7G,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,IAAI,CAAC;IAC9D,6EAA6E;IAC7E,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;CAC5E,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,CA4BnG"}
|
package/dist/timers.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Longest delay `setTimeout` supports; a longer one is waited for in several steps. */
|
|
2
|
+
const MAX_TIMER_DELAY = 2_147_483_647;
|
|
3
|
+
/**
|
|
4
|
+
* Cloudflare Workers: a pending timer keeps a Durable Object from hibernating or being evicted, even an unreferenced
|
|
5
|
+
* one (`nodejs_compat` gives timers an `unref()` that changes nothing).
|
|
6
|
+
*/
|
|
7
|
+
const inWorkers = typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers";
|
|
8
|
+
/**
|
|
9
|
+
* The default `HarnessOptions.setTimer`: `setTimeout`, waiting out delays beyond its limit in steps. A background timer
|
|
10
|
+
* is unreferenced, so it never keeps the process alive. It is declined in Cloudflare Workers and wherever timers cannot
|
|
11
|
+
* be unreferenced.
|
|
12
|
+
*/
|
|
13
|
+
export const nativeTimer = (callback, delayMs, options) => {
|
|
14
|
+
if (options.background && inWorkers)
|
|
15
|
+
return undefined;
|
|
16
|
+
const arm = (remaining) => {
|
|
17
|
+
const next = setTimeout(() => {
|
|
18
|
+
if (remaining > MAX_TIMER_DELAY)
|
|
19
|
+
handle = arm(remaining - MAX_TIMER_DELAY);
|
|
20
|
+
else
|
|
21
|
+
callback();
|
|
22
|
+
}, Math.min(remaining, MAX_TIMER_DELAY));
|
|
23
|
+
if (options.background)
|
|
24
|
+
next.unref?.();
|
|
25
|
+
return next;
|
|
26
|
+
};
|
|
27
|
+
let handle = arm(Math.max(0, delayMs));
|
|
28
|
+
if (options.background && typeof handle.unref !== "function") {
|
|
29
|
+
clearTimeout(handle);
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return () => clearTimeout(handle);
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Wrap a host `setTimer`. A foreground timer must fire, or work stalls: one the host throws on or declines is armed
|
|
36
|
+
* with `setTimeout`, and the first such failure is reported. A background one the host throws on is reported and
|
|
37
|
+
* declined.
|
|
38
|
+
*/
|
|
39
|
+
export function hostTimers(setTimer, report) {
|
|
40
|
+
const arm = setTimer ?? nativeTimer;
|
|
41
|
+
let reported = false;
|
|
42
|
+
const fallBack = (error) => {
|
|
43
|
+
if (reported)
|
|
44
|
+
return;
|
|
45
|
+
reported = true;
|
|
46
|
+
report(error);
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
foreground: (callback, delayMs) => {
|
|
50
|
+
try {
|
|
51
|
+
const disarm = arm(callback, delayMs, { background: false });
|
|
52
|
+
if (disarm !== undefined)
|
|
53
|
+
return disarm;
|
|
54
|
+
fallBack(new Error("HarnessOptions.setTimer declined a timer that paces work; using setTimeout"));
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
fallBack(error);
|
|
58
|
+
}
|
|
59
|
+
return nativeTimer(callback, delayMs, { background: false });
|
|
60
|
+
},
|
|
61
|
+
background: (callback, delayMs) => {
|
|
62
|
+
try {
|
|
63
|
+
return arm(callback, delayMs, { background: true });
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
report(error);
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=timers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timers.js","sourceRoot":"","sources":["../src/timers.ts"],"names":[],"mappings":"AAEA,wFAAwF;AACxF,MAAM,eAAe,GAAG,aAAa,CAAC;AAEtC;;;GAGG;AACH,MAAM,SAAS,GAAG,OAAO,SAAS,KAAK,WAAW,IAAI,SAAS,CAAC,SAAS,KAAK,oBAAoB,CAAC;AAEnG;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE;IACnE,IAAI,OAAO,CAAC,UAAU,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IACtD,MAAM,GAAG,GAAG,CAAC,SAAiB,EAAiC,EAAE;QAChE,MAAM,IAAI,GAAG,UAAU,CACtB,GAAG,EAAE;YACJ,IAAI,SAAS,GAAG,eAAe;gBAAE,MAAM,GAAG,GAAG,CAAC,SAAS,GAAG,eAAe,CAAC,CAAC;;gBACtE,QAAQ,EAAE,CAAC;QACjB,CAAC,EACD,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,eAAe,CAAC,CACpC,CAAC;QACF,IAAI,OAAO,CAAC,UAAU;YAAG,IAA+B,CAAC,KAAK,EAAE,EAAE,CAAC;QACnE,OAAO,IAAI,CAAC;IACb,CAAC,CAAC;IACF,IAAI,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IACvC,IAAI,OAAO,CAAC,UAAU,IAAI,OAAQ,MAA8B,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QACvF,YAAY,CAAC,MAAM,CAAC,CAAC;QACrB,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;AACnC,CAAC,CAAC;AAUF;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,QAA8B,EAAE,MAAgC;IAC1F,MAAM,GAAG,GAAG,QAAQ,IAAI,WAAW,CAAC;IACpC,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAQ,EAAE;QACzC,IAAI,QAAQ;YAAE,OAAO;QACrB,QAAQ,GAAG,IAAI,CAAC;QAChB,MAAM,CAAC,KAAK,CAAC,CAAC;IACf,CAAC,CAAC;IACF,OAAO;QACN,UAAU,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE;YACjC,IAAI,CAAC;gBACJ,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC7D,IAAI,MAAM,KAAK,SAAS;oBAAE,OAAO,MAAM,CAAC;gBACxC,QAAQ,CAAC,IAAI,KAAK,CAAC,4EAA4E,CAAC,CAAC,CAAC;YACnG,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,QAAQ,CAAC,KAAK,CAAC,CAAC;YACjB,CAAC;YACD,OAAO,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAE,CAAC;QAC/D,CAAC;QACD,UAAU,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE;YACjC,IAAI,CAAC;gBACJ,OAAO,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;YACrD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,MAAM,CAAC,KAAK,CAAC,CAAC;gBACd,OAAO,SAAS,CAAC;YAClB,CAAC;QACF,CAAC;KACD,CAAC;AACH,CAAC","sourcesContent":["import type { SetTimer } from \"./types.ts\";\n\n/** Longest delay `setTimeout` supports; a longer one is waited for in several steps. */\nconst MAX_TIMER_DELAY = 2_147_483_647;\n\n/**\n * Cloudflare Workers: a pending timer keeps a Durable Object from hibernating or being evicted, even an unreferenced\n * one (`nodejs_compat` gives timers an `unref()` that changes nothing).\n */\nconst inWorkers = typeof navigator !== \"undefined\" && navigator.userAgent === \"Cloudflare-Workers\";\n\n/**\n * The default `HarnessOptions.setTimer`: `setTimeout`, waiting out delays beyond its limit in steps. A background timer\n * is unreferenced, so it never keeps the process alive. It is declined in Cloudflare Workers and wherever timers cannot\n * be unreferenced.\n */\nexport const nativeTimer: SetTimer = (callback, delayMs, options) => {\n\tif (options.background && inWorkers) return undefined;\n\tconst arm = (remaining: number): ReturnType<typeof setTimeout> => {\n\t\tconst next = setTimeout(\n\t\t\t() => {\n\t\t\t\tif (remaining > MAX_TIMER_DELAY) handle = arm(remaining - MAX_TIMER_DELAY);\n\t\t\t\telse callback();\n\t\t\t},\n\t\t\tMath.min(remaining, MAX_TIMER_DELAY),\n\t\t);\n\t\tif (options.background) (next as { unref?: () => void }).unref?.();\n\t\treturn next;\n\t};\n\tlet handle = arm(Math.max(0, delayMs));\n\tif (options.background && typeof (handle as { unref?: unknown }).unref !== \"function\") {\n\t\tclearTimeout(handle);\n\t\treturn undefined;\n\t}\n\treturn () => clearTimeout(handle);\n};\n\n/** Timers the Session and Harness arm through the host's `setTimer`. */\nexport type Timers = {\n\t/** A timer that paces or resumes work; when the host throws or declines it, `setTimeout` arms it instead. */\n\tforeground(callback: () => void, delayMs: number): () => void;\n\t/** A timer that only frees memory; `undefined` when the host declines it. */\n\tbackground(callback: () => void, delayMs: number): (() => void) | undefined;\n};\n\n/**\n * Wrap a host `setTimer`. A foreground timer must fire, or work stalls: one the host throws on or declines is armed\n * with `setTimeout`, and the first such failure is reported. A background one the host throws on is reported and\n * declined.\n */\nexport function hostTimers(setTimer: SetTimer | undefined, report: (error: unknown) => void): Timers {\n\tconst arm = setTimer ?? nativeTimer;\n\tlet reported = false;\n\tconst fallBack = (error: unknown): void => {\n\t\tif (reported) return;\n\t\treported = true;\n\t\treport(error);\n\t};\n\treturn {\n\t\tforeground: (callback, delayMs) => {\n\t\t\ttry {\n\t\t\t\tconst disarm = arm(callback, delayMs, { background: false });\n\t\t\t\tif (disarm !== undefined) return disarm;\n\t\t\t\tfallBack(new Error(\"HarnessOptions.setTimer declined a timer that paces work; using setTimeout\"));\n\t\t\t} catch (error) {\n\t\t\t\tfallBack(error);\n\t\t\t}\n\t\t\treturn nativeTimer(callback, delayMs, { background: false })!;\n\t\t},\n\t\tbackground: (callback, delayMs) => {\n\t\t\ttry {\n\t\t\t\treturn arm(callback, delayMs, { background: true });\n\t\t\t} catch (error) {\n\t\t\t\treport(error);\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
|
|
@@ -40,7 +40,10 @@ export type ImageProcessor = {
|
|
|
40
40
|
};
|
|
41
41
|
/** Formats every provider takes inline; others, such as BMP, need converting. */
|
|
42
42
|
export declare const INLINE_IMAGE_TYPES: ReadonlySet<string>;
|
|
43
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Standard base64 of `bytes`. `btoa` takes a binary string, built 8 KiB at a time: a single `String.fromCharCode`
|
|
45
|
+
* call with megabytes of arguments overflows the stack.
|
|
46
|
+
*/
|
|
44
47
|
export declare function toBase64(bytes: Uint8Array): string;
|
|
45
48
|
/** The length of the base64 of `byteLength` bytes. */
|
|
46
49
|
export declare function base64Length(byteLength: number): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-processor.d.ts","sourceRoot":"","sources":["../../src/tools/image-processor.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,MAAM,MAAM,WAAW,GAAG;IACzB,4BAA4B;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4BAA4B;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,WAA8E,CAAC;AAElH,qFAAqF;AACrF,MAAM,MAAM,aAAa,GAAG;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,wGAAwG;IACxG,QAAQ,CAAC,OAAO,CAAC,EAAE;QAClB,QAAQ,CAAC,IAAI,EAAE;YAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;QACnE,QAAQ,CAAC,EAAE,EAAE;YAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;KACjE,CAAC;IACF,8GAA8G;IAC9G,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG;IAC5B;;;OAGG;IACH,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC;CACtG,CAAC;AAEF,iFAAiF;AACjF,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAmE,CAAC;
|
|
1
|
+
{"version":3,"file":"image-processor.d.ts","sourceRoot":"","sources":["../../src/tools/image-processor.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,MAAM,MAAM,WAAW,GAAG;IACzB,4BAA4B;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4BAA4B;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,WAA8E,CAAC;AAElH,qFAAqF;AACrF,MAAM,MAAM,aAAa,GAAG;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,wGAAwG;IACxG,QAAQ,CAAC,OAAO,CAAC,EAAE;QAClB,QAAQ,CAAC,IAAI,EAAE;YAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;QACnE,QAAQ,CAAC,EAAE,EAAE;YAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;KACjE,CAAC;IACF,8GAA8G;IAC9G,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG;IAC5B;;;OAGG;IACH,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC;CACtG,CAAC;AAEF,iFAAiF;AACjF,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAmE,CAAC;AAEvH;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAOlD;AAED,sDAAsD;AACtD,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEvD"}
|
|
@@ -1,27 +1,17 @@
|
|
|
1
1
|
export const DEFAULT_IMAGE_LIMITS = { maxWidth: 2000, maxHeight: 2000, maxBytes: 4.5 * 1024 * 1024 };
|
|
2
2
|
/** Formats every provider takes inline; others, such as BMP, need converting. */
|
|
3
3
|
export const INLINE_IMAGE_TYPES = new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Standard base64 of `bytes`. `btoa` takes a binary string, built 8 KiB at a time: a single `String.fromCharCode`
|
|
6
|
+
* call with megabytes of arguments overflows the stack.
|
|
7
|
+
*/
|
|
6
8
|
export function toBase64(bytes) {
|
|
7
9
|
const parts = [];
|
|
8
|
-
let
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const b = bytes[i + 1];
|
|
12
|
-
const c = bytes[i + 2];
|
|
13
|
-
chunk += BASE64[a >> 2];
|
|
14
|
-
chunk += BASE64[((a & 3) << 4) | ((b ?? 0) >> 4)];
|
|
15
|
-
chunk += b === undefined ? "=" : BASE64[((b & 15) << 2) | ((c ?? 0) >> 6)];
|
|
16
|
-
chunk += c === undefined ? "=" : BASE64[c & 63];
|
|
17
|
-
// Join in pieces: one string grown a character at a time is slow for megabytes.
|
|
18
|
-
if (chunk.length >= 8192) {
|
|
19
|
-
parts.push(chunk);
|
|
20
|
-
chunk = "";
|
|
21
|
-
}
|
|
10
|
+
for (let i = 0; i < bytes.length; i += 8192) {
|
|
11
|
+
// `apply` takes any array-like, though its type asks for an array.
|
|
12
|
+
parts.push(String.fromCharCode.apply(null, bytes.subarray(i, i + 8192)));
|
|
22
13
|
}
|
|
23
|
-
parts.
|
|
24
|
-
return parts.join("");
|
|
14
|
+
return btoa(parts.join(""));
|
|
25
15
|
}
|
|
26
16
|
/** The length of the base64 of `byteLength` bytes. */
|
|
27
17
|
export function base64Length(byteLength) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-processor.js","sourceRoot":"","sources":["../../src/tools/image-processor.ts"],"names":[],"mappings":"AAUA,MAAM,CAAC,MAAM,oBAAoB,GAAgB,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;AA4BlH,iFAAiF;AACjF,MAAM,CAAC,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;AAEvH
|
|
1
|
+
{"version":3,"file":"image-processor.js","sourceRoot":"","sources":["../../src/tools/image-processor.ts"],"names":[],"mappings":"AAUA,MAAM,CAAC,MAAM,oBAAoB,GAAgB,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;AA4BlH,iFAAiF;AACjF,MAAM,CAAC,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;AAEvH;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAiB;IACzC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC;QAC7C,mEAAmE;QACnE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAwB,CAAC,CAAC,CAAC;IACjG,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;AAC7B,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,YAAY,CAAC,UAAkB;IAC9C,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;AACtC,CAAC","sourcesContent":["/** Limits an image the model sees must keep; the defaults below are the coding agent's. */\nexport type ImageLimits = {\n\t/** Pixels. Default 2000. */\n\treadonly maxWidth: number;\n\t/** Pixels. Default 2000. */\n\treadonly maxHeight: number;\n\t/** Bytes of base64. Default 4.5 MiB, below Anthropic's 5 MB. */\n\treadonly maxBytes: number;\n};\n\nexport const DEFAULT_IMAGE_LIMITS: ImageLimits = { maxWidth: 2000, maxHeight: 2000, maxBytes: 4.5 * 1024 * 1024 };\n\n/** An image the model can take: a supported format, within the limits, as base64. */\nexport type PreparedImage = {\n\treadonly data: string;\n\treadonly mimeType: string;\n\t/** Set when the image was resized: its size before and after, for the model to map coordinates back. */\n\treadonly resized?: {\n\t\treadonly from: { readonly width: number; readonly height: number };\n\t\treadonly to: { readonly width: number; readonly height: number };\n\t};\n\t/** The original format, set when it differs from `mimeType`: BMP becomes PNG, a large PNG may become JPEG. */\n\treadonly convertedFrom?: string;\n};\n\n/**\n * Decodes, orients, resizes, and re-encodes images so the model can take them. `read` uses one when given; without\n * one, it passes supported images within the byte limit through as they are. `@earendil-works/pi-durable/images`\n * provides a Photon-based one.\n */\nexport type ImageProcessor = {\n\t/**\n\t * The image, prepared to fit `limits`, or `undefined` when it cannot be decoded or made to fit. A promise, so a\n\t * processor can do the work off the event loop.\n\t */\n\tprepare(bytes: Uint8Array, mimeType: string, limits: ImageLimits): Promise<PreparedImage | undefined>;\n};\n\n/** Formats every provider takes inline; others, such as BMP, need converting. */\nexport const INLINE_IMAGE_TYPES: ReadonlySet<string> = new Set([\"image/png\", \"image/jpeg\", \"image/gif\", \"image/webp\"]);\n\n/**\n * Standard base64 of `bytes`. `btoa` takes a binary string, built 8 KiB at a time: a single `String.fromCharCode`\n * call with megabytes of arguments overflows the stack.\n */\nexport function toBase64(bytes: Uint8Array): string {\n\tconst parts: string[] = [];\n\tfor (let i = 0; i < bytes.length; i += 8192) {\n\t\t// `apply` takes any array-like, though its type asks for an array.\n\t\tparts.push(String.fromCharCode.apply(null, bytes.subarray(i, i + 8192) as unknown as number[]));\n\t}\n\treturn btoa(parts.join(\"\"));\n}\n\n/** The length of the base64 of `byteLength` bytes. */\nexport function base64Length(byteLength: number): number {\n\treturn Math.ceil(byteLength / 3) * 4;\n}\n"]}
|
package/dist/tools/read.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAU5C,OAAO,KAAK,EAAyD,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACnH,OAAO,EAIN,KAAK,gBAAgB,EAGrB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAIN,KAAK,cAAc,EAGnB,MAAM,sBAAsB,CAAC;AAG9B,QAAA,MAAM,UAAU;;;;EAId,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAEtD,MAAM,MAAM,eAAe,GAAG;IAC7B,yEAAyE;IACzE,UAAU,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC;CAC/C,CAAC;AAmCF,MAAM,MAAM,eAAe,GAAG;IAC7B,wHAAwH;IACxH,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;CACjC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,eAAoB,GAAG,gBAAgB,CAAC,OAAO,UAAU,EAAE,eAAe,CAAC,CA0ClH"}
|
package/dist/tools/read.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { characterEnd } from "@earendil-works/chord/output";
|
|
1
2
|
import { Type } from "typebox";
|
|
2
3
|
import { getOrThrow, rangeDecoder, startsWithBom, } from "../env/index.js";
|
|
3
4
|
import { defineTool } from "../harness/define.js";
|
|
4
|
-
import { characterEnd } from "../harness/output.js";
|
|
5
5
|
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateHeadOf, utf8ByteLength, } from "../truncate.js";
|
|
6
6
|
import { requireEnv } from "./env.js";
|
|
7
7
|
import { detectSupportedImageMimeTypeOf, imageDimensions } from "./image.js";
|
package/dist/tools/read.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context } from \"@earendil-works/chord\";\nimport { type Static, Type } from \"typebox\";\nimport {\n\ttype BinaryReader,\n\ttype FileInfo,\n\tgetOrThrow,\n\ttype LineScan,\n\trangeDecoder,\n\tstartsWithBom,\n} from \"../env/index.ts\";\nimport { defineTool } from \"../harness/define.ts\";\nimport { characterEnd } from \"../harness/output.ts\";\nimport type { ToolDiagnostic, ToolExecutionApi, ToolExecutionResult, ToolRegistration } from \"../harness/types.ts\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\tformatSize,\n\ttype TruncationResult,\n\ttruncateHeadOf,\n\tutf8ByteLength,\n} from \"../truncate.ts\";\nimport { requireEnv } from \"./env.ts\";\nimport { detectSupportedImageMimeTypeOf, imageDimensions } from \"./image.ts\";\nimport {\n\tbase64Length,\n\tDEFAULT_IMAGE_LIMITS,\n\ttype ImageLimits,\n\ttype ImageProcessor,\n\tINLINE_IMAGE_TYPES,\n\ttoBase64,\n} from \"./image-processor.ts\";\nimport { resolveReadToolPath } from \"./path-utils.ts\";\n\nconst readSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n\toffset: Type.Optional(Type.Number({ description: \"Line number to start reading from (1-indexed)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\nexport type ReadToolInput = Static<typeof readSchema>;\n\nexport type ReadToolDetails = {\n\t/** How the shown text was cut; the text itself is the result content. */\n\ttruncation?: Omit<TruncationResult, \"content\">;\n};\n\nconst READ_CHUNK = 64 * 1024;\n\n/** `Array.prototype.slice`'s conversion of an index: NaN is 0, other values truncate toward zero. */\nfunction sliceIndex(value: number): number {\n\treturn Number.isNaN(value) ? 0 : Math.trunc(value);\n}\n\n/**\n * The decoded start of bytes `[start, end)` of the file, decoded as part of the whole file: all of it, or enough for\n * `truncateHeadOf` (more than `DEFAULT_MAX_BYTES + 1` bytes, or `DEFAULT_MAX_LINES` newlines).\n */\nasync function readHead(\n\treader: BinaryReader,\n\tstart: number,\n\tend: number,\n\tskipBom: boolean,\n\tcontext: Context,\n): Promise<string> {\n\tconst decoder = rangeDecoder();\n\tlet text = \"\";\n\tlet newlines = 0;\n\tfor (let position = skipBom && start === 0 ? 3 : start; position < end; ) {\n\t\tconst bytes = getOrThrow(await reader.read(position, Math.min(READ_CHUNK, end - position), context));\n\t\tif (bytes.length === 0) break;\n\t\tposition += bytes.length;\n\t\tconst decoded = decoder.decode(bytes, { stream: true });\n\t\ttext += decoded;\n\t\tfor (let index = decoded.indexOf(\"\\n\"); index !== -1; index = decoded.indexOf(\"\\n\", index + 1)) newlines++;\n\t\tif (newlines >= DEFAULT_MAX_LINES || utf8ByteLength(text) > DEFAULT_MAX_BYTES + 1) return text;\n\t}\n\treturn text + decoder.decode();\n}\n\nexport type ReadToolOptions = {\n\t/** Resizes and converts images; without one, images within `DEFAULT_IMAGE_LIMITS.maxBytes` pass through as they are. */\n\treadonly images?: ImageProcessor;\n};\n\n/**\n * Reads text files and images. Remarks about truncation, continuation, and image processing are diagnostics; the content\n * is only file text, or one image, which programs get as its `ImageContent`.\n */\nexport function createReadTool(options: ReadToolOptions = {}): ToolRegistration<typeof readSchema, ReadToolDetails> {\n\tconst formats = options.images === undefined ? \"jpg, png, gif, webp\" : \"jpg, png, gif, webp, bmp\";\n\treturn defineTool({\n\t\tname: \"read\",\n\t\tdescription: `Read the contents of a file. Supports text files and images (${formats}). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,\n\t\tparameters: readSchema,\n\t\tasync execute(args, api, context) {\n\t\t\tconst { path, offset, limit } = args;\n\t\t\tconst env = requireEnv(api);\n\t\t\tconst absolutePath = await resolveReadToolPath(env, path, context);\n\t\t\tconst reader = getOrThrow(await env.openBinaryReader(absolutePath, undefined, context));\n\t\t\ttry {\n\t\t\t\t// A concurrent writer can change the file between the scan and the reads. Appending (a growing log) leaves\n\t\t\t\t// the scanned text as it was; an image must be whole. Otherwise the file is read again once.\n\t\t\t\tfor (let attempt = 0; ; attempt++) {\n\t\t\t\t\tconst before = getOrThrow(await reader.info(context));\n\t\t\t\t\tconst mimeType = await detectSupportedImageMimeTypeOf({\n\t\t\t\t\t\tsize: before.size,\n\t\t\t\t\t\tread: async (position, length) => getOrThrow(await reader.read(position, length, context)),\n\t\t\t\t\t});\n\t\t\t\t\tconst result =\n\t\t\t\t\t\tmimeType === undefined\n\t\t\t\t\t\t\t? await readText(reader, path, offset, limit, context)\n\t\t\t\t\t\t\t: await readImage(\n\t\t\t\t\t\t\t\t\treader,\n\t\t\t\t\t\t\t\t\tbefore,\n\t\t\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\t\t\tmimeType,\n\t\t\t\t\t\t\t\t\toptions.images,\n\t\t\t\t\t\t\t\t\tawait imageModel(api, context),\n\t\t\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\tconst after = getOrThrow(await reader.info(context));\n\t\t\t\t\tconst unchanged = after.size === before.size && after.mtimeMs === before.mtimeMs;\n\t\t\t\t\tif (unchanged || (mimeType === undefined && after.size > before.size)) return result;\n\t\t\t\t\tif (attempt === 1) throw new Error(`${path} changed while it was read`);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tawait reader.close(context);\n\t\t\t}\n\t\t},\n\t});\n}\n\n/**\n * One image block, prepared by `processor` or, without one, as it is when its format is inline and it fits the limits\n * (its size is checked before reading it, its dimensions from its header); otherwise an error result saying why. What happened to the image is an `info` diagnostic, so programs get the\n * bare `ImageContent`. A model without image input sees pi-ai's placeholder instead; a diagnostic says so.\n */\nasync function readImage(\n\treader: BinaryReader,\n\tinfo: FileInfo,\n\tpath: string,\n\tmimeType: string,\n\tprocessor: ImageProcessor | undefined,\n\t{ vision, limits }: ImageModel,\n\tcontext: Context,\n): Promise<ToolExecutionResult<ReadToolDetails>> {\n\tlet image: { readonly data: string; readonly mimeType: string };\n\tconst notes: string[] = [];\n\tif (processor !== undefined) {\n\t\tconst bytes = getOrThrow(await reader.read(0, info.size, context));\n\t\tconst prepared = await processor.prepare(bytes, mimeType, limits);\n\t\tif (prepared === undefined)\n\t\t\treturn imageError(`${path} is an image (${mimeType}) that cannot be prepared for the model`);\n\t\timage = prepared;\n\t\tif (prepared.convertedFrom !== undefined)\n\t\t\tnotes.push(`Converted from ${prepared.convertedFrom} to ${prepared.mimeType}.`);\n\t\tif (prepared.resized !== undefined) {\n\t\t\tconst { from, to } = prepared.resized;\n\t\t\tconst scale = (from.width / to.width).toFixed(2);\n\t\t\tnotes.push(\n\t\t\t\t`Resized from ${from.width}x${from.height} to ${to.width}x${to.height}. Multiply coordinates by ${scale} to map them to the original.`,\n\t\t\t);\n\t\t}\n\t} else {\n\t\tif (!INLINE_IMAGE_TYPES.has(mimeType)) {\n\t\t\treturn imageError(\n\t\t\t\t`${path} is an image (${mimeType}) that needs converting, and no image processor is configured`,\n\t\t\t);\n\t\t}\n\t\tconst unshrinkable = \"and no image processor is configured to shrink it\";\n\t\tif (base64Length(info.size) > limits.maxBytes) {\n\t\t\treturn imageError(\n\t\t\t\t`${path} is an image (${mimeType}) of ${formatSize(info.size)}, too large to send, ${unshrinkable}`,\n\t\t\t);\n\t\t}\n\t\tconst bytes = getOrThrow(await reader.read(0, info.size, context));\n\t\tconst size = imageDimensions(bytes, mimeType);\n\t\tif (size === undefined) return imageError(`${path} is an image (${mimeType}) whose size cannot be read`);\n\t\tif (size.width > limits.maxWidth || size.height > limits.maxHeight) {\n\t\t\treturn imageError(\n\t\t\t\t`${path} is an image (${mimeType}) of ${size.width}x${size.height}, larger than ${limits.maxWidth}x${limits.maxHeight}, ${unshrinkable}`,\n\t\t\t);\n\t\t}\n\t\timage = { data: toBase64(bytes), mimeType };\n\t}\n\tif (!vision) notes.push(\"The current model does not support images; it sees a placeholder instead.\");\n\tconst diagnostics: ToolDiagnostic[] = [\n\t\t{ severity: \"info\", code: \"image\", message: [`Read image file [${image.mimeType}].`, ...notes].join(\" \") },\n\t];\n\treturn { output: [{ type: \"image\", data: image.data, mimeType: image.mimeType }], diagnostics };\n}\n\n/** What the conversation's model takes: whether images, and within which limits. */\ntype ImageModel = { readonly vision: boolean; readonly limits: ImageLimits };\n\n/**\n * The conversation model's image input: `DEFAULT_IMAGE_LIMITS` overridden by its `inputLimits.images.resize`. Without\n * a resolvable model, it counts as one that takes images within the defaults.\n */\nasync function imageModel(api: ToolExecutionApi, context: Context): Promise<ImageModel> {\n\tconst ref = (await api.agent(context)).model;\n\tconst model = ref === undefined ? undefined : api.models.getModel(ref.provider, ref.modelId);\n\tconst resize = model?.inputLimits?.images?.resize;\n\treturn {\n\t\tvision: model?.input.includes(\"image\") ?? true,\n\t\tlimits: {\n\t\t\tmaxWidth: resize?.maxWidth ?? DEFAULT_IMAGE_LIMITS.maxWidth,\n\t\t\tmaxHeight: resize?.maxHeight ?? DEFAULT_IMAGE_LIMITS.maxHeight,\n\t\t\tmaxBytes: resize?.maxBytes ?? DEFAULT_IMAGE_LIMITS.maxBytes,\n\t\t},\n\t};\n}\n\nfunction imageError(message: string): ToolExecutionResult<ReadToolDetails> {\n\treturn { output: [], isError: true, diagnostics: [{ severity: \"error\", code: \"unsupported_image\", message }] };\n}\n\n/**\n * The read result for the opened file. It equals decoding the whole file with `TextDecoder`, splitting it on `\\n`,\n * and bounding the selected lines with `truncateHead`, while reading only one scan's worth of the file plus the head.\n */\nasync function readText(\n\treader: BinaryReader,\n\tpath: string,\n\toffset: number | undefined,\n\tlimit: number | undefined,\n\tcontext: Context,\n) {\n\tconst startLine = offset ? Math.max(0, offset - 1) : 0;\n\tconst startLineDisplay = startLine + 1;\n\t// Lines are selected like `allLines.slice(startLine, endLine)`, which truncates fractional indices.\n\tconst sliceStart = sliceIndex(startLine);\n\t// One pass finds the line count and the selection; a selection past the last line ends with it, as `slice` does,\n\t// and an empty one (a zero or negative limit) is scanned as one line and then ignored.\n\t// A start beyond any file is scanned from 0 only to count lines; the offset check below then fails as before.\n\tconst scanStart = Number.isSafeInteger(sliceStart) ? sliceStart : 0;\n\tconst requestedEnd = limit === undefined ? undefined : Math.max(scanStart + 1, sliceIndex(startLine + limit));\n\tconst scanEnd = requestedEnd !== undefined && Number.isSafeInteger(requestedEnd) ? requestedEnd : undefined;\n\tconst scanOf = async (endLine: number | undefined): Promise<LineScan> =>\n\t\tgetOrThrow(\n\t\t\tawait reader.scanLines({ startLine: scanStart, ...(endLine === undefined ? {} : { endLine }) }, context),\n\t\t);\n\tlet scan = await scanOf(scanEnd);\n\tconst totalFileLines = scan.newlines + 1;\n\tif (startLine >= totalFileLines) {\n\t\tthrow new Error(`Offset ${offset} is beyond end of file (${totalFileLines} lines total)`);\n\t}\n\n\tlet userLimitedLines: number | undefined;\n\tlet selectedLineCount = totalFileLines - sliceStart;\n\tif (limit !== undefined) {\n\t\tconst endLine = Math.min(startLine + limit, totalFileLines);\n\t\tuserLimitedLines = endLine - startLine;\n\t\t// `slice` counts a negative end from the end of the lines, which only the line count tells; scan again for it.\n\t\tconst relativeEnd = sliceIndex(endLine);\n\t\tconst sliceEnd = relativeEnd < 0 ? Math.max(totalFileLines + relativeEnd, 0) : relativeEnd;\n\t\tselectedLineCount = Math.max(0, sliceEnd - sliceStart);\n\t\tif (selectedLineCount > 0 && relativeEnd < 0) scan = await scanOf(sliceEnd);\n\t}\n\tconst empty = selectedLineCount === 0;\n\t// Counted like `truncateHead`: a trailing newline adds no line, and empty text has none.\n\tconst endsWithNewline = !empty && scan.lastLineStart === scan.end && scan.lastLineStart > scan.start;\n\tconst totals = {\n\t\tlines: empty || scan.selectedBytes === 0 ? 0 : selectedLineCount - (endsWithNewline ? 1 : 0),\n\t\tbytes: empty ? 0 : scan.selectedBytes,\n\t};\n\tconst firstBytes = getOrThrow(await reader.read(0, 3, context));\n\tconst head = empty ? \"\" : await readHead(reader, scan.start, scan.end, startsWithBom(firstBytes), context);\n\n\tconst { content: headText, ...truncation } = truncateHeadOf(head, totals);\n\tconst diagnostics: ToolDiagnostic[] = [];\n\tlet outputText = headText;\n\tlet details: ReadToolDetails | undefined;\n\tif (truncation.firstLineExceedsLimit) {\n\t\t// Show the start of the line, cut at the byte limit on a character boundary. Like `allLines[startLine]`, a\n\t\t// fractional start line names no line.\n\t\tconst integral = Number.isInteger(startLine);\n\t\tconst lineBytes = new TextEncoder().encode(integral ? (head.split(\"\\n\")[0] ?? \"\") : \"\");\n\t\tconst lineSize = integral ? scan.firstLineBytes : 0;\n\t\tconst end = characterEnd(lineBytes, DEFAULT_MAX_BYTES);\n\t\toutputText = new TextDecoder().decode(lineBytes.subarray(0, end));\n\t\tdiagnostics.push({\n\t\t\tseverity: \"warn\",\n\t\t\tcode: \"truncated\",\n\t\t\tmessage: `Line ${startLineDisplay} is ${formatSize(lineSize)}, exceeds the ${formatSize(DEFAULT_MAX_BYTES)} limit; showing its first ${formatSize(end)}. Use bash: sed -n '${startLineDisplay}p' ${path} | tail -c +${end + 1}`,\n\t\t});\n\t\tdetails = { truncation: { ...truncation, outputBytes: end, outputLines: 1 } };\n\t} else if (truncation.truncated) {\n\t\tconst endLineDisplay = startLineDisplay + truncation.outputLines - 1;\n\t\tconst nextOffset = endLineDisplay + 1;\n\t\tconst limitText = truncation.truncatedBy === \"lines\" ? \"\" : ` (${formatSize(DEFAULT_MAX_BYTES)} limit)`;\n\t\tdiagnostics.push({\n\t\t\tseverity: \"info\",\n\t\t\tcode: \"truncated\",\n\t\t\tmessage: `Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}${limitText}. Use offset=${nextOffset} to continue.`,\n\t\t});\n\t\tdetails = { truncation };\n\t} else if (userLimitedLines !== undefined && startLine + userLimitedLines < totalFileLines) {\n\t\tconst remaining = totalFileLines - (startLine + userLimitedLines);\n\t\tconst nextOffset = startLine + userLimitedLines + 1;\n\t\tdiagnostics.push({\n\t\t\tseverity: \"info\",\n\t\t\tmessage: `${remaining} more lines in file. Use offset=${nextOffset} to continue.`,\n\t\t});\n\t}\n\n\treturn {\n\t\toutput: outputText === \"\" ? [] : [{ type: \"text\" as const, text: outputText }],\n\t\t...(details === undefined ? {} : { details }),\n\t\tdiagnostics,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context } from \"@earendil-works/chord\";\nimport { characterEnd } from \"@earendil-works/chord/output\";\nimport { type Static, Type } from \"typebox\";\nimport {\n\ttype BinaryReader,\n\ttype FileInfo,\n\tgetOrThrow,\n\ttype LineScan,\n\trangeDecoder,\n\tstartsWithBom,\n} from \"../env/index.ts\";\nimport { defineTool } from \"../harness/define.ts\";\nimport type { ToolDiagnostic, ToolExecutionApi, ToolExecutionResult, ToolRegistration } from \"../harness/types.ts\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\tformatSize,\n\ttype TruncationResult,\n\ttruncateHeadOf,\n\tutf8ByteLength,\n} from \"../truncate.ts\";\nimport { requireEnv } from \"./env.ts\";\nimport { detectSupportedImageMimeTypeOf, imageDimensions } from \"./image.ts\";\nimport {\n\tbase64Length,\n\tDEFAULT_IMAGE_LIMITS,\n\ttype ImageLimits,\n\ttype ImageProcessor,\n\tINLINE_IMAGE_TYPES,\n\ttoBase64,\n} from \"./image-processor.ts\";\nimport { resolveReadToolPath } from \"./path-utils.ts\";\n\nconst readSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n\toffset: Type.Optional(Type.Number({ description: \"Line number to start reading from (1-indexed)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\nexport type ReadToolInput = Static<typeof readSchema>;\n\nexport type ReadToolDetails = {\n\t/** How the shown text was cut; the text itself is the result content. */\n\ttruncation?: Omit<TruncationResult, \"content\">;\n};\n\nconst READ_CHUNK = 64 * 1024;\n\n/** `Array.prototype.slice`'s conversion of an index: NaN is 0, other values truncate toward zero. */\nfunction sliceIndex(value: number): number {\n\treturn Number.isNaN(value) ? 0 : Math.trunc(value);\n}\n\n/**\n * The decoded start of bytes `[start, end)` of the file, decoded as part of the whole file: all of it, or enough for\n * `truncateHeadOf` (more than `DEFAULT_MAX_BYTES + 1` bytes, or `DEFAULT_MAX_LINES` newlines).\n */\nasync function readHead(\n\treader: BinaryReader,\n\tstart: number,\n\tend: number,\n\tskipBom: boolean,\n\tcontext: Context,\n): Promise<string> {\n\tconst decoder = rangeDecoder();\n\tlet text = \"\";\n\tlet newlines = 0;\n\tfor (let position = skipBom && start === 0 ? 3 : start; position < end; ) {\n\t\tconst bytes = getOrThrow(await reader.read(position, Math.min(READ_CHUNK, end - position), context));\n\t\tif (bytes.length === 0) break;\n\t\tposition += bytes.length;\n\t\tconst decoded = decoder.decode(bytes, { stream: true });\n\t\ttext += decoded;\n\t\tfor (let index = decoded.indexOf(\"\\n\"); index !== -1; index = decoded.indexOf(\"\\n\", index + 1)) newlines++;\n\t\tif (newlines >= DEFAULT_MAX_LINES || utf8ByteLength(text) > DEFAULT_MAX_BYTES + 1) return text;\n\t}\n\treturn text + decoder.decode();\n}\n\nexport type ReadToolOptions = {\n\t/** Resizes and converts images; without one, images within `DEFAULT_IMAGE_LIMITS.maxBytes` pass through as they are. */\n\treadonly images?: ImageProcessor;\n};\n\n/**\n * Reads text files and images. Remarks about truncation, continuation, and image processing are diagnostics; the content\n * is only file text, or one image, which programs get as its `ImageContent`.\n */\nexport function createReadTool(options: ReadToolOptions = {}): ToolRegistration<typeof readSchema, ReadToolDetails> {\n\tconst formats = options.images === undefined ? \"jpg, png, gif, webp\" : \"jpg, png, gif, webp, bmp\";\n\treturn defineTool({\n\t\tname: \"read\",\n\t\tdescription: `Read the contents of a file. Supports text files and images (${formats}). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,\n\t\tparameters: readSchema,\n\t\tasync execute(args, api, context) {\n\t\t\tconst { path, offset, limit } = args;\n\t\t\tconst env = requireEnv(api);\n\t\t\tconst absolutePath = await resolveReadToolPath(env, path, context);\n\t\t\tconst reader = getOrThrow(await env.openBinaryReader(absolutePath, undefined, context));\n\t\t\ttry {\n\t\t\t\t// A concurrent writer can change the file between the scan and the reads. Appending (a growing log) leaves\n\t\t\t\t// the scanned text as it was; an image must be whole. Otherwise the file is read again once.\n\t\t\t\tfor (let attempt = 0; ; attempt++) {\n\t\t\t\t\tconst before = getOrThrow(await reader.info(context));\n\t\t\t\t\tconst mimeType = await detectSupportedImageMimeTypeOf({\n\t\t\t\t\t\tsize: before.size,\n\t\t\t\t\t\tread: async (position, length) => getOrThrow(await reader.read(position, length, context)),\n\t\t\t\t\t});\n\t\t\t\t\tconst result =\n\t\t\t\t\t\tmimeType === undefined\n\t\t\t\t\t\t\t? await readText(reader, path, offset, limit, context)\n\t\t\t\t\t\t\t: await readImage(\n\t\t\t\t\t\t\t\t\treader,\n\t\t\t\t\t\t\t\t\tbefore,\n\t\t\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\t\t\tmimeType,\n\t\t\t\t\t\t\t\t\toptions.images,\n\t\t\t\t\t\t\t\t\tawait imageModel(api, context),\n\t\t\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\tconst after = getOrThrow(await reader.info(context));\n\t\t\t\t\tconst unchanged = after.size === before.size && after.mtimeMs === before.mtimeMs;\n\t\t\t\t\tif (unchanged || (mimeType === undefined && after.size > before.size)) return result;\n\t\t\t\t\tif (attempt === 1) throw new Error(`${path} changed while it was read`);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tawait reader.close(context);\n\t\t\t}\n\t\t},\n\t});\n}\n\n/**\n * One image block, prepared by `processor` or, without one, as it is when its format is inline and it fits the limits\n * (its size is checked before reading it, its dimensions from its header); otherwise an error result saying why. What happened to the image is an `info` diagnostic, so programs get the\n * bare `ImageContent`. A model without image input sees pi-ai's placeholder instead; a diagnostic says so.\n */\nasync function readImage(\n\treader: BinaryReader,\n\tinfo: FileInfo,\n\tpath: string,\n\tmimeType: string,\n\tprocessor: ImageProcessor | undefined,\n\t{ vision, limits }: ImageModel,\n\tcontext: Context,\n): Promise<ToolExecutionResult<ReadToolDetails>> {\n\tlet image: { readonly data: string; readonly mimeType: string };\n\tconst notes: string[] = [];\n\tif (processor !== undefined) {\n\t\tconst bytes = getOrThrow(await reader.read(0, info.size, context));\n\t\tconst prepared = await processor.prepare(bytes, mimeType, limits);\n\t\tif (prepared === undefined)\n\t\t\treturn imageError(`${path} is an image (${mimeType}) that cannot be prepared for the model`);\n\t\timage = prepared;\n\t\tif (prepared.convertedFrom !== undefined)\n\t\t\tnotes.push(`Converted from ${prepared.convertedFrom} to ${prepared.mimeType}.`);\n\t\tif (prepared.resized !== undefined) {\n\t\t\tconst { from, to } = prepared.resized;\n\t\t\tconst scale = (from.width / to.width).toFixed(2);\n\t\t\tnotes.push(\n\t\t\t\t`Resized from ${from.width}x${from.height} to ${to.width}x${to.height}. Multiply coordinates by ${scale} to map them to the original.`,\n\t\t\t);\n\t\t}\n\t} else {\n\t\tif (!INLINE_IMAGE_TYPES.has(mimeType)) {\n\t\t\treturn imageError(\n\t\t\t\t`${path} is an image (${mimeType}) that needs converting, and no image processor is configured`,\n\t\t\t);\n\t\t}\n\t\tconst unshrinkable = \"and no image processor is configured to shrink it\";\n\t\tif (base64Length(info.size) > limits.maxBytes) {\n\t\t\treturn imageError(\n\t\t\t\t`${path} is an image (${mimeType}) of ${formatSize(info.size)}, too large to send, ${unshrinkable}`,\n\t\t\t);\n\t\t}\n\t\tconst bytes = getOrThrow(await reader.read(0, info.size, context));\n\t\tconst size = imageDimensions(bytes, mimeType);\n\t\tif (size === undefined) return imageError(`${path} is an image (${mimeType}) whose size cannot be read`);\n\t\tif (size.width > limits.maxWidth || size.height > limits.maxHeight) {\n\t\t\treturn imageError(\n\t\t\t\t`${path} is an image (${mimeType}) of ${size.width}x${size.height}, larger than ${limits.maxWidth}x${limits.maxHeight}, ${unshrinkable}`,\n\t\t\t);\n\t\t}\n\t\timage = { data: toBase64(bytes), mimeType };\n\t}\n\tif (!vision) notes.push(\"The current model does not support images; it sees a placeholder instead.\");\n\tconst diagnostics: ToolDiagnostic[] = [\n\t\t{ severity: \"info\", code: \"image\", message: [`Read image file [${image.mimeType}].`, ...notes].join(\" \") },\n\t];\n\treturn { output: [{ type: \"image\", data: image.data, mimeType: image.mimeType }], diagnostics };\n}\n\n/** What the conversation's model takes: whether images, and within which limits. */\ntype ImageModel = { readonly vision: boolean; readonly limits: ImageLimits };\n\n/**\n * The conversation model's image input: `DEFAULT_IMAGE_LIMITS` overridden by its `inputLimits.images.resize`. Without\n * a resolvable model, it counts as one that takes images within the defaults.\n */\nasync function imageModel(api: ToolExecutionApi, context: Context): Promise<ImageModel> {\n\tconst ref = (await api.agent(context)).model;\n\tconst model = ref === undefined ? undefined : api.models.getModel(ref.provider, ref.modelId);\n\tconst resize = model?.inputLimits?.images?.resize;\n\treturn {\n\t\tvision: model?.input.includes(\"image\") ?? true,\n\t\tlimits: {\n\t\t\tmaxWidth: resize?.maxWidth ?? DEFAULT_IMAGE_LIMITS.maxWidth,\n\t\t\tmaxHeight: resize?.maxHeight ?? DEFAULT_IMAGE_LIMITS.maxHeight,\n\t\t\tmaxBytes: resize?.maxBytes ?? DEFAULT_IMAGE_LIMITS.maxBytes,\n\t\t},\n\t};\n}\n\nfunction imageError(message: string): ToolExecutionResult<ReadToolDetails> {\n\treturn { output: [], isError: true, diagnostics: [{ severity: \"error\", code: \"unsupported_image\", message }] };\n}\n\n/**\n * The read result for the opened file. It equals decoding the whole file with `TextDecoder`, splitting it on `\\n`,\n * and bounding the selected lines with `truncateHead`, while reading only one scan's worth of the file plus the head.\n */\nasync function readText(\n\treader: BinaryReader,\n\tpath: string,\n\toffset: number | undefined,\n\tlimit: number | undefined,\n\tcontext: Context,\n) {\n\tconst startLine = offset ? Math.max(0, offset - 1) : 0;\n\tconst startLineDisplay = startLine + 1;\n\t// Lines are selected like `allLines.slice(startLine, endLine)`, which truncates fractional indices.\n\tconst sliceStart = sliceIndex(startLine);\n\t// One pass finds the line count and the selection; a selection past the last line ends with it, as `slice` does,\n\t// and an empty one (a zero or negative limit) is scanned as one line and then ignored.\n\t// A start beyond any file is scanned from 0 only to count lines; the offset check below then fails as before.\n\tconst scanStart = Number.isSafeInteger(sliceStart) ? sliceStart : 0;\n\tconst requestedEnd = limit === undefined ? undefined : Math.max(scanStart + 1, sliceIndex(startLine + limit));\n\tconst scanEnd = requestedEnd !== undefined && Number.isSafeInteger(requestedEnd) ? requestedEnd : undefined;\n\tconst scanOf = async (endLine: number | undefined): Promise<LineScan> =>\n\t\tgetOrThrow(\n\t\t\tawait reader.scanLines({ startLine: scanStart, ...(endLine === undefined ? {} : { endLine }) }, context),\n\t\t);\n\tlet scan = await scanOf(scanEnd);\n\tconst totalFileLines = scan.newlines + 1;\n\tif (startLine >= totalFileLines) {\n\t\tthrow new Error(`Offset ${offset} is beyond end of file (${totalFileLines} lines total)`);\n\t}\n\n\tlet userLimitedLines: number | undefined;\n\tlet selectedLineCount = totalFileLines - sliceStart;\n\tif (limit !== undefined) {\n\t\tconst endLine = Math.min(startLine + limit, totalFileLines);\n\t\tuserLimitedLines = endLine - startLine;\n\t\t// `slice` counts a negative end from the end of the lines, which only the line count tells; scan again for it.\n\t\tconst relativeEnd = sliceIndex(endLine);\n\t\tconst sliceEnd = relativeEnd < 0 ? Math.max(totalFileLines + relativeEnd, 0) : relativeEnd;\n\t\tselectedLineCount = Math.max(0, sliceEnd - sliceStart);\n\t\tif (selectedLineCount > 0 && relativeEnd < 0) scan = await scanOf(sliceEnd);\n\t}\n\tconst empty = selectedLineCount === 0;\n\t// Counted like `truncateHead`: a trailing newline adds no line, and empty text has none.\n\tconst endsWithNewline = !empty && scan.lastLineStart === scan.end && scan.lastLineStart > scan.start;\n\tconst totals = {\n\t\tlines: empty || scan.selectedBytes === 0 ? 0 : selectedLineCount - (endsWithNewline ? 1 : 0),\n\t\tbytes: empty ? 0 : scan.selectedBytes,\n\t};\n\tconst firstBytes = getOrThrow(await reader.read(0, 3, context));\n\tconst head = empty ? \"\" : await readHead(reader, scan.start, scan.end, startsWithBom(firstBytes), context);\n\n\tconst { content: headText, ...truncation } = truncateHeadOf(head, totals);\n\tconst diagnostics: ToolDiagnostic[] = [];\n\tlet outputText = headText;\n\tlet details: ReadToolDetails | undefined;\n\tif (truncation.firstLineExceedsLimit) {\n\t\t// Show the start of the line, cut at the byte limit on a character boundary. Like `allLines[startLine]`, a\n\t\t// fractional start line names no line.\n\t\tconst integral = Number.isInteger(startLine);\n\t\tconst lineBytes = new TextEncoder().encode(integral ? (head.split(\"\\n\")[0] ?? \"\") : \"\");\n\t\tconst lineSize = integral ? scan.firstLineBytes : 0;\n\t\tconst end = characterEnd(lineBytes, DEFAULT_MAX_BYTES);\n\t\toutputText = new TextDecoder().decode(lineBytes.subarray(0, end));\n\t\tdiagnostics.push({\n\t\t\tseverity: \"warn\",\n\t\t\tcode: \"truncated\",\n\t\t\tmessage: `Line ${startLineDisplay} is ${formatSize(lineSize)}, exceeds the ${formatSize(DEFAULT_MAX_BYTES)} limit; showing its first ${formatSize(end)}. Use bash: sed -n '${startLineDisplay}p' ${path} | tail -c +${end + 1}`,\n\t\t});\n\t\tdetails = { truncation: { ...truncation, outputBytes: end, outputLines: 1 } };\n\t} else if (truncation.truncated) {\n\t\tconst endLineDisplay = startLineDisplay + truncation.outputLines - 1;\n\t\tconst nextOffset = endLineDisplay + 1;\n\t\tconst limitText = truncation.truncatedBy === \"lines\" ? \"\" : ` (${formatSize(DEFAULT_MAX_BYTES)} limit)`;\n\t\tdiagnostics.push({\n\t\t\tseverity: \"info\",\n\t\t\tcode: \"truncated\",\n\t\t\tmessage: `Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}${limitText}. Use offset=${nextOffset} to continue.`,\n\t\t});\n\t\tdetails = { truncation };\n\t} else if (userLimitedLines !== undefined && startLine + userLimitedLines < totalFileLines) {\n\t\tconst remaining = totalFileLines - (startLine + userLimitedLines);\n\t\tconst nextOffset = startLine + userLimitedLines + 1;\n\t\tdiagnostics.push({\n\t\t\tseverity: \"info\",\n\t\t\tmessage: `${remaining} more lines in file. Use offset=${nextOffset} to continue.`,\n\t\t});\n\t}\n\n\treturn {\n\t\toutput: outputText === \"\" ? [] : [{ type: \"text\" as const, text: outputText }],\n\t\t...(details === undefined ? {} : { details }),\n\t\tdiagnostics,\n\t};\n}\n"]}
|
package/dist/truncate.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { utf8ByteLength } from "@earendil-works/chord/output";
|
|
1
2
|
/**
|
|
2
3
|
* Shared truncation utilities for tool outputs.
|
|
3
4
|
*
|
|
@@ -5,7 +6,7 @@
|
|
|
5
6
|
* - Line limit (default: 2000 lines)
|
|
6
7
|
* - Byte limit (default: 50KB)
|
|
7
8
|
*
|
|
8
|
-
* Never returns partial lines. Tool output streams are bounded by `
|
|
9
|
+
* Never returns partial lines. Tool output streams are bounded by `OutputBuffer` from `@earendil-works/chord/output` instead.
|
|
9
10
|
*/
|
|
10
11
|
export declare const DEFAULT_MAX_LINES = 2000;
|
|
11
12
|
export declare const DEFAULT_MAX_BYTES: number;
|
|
@@ -39,7 +40,7 @@ export interface TruncationOptions {
|
|
|
39
40
|
/** Maximum number of bytes (default: 50KB) */
|
|
40
41
|
maxBytes?: number;
|
|
41
42
|
}
|
|
42
|
-
export
|
|
43
|
+
export { utf8ByteLength };
|
|
43
44
|
/**
|
|
44
45
|
* Format bytes as human-readable size.
|
|
45
46
|
*/
|
package/dist/truncate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"truncate.d.ts","sourceRoot":"","sources":["../src/truncate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,QAAY,CAAC;AAE3C,MAAM,WAAW,gBAAgB;IAChC,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,WAAW,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAAC;IACtC,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,eAAe,EAAE,OAAO,CAAC;IACzB,2EAA2E;IAC3E,qBAAqB,EAAE,OAAO,CAAC;IAC/B,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IACjC,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;
|
|
1
|
+
{"version":3,"file":"truncate.d.ts","sourceRoot":"","sources":["../src/truncate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D;;;;;;;;GAQG;AAEH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,QAAY,CAAC;AAE3C,MAAM,WAAW,gBAAgB;IAChC,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,WAAW,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAAC;IACtC,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,eAAe,EAAE,OAAO,CAAC;IACzB,2EAA2E;IAC3E,qBAAqB,EAAE,OAAO,CAAC;IAC/B,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IACjC,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAGD,OAAO,EAAE,cAAc,EAAE,CAAC;AAS1B;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQhD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,gBAAgB,CAM/F;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC7B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAC1D,OAAO,GAAE,iBAAsB,GAC7B,gBAAgB,CAgFlB"}
|
package/dist/truncate.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { utf8ByteLength } from "@earendil-works/chord/output";
|
|
1
2
|
/**
|
|
2
3
|
* Shared truncation utilities for tool outputs.
|
|
3
4
|
*
|
|
@@ -5,43 +6,12 @@
|
|
|
5
6
|
* - Line limit (default: 2000 lines)
|
|
6
7
|
* - Byte limit (default: 50KB)
|
|
7
8
|
*
|
|
8
|
-
* Never returns partial lines. Tool output streams are bounded by `
|
|
9
|
+
* Never returns partial lines. Tool output streams are bounded by `OutputBuffer` from `@earendil-works/chord/output` instead.
|
|
9
10
|
*/
|
|
10
11
|
export const DEFAULT_MAX_LINES = 2000;
|
|
11
12
|
export const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export function utf8ByteLength(content) {
|
|
15
|
-
if (runtimeBuffer)
|
|
16
|
-
return runtimeBuffer.byteLength(content, "utf8");
|
|
17
|
-
const firstNonAscii = content.search(nonAsciiPattern);
|
|
18
|
-
if (firstNonAscii === -1)
|
|
19
|
-
return content.length;
|
|
20
|
-
let bytes = firstNonAscii;
|
|
21
|
-
for (let i = firstNonAscii; i < content.length; i++) {
|
|
22
|
-
const code = content.charCodeAt(i);
|
|
23
|
-
if (code <= 0x7f) {
|
|
24
|
-
bytes += 1;
|
|
25
|
-
}
|
|
26
|
-
else if (code <= 0x7ff) {
|
|
27
|
-
bytes += 2;
|
|
28
|
-
}
|
|
29
|
-
else if (code >= 0xd800 && code <= 0xdbff && i + 1 < content.length) {
|
|
30
|
-
const next = content.charCodeAt(i + 1);
|
|
31
|
-
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
32
|
-
bytes += 4;
|
|
33
|
-
i++;
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
bytes += 3;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
bytes += 3;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return bytes;
|
|
44
|
-
}
|
|
13
|
+
// One implementation, shared with the output buffer.
|
|
14
|
+
export { utf8ByteLength };
|
|
45
15
|
function splitLinesForCounting(content) {
|
|
46
16
|
if (content.length === 0)
|
|
47
17
|
return [];
|
package/dist/truncate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"truncate.js","sourceRoot":"","sources":["../src/truncate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared truncation utilities for tool outputs.\n *\n * Truncation is based on two independent limits - whichever is hit first wins:\n * - Line limit (default: 2000 lines)\n * - Byte limit (default: 50KB)\n *\n * Never returns partial lines. Tool output streams are bounded by `src/harness/output.ts` instead.\n */\n\nexport const DEFAULT_MAX_LINES = 2000;\nexport const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB\n\nexport interface TruncationResult {\n\t/** The truncated content */\n\tcontent: string;\n\t/** Whether truncation occurred */\n\ttruncated: boolean;\n\t/** Which limit was hit: \"lines\", \"bytes\", or null if not truncated */\n\ttruncatedBy: \"lines\" | \"bytes\" | null;\n\t/** Total number of lines in the original content */\n\ttotalLines: number;\n\t/** Total number of bytes in the original content */\n\ttotalBytes: number;\n\t/** Number of complete lines in the truncated output */\n\toutputLines: number;\n\t/** Number of bytes in the truncated output */\n\toutputBytes: number;\n\t/** Whether the last line was partially truncated (only for tail truncation edge case) */\n\tlastLinePartial: boolean;\n\t/** Whether the first line exceeded the byte limit (for head truncation) */\n\tfirstLineExceedsLimit: boolean;\n\t/** The max lines limit that was applied */\n\tmaxLines: number;\n\t/** The max bytes limit that was applied */\n\tmaxBytes: number;\n}\n\nexport interface TruncationOptions {\n\t/** Maximum number of lines (default: 2000) */\n\tmaxLines?: number;\n\t/** Maximum number of bytes (default: 50KB) */\n\tmaxBytes?: number;\n}\n\ninterface RuntimeBuffer {\n\tbyteLength(content: string, encoding: \"utf8\"): number;\n}\n\nconst runtimeBuffer = (globalThis as { Buffer?: RuntimeBuffer }).Buffer;\nconst nonAsciiPattern = /[^\\x00-\\x7f]/;\n\nexport function utf8ByteLength(content: string): number {\n\tif (runtimeBuffer) return runtimeBuffer.byteLength(content, \"utf8\");\n\n\tconst firstNonAscii = content.search(nonAsciiPattern);\n\tif (firstNonAscii === -1) return content.length;\n\n\tlet bytes = firstNonAscii;\n\tfor (let i = firstNonAscii; i < content.length; i++) {\n\t\tconst code = content.charCodeAt(i);\n\t\tif (code <= 0x7f) {\n\t\t\tbytes += 1;\n\t\t} else if (code <= 0x7ff) {\n\t\t\tbytes += 2;\n\t\t} else if (code >= 0xd800 && code <= 0xdbff && i + 1 < content.length) {\n\t\t\tconst next = content.charCodeAt(i + 1);\n\t\t\tif (next >= 0xdc00 && next <= 0xdfff) {\n\t\t\t\tbytes += 4;\n\t\t\t\ti++;\n\t\t\t} else {\n\t\t\t\tbytes += 3;\n\t\t\t}\n\t\t} else {\n\t\t\tbytes += 3;\n\t\t}\n\t}\n\treturn bytes;\n}\n\nfunction splitLinesForCounting(content: string): string[] {\n\tif (content.length === 0) return [];\n\tconst lines = content.split(\"\\n\");\n\tif (content.endsWith(\"\\n\")) lines.pop();\n\treturn lines;\n}\n\n/**\n * Format bytes as human-readable size.\n */\nexport function formatSize(bytes: number): string {\n\tif (bytes < 1024) {\n\t\treturn `${bytes}B`;\n\t} else if (bytes < 1024 * 1024) {\n\t\treturn `${(bytes / 1024).toFixed(1)}KB`;\n\t} else {\n\t\treturn `${(bytes / (1024 * 1024)).toFixed(1)}MB`;\n\t}\n}\n\n/**\n * Truncate content from the head (keep first N lines/bytes).\n * Suitable for file reads where you want to see the beginning.\n *\n * Never returns partial lines. If first line exceeds byte limit,\n * returns empty content with firstLineExceedsLimit=true.\n */\nexport function truncateHead(content: string, options: TruncationOptions = {}): TruncationResult {\n\treturn truncateHeadOf(\n\t\tcontent,\n\t\t{ lines: splitLinesForCounting(content).length, bytes: utf8ByteLength(content) },\n\t\toptions,\n\t);\n}\n\n/**\n * `truncateHead` of a text known by a prefix and its totals (`lines` counted like `truncateHead`, ignoring a trailing\n * newline). The prefix must be the whole text, or longer than `maxBytes + 1` UTF-8 bytes, or hold at least `maxLines`\n * newlines; then the result equals `truncateHead` of the whole text.\n */\nexport function truncateHeadOf(\n\tprefix: string,\n\ttotals: { readonly lines: number; readonly bytes: number },\n\toptions: TruncationOptions = {},\n): TruncationResult {\n\tconst maxLines = options.maxLines ?? DEFAULT_MAX_LINES;\n\tconst maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n\n\tconst totalBytes = totals.bytes;\n\tconst lines = splitLinesForCounting(prefix);\n\tconst totalLines = totals.lines;\n\n\t// Check if no truncation needed\n\tif (totalLines <= maxLines && totalBytes <= maxBytes) {\n\t\treturn {\n\t\t\tcontent: prefix,\n\t\t\ttruncated: false,\n\t\t\ttruncatedBy: null,\n\t\t\ttotalLines,\n\t\t\ttotalBytes,\n\t\t\toutputLines: totalLines,\n\t\t\toutputBytes: totalBytes,\n\t\t\tlastLinePartial: false,\n\t\t\tfirstLineExceedsLimit: false,\n\t\t\tmaxLines,\n\t\t\tmaxBytes,\n\t\t};\n\t}\n\n\t// Check if first line alone exceeds byte limit\n\tconst firstLineBytes = utf8ByteLength(lines[0]);\n\tif (firstLineBytes > maxBytes) {\n\t\treturn {\n\t\t\tcontent: \"\",\n\t\t\ttruncated: true,\n\t\t\ttruncatedBy: \"bytes\",\n\t\t\ttotalLines,\n\t\t\ttotalBytes,\n\t\t\toutputLines: 0,\n\t\t\toutputBytes: 0,\n\t\t\tlastLinePartial: false,\n\t\t\tfirstLineExceedsLimit: true,\n\t\t\tmaxLines,\n\t\t\tmaxBytes,\n\t\t};\n\t}\n\n\t// Collect complete lines that fit\n\tconst outputLinesArr: string[] = [];\n\tlet outputBytesCount = 0;\n\tlet truncatedBy: \"lines\" | \"bytes\" = \"lines\";\n\n\tfor (let i = 0; i < lines.length && i < maxLines; i++) {\n\t\tconst line = lines[i];\n\t\tconst lineBytes = utf8ByteLength(line) + (i > 0 ? 1 : 0); // +1 for newline\n\n\t\tif (outputBytesCount + lineBytes > maxBytes) {\n\t\t\ttruncatedBy = \"bytes\";\n\t\t\tbreak;\n\t\t}\n\n\t\toutputLinesArr.push(line);\n\t\toutputBytesCount += lineBytes;\n\t}\n\n\t// Without a byte break, only omitted lines prove the line limit was reached; otherwise a trailing newline exceeded bytes.\n\tif (truncatedBy !== \"bytes\") truncatedBy = outputLinesArr.length < totalLines ? \"lines\" : \"bytes\";\n\n\tconst outputContent = outputLinesArr.join(\"\\n\");\n\tconst finalOutputBytes = utf8ByteLength(outputContent);\n\n\treturn {\n\t\tcontent: outputContent,\n\t\ttruncated: true,\n\t\ttruncatedBy,\n\t\ttotalLines,\n\t\ttotalBytes,\n\t\toutputLines: outputLinesArr.length,\n\t\toutputBytes: finalOutputBytes,\n\t\tlastLinePartial: false,\n\t\tfirstLineExceedsLimit: false,\n\t\tmaxLines,\n\t\tmaxBytes,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"truncate.js","sourceRoot":"","sources":["../src/truncate.ts"],"names":[],"mappings":"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","sourcesContent":["import { utf8ByteLength } from \"@earendil-works/chord/output\";\n/**\n * Shared truncation utilities for tool outputs.\n *\n * Truncation is based on two independent limits - whichever is hit first wins:\n * - Line limit (default: 2000 lines)\n * - Byte limit (default: 50KB)\n *\n * Never returns partial lines. Tool output streams are bounded by `OutputBuffer` from `@earendil-works/chord/output` instead.\n */\n\nexport const DEFAULT_MAX_LINES = 2000;\nexport const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB\n\nexport interface TruncationResult {\n\t/** The truncated content */\n\tcontent: string;\n\t/** Whether truncation occurred */\n\ttruncated: boolean;\n\t/** Which limit was hit: \"lines\", \"bytes\", or null if not truncated */\n\ttruncatedBy: \"lines\" | \"bytes\" | null;\n\t/** Total number of lines in the original content */\n\ttotalLines: number;\n\t/** Total number of bytes in the original content */\n\ttotalBytes: number;\n\t/** Number of complete lines in the truncated output */\n\toutputLines: number;\n\t/** Number of bytes in the truncated output */\n\toutputBytes: number;\n\t/** Whether the last line was partially truncated (only for tail truncation edge case) */\n\tlastLinePartial: boolean;\n\t/** Whether the first line exceeded the byte limit (for head truncation) */\n\tfirstLineExceedsLimit: boolean;\n\t/** The max lines limit that was applied */\n\tmaxLines: number;\n\t/** The max bytes limit that was applied */\n\tmaxBytes: number;\n}\n\nexport interface TruncationOptions {\n\t/** Maximum number of lines (default: 2000) */\n\tmaxLines?: number;\n\t/** Maximum number of bytes (default: 50KB) */\n\tmaxBytes?: number;\n}\n\n// One implementation, shared with the output buffer.\nexport { utf8ByteLength };\n\nfunction splitLinesForCounting(content: string): string[] {\n\tif (content.length === 0) return [];\n\tconst lines = content.split(\"\\n\");\n\tif (content.endsWith(\"\\n\")) lines.pop();\n\treturn lines;\n}\n\n/**\n * Format bytes as human-readable size.\n */\nexport function formatSize(bytes: number): string {\n\tif (bytes < 1024) {\n\t\treturn `${bytes}B`;\n\t} else if (bytes < 1024 * 1024) {\n\t\treturn `${(bytes / 1024).toFixed(1)}KB`;\n\t} else {\n\t\treturn `${(bytes / (1024 * 1024)).toFixed(1)}MB`;\n\t}\n}\n\n/**\n * Truncate content from the head (keep first N lines/bytes).\n * Suitable for file reads where you want to see the beginning.\n *\n * Never returns partial lines. If first line exceeds byte limit,\n * returns empty content with firstLineExceedsLimit=true.\n */\nexport function truncateHead(content: string, options: TruncationOptions = {}): TruncationResult {\n\treturn truncateHeadOf(\n\t\tcontent,\n\t\t{ lines: splitLinesForCounting(content).length, bytes: utf8ByteLength(content) },\n\t\toptions,\n\t);\n}\n\n/**\n * `truncateHead` of a text known by a prefix and its totals (`lines` counted like `truncateHead`, ignoring a trailing\n * newline). The prefix must be the whole text, or longer than `maxBytes + 1` UTF-8 bytes, or hold at least `maxLines`\n * newlines; then the result equals `truncateHead` of the whole text.\n */\nexport function truncateHeadOf(\n\tprefix: string,\n\ttotals: { readonly lines: number; readonly bytes: number },\n\toptions: TruncationOptions = {},\n): TruncationResult {\n\tconst maxLines = options.maxLines ?? DEFAULT_MAX_LINES;\n\tconst maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n\n\tconst totalBytes = totals.bytes;\n\tconst lines = splitLinesForCounting(prefix);\n\tconst totalLines = totals.lines;\n\n\t// Check if no truncation needed\n\tif (totalLines <= maxLines && totalBytes <= maxBytes) {\n\t\treturn {\n\t\t\tcontent: prefix,\n\t\t\ttruncated: false,\n\t\t\ttruncatedBy: null,\n\t\t\ttotalLines,\n\t\t\ttotalBytes,\n\t\t\toutputLines: totalLines,\n\t\t\toutputBytes: totalBytes,\n\t\t\tlastLinePartial: false,\n\t\t\tfirstLineExceedsLimit: false,\n\t\t\tmaxLines,\n\t\t\tmaxBytes,\n\t\t};\n\t}\n\n\t// Check if first line alone exceeds byte limit\n\tconst firstLineBytes = utf8ByteLength(lines[0]);\n\tif (firstLineBytes > maxBytes) {\n\t\treturn {\n\t\t\tcontent: \"\",\n\t\t\ttruncated: true,\n\t\t\ttruncatedBy: \"bytes\",\n\t\t\ttotalLines,\n\t\t\ttotalBytes,\n\t\t\toutputLines: 0,\n\t\t\toutputBytes: 0,\n\t\t\tlastLinePartial: false,\n\t\t\tfirstLineExceedsLimit: true,\n\t\t\tmaxLines,\n\t\t\tmaxBytes,\n\t\t};\n\t}\n\n\t// Collect complete lines that fit\n\tconst outputLinesArr: string[] = [];\n\tlet outputBytesCount = 0;\n\tlet truncatedBy: \"lines\" | \"bytes\" = \"lines\";\n\n\tfor (let i = 0; i < lines.length && i < maxLines; i++) {\n\t\tconst line = lines[i];\n\t\tconst lineBytes = utf8ByteLength(line) + (i > 0 ? 1 : 0); // +1 for newline\n\n\t\tif (outputBytesCount + lineBytes > maxBytes) {\n\t\t\ttruncatedBy = \"bytes\";\n\t\t\tbreak;\n\t\t}\n\n\t\toutputLinesArr.push(line);\n\t\toutputBytesCount += lineBytes;\n\t}\n\n\t// Without a byte break, only omitted lines prove the line limit was reached; otherwise a trailing newline exceeded bytes.\n\tif (truncatedBy !== \"bytes\") truncatedBy = outputLinesArr.length < totalLines ? \"lines\" : \"bytes\";\n\n\tconst outputContent = outputLinesArr.join(\"\\n\");\n\tconst finalOutputBytes = utf8ByteLength(outputContent);\n\n\treturn {\n\t\tcontent: outputContent,\n\t\ttruncated: true,\n\t\ttruncatedBy,\n\t\ttotalLines,\n\t\ttotalBytes,\n\t\toutputLines: outputLinesArr.length,\n\t\toutputBytes: finalOutputBytes,\n\t\tlastLinePartial: false,\n\t\tfirstLineExceedsLimit: false,\n\t\tmaxLines,\n\t\tmaxBytes,\n\t};\n}\n"]}
|